起因:在工作中常常要用到mybatis框架,如果对其执行流程不清楚的话,就会有一种出了bug不知道要去什么地方找的尴尬。本文为学习《Mybatis源码深度解析》后的总结。感谢江荣波的这本书。
MyBatis通过动态代理将Mapper方法的调用转换为调用SqlSession提供的增删改查方法,以Mapper的Id作为参数,执行数据库的增删改查操作
以SELECT语句为例介绍SqlSession执行Mapper的过程。SqlSession接口只有一个默认的实现,即DefaultSqlSession。DefaultSqlSession类对SqlSession接口中定义的selectList()方法的实现:
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
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();
}
}
在DefaultSqlSession的selectList()方法中,首先根据Mapper的Id从Configuration对象中获取对应的MappedStatement对象,然后以MappedStatement对象作为参数,调用Executor实例的query()方法完成查询操作。
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
在BaseExecutor类的query()方法中,首先从MappedStatement对象中获取BoundSql对象,BoundSql类中封装了经过解析后的SQL语句及参数映射信息。然后创建CacheKey对象,该对象用于缓存的Key值。接着调用重载的query()方法。
@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;
}
在重载的query()方法中,首先从MyBatis一级缓存中获取查询结果,如果缓存中没有,则调用BaseExecutor类的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 {
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;
}
在queryFromDatabase()方法中,调用doQuery()方法进行查询,然后将查询结果进行缓存,doQuery()是一个模板方法,由BaseExecutor子类实现。在学习MyBatis核心组件时,我们了解到Executor有几个不同的实现,分别为BatchExecutor、SimpleExecutor和ReuseExecutor。
在SimpleExecutor类的doQuery()方法中,首先调用Configuration对象的newStatementHandler()方法创建StatementHandler对象。newStatementHandler()方法返回的是RoutingStatementHandler的实例。在RoutingStatementHandler类中,会根据配置Mapper时statementType属性指定的StatementHandler类型创建对应的StatementHandler实例进行处理,例如statementType属性值为SIMPLE时,则创建SimpleStatementHandler实例。
@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.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
StatementHandler对象创建完毕后,接着调用SimpleExecutor类的prepareStatement()方法创建JDBC中的Statement对象,然后为Statement对象设置参数操作。Statement对象初始化工作完成后,再调用StatementHandler的query()方法执行查询操作。
SimpleExecutor类中prepareStatement()方法的具体内容
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;
}
在SimpleExecutor类的prepareStatement()方法中,首先获取JDBC中的Connection对象,然后调用StatementHandler对象的prepare()方法创建Statement对象,接着调用StatementHandler对象的parameterize()方法(parameterize()方法中会使用ParameterHandler为Statement对象设置参数)。具体逻辑读者可以参考MyBatis对应的源代码。MyBatis的StatementHandler接口有几个不同的实现类,分别为SimpleStatementHandler、PreparedStatementHandler和CallableStatementHandler。MyBatis默认情况下会使用PreparedStatementHandler与数据库交互。接下来我们了解一下PreparedStatementHandler的query()方法的实现,
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
在PreparedStatementHandler的query()方法中,首先调用PreparedStatement对象的execute()方法执行SQL语句,然后调用ResultSetHandler的handleResultSets()方法处理结果集。ResultSetHandler只有一个默认的实现,即DefaultResultSetHandler类,DefaultResultSetHandler处理结果集的逻辑在第4章介绍MyBatis核心组件时已经介绍过了。
@Override
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;
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);
}
DefaultResultSetHandler类的handleResultSets()方法具体逻辑如下:
(1)首先从Statement对象中获取ResultSet对象,然后将ResultSet包装为ResultSetWrapper对象,通过ResultSetWrapper对象能够更方便地获取数据库字段名称以及字段对应的TypeHandler信息。
(2)获取Mapper SQL配置中通过resultMap属性指定的ResultMap信息,一条SQL Mapper配置一般只对应一个ResultMap。
(3)调用handleResultSet()方法对ResultSetWrapper对象进行处理,将结果集转换为Java实体对象,然后将生成的实体对象存放在multipleResults列表中。
(4)调用collapseSingleResultList()方法对multipleResults进行处理,如果只有一个结果集,就返回结果集中的元素,否则返回多个结果集。
MyBatis如何通过调用Mapper接口定义的方法执行注解或者XML文件中配置的SQL语句这一整条链路介绍完毕。
不要以为每天把功能完成了就行了,这种思想是要不得的,互勉~!