Mybatis执行流程源码解析
上一文中,我们将Mybatis的配置解析说清楚,在此基础之上,再来看执行流程,会相对清晰和简单一些。
常规的Mybatis执行流程总体分为3步:
- 获取SqlSession
- 通过SqlSession获取Mapper接口实例
- 通过Mapper接口执行对应的方法
下面我们一步一步的来分析下整个过程。
获取SqlSession
SqlSession是从SqlSessionFactory中返回的,在前一文中,我们知道,默认使用的DefaultSqlSessionFactory。从最简单的无参DefaultSqlSessionFactory#openSession()方法走起:
public SqlSession openSession() {
//从数据源打开一个新的Session
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
跟入DefaultSqlSessionFactory#openSessionFromDataSource方法,这就是打开一个新session的逻辑:
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
//从configuration中获取environment,环境中就包含了Datasource和TransactionFactory
final Environment environment = configuration.getEnvironment();
//从环境中获取事务工厂(事务工厂是用来创建事务对象的)
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
//通过事务工厂创建一个新的事务。
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//关键方法,通过configuration来构建一个新的执行器Executor。
final Executor executor = configuration.newExecutor(tx, execType);
//构建一个新的DefaultSqlSession对象返回,其核心属性是executor。
return new DefaultSqlSession(configuration, executor, autoCommit);
} .....
}
先看下新事务是如何创建的,以示例代码配置<transactionManager type="JDBC"/>
为例,在解析配置中,会创建JdbcTransactionFactory类型的事务工厂,它JdbcTransactionFactory#newTransaction方法会创建一个新的JdbcTransaction类型的事务:
public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
//创建一个新的事务对象,里面会包含数据源。
return new JdbcTransaction(ds, level, autoCommit);
}
接下来,看看核心对象Executor是如何构建出来的,Configuration#newExecutor方法:
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
//确定执行器类型,默认使用SIMPLE。
executorType = executorType == null ? defaultExecutorType : executorType;
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
//创建一个SimpleExecutor,并传入configuration对象和前面创建的transaction对象。
executor = new SimpleExecutor(this, transaction);
}
//如果打开二级缓存,默认是打开的。注意想要二级缓存生效,除了这个开关,还必须配置cache才行。
if(cacheEnabled) {
//使用CachingExecutor对当前的执行器包装,典型的装饰者模式使用。
executor = new CachingExecutor(executor);
}
//使用插件链对当前的执行器进行包装
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
在InterceptorChain#pluginAll方法中,依次回调插件的plugin方法,对executor进行一层层的包装:
public Object pluginAll(Object target) {
//遍历所有插件,执行插件的plugin方法,传入要扩展的目标对象。
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
//返回执行插件plugin方法之后的对象。
return target;
}
使用构建好的executor对象,new一个DefaultSqlSession返回,这就完成了SqlSession获取。
在Mybaits中有4大插件扩展点,Executor就是其中之一,我们可以编写插件,拦截Executor的所有方法,执行额外的逻辑。另外3个扩展点,随着执行流程的推进,会一个一个浮出水面。
通过SqlSession直接查询
为了不让Mapper接口的方式产生干扰,先把清楚执行的核心脉络,先跳过Mapper接口方式,直接使用sqlsession进行操作,比如sqlSession.selectList("com.zyy.demo.mapper.CountryMapper.selectAll");
。
在DefaultSqlSession中,不会有具体的执行逻辑,具体逻辑封装到Executor中,常规的门面模式使用。
DefaultSqlSession#selectList方法逻辑:
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//通过语句ID获取MappedStatement,这都是在初始化时解析好的。
MappedStatement ms = configuration.getMappedStatement(statement);
//使用执行器,来执行对应的MappedStatement
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} ...
}
通过前面的执行器构造过程,我们知道这儿调用的执行器有可能是插件包装过了的,就算没有配置插件,默认也是被CachingExecutor包装过的,插件的先抛开不说,这是要和具体的插件逻辑绑定。 而CachingExecutor的逻辑参考 Mybatis缓存源码详解 的二级缓存源码解析。( https://blog.csdn.net/gruelxsp/article/details/103769381 ),详细的介绍了二级缓存的构建过程,如何通过装饰者模式来完成整个缓存的各种功能职责划分。这儿就不再解析一遍,因此我们直接定位到最终的SimpleExecutor上来,SimpleExecutor继承至BaseExecutor,执行流程的大部分逻辑在BaseExecutor中,BaseExecutor#query方法:
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//获取
BoundSql boundSql = ms.getBoundSql(parameter);
//生成缓存key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
//执行查询
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
跟入query方法:
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
...
//有必要的话,情况本地缓存
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
...
//从本地缓存获取
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
...//本地缓存获取到了时,其他处理
} else {
//本地缓存没获取到,从数据库查询
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
...//其他处理
return list;
}
当然继续跟queryFromDatabase方法
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 {
//具体的数据库查询逻辑,这儿就会调用到SimpleExecutor上了。
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
//本地缓存移除占位的
localCache.removeObject(key);
}
//本地缓存放入数据库查询结果
localCache.putObject(key, list);
...
return list;
}
内部的doQuery方法,利用了模板方法模式,具体实现由子类SimpleExecutor实现,进入SimpleExecutor#doQuery方法:
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的构建,也是通过configuration来构建的
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//准备JDBC驱动的statement对象(语句对象)
stmt = prepareStatement(handler, ms.getStatementLog());
//调用StatementHandler的query方法,来进行具体的数据查询。
return handler.<E>query(stmt, resultHandler);
} finally {
//关闭语句。
closeStatement(stmt);
}
}
这儿的三个方法都很重要,我们需要一个一个拆开瞧仔细了。
Configuration#newStatementHandler方法类构建核心对象StatementHandler:
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//new一个RoutingStatementHandler,这儿又是用了装饰者模式。
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
//使用插件链对构建的RoutingStatementHandler进行扩展。
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
看下RoutingStatementHandler的实现:
public class RoutingStatementHandler implements StatementHandler {
//包装的实际StatementHandler
private final StatementHandler delegate;
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//在构造方法中,通过语句类型,选择不同的StatementHandler。
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
//默认都是用PreparedStatementHandler,其他的几乎可以不用管。
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());
}
}
//其他实现的StatementHandler接口方法,均是直接调用delegate的对应方法。
可以看出RoutingStatementHandler仅仅做了一件事情,就在在构造方法中判断语句的类型,然后new一个对应的StatementHandler出来,默认使用的是PreparedStatementHandler这个,在new PreparedStatementHandler时,其内部又初始化了两个核心的对象ParameterHandler、ResultSetHandler:
protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
this.configuration = mappedStatement.getConfiguration();
this.executor = executor;
this.mappedStatement = mappedStatement;
this.rowBounds = rowBounds;
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.objectFactory = configuration.getObjectFactory();
if (boundSql == null) { // issue #435, get the key before calculating the statement
generateKeys(parameterObject);
boundSql = mappedStatement.getBoundSql(parameterObject);
}
this.boundSql = boundSql;
//核心对象ParameterHandler的构建,通过configuration来构建的
this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
//核心对象ResultSetHandler的构建,也是通过configuration来构建的
this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
}
通过configuration.newParameterHandler方法来构建核心对象ParameterHandler:
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
//通过mappedStatement获取到具体的LanguageDriver,通过它来构建ParameterHandler
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
//使用插件链对构建的ParameterHandler进行扩展。
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
以XMLLanguageDriver(默认)为例,创建ParameterHandler就是new一个DefaultParameterHandler对象,XMLLanguageDriver#createParameterHandler:
public ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
return new DefaultParameterHandler(mappedStatement, parameterObject, boundSql);
}
同样,通过configuration.newResultSetHandler方法来构建核心对象ResultSetHandler:
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
//new一个DefaultResultSetHandler对象
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
//使用插件链对构建的ResultSetHandler进行扩展。
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
到此,我们拆解了第一方法,StatementHandler是如何构建出来的。同时我们也见识了Mybaits的除Executor之外的其他3个插件扩展点。不知道大家有没有发现一个特点,Mybatis的4大核心对象Executor、StatementHandler、ParameterHandler、ResultSetHandler的创建,都是通过configuration.newXXX方法来构建的。
现在来看看第二个方法:
//准备JDBC驱动的statement对象(语句对象)
stmt = prepareStatement(handler, ms.getStatementLog());
SimpleExecutor#prepareStatement方法:
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//获取连接
Connection connection = getConnection(statementLog);
//调用StatementHandler.prepare方法,准备Statement对象
stmt = handler.prepare(connection, transaction.getTimeout());
//调用StatementHandler.parameterize方法,为Statement对象设置参数
handler.parameterize(stmt);
return stmt;
}
在父类BaseExecutor#getConnection方法中,
protected Connection getConnection(Log statementLog) throws SQLException {
//通过事务对象来获取数据库连接(事务对象中有Datasouce,最终连接是从Datasource中获取的)
Connection connection = transaction.getConnection();
//如果打开了Debug日志,会使用JDK动态代理生成连接的代理对象返回。(这个连接代理对象生成Statement语句时也会生成代理语句对象,实现答应日志功能)
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
以JdbcTransaction为例,其获取连接的逻辑如下:
public Connection getConnection() throws SQLException {
//缓存的Connection为null,则打开一个连接
if (connection == null) {
openConnection();
}
return connection;
}
protected void openConnection() throws SQLException {
//从dataSource中获取连接
connection = dataSource.getConnection();
if (level != null) {
connection.setTransactionIsolation(level.getLevel());
}
setDesiredAutoCommit(autoCommmit);
}
获取数据库连接这块,这儿展开解析,因为当Mybatis和Spring集成后,数据源是有Spring管理的,就会调用到spring的代码上了。
获取连接后,就可以通过连接来生成Statement对象了,stmt = handler.prepare(connection, transaction.getTimeout());
,同样定位到最终的 BaseStatementHandler#prepare方法上来,这又是一个模板方法:
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
...
Statement statement = null;
try {
//调用子类来生成具体的Statement,默认使用子类是PreparedStatementHandler
statement = instantiateStatement(connection);
//设置Statement的属性
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} ...
}
那么来看下子类PreparedStatementHandler#instantiateStatement方法实现:
protected Statement instantiateStatement(Connection connection) throws SQLException {
//获取sql,此时的sql是已经处理好的,可以预编译的sql。
String sql = boundSql.getSql();
//KeyGenerator相关处理
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() != null) {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
//调用连接的prepareStatement方法,创建一个预编译的statement。
return connection.prepareStatement(sql);
}
}
PreparedStatement对象创建好了之后,就可以往里面设置参数了,调用StatementHandler.parameterize方法,为Statement对象设置参数,handler.parameterize(stmt);
,同理,抛开可能的插件逻辑,直接定位到PreparedStatementHandler#parameterize方法:
public void parameterize(Statement statement) throws SQLException {
//委托给StatementHandler内部的parameterHandler来进行参数设置。
parameterHandler.setParameters((PreparedStatement) statement);
}
在前面我们知道parameterHandler是new的一个DefaultParameterHandler对象,因此直接进入DefaultParameterHandler#setParameters方法:
public void setParameters(PreparedStatement ps) {
...
//拿到SQL语句中需要设置的参数列表
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 = 获取对应参数值(这儿代码比较繁琐,直接省略了)
//获取参数对应的TypeHandler
TypeHandler typeHandler = parameterMapping.getTypeHandler();
...
try {
//调用对应typeHandler的setParameter方法来设置参数。
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} ...
}
}
}
}
可以看到ParameterHandler内部,最终使用的是typeHandler来完成设置参数值的操作的。
现在第二步准备JDBC驱动的statement对象(语句对象)完成了。
最后来第三步,完成最后的数据库查询。
//调用StatementHandler的query方法,来进行具体的数据查询。
return handler.<E>query(stmt, resultHandler);
同理,忽略各种包装,定位到PreparedStatementHandler#query方法:
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
//执行语句
ps.execute();
//通过StatementHandler中的resultSetHandler来处理结果集。
return resultSetHandler.<E> handleResultSets(ps);
}
同样,在构建StatementHandler时,其内部构建的resultSetHandler是DefaultResultSetHandler,因此看看它是如何处理结果集的,DefaultResultSetHandler#handleResultSets方法:
public List<Object> handleResultSets(Statement stmt) throws SQLException {
...
final List<Object> multipleResults = new ArrayList<Object>();
//结果集数
int resultSetCount = 0;
//获取第一个结果集(默认使用的是PreparedStatement,只会有一个结果集)
ResultSetWrapper rsw = getFirstResultSet(stmt);
//从mappedStatement获取配置的ResultMap,这儿返回的是列表,通常只会有一个,对应一个结果集
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);
//获取下一个结果集(PreparedStatement只会有一个)
rsw = getNextResultSet(stmt);
//处理完一个结果集后,清除临时数据。
cleanUpAfterHandlingResultSet();
//结果数+1
resultSetCount++;
}
...
//包装一下返回。对但结果集来说,返回的就是multipleResults.get(0)这个列表。
return collapseSingleResultList(multipleResults);
}
跟入最关键的handleResultSet方法:(省略了其他分支,不代表不重要)
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
...
//创建一个DefaultResultHandler,它功能很简单,就是把映射的结果对象放到一个list中去
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
//处理结果集的数据
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
//把映射的结果List取出来,放到multipleResults这个list中去(multipleResults的结构就是List<List<Object>>。
multipleResults.add(defaultResultHandler.getResultList());
...
}
继续跟进handleRowValues方法(同样省略一些分支):
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
...
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
继续handleRowValuesForSimpleResultMap方法:
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
//创建一个DefaultResultContext上下文对象,用于保存结果映射过程中的数据和状态
DefaultResultContext<Object> resultContext = new DefaultResultContext<Object>();
//这个方法有意思,这就是Mybatis默认分页内存分页方式,结果集是有全部数据,通过这个方法,将结果集定位到想要的数据行上。
skipRows(rsw.getResultSet(), rowBounds);
//循环获取结果集中每一行数据,进行映射处理。注意这儿的退出条件是,取够想要的一页数据,或者结果集中已经没有数据了。
while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) {
//鉴别器映射特性处理(感兴趣可看下)
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
// 这才是最核心的地方,结果集映射为我们想要的JAVA对象。
Object rowValue = getRowValue(rsw, discriminatedResultMap);
// 把映射生成出来的java对象保存到上下文中,并通过resultHandler进行处理(默认就是放到一个list中去)
storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());
}
}
先看下比较的简单的skipRows方法,用于将结果集定位到某一行数据上:
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();
}
}
}
关键的映射处理代码DefaultResultSetHandler#getRowValue方法:
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
//构建一个空的java对象出来。可以简单的理解为使用objectFactory.create方法来实例化一个对应的java类型对象出来,实际情况比这个会复制得多,就不展开细看了。
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
//为刚创建的新java对象构建一个MetaObject对象出来,MetaObject在这儿的主要作用是为我们的java对象设置属性值(通过反射的方式)
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
//先通过自动映射的方式设置一遍值
if (shouldApplyAutomaticMappings(resultMap, false)) {
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues;
}
//然后再通过属性映射设置一遍值
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
//这儿是当没有设置任何值时,是返回一个空的对象呢(即属性都没有设置值),还是返回null
rowValue = (foundValues || configuration.isReturnInstanceForEmptyRow()) ? rowValue : null;
}
return rowValue;
}
自动映射的设置值applyAutomaticMappings方法:
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
//自动映射,这儿为自动推断结果集映射出来,并缓存起来。具体的推断逻辑不展开
List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
boolean foundValues = false;
if (autoMapping.size() > 0) {
//遍历每一个映射属性,通过typeHandler从结果集中获取值,然后通过前面创建的metaObject设置到我们的java对象中去。
for (UnMappedColumnAutoMapping mapping : autoMapping) {
//调用自动推断出来的typeHandler的getResult方法,获取结果集中对应字段的值
final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
if (value != null) {
foundValues = true;
}
if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
// gcode issue #377, call setter on nulls (value is not 'found')
//把获取的值设置到java对象中对应的属性上去。
metaObject.setValue(mapping.property, value);
}
}
}
return foundValues;
}
在来看看属性映射 applyPropertyMappings 方法(省去部分分支):
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)
throws SQLException {
final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
boolean foundValues = false;
//拿到属性映射列表
final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
//遍历每个属性映射配置,
for (ResultMapping propertyMapping : propertyMappings) {
String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
...
//获取结果集中对应字段的值,可简单的理解为typeHandler.getResult(rs, column)来获取值,实际情况因为要处理内嵌的情况,比较复杂,不展开。
Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);
//获取属性名
final String property = propertyMapping.getProperty();
...
//把获取的值设置到java对象中对应的属性上去。
metaObject.setValue(property, value);
...
}
return foundValues;
}
到此,终于分析完了(初略的)第三步的,执行数据库查询和结果集映射处理,可以看出结果集的映射代码逻辑相比于其他功能,还是很复杂的。
到这儿,经典的selectList方法分析完成,不过可能有点绕晕了,有必要来个图总结下,加深点映像。
通过Mapper接口查询
通过Mapper接口方法来进行查询,是常规是用方式,可以拆解为两步来分析:
- 通过SqlSession获取Mapper接口实例
- 通过Mapper接口执行对应的方法
第一步,通过sqlSession.getMapper(SysUserMapper.class);
方式来获取Mapper接口的一个代理对象,定位到DefaultSqlSession#getMapper方法:
public <T> T getMapper(Class<T> type) {
//委托给configuration的getMapper方法
return configuration.<T>getMapper(type, this);
}
进入到Configuration#getMapper方法:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//委托给mapperRegistry的getMapper方法
return mapperRegistry.getMapper(type, sqlSession);
}
再进入到MapperRegistry#getMapper方法,这里面就是来创建mapper接口代理对象的地方:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//首先获取到对应mapper类型的MapperProxyFactory,这个MapperProxyFactory是在解析的过程中构建好的。
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
...
//通过MapperProxyFactory来构建一个代理对象返回。
return mapperProxyFactory.newInstance(sqlSession);
...
}
跟进去MapperProxyFactory#newInstance方法:
public T newInstance(SqlSession sqlSession) {
//new一个MapperProxy对象,注意MapperProxy是实现了InvocationHandler接口的。
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
//调用newInstance(MapperProxy<T> mapperProxy)方法,它会使用jdk动态代理生成代理对象。
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
//通过jdk的动态代理生成mapper接口的代理对象返回。
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
到这儿,第一步获取mapper接口的代理对象完成。
第二步,调用Mapper接口中定义的方法,如userMapper.selectById((long) 1);
,它会调用到代理对象上去,即会调用到MapperProxy#invoke方法上去:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//进行一些额外的处理。
...
//获取MapperMethod对象,这儿会有一层缓存处理
final MapperMethod mapperMethod = cachedMapperMethod(method);
//通过MapperMethod的execute方法来执行具体的逻辑。
return mapperMethod.execute(sqlSession, args);
}
简单看下cachedMapperMethod方法:
private MapperMethod cachedMapperMethod(Method method) {
//从缓存中获取
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
//没有获取到就new一个出来,并放到缓存中,主要构建SqlCommand和MethodSignature这两个对象保存起来。
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
主要还是来看下MapperMethod#execute方法,就是通过它将执行转移到了sqlsession上:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
//insert语句,调用到sqlSession.insert方法
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
//UPDATE语句,调用到sqlSession.update方法
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
//UPDATE语句,调用到sqlSession.delete方法
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
//select语句,根据方法的返回结果类型,进行不同的逻辑处理。
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
//里面会调用sqlSession.select方法
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
//里面会调用sqlSession.selectList方法
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
//里面会调用sqlSession.selectMap方法
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
//里面会调用sqlSession.selectCursor方法
result = executeForCursor(sqlSession, args);
} else {
//返回结果是常规java对象时,直接调用sqlSession.selectOne方法。
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
...
return result;
}
调用到了SqlSession的方法上之后,后续的流程就和通过SqlSession直接查询的逻辑一样了。因此通过Mapper接口这种方式大大的提高了使用的便利性,但其实现并不复杂,只是通过JDK动态代理为Mapper接口生成代理对象,代理逻辑中将方法调用转移到SqlSession对应的方法上即可。