mybatis源码分析4 - sqlSession读写数据库完全解析

时间:2021-10-03 18:37:10

1 引言和主要类

创建完sqlSession实例后,我们就可以进行数据库操作了。比如通过selectOne()方法查询数据库,如代码

// 读取XML配置文件
String resource = "main/resources/SqlMapConfig.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
// 创建sqlSessionFactory单例,初始化mybatis容器
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 创建sqlSession实例,用它来进行数据库操作,mybatis运行时的门面
SqlSession session = sessionFactory.openSession();
// 进行数据库查询操作
User user = session.selectOne("test.findUserById", 1); // 访问数据库,statement为mapper.xml中的id

创建sqlSessionFactory单例和sqlSession实例在前两节中分析过了,下面我们着重来分析sqlSession操作数据库的过程。

sqlSession操作数据库有两种方法,可以直接使用select update insert delete等方法;也可以通过getMapper()先获取mapper动态代理实例,然后再进行数据库操作。相比而言mapper方式更灵活且不易出错,是mybatis推荐的方式。本节我们分析直接使用select等方法的流程,下一节再分析mapper方式。

本节以selectOne()方法的实现过程为例来分析sqlSession操作数据库的流程,涉及的主要类如下。

  1. DefaultSqlSession:SqlSession的默认实现,其方法基本都是利用Executor代理实现。
  2. Executor:mybatis运行的核心,调度器。调度mybatis其他三大组件的执行。
  3. StatementHandler:SQL语句执行器,cache的管理等
  4. ParameterHandler:入参处理器,statementType为PREPARE是需要使用到它,来解析入参到preparedStatement中
  5. ResultSetHandler:结果集映射处理器,将数据库操作原始结果(主要是查询操作),映射为Java POJO。这正是ORM要解决的关键问题。

2 流程

2.1 DefaultSqlSession的selectOne()

先从DefaultSqlSession的selectOne()方法看起。

public <T> T selectOne(String statement, Object parameter) {
// selectOne本质上是调用selectList实现,如果结果集大于一个,则报TooManyResultsException。
List<T> list = this.<T>selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}

public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}

// 由sql语句的标示statement和入参parameter,查询满足条件的数据列表
// @Param statement: mapper.xml中mapper节点下的select delete update insert等子节点的id属性
// @Param parameter: 传入sql语句的入参
// @Param rowBounds: 逻辑分页,包含offset和limit两个主要成员变量。mybatis分页逻辑为舍弃offset之前条目,取剩下的limit条。默认DEFAULT不分页
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 从mappers节点初始化阶段创建好的mappedStatements这个Map中,找到key为当前要找到的sql的id的那条
MappedStatement ms = configuration.getMappedStatement(statement);

// 通过执行器Executor作为总调度来执行查询语句,后面以BaseExecutor来分析。
// BatchExecutor ReuseExecutor SimpleExecutor均继承了BaseExecutor
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}

selectOne()方法其实是通过调用selectList()实现的,因为二者本质是完全相同的,只是前者返回一个对象,而后者为列表List而已。selectList()采用代理模式,使用调度器Executor的query()方法实现。上一节着重讲过Executor是SqlSession各个方法的具体实现,是mybatis运行的核心,通过调度StatementHandler ParameterHandler ResultSetHandler三个组件来完成sqlSession操作数据库的整个过程。Executor的实现类有SimpleExecutor ReuseExecutor BatchExecutor等,它们的基类都是BaseExecutor。下面来分析BaseExecutor的query

2.2 BaseExecutor的query() 调度器开始数据库query

// BaseExecutor的查找方法
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 从MappedStatement中找到boundSql成员变量,前面SqlSessionFactory创建部分讲到过Mapper解析时的三大组件:MappedStatement SqlSource BoundSql
// 其中BoundSql通过sql执行语句和入参,来组装最终查询数据库用到的sql。
BoundSql boundSql = ms.getBoundSql(parameter);

// 创建CacheKey,用作缓存的key,不用深入理解。
// sql的id,逻辑分页rowBounds的offset和limit,boundSql的sql语句均相同时(主要是动态sql的存在),也就是组装后的SQL语句完全相同时,才认为是同一个cacheKey
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);

// 真正的查询语句执行处,关键代码
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}

// 查找方法
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
// 调度器已经close了则报错
if (closed) {
throw new ExecutorException("Executor was closed.");
}

// flush cache, 即写入并清空cache。之后就只能从数据库中读取了,这样可以防止脏cache
// localCache和localOutputParameterCache为BaseExecutor的成员变量,它们构成了mybatis的一级缓存,也就是sqlSession级别的缓存,默认是开启的。
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}

// 从缓存或数据库中查询结果list
List<E> list;
try {
// queryStack用来记录当前有几条同样的查询语句在同时执行,也就是并发
queryStack++;
// 未定义resultHandler时,先尝试从缓存中取。
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
// 缓存命中时,直接从本地缓存中取出即可,不做详细分析
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 缓存未命中,必须从数据库中查询。后面详细分析
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}

// 当前所有查询语句都结束时,开始处理延迟加载。从缓存中取出执行结果,因为前面已经有过本查询语句了。
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
// 延迟加载从缓存中获取结果
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// statement级别的缓存,只缓存id相同的sql。当所有查询语句和延迟加载的查询语句均执行完毕后,可清空cache。这样可节约内存
clearLocalCache();
}
}
return list;
}

调度器的query方法先从MappedStatement中获取BoundSql,它包含了sql语句和入参对象等变量,再构造缓存的key,即cacheKey。然后先尝试从缓存中取,缓存未命中则直接从数据库中查询。最后处理延迟加载,直接从缓存中取出查询数据即可。下面我们着重分析直接从数据库中查询的过程,也即queryFromDatabase()方法。

// 直接从数据库中查询
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
// 先利用占位符将本次查询设置到本地cache中,个人理解是防止后面延迟加载时cache为空
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 真正的数据库查询,后面详细分析
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
// 查到了结果后,将前面的占位符的cache删掉
localCache.removeObject(key);
}

// 将查询结果放到本地cache中缓存起来
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}

doQuery()进行真正的数据库查询,它由SimpleExecutor等具体类来实现。我们以SimpleExecutor为例分析。

// 数据库查询
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// 创建StatementHandler,用来执行sql语句。SimpleExecutor创建的是RoutingStatementHandler。
// 它的是一个门面类,几乎所有方法都是通过代理来实现。代理则由配置XML settings节点的statementType区分。故仅仅是一个分发和路由。后面详细分析
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);

// 构造Statement,后面详细分析
stmt = prepareStatement(handler, ms.getStatementLog());

// 通过语句执行器的query方法进行查询, 查询结果通过resultHandler处理后返回。后面详细分析
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}

doQuery流程为

  1. 先创建StatementHandler语句处理器。前面讲过StatementHandler是mybatis四大组件之一,负责sql语句的执行。根据XML配置文件的settings节点的statementType子元素,来创建不同的实现类,如SimpleStatementHandler,PreparedStatementHandler,CallableStatementHandler。他们的基类统一为BaseStatementHandler,外观类为RoutingStatementHandler(后面详细分析)。
  2. 创建完StatementHandler后,调用prepareStatement进行初始化,
  3. 然后调用实现类的query方法进行查询。

2.3 StatementHandler的query(), 语句处理器进行查询

下面我们来看StatementHandler是如何执行的,先看StatementHandler的创建过程。

2.3.1 StatementHandler的创建过程

// 创建RoutingStatementHandler,它是StatementHandler的外观类,也是StatementHandler的一个实现类
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 直接构造一个RoutingStatementHandler
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
// 将statementHandler,添加为插件的目标执行器。插件通过配置XML文件的plugins节点设置。
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}

// RoutingStatementHandler的构造器,根据statementType变量来创建不同的StatementHandler实现,作为它的代理
// RoutingStatementHandler的几乎所有方法都是通过这些代理实现的,典型的代理模式。
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
// 根据statementType创建不同的StatementHandler实现类。statementType是在xml配置文件的settngs节点的statementType子元素中设置的。
switch (ms.getStatementType()) {
case STATEMENT:
// 直接操作sql,不进行预编译。此时直接进行字符串拼接构造sql String
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
// 默认的类型。预处理,需要进行预编译。可以使用参数替换,会将#转换为?,再设置对应的参数的值。
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
// 执行存储过程
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}

}

2.3.2 StatementHandler的初始化

StatementHandler的初始化如下

// 通过事务构造sql执行语句statement,如JdbcTransaction
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;

// 开启数据库连接,创建Connection对象。JdbcTransaction事务直接通过JDBC创建connection
Connection connection = getConnection(statementLog);

// 初始化statement并设置期相关变量,不同的StatementHandler实现不同。后面以RoutingStatementHandler为例分析
stmt = handler.prepare(connection, transaction.getTimeout());

// 设置parameterHandler,对于SimpleStatementHandler来说不用处理
handler.parameterize(stmt);
return stmt;
}

StatementHandler初始化步骤如下:

  1. 先开启一个数据库连接connection,
  2. 然后初始化statementHandler,
  3. 最后进行参数预处理。

先开启数据库连接connection,直接获取数据源dataSource的connection,即通过数据库本身来开启连接。

// JdbcTransaction和ManagedTransaction都是直接调用dataSource的getConnection
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}

再进行初始化statementHandler,调用基类BaseStatementHandler的prepare方法完成

// 初始化statementHandler
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
// 典型的代理模式,不同的statementType创建不同的Statement,但这儿他们都调用到他们的基类BaseStatementHandler中的prepare方法
return delegate.prepare(connection, transactionTimeout);
}

// BaseStatementHandler初始化statement并设置期相关变量,不同的StatementHandler实现不同。
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
// 初始化statement,由具体的StatementHandler来实现。比如SimpleStatementHandler通过JDBC connection的createStatement来创建
statement = instantiateStatement(connection);

// 设置timeout(超时时间)和fetchSize(获取数据库的行数)
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}

最后进行参数预处理。不同的statementHandler实现类有不同的参数预处理方式。

  1. SimpleStatementHandler不进行任何参数预处理,它的sql直接通过字符串拼接而成。
  2. PreparedStatementHandler进行预处理,会将#转换为?,然后设置对应的变量到sql String中。
// RoutingStatementHandler的parameterize方法,通过代理模式实现
public void parameterize(Statement statement) throws SQLException {
// 又是代理模式,由具体的statementHandler实现类来实现
delegate.parameterize(statement);
}

// SimpleStatementHandler不做参数预处理
public void parameterize(Statement statement) throws SQLException {
// N/A
}

// PreparedStatementHandler进行参数预处理,通过parameterHandler实现
public void parameterize(Statement statement) throws SQLException {
// parameterHandler可以由用户通过插件方式实现,mybatis默认为DefaultParameterHandler。这个方法我们不进行详细分析了。
parameterHandler.setParameters((PreparedStatement) statement);
}

2.3.3 statementHandler的query()进行数据库查询

创建和初始化statementHandler后,就可以调用它的query()方法来执行语句查询了。先看SimpleStatementHandler的query过程。

// SimpleStatementHandler的query操作过程
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
// 获取存放在boundSql中的sql执行语句
String sql = boundSql.getSql();

// 通过JDBC sql的statement,直接执行sql语句。入参在statement预编译时进行了转换并设置到statement中了。
statement.execute(sql);

// resultSetHandler处理查询结果,并返回。这一部分十分复杂,但也体现了mybatis的设计精巧之处,可以兼容很多复杂场景下的数据库结果转换。如数据库列名和Java POJO属性名不同时的映射,关联数据库的映射等。
return resultSetHandler.<E>handleResultSets(statement);
}

PrepareStatementHandler的query操作过程如下

// PrepareStatementHandler的query操作过程
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
// PREPARE方式下,sql statement进行了预编译,并注入了入参。它是一个PreparedStatement类型
PreparedStatement ps = (PreparedStatement) statement;

// 直接调用JDBC PreparedStatement的execute方法操作数据库。大家应该对这儿很熟悉了,JDBC的操作
ps.execute();

// 结果集处理,后面详细分析
return resultSetHandler.<E> handleResultSets(ps);
}

query先从boundSql中获取具体执行语句,然后通过JDBC的statement直接执行SQL语句。这两步完成后,就从数据库中查找到了结果集了。从这儿可见,mybatis最底层还是通过JDBC来操作数据库的。

mybatis对结果集的处理十分复杂,我们下面详细分析。

2.4 ResultSetHandler处理数据库结果集

通过JDBC完成数据库的操作后,我们就拿到了原始的数据库结果了。此时要将数据库结果集ResultSet转换为Java POJO。这一步通过mybatis四大组件之一的ResultSetHandler来实现。ResultSetHandler默认实现类为DefaultResultSetHandler,用户也可以通过插件的方式覆盖它。插件在xml配置文件的plugins子节点下添加。下面详细分析DefaultResultSetHandler的handleResultSets()方法。

// DefaultResultSetHandler通过handleResultSets处理数据库结果集,处理后作为真正的结果返回。此处的关键是处理resultMap映射
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());

final List<Object> multipleResults = new ArrayList<Object>();
int resultSetCount = 0;

// 1 从JDBC操作数据库后的statement中取出结果集ResultSet
ResultSetWrapper rsw = getFirstResultSet(stmt);

// 2 获取resultMaps, mapper.xml中设置,并在mybatis初始化阶段存入mappedStatement中。
// resultMap定义了jdbc列到Java属性的映射关系,可以解决列名和Java属性名不一致,关联数据库映射等诸多问题。
// 它是mybatis中比较复杂的地方,同时也大大扩展了mybatis的功能
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);

// 3 一条条处理resultSet
while (rsw != null && resultMapCount > resultSetCount) {
// 取出一条resultMap,即结果映射
ResultMap resultMap = resultMaps.get(resultSetCount);
// 进行数据库列到Java属性的映射,后面详细分析
handleResultSet(rsw, resultMap, multipleResults, null);
// 取出下一条resultSet
rsw = getNextResultSet(stmt);
// 清空nestedResultObjects,即嵌套的Result结果集
cleanUpAfterHandlingResultSet();
resultSetCount++;
}

// 4 处理嵌套的resultMap,即映射结果中的某些子属性也需要resultMap映射时
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
// 取出父ResultMapping,用于嵌套情况
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
// 通过嵌套ResultMap的id,取出ResultMap
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);

// 处理ResultSet,后面详细分析
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}

// 5 构造成List,将处理后的结果集返回
return collapseSingleResultList(multipleResults);
}

handleResultSets流程如下

  1. 从JDBC操作数据库后的statement中取出结果集ResultSet
  2. 获取resultMaps, 它们定义了数据库结果集到Java POJO的映射关系
  3. 一条条处理resultSet,调用handleResultSet做数据库列到Java属性的映射
  4. 处理嵌套的resultMap,即映射结果中的某些子属性也需要resultMap映射时
  5. 构造成List,将处理后的结果集返回

这其中的关键是handleResultSet()方法进行数据库列到Java属性的映射,也是ORM关键所在。我们接着分析。

// 通过resultMap映射,处理数据库结果集resultSet
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
if (parentMapping != null) {
// parentMapping不为空,表示处理的是嵌套resultMap中的子resultMap。handleRowValues后面详细分析
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
// 非嵌套resultMap
if (resultHandler == null) {
// 用户没有自定义resultHandler时,采用DefaultResultHandler。并将最终的处理结果添加到multipleResults中
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
multipleResults.add(defaultResultHandler.getResultList());
} else {
// 用户定义了resultHandler时,采用用户自定义的resultHandler
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
closeResultSet(rsw.getResultSet());
}
}

handleResultSet针对嵌套resultMap和非嵌套resultMap做了分别处理,但都是调用的handleRowValues()方法,接着看。

public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
if (resultMap.hasNestedResultMaps()) {
// 有嵌套resultMap时
ensureNoRowBounds();
checkResultHandler();
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {
// 无嵌套resultMap时
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}

嵌套resultMap的处理比较麻烦,这儿不分析了,我们看非嵌套的,即handleRowValuesForSimpleResultMap。

// 非嵌套ResultMap的处理方法。根据resultMap一行行处理数据库结果集到Java属性的映射
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();

// mybatis的逻辑分页规则为跳过rowBounds中offset之前的部分,取limit行数的数据
// skipRows方法会跳过rowBounds中offset之前的部分。
skipRows(rsw.getResultSet(), rowBounds);

// 一行行处理数据库结果集,直到取出的行数等于rowBounds的limit变量(逻辑分页),或者所有行都取完了。
while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
// 处理resultMap中的discriminator,使用结果值来决定使用哪个结果映射。可以将不同的数据库结果映射成不同的Java类型。此处不详细分析了
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
// 处理这一行数据, 得到映射后的Java结果
Object rowValue = getRowValue(rsw, discriminatedResultMap);
// 使用resultHandler处理得到的Java结果,这才是最终返回的Java属性值。
// 用户可自定义resultHandler,否则使用DefaultResultHandler。
// 用户可使用ResultSetHandler插件来自定义结果处理方式,此处体现了mybatis设计精巧之处
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
}
}

// 逻辑分页rowBounds。skipRows方法会跳过rowBounds中offset之前的部分
private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {
rs.absolute(rowBounds.getOffset());
}
} else {
for (int i = 0; i < rowBounds.getOffset(); i++) {
rs.next();
}
}
}

// 逻辑分页rowBounds。shouldProcessMoreRows取limit条数据库查询结果
private boolean shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds) throws SQLException {
return !context.isStopped() && context.getResultCount() < rowBounds.getLimit();
}

mybatis的逻辑分页规则为跳过rowBounds中offset之前的部分,取limit行数的数据。通过skipRows()和shouldProcessMoreRows()两个方法共同完成这个功能。遍历resultSet,通过getRowValue()方法处理一行行数据。最后调用resultHandler来处理转换后的结果。

storeObject结果处理的代码如下

// 利用resultHandler处理经过resultMap映射的Java结果
private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException {
if (parentMapping != null) {
// 嵌套的resultMap,也就是子resultSet结果。链接到父resultSet中,由父resultMap一起处理。不详细分析了
linkToParents(rs, parentMapping, rowValue);
} else {
// 不是嵌套时,直接调用resultHandler进行最后的处理,后面详细看
callResultHandler(resultHandler, resultContext, rowValue);
}
}

private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue) {
// 构建resultContext上下文,然后利用resultHandler处理。后面以DefaultResultHandler来分析
resultContext.nextResultObject(rowValue);
((ResultHandler<Object>) resultHandler).handleResult(resultContext);
}

// ResultHandler对映射后的结果做最后的处理
public void handleResult(ResultContext<? extends Object> context) {
// DefaultResultHandler对经过resultMap映射后的Java结果不做任何处理,仅仅添加到list中,最后将list返回给selectList()等方法。
list.add(context.getResultObject());
}

3 总结

mybatis操作数据库的流程,也是它的四大组件Executor StatementHandler ParameterHandler ResultSetHandler的执行过程。其中Executor是调度器,StatementHandler为SQL语句执行器,ParameterHandler为入参执行器,ResultSetHandler为结果集映射执行器。四大组件分层合理,运行流程清晰,都有默认实现,同时用户也可以利用plugin来覆盖它。这些无一不体现了mybatis的灵活和设计精巧,值得我们平时设计构架时学习。

相关文章

mybatis源码分析1 - 框架

mybatis源码分析2 - SqlSessionFactory的创建

mybatis源码分析3 - sqlSession的创建

mybatis源码分析4 - sqlSession读写数据库完全解析

mybatis源码分析5 - mapper读写数据库完全解析

mybatis源码分析6 - mybatis-spring容器初始化

mybatis源码分析7 - mybatis-spring读写数据库全过程