前言
最近在学习Mybatis源码,学习到了SqlSession,里边查询相关的方法,主要有selectOne
、selectList
等,而且selectOne
方法,其实也是调用了selectList
方法,然后根据数据条数进行了处理。
所以,在这里想把整个SqlSession.selectOne
方法的执行流程梳理一遍,过程中会涉及sql获取
、一二级缓存设置方式&查询优先级
、查询参数设置
、结果集封装
等内容。梳理整个流程,一来方便自己复习,学习优秀代码。二来也希望能够帮助到大家,话不多说,下面开始!
正文
selectOne方法
在Mybatis的数据库操作执行流程中,都会走到SqlSession
接口,而在Mybatis初始化过程中,返回的是DefaultSqlSession
,先看一下这个类的源码:
类中有多个重载的selectOne方法,我们就先看一下两个参数的方法,即
java
public class DefaultSqlSession implements SqlSession {
@Override
public <T> T selectOne(String statement, Object parameter) {
List<T> list = this.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;
}
}
方法入参说明:
- String statement:传入的是statementId,是一条sql的唯一标识。由xml文件中的
namespace
和sql标签的id属性的值
拼接而来,例如com.xx.mapper.UserMapper.getUserById
,com.xx.mapper.UserMapper
是namespace的值,getUserById
为select标签的id属性的值。 - Object parameter:传入的是sql的参数,这里是Object类型,因为传入的对象类型不固定。可能为一个字符串,也可能是一个User对象等等。
可以看出,上面的方法中,实际是调用了本类中的selectList
方法,同时将两个参数进行了传入。
获取到结果后,对List集合的元素个数进行了判断,如果为1,直接返回第一个元素
。如果大于1,则抛出异常
,否则,就直接返回null
。所以实际的查询逻辑 还是在selectList
中,下面看一下这个方法
selectList方法
selectList其实有多个重载的方法,如下
java
public class DefaultSqlSession implements SqlSession {
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
}
}
最后都会调用到一个四个参数的私有方法,这里先对方法的入参做一下说明
- String statement:statementId,一条sql的唯一标识。
- Object parameter:sql执行具体参数。
- RowBounds rowBounds:分页参数对象,根据该对象可以去实现逻辑分页。
- ResultHandler handler:结果集处理器,对查询结果进行处理封装。
java
public class DefaultSqlSession implements SqlSession {
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
}
在这个方法中,先通过调用全局配置对象configuration的getMappedStatement
方法,获取了MappedStatement对象,传入的参数是statement,也就是当前正在做查询操作的具体sql。
然后,将查询的操作继续委派给了底层的执行器,这个执行器的类型,应该是CachingExecutor
,因为在Mybatis去创建SqlSession对象的时候,会进行框架是否允许缓存的判断,如果允许,会创建CachingExecutor
对象,也就是执行器的装饰类,应用了装饰器模式,来对执行器的功能进行了增强(可见 Mybatis源码 - sqlSessionFactory.openSession()方法解析 创建事务对象 装饰器模式创建执行器)。
委派的过程中,参数也直接进行了传递,有所不同的是,sql的参数在这里进行了一层封装。
CachingExecutor.query()
那接着来看一下CachingExecutor的query方法
java
public class CachingExecutor implements Executor {
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
}
该方法的执行主要由三个步骤:
- 先通过MappedStatement对象,获取了
BoundSql
对象,该对象就是对每一条sql的封装。 - 创建了CacheKey对象,这里其实就是Mybatis本地缓存时使用的key,Mybatis本地缓存实际是一个Map集合,key为CacheKey对象,value为查询结果。
- 调用了本类方法query,执行了查询逻辑,并返回结果。
下面来详细分析一下每一步的细节
getBoundSql方法
MappedStatement类中的getBoundSql方法源码如下
java
public BoundSql getBoundSql(Object parameterObject) {
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings == null || parameterMappings.isEmpty()) {
boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
}
// check for nested result maps in parameter mappings (issue #30)
for (ParameterMapping pm : boundSql.getParameterMappings()) {
String rmId = pm.getResultMapId();
if (rmId != null) {
ResultMap rm = configuration.getResultMap(rmId);
if (rm != null) {
hasNestedResultMaps |= rm.hasNestedResultMaps();
}
}
}
return boundSql;
}
首先调用sqlSource对象的getBoundSql方法,获取BoundSql对象。这里的sqlSource对象是什么呢?
SqlSource是一个接口,它的实例由createSqlSource方法创建,它有不同的实现类,里面封装着解析完毕的sql语句,包括了完成占位符替换,参数值保存,动态标签解析等工作,换言之SqlSource中保存的就是 jdbc能够直接识别的sql语句(?作为占位符),请求参数的值
等(具体可见 Mybatis源码 - createSqlSource()方法解析 占位符替换 动态sql标签解析 )。
通过SqlSource获取到BoundSql对象之后,对parameterMappings进行了一些处理,例如 当parameterMappings为空时,说明sql语句中没有#{}
,这里创建了一个新的BoundSql对象,用来返回。还有会对ResultMapId不为null时进行了处理,这里是对嵌套结果集映射进行了处理。
方法的最后,返回了BoundSql对象。
createCacheKey方法
createCacheKey方法,顾名思义,创建一个对象,用来当作Mybatis本地缓存中的key,方法源码如下:
java
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
}
这里调用了成员变量delegate的createCacheKey方法,这里的delegate对象,实际上就是SimpleExecutor
,当前的执行器对象CachingExecutor,是对SimpleExecutor的一个包装。
而SimpleExecutor中并没有createCacheKey方法,那么就来到它的父类BaseExecutor
中,这里其实是父类BaseExecutor提供的一个createCacheKey方法,用来规范了CacheKey的创建标准。看一下源码:
java
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
for (ParameterMapping parameterMapping : parameterMappings) {
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
cacheKey.update(value);
}
}
if (configuration.getEnvironment() != null) {
// issue #176
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
第6行,创建了CacheKey对象,可以先在下面的标题中了解一下CacheKey类的结构。
可以看到,几乎将每个参数,包括运行环境的ID,都传入了CacheKey对象的update方法,这些操作都会更新CacheKey里面成员变量的值。
第14-30行,对parameterMappings
进行了处理,获取到了参数的值,最后也调用了CacheKey对象的update方法,更新了CacheKey。
方法最后,将创建好的CacheKey对象进行了返回。
拓展 - CacheKey类的结构
CacheKey源码如下,省略了部分代码
java
public class CacheKey implements Cloneable, Serializable {
private static final int DEFAULT_MULTIPLIER = 37;
private static final int DEFAULT_HASHCODE = 17;
private final int multiplier;
private int hashcode;
private long checksum;
private int count;
private List<Object> updateList;
public CacheKey() {
this.hashcode = DEFAULT_HASHCODE;
this.multiplier = DEFAULT_MULTIPLIER;
this.count = 0;
this.updateList = new ArrayList<>();
}
public void update(Object object) {
int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
count++;
checksum += baseHashCode;
baseHashCode *= count;
hashcode = multiplier * hashcode + baseHashCode;
updateList.add(object);
}
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (!(object instanceof CacheKey)) {
return false;
}
final CacheKey cacheKey = (CacheKey) object;
if (hashcode != cacheKey.hashcode) {
return false;
}
if (checksum != cacheKey.checksum) {
return false;
}
if (count != cacheKey.count) {
return false;
}
for (int i = 0; i < updateList.size(); i++) {
Object thisObject = updateList.get(i);
Object thatObject = cacheKey.updateList.get(i);
if (!ArrayUtil.equals(thisObject, thatObject)) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
return hashcode;
}
}
可以看到几个细节:
- 类中定义了一系列的成员变量,例如
multiplier、hashcode、checksum、count
等,这些在update
方法中都会重新进行计算,update方法传入的参数为Object,意味着可以传入任何类型的参数,在每传入一个参数,都会将上面的成员变量进行重新计算,最后会将这个参数添加到updateList这个集合中。 - 因为CacheKey对象,将来会放到Map集合中作为key来使用,所以为了
避免哈希冲突
,这里也对equals方法和hashCode方法进行了重写
,制定了自己的计算规则。
本类query方法
在上面获取完BoundSql和创建完CahceKey之后,调用了本类中的query方法,源码如下
java
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
方法一开始,执行了Cache cache = ms.getCache();
,这里是从MappedStatement获取cache,这里是先来判断 是否开启了二级缓存
,因为MappedStatement是解析了映射配置文件而来的,所以这里如果想要开启二级缓存,需要在映射配置文件中,添加<cache/>
标签。
如果配置了二级缓存,那么执行流程如下:
- 第6行:会先调用
flushCacheIfRequired
方法,这个方法会判断当前sql,是否配置了flushCache
为true,如果是的话,将会清除二级缓存。也就是说:如果当前mapper中配置了<cache/>
标签,开启了二级缓存。但是sql中又配置了flushCache
为true,那么相当于是不走缓存的,还是去查询数据库。 - 第7行:会判断当前sql是否配置了useCache为true,并且resultHandler==null,满足的话才会走下面的查询二级缓存逻辑。
- 第10-14行:从二级缓存中如果查不到数据,会调用delegate.query方法,去查询数据库,这里的delegate实际上是
BaseExecutor
。查询出数据之后,返回结果之前,会将查询出的结果再放回二级缓存(此处只是存入一个Map集合,并未真正放入二级缓存)。
如果没有配置二级缓存,那么将直接执行18行的delegate.query方法,去查询数据库。
这里可以总结出:二级缓存的开启方式是在mapper.xml文件中配置标签,同时,查询过程中 二级缓存是优先于一级缓存和数据库的。
BaseExecutor.query()
下面看一下,查询一级缓存以及数据库的流程,在前面的内容可以看出,最后调用的是BaseExecutor这个父类里面的query方法,下面看一下源码:
java
@Override
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());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
//如果sql配置了flushCache,并且queryStack == 0,那么将清除一级缓存
//queryStack在这里应该是一个计数用的控制属性
if (queryStack == 0 && ms.isFlushCacheRequired()) {
//清除一级缓存,即PerpetualCache类中的一个Map集合
clearLocalCache();
}
List<E> list;
try {
queryStack++;
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
//从一级缓存中查询到了数据,处理结果输出参数(只有StatementType是CALLABLE时)
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
//从一级缓存中查不到数据,调用queryFromDatabase去查询数据库
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) {
// issue #482
clearLocalCache();
}
}
return list;
}
第9行:判断了ms.isFlushCacheRequired()
,即sql中是否配置了flushCache属性,并且queryStack == 0的话,将清除一级缓存。
第16-23行:查询了一级缓存,如果没有数据,调用queryFromDatabase
方法查询数据库,如果一级缓存中有数据,且StatementType == CALLABLE时(存储过程),会做一些结果输出参数的处理。
queryFromDatabase方法
该方法就是去查询数据库,看一下源码:
java
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
第3-9行:这里先在一级缓存中占了个位,然后调用了doQuery方法查询数据库,finally块中又删除了一级缓存。然后又将查询出来的结果,放到了一级缓存中,最后将list返回。
查询数据时 调用了doQuery
来查询数据,下面看一下这个方法。
SimpleExecutor.doQuery()
上面调用的doQuery方法,实际是SimpleExecutor中的方法。看一下源码:
java
@Override
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 handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
第5行:通过MappedStatement对象获取到了全局配置对象。
第6行:通过全局配置对象的newStatementHandler
方法,获取到StatementHandler
对象,这个是语句处理器,语句处理器是一个接口,有多个子类,这里返回的是RoutingStatementHandler
,是一个装饰器类。类图如下:
newStatementHandler方法
看一下newStatementHandler
的源码:
java
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
第2行:直接创建了RoutingStatementHandler
对象
第3行:实现插件逻辑。
java
public class RoutingStatementHandler implements StatementHandler {
private final StatementHandler delegate;
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
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());
}
}
}
可以看出,RoutingStatementHandler
就是一个装饰类,里边有一个成员变量delegate
来封装StatementHandler实例的引用,构造器中通过判断statementType
(在sql语句的标签中配置属性值),为成员变量赋值了不同的语句处理器。
prepareStatement方法
接下来通过该方法,创建了Statement对象,看一下这个方法的源码:
java
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
先对方法的入参做一个说明:
- StatementHandler handler:这就是语句处理器,传入的是
RoutingStatementHandler
,是一个装饰器类。 - Log statementLog:传入的是ms.getStatementLog(),是MappedStatement中的statementLog属性。
第3行:获取了Connection对象,获取过程中,会根据是否开启debug
,返回不同的对象。如果开启了debug,会返回一个代理对象
,否则就是普通的Connection对象。
java
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
第4行:调用RoutingStatementHandler
的prepare
方法,获取了Statement对象,获取过程中也会根据不同的情况,返回不同的对象。
java
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
statement = instantiateStatement(connection);
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);
}
}
继续调用instantiateStatement
方法,获取Statement对象。
java
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
return connection.prepareStatement(sql);
} else {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
}
}
handler.parameterize() 参数设置
接下来通过该方法进行参数的设置,调用到PreparedStatementHandler
中后,会委派给ParameterHandler
来进行参数的设置,这是一个接口,默认的实现类为DefaultParameterHandler
,下面来看一下这个类里边的setParameters
方法,该方法完成了参数的设置。
java
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
第4-5行:首先会获取存放参数的集合,参数集合不为空,才去遍历处理。
第8-20行:进行一系列的判断处理,例如 判断参数不是出参后,才继续处理等等。
第21-25行:获取到类型处理器TypeHandler
,获取到JdbcType
,还进行了jdbcType的校验。
第27行:通过TypeHandler
的setParameter
方法,完成了参数的设置。这里参数是从1开始的,所以设置时下标为i+1
。
handler.query()方法
SimpleExecutor.doQuery()方法中的最后一行,执行查询操作。源码第8行 ↓
java
@Override
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 handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
最后会调用到PreparedStatementHandler
的query
方法。
java
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.handleResultSets(ps);
}
第4行:执行sql语句
第5行:通过ResultSetHandler对结果集进行封装处理
ResultSetHandler处理结果集
Mybatis是通过ResultSetHandler
的handleResultSets
方法来对查询结果进行处理的。该方法传入一个Statement
,返回List<Object>
集合,看一下源码:
java
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
//如果存在多个结果集,那么直接取出第一个
return collapseSingleResultList(multipleResults);
}
第8行:调用了getFirstResultSet
,获取到第一个结果集,这里将返回值封装成了一个包装类ResultSetWrapper
,看一下这个类的构造器:
java
public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
super();
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.resultSet = rs;
final ResultSetMetaData metaData = rs.getMetaData();
final int columnCount = metaData.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
classNames.add(metaData.getColumnClassName(i));
}
}
可以看出,这里不只封装了查询结果集,还封装了一些元数据信息,例如字段名、jdbcType等等。
第10-12行,获取了mapper中配置的ResultMap的个数,并进行了一些校验。
第13-19行:开始对结果集进行封装,重点在于15行的handleResultSet方法,该方法完成了结果集的封装,将结果封装到了multipleResults中,下面看一下源码
handleResultSet() 封装结果集
java
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
if (parentMapping != null) {
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
if (resultHandler == null) {
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
multipleResults.add(defaultResultHandler.getResultList());
} else {
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
closeResultSet(rsw.getResultSet());
}
}
第7行:创建了DefaultResultHandler
。
第8行:调用了handleRowValues
方法,完成结果集封装。
第9行:将defaultResultHandler中的结果集添加到multipleResults
,也就是最终结果集中。
handleRowValues方法
java
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
if (resultMap.hasNestedResultMaps()) {
ensureNoRowBounds();
checkResultHandler();
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
这里看一下简单结果映射的方法handleRowValuesForSimpleResultMap
java
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
ResultSet resultSet = rsw.getResultSet();
skipRows(resultSet, rowBounds);
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
//通过<resultMap>标签的子标签<discriminator>对结果映射进行鉴别
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
//将查询结果封装到pojo中
Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
//保存映射结果
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
第4行:获取到结果集数据。
第5行:根据分页信息,提取相应记录。
第6-10:处理赋值多条记录。
第12行:保存映射结果。
getRowValue方法
java
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
if (shouldApplyAutomaticMappings(resultMap, false)) {
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
return rowValue;
}
第3行:根据resultType,创建一个对应的返回对象。
第5行:调用全局配置对象的newMetaObject
方法,根据传入的rowValue
,创建一个包含元数据的MetaObject对象。
第7行:判断是否要进行自动映射。(如果配置了resultType
,则进行自动映射)。如果是,则调用applyAutomaticMappings
进行循环赋值。