目录
概述
经过前两章对MyBatis中一些基础内容介绍之后,本章主要讲解下MyBatis中SQL执行的核心流程。
链接跳转
(一)MyBatis整合SpringBoot源码解析——整体大纲
(二)MyBatis整合SpringBoot源码解析——前置内容
(三)MyBatis整合SpringBoot源码解析——配置初始化
(四)MyBatis整合SpringBoot源码解析——SQL执行流程
注:代码部分仅列出主要代码,会省略一些不影响核心流程的分支以及异常处理,部分为伪代码,尽量保证简洁。
内容
1.整体流程
本章节中,将MyBatis中SQL执行流程拆解为4个子流程进行讲解,主要为元数据获取、会话创建、执行器执行、会话关闭。
2.元数据获取
2-1.功能描述
MyBatis核心流程的方法入口是在MapperProxy#Invoke,MapperProxy会对接口方法映射的元数据(MapperMethod)进行管理,在invoke方法中其实就是获取方法对应的MapperMethod,并委托给它执行。
2-2.主要流程代码
MapperProxy#Invoke
public class MapperProxy<T> implements InvocationHandler, Serializable {
// 与Spring集成后,实际实现类是SqlSessionTemplate
private final SqlSession sqlSession;
// 接口信息
private final Class<T> mapperInterface;
// 方法对应的MapperMethod
private final Map<Method,MapperMethodInvoker> methodCache;
// 方法入口
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 从缓存中获取方法对应的MapperMethod,并且委托给它执行
cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
return methodCache.computeIfAbsent(method, m -> {
// 省略部分分支
// 正常情况下使用PlainMethodInvoker进行包装MapperMethod,除非该方法是一个default方法。
// PlainMethodInvoker内部实际还是直接调用MapperMethod.invoke方法
return PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
});
}
}
MapperMethod#execute,此处为伪代码
public class MapperMethod {
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 参数转换
Object param = method.convertArgsToSqlCommandParam(args);
switch (command.getType()) {
case INSERT/UPDATE/DELETE: {
// 根据INSERT/UPDATE/DELETE类型,选择执行sqlSession.insert/update/delete方法
break;
}
case SELECT: {
// 根据返回类型判定最终执行sqlSession.selectMap/selectList/selectCursor
break;
}
}
return result;
}
}
3.会话创建
3-1.功能描述
获取到元数据之后,会根据SQL的类型去选择Sqlsession接口中的insert/update/delete/selectList方法执行,此处的Sqlsession接口的实现类是SqlSessionTemplate,该类只是做了会话和事务的管理,真正的执行逻辑还是委托给Sqlsession的另一个实现类(DefaultSqlSession)执行。首先创建会话(DefaultSqlSession)和事务(Transaction的实现类SpringManagedTransaction),然后将方法委托给DefaultSqlSession执行,执行完成之后再提交事务并且关闭会话。
3-2.主要流程代码
SqlSessionTemplate#invoke
public class SqlSessionTemplate implements SqlSession, DisposableBean {
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 创建会话,详情见下面的getSqlSession
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
// 委托给另一个实现类DefaultSqlSession执行
Object result = method.invoke(sqlSession, args);
// 如果当前会话没有被Spring的事务管理(TransactionSynchronizationManager),则执行提交操作
// 即如果开启了Spring的事务的话,MyBatis在此处不会提交事务,而是交给Spring的事务管理器去处理
// 比如使用了@Transaction注解,那么此处就不会提交事务,而是委托给Spring的事务管理器处理。
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}
return result;
} finally {
if (sqlSession != null) {
// 同理,如果当前会话被Spring的事务管理,则不会关闭会话
// 因为后续可能还会使用到,统一交由Spring的事务管理器去处理即可
// 反之关闭
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
// 从线程上下文获取会话
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
// 如果是在Spring事务管理下执行的话,此处即可以直接获取到会话并且直接返回
// 也就是如果开启了事务情况下,事务内的多个语句执行都是用的同一个会话
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
// 非Spring事务的情况下,每次执行SQL都会开启一个新的会话
session = sessionFactory.openSession(executorType);
return session;
}
}
4.执行器执行
4-1.功能描述
DefaultSqlSession从元数据中获取方法对应的SQL语句,从而委托给Executor执行,Executor分多个实现,使用了包装器模式,BaseExecutor是基础的实现类,如果开启了二级缓存的情况下,Executor会被CacheExecutor装饰,执行SQL之前先从缓存中获取结果,二级缓存是同一个namespace下的所有语句(一般情况下对应每个xxMapper.xml文件),所有会话共享
4-2.主要流程代码
DefaultSqlSession#selectList、DefaultSqlSession#update
public class DefaultSqlSession implements SqlSession {
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
// 根据接口+方法名从全局配置类中获取对应的SQL
MappedStatement ms = configuration.getMappedStatement(statement);
// 委托给执行器执行,如果开启二级缓存
// 那么此处的执行器会被CachingExecutor包装,调用链为CachingExecutor -> BaseExecutor,
// 在CachingExecutor的query方法中,会从二级缓存(NameSpace级别)中获取数据,如果不存在再查询数据库
// 而BaseExecutor的方法中,会从一级缓存(SqlSession级别)获取数据,如果不存在再查询数据库
// 也就是如果一二级缓存都使用的情况下,二级缓存的优先级大于一级缓存。
// 而最终执行的query方法中就是获取连接,并执行语句。
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
}
@Override
public int update(String statement, Object parameter) {
// 根据接口+方法名从全局配置类中获取对应的SQL
MappedStatement ms = configuration.getMappedStatement(statement);
// 不论执行的是update/insert/delete,最终底层调用的都是update方法
// 同查询一样,此处也会委托给执行器执行,如果开启二级缓存
// 那么此处的执行器会被CachingExecutor包装,调用链为CachingExecutor -> BaseExecutor,
// 在CachingExecutor的update方法中,会先去清除二级缓存,在执行update操作。
// 而BaseExecutor的方法中,会先去清除一级缓存,在执行update操作。
// 而最终执行的update方法中就是获取连接,并执行语句。
return executor.update(ms, wrapCollection(parameter));
}
}
5.会话关闭
会话关闭的代码参考3-2。