MyBatis框架原理2:SqlSession运行过程
获取SqlSession对象
SqlSession session = sqlSessionFactory.openSession();
首先通过SqlSessionFactory的openSession方法获取SqlSession接口的实现类DefaultSqlSession对象。
public interface SqlSessionFactory {
SqlSession openSession();
SqlSession openSession(boolean autoCommit);
SqlSession openSession(Connection connection);
SqlSession openSession(TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType);
SqlSession openSession(ExecutorType execType, boolean autoCommit);
SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level);
SqlSession openSession(ExecutorType execType, Connection connection);
Configuration getConfiguration();
}
SqlSessionFactory接口提供一系列重载的openSession方法,其参数如下:
- boolean autoCommit:是否开启JDBC事务的自动提交,默认为false。
- Connection:提供连接。
- TransactionIsolationLevel:定义事务隔离级别。
- ExecutorType:定义执行器类型。
DefaultSqlSessionFactory对象调用覆写的openSession方法:
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
得到一个定义了ExecutorType为configuration的默认执行器SIMPLE,事务隔离级别为null,JDBC事务自动提交为false的DefaultSqlSession对象。
获取MapperProxy代理对象
有了DefaultSqlSession对象,以查询一条数据为例,来看一下整个处理过程。
For example:
SqlSession session = sqlSessionFactory.openSession();
try {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);
} finally {
session.close();
}
MyBatis时序图:
根据MyBatis文档推荐的方法,调用Mapper接口中的方法实现对数据库的操作,上述例子中根据blog ID获取Blog对象。
通过DefaultSqlSession对象的getMapper方法获取的是一个MapperProxy代理对象,这也是Mapper接口不用实现类的原因。当调用BlogMapper中的方法时,由于BlogMapper是一个JDK动态代理对象,它会运行invoke方法,代码如下:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//判断代理对象是否是一个类
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//生成MapperMethod对象
final MapperMethod mapperMethod = cachedMapperMethod(method);
//执行execute方法
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
...
invoke方法判断代理的对象是否是一个类,由于代理对象是一个接口,所以通过cachedMapperMethod生成一个MappedMethod对象,然后执行execute方法,execute方法代码如下:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
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());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
因为这里是根据ID查询一个对象,所以最终调用了DefaultSqlSession的selectOne方法,selectOne方法又调用自身selectList方法,最终将查询操作委托给Executor:
@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
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);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//根据id获取MappedStatement对象
MappedStatement ms = configuration.getMappedStatement(statement);
//wrapCollection方法处理集合参数
//委托Exector执行SQL
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();
}
}
Executor
Executor在MyBatis加载全局配置文件时初始化,我们可以在全局配置文件settings元素中配置Executor类型,MyBatis默认使用SimpleExecutor,如果开启了二级缓存,则再用CachingExecutor进行包装。
如果开启了二级缓存,SqlSession调用CachingExecutor执行器的query方法,先从二级缓存获取数据,当无法从二级缓存获取数据时,则委托给BaseExecutor的子类进行操作,CachingExecutor执行过程代码如下:
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);
//如果二级缓存没有数据则委托给BaseExcutor的子类进行操作
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
//如果没有二级缓存则委托给BaseExcutor的子类进行操作
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
MyBatis默认使用SimpleExecutor,调用父类BaseExecutor的query方法:
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.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
//从一级缓存获取数据
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) {
// issue #482
clearLocalCache();
}
}
return list;
}
最后,queryFromDatabase方法则调用SimpleExecutor的doQuery方法,通过Configuration构建StatementHandler对SQL和参数编译,parameterize()方法通过ParameterHandler对参数进行设置,使用TypeHandler转换参数类型,执行查询后再通过ResultSetHandler封装结果并返回,其方法代码如下:
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();
//根据Configuration构建StatementHandler
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
//编译SQL
stmt = prepareStatement(handler, ms.getStatementLog());
//ResultSetHandler处理结果,并返回处理后的结果
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
//处理SQL参数
handler.parameterize(stmt);
return stmt;
}
通过以上流程发现,MyBatis核心工作实际上是由Executor、StatementHandler、ParameterHandler和ResultSetHandler四个接口完成的,掌握这四个接口的工作原理,对理解MyBatis底层工作原理有很大帮助。
StatementHandler
StatementHandler接口设计采用了适配器模式, 实现类RoutingStatementHandler根据上下文来选择适配器生成相应的StatementHandler。三个适配器分别是SimpleStatementHandler、PreparedStatementHandler和CallableStatementHandler。StatementHandler初始化过程如下:
//在Configuration中构建StatementHandler
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;
}
RoutingStatementHandler构建过程:
public class RoutingStatementHandler implements StatementHandler {
private final StatementHandler delegate;
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
//根据我们映射配置文件中的SQL选择适配器,MyBatis默认使用PreparedStatement
switch (ms.getStatementType()) {
case STATEMENT:
//SimpleStatementHandler对应JDBC中的Statement
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
//PreparedStatementHandler对应JDBC中PreparedStatement
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
//CallableStatementHandler对应JDBC中CallableStatement
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
通常我们使用PreparedStatementHandler,调用父类prepare方法,对SQL预编译:
@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);
}
}
通过PreparedStatementHandler的instantiateStatement方法可以看到,这里实际就是在调用JDBC中的prepareStatement方法进行SQL的预编译,query方法则是调用JDBC的execute方法来执行编译好的SQL和返回结果:
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
//JDBC方法创建PrepareStatement对象
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 {
return connection.prepareStatement(sql);
}
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
//JDBC中PreparedStatement对象的execute()方法执行SQL
ps.execute();
//ResultSetHandler对结果进行封装和返回
return resultSetHandler.<E> handleResultSets(ps);
}
ParameterHandler
ParameterHandler接口作用就是设置预编译SQL的参数:
public interface ParameterHandler {
Object getParameterObject();
void setParameters(PreparedStatement ps)
throws SQLException;
}
接口提供getParameterObject和setParameters方法,前者作用获取参数对象,后者作用是设置预编译SQL的参数,由实现类DefaultParameterHandler执行,使用TypeHandler将参数对象类型转换成jdbcType,完成预编译SQL的参数设置。
@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();
}
//设置参数类型为jdbcType
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
ResultSetHandler
ResultSetHandler接口根据配置文件中定义的规则将结果映射成相应对象,接口定义了三个方法,源码如下:
public interface ResultSetHandler {
// 处理结果集,映射成对应的对象集合
<E> List<e> handleResultSets(Statement stmt) throws SQLException;
// 处理结果集,返回相应的游标对象
<E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
// 处理储存过程输出参数
void handleOutputParameters(CallableStatement cs) throws SQLException;
}
ResultSetHandler接口的具体实现是DefaultResultSetHandler,我们通过SELECT语句执行得到的结果集由其handleResultSets方法处理,方法如下:
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
// 生成ArrayList用于保存结果集映射的对象
final List<Object> multipleResults = new ArrayList<Object>();
int resultSetCount = 0;
// 获取第一个ResultSet对象
ResultSetWrapper rsw = getFirstResultSet(stmt);
// 获取MyBatis初始化时解析映射器配置文件中的resultMap节点生成的ResultMap对象
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
// 验证resultMap是否存在
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
// 根据映射规则将查询结果映射成ResultMap并放入multipleResults集合中
handleResultSet(rsw, resultMap, multipleResults, null);
// 获取下一个结果
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
//递增直到所有结果映射完成
resultSetCount++;
}
//此处省略resultSets多结果集处理方法
//...
return collapseSingleResultList(multipleResults);
}
通过对4个核心功能接口作用的简单分析,我们对MyBatis底层工作原理就有了初步认识,但是要进一步深入理解MyBatis框架原理还需要对MyBatis其他组件的原理进行探究。