写在前面
在上篇文章中我们分析了在spring中mybatis的初始化过程,本片文章分析spring对于mybatis的org.apache.ibatis.session.SqlSession
的封装,在直接使用mybatis时,因为SqlSession本身是非线程安装的,因此每次都需要通过openSession方法打开一个新的会话,这个过程模拟代码可能如下:
@Test
publicvoid multiInsertByQueryParamter() {
// 假设用户A接口调用导致的数据库执行
TestResultmapAndParametermapQueryDto newDto = new TestResultmapAndParametermapQueryDto();
newDto.setRealAge(new Random().nextInt(100));
newDto.setRealName(UUID.randomUUID().toString());
this.sqlSessionFactory.openSession().getMapper(TestResultmapAndParametermapMapper.class).insertByQueryParamter(newDto);
sqlSession.commit();
// 假设用户B接口调用导致的数据库操作
TestResultmapAndParametermapQueryDto newDto1 = new TestResultmapAndParametermapQueryDto();
newDto1.setRealAge(new Random().nextInt(100));
newDto1.setRealName(UUID.randomUUID().toString());
this.sqlSessionFactory.openSession().getMapper(TestResultmapAndParametermapMapper.class).insertByQueryParamter(newDto1);
sqlSession.commit();
}
注意到this.sqlSessionFactory.openSession()
被重复调用了,而在spring中就不需要了,可能有如下的代码:
@Test
public void multiInsertOne() {
TestResultmapAndParametermapMapper testResultmapAndParametermapMapper = ac.getBean(TestResultmapAndParametermapMapper.class);
System.out.println(testResultmapAndParametermapMapper);
TestResultmapAndParametermapQueryDto newDto = new TestResultmapAndParametermapQueryDto();
newDto.setRealAge(new Random().nextInt(100));
newDto.setRealName(UUID.randomUUID().toString());
testResultmapAndParametermapMapper.insertByQueryParamter(newDto);
TestResultmapAndParametermapQueryDto newDto1 = new TestResultmapAndParametermapQueryDto();
newDto.setRealAge(new Random().nextInt(100));
newDto.setRealName(UUID.randomUUID().toString());
testResultmapAndParametermapMapper.insertByQueryParamter(newDto1);
}
注意到我们使用从spring的IOC容器中获取的testResultmapAndParametermapMapper
,直接使用其进行了数据库操作,而并没有显式openSession操作,可以通过在org.apache.ibatis.session.defaults.DefaultSqlSession#update(java.lang.String, java.lang.Object)
方法中打断点,来验证每次都是不同的SqlSession对象,如下是我本地的测试结果:
- 第一次
- 第二次
spring实现这种功能依赖是其org.mybatis.spring.SqlSessionTemplate
来实现的,下面我们就一起来看下这个功能具体是如何实现的。
1:SqlSessionTemplate
1.1:构造函数
org.mybatis.spring.SqlSessionTemplate#SqlSessionTemplate(org.apache.ibatis.session.SqlSessionFactory, org.apache.ibatis.session.ExecutorType, org.springframework.dao.support.PersistenceExceptionTranslator)
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
// 强制要求要mybatis的org.apache.mybatis.session.SqlSessionFactory
notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
// 强制要求有executorType,默认为org.apache.ibatis.session.ExecutorType#SIMPLE
notNull(executorType, "Property 'executorType' is required");
// 设置sqlSessionFactory
this.sqlSessionFactory = sqlSessionFactory;
// 设置executorType
this.executorType = executorType;
// 设置转换mybatis抛出的org.apache.ibatis.execption.PersistenceExecption的异常转换对象
this.exceptionTranslator = exceptionTranslator;
// 创建SqlSession的代理类
// 注意这里调用的是java.lang.refelct.Proxy的newProxyInstance方法创建动态代理对象
// 使用了静态导入:import static java.lang.reflect.Proxy.newProxyInstance;
// 2021-09-03 15:29:04
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
2021-09-03 15:29:04
处是生成SqlSession的代理类,这里我们需要关注的是SqlSessionInterceptor,具体参考1.2:SqlSessionInterceptor
。
1.2:SqlSessionInterceptor
源码如下:
org.mybatis.spring.SqlSessionTemplate.SqlSessionInterceptor
//
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取被spring事务管理器托管的SqlSession
// 2021-09-03 16:48:56
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
// 反射调用org.apache.ibatis.session.SqlSession的目标方法获取结果
Object result = method.invoke(sqlSession, args);
// 如果不是spring事务管理器托管的SqlSession,则直接调用sqlSession的commit方法提交事务
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}
// 返回查询结果
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
// 如果是指定了异常转换对象,并且当前异常是org.apache.ibatis.exception.PersistenceException
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// 尝试关闭SqlSession
// 2021-09-03 17:03:36
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
// 获取转换的异常对象
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
// 如果转换的异常对象不为null,则赋值到当前异常
if (translated != null) {
unwrapped = translated;
}
}
// 抛出异常,可能是原始异常,也可能是转换后的异常
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
2021-09-03 16:48:56
处是获取被spring事务管理器托管的SqlSession对象,关于此具体参考这篇文章。2021-09-03 17:03:36
处是尝试关闭sqlSesion,具体参考1.3.1:关闭SqlSession
。
1.3.1:关闭SqlSession
源码如下:
org.mybatis.spring.SqlSessionUtils#closeSqlSession
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
// 非空判断
notNull(session, NO_SQL_SESSION_SPECIFIED);
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
// 获取封装SqlSession对象的SqlSessionHolder
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
// 如果holder不为空,并且当前的sqlSession和和holder中获取的是相同的对象
// 则说明当前的SqlSession是被spring的事务管理器托管的
if ((holder != null) && (holder.getSqlSession() == session)) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Releasing transactional SqlSession [" + session + "]");
}
// 直接调用release方法,后续由spring负责关闭会话
holder.released();
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Closing non transactional SqlSession [" + session + "]");
}
// 如果不是spring事务管理器托管的SqlSession,则直接close
session.close();
}
}