前言
今天更新一个小知识点,也是初学者比较容易遗漏的问题,那就是如果开启事务,sqlSession在事务周期之内,都是共享的,方便批量提交和回滚,实现数据一致性。
涉及到的序列图
关键步骤
- 前提:方法或者类上,开启事务;
- 在SqlSessionHolder里获取缓存的sqlSession时,如果返回空,则需要新建sqlSession或者注册holder;
- 用上一步新建的sqlSession,作为构造器参数,新建holder;
- 后续查询,直接从holder返回sqlSession,实现事务周期内共享;
源码说明
获取sqlSession
这里主要是看SqlSessionUtils里的getSqlSession方法
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
// 获取事务内的sqlSessionHolder
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
// 从holder里获取sqlSession
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
// 如果有缓存,则直接共享
return session;
}
// 如果session为空,则新建
session = sessionFactory.openSession(executorType);
// 并将session作为参数,新建holder
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
注意这一行:TransactionSynchronizationManager.getResource(sessionFactory);是从resource的map集合中,获取holder,后续注册的时候,也是以sessionFactory为key,将holder作为value保存到事务上下文的resource中。
从holder里获取sqlSession
private static SqlSession sessionHolder(ExecutorType executorType, SqlSessionHolder holder) {
SqlSession session = null;
if (holder != null && holder.isSynchronizedWithTransaction()) {
// 引用数 +1
holder.requested();
// 获取sqlSession
session = holder.getSqlSession();
}
return session;
}
方法代码很少,主要是判空和isSynchronizedWithTransaction判定,但SynchronizedWithTransaction都是被设置为true的,基本上可以忽略不计,毕竟事务内执行,几乎都是需要同步执行的。下面是设置SynchronizedWithTransaction属性的地方,没有为false的,除非是用自定义插件进行修改。
注册SessionHolder
如果从holder里获取不到sqlSession,那么可以肯定的是,holder也是null,需要新建。
private static void registerSessionHolder(SqlSessionFactory sessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator, SqlSession session) {
SqlSessionHolder holder;
// 是否开启事务
if (TransactionSynchronizationManager.isSynchronizationActive()) {
Environment environment = sessionFactory.getConfiguration().getEnvironment();
// 是否为spring事务
if (environment.getTransactionFactory() instanceof SpringManagedTransactionFactory) {
// 新建holder,sqlSession作为构造参数
holder = new SqlSessionHolder(session, executorType, exceptionTranslator);
// 设定键值对映射,后续获取holder时,执行Manager.getResource(sessionFactory)方法;
TransactionSynchronizationManager.bindResource(sessionFactory, holder);
// 请求次数+1
holder.requested();
}
}
}
步骤:
- 前提条件是开启事务,在TransactionManager.startTransaction中设置此状态位;
- 新建holder,sqlSession作为构造参数
- 设定键值对映射,后续获取holder时,执行Manager.getResource(sessionFactory)方法;
- holder请求次数+1;
销毁holder
众所周知,sqlSession在事务关闭时销毁,那么SqlSessionHolder是否在同一时间节点销毁呢?看一下源码:
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
// 获取事务中的holder
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
if ((holder != null) && (holder.getSqlSession() == session)) {
// 如果holder满足条件,则引用次数-1
holder.released();
} else {
// 否则,直接关闭sqlSession,一般情况下,没有开启事务的,都会走这个逻辑分支的
session.close();
}
}
步骤:
- 获取注册在事务中的holder;
- 如果holder满足条件,则引用次数 -1;
- 否则,直接关闭sqlSession,一般情况下,没有开启事务的,都会走这个逻辑分支的
那什么时候销毁holder呢?其实是在事务完成以后,TransactionManager.doCleanupAfterCompletion方法中执行。