Spring事务管理器如何管理Mybatis SqlSession
背景
spring管理mybatis时要依赖mybatis-spring,这里面究竟做了些什么?
spring事务管理器
TransactionAspectSupport#invokeWithinTransaction()
定时了spring事务的模板
@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
final InvocationCallback invocation) throws Throwable {
// If the transaction attribute is null, the method is non-transactional.
TransactionAttributeSource tas = getTransactionAttributeSource();
final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
final TransactionManager tm = determineTransactionManager(txAttr);
// ...省略其他
PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.
// 创建事务
TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
Object retVal;
try {
// This is an around advice: Invoke the next interceptor in the chain.
// This will normally result in a target object being invoked.
// 调用代理方法
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// target invocation exception
// 出现异常,回滚事务后将异常继续往上抛
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
cleanupTransactionInfo(txInfo);
}
if (retVal != null && vavrPresent && VavrDelegate.isVavrTry(retVal)) {
// Set rollback-only in case of Vavr failure matching our rollback rules...
TransactionStatus status = txInfo.getTransactionStatus();
if (status != null && txAttr != null) {
retVal = VavrDelegate.evaluateTryFailure(retVal, txAttr, status);
}
}
// 提交事务
commitTransactionAfterReturning(txInfo);
return retVal;
}
}
以DataSourceTransactionManager
为例,一个典型事务的生命周期经历以下阶段
- 创建事务(Creating new transaction)
- 获取连接(Acquired Connection)
- 将连接改成手动提交(Switching JDBC Connection to manual commit)
- 准备事务提交(Initiating transaction commit)
- 提交事务(Committing JDBC transaction)
- 释放连接(Releasing JDBC Connection)
mybatis通用用法
SqlSession sqlSession = sqlSessionFactory.openSession();
sqlSession.selectOne("xxx");
sqlSession.commit();
sqlSession.close();
围绕着SqlSession,分为获取,使用,提交,关闭等几个步骤
spring管理mybatis
如果spring要管理mybatis,必须解决两个问题,代理SqlSession和在事务中管理SqlSession的生命周期
SqlSessionTemplate
SqlSessionTemplate代理了SqlSession,在spring管理下所有mapper都必须经过这个切面得到真正的SqlSession
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 获取SqlSession,如果事务已经获取过则复用
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
// 省略
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
管理SqlSession生命周期
在事务中必须管理SqlSession生命周期,包括SqlSession的提交和关闭等
spring事务在TransactionSynchronization
提供了回调接口,供第三方拓展,包括beforeCommit,beforeCompletion,afterCommit,afterCompletion
等方法,同时在AbstractPlatformTransactionManager
适当时候调用上述几个回调接口
在TransactionSynchronizationManager
里管理里同步需要的资源
// 同步所需的事务资源,例如Connection, SqlSession
private static final ThreadLocal<Map<Object, Object>> resources =
new NamedThreadLocal<>("Transactional resources");
// 同步所需回调的接口,例如mybatis的SqlSessionSynchronization
private static final ThreadLocal<Set<TransactionSynchronization>> synchronizations =
new NamedThreadLocal<>("Transaction synchronizations");
例如,在AbstractPlatformTransactionManager#processCommit
里,能够回调triggerBeforeCommit
和triggerBeforeCompletion
等方法
// AbstractPlatformTransactionManager.java
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
// 省略...
try {
boolean unexpectedRollback = false;
prepareForCommit(status);
triggerBeforeCommit(status); // 回调TransactionSynchronization#beforeCommit
triggerBeforeCompletion(status); // 回调TransactionSynchronization#beforeCompletion
beforeCompletionInvoked = true;
// 省略...
}
}
protected final void triggerBeforeCommit(DefaultTransactionStatus status) {
if (status.isNewSynchronization()) {
if (status.isDebug()) {
logger.trace("Triggering beforeCommit synchronization");
}
TransactionSynchronizationUtils.triggerBeforeCommit(status.isReadOnly());
}
}
// TransactionSynchronizationUtils.java
public static void triggerBeforeCommit(boolean readOnly) {
for (TransactionSynchronization synchronization : TransactionSynchronizationManager.getSynchronizations()) {
// 例如,最终回调beforeCommit
synchronization.beforeCommit(readOnly);
}
}
spring在getSqlSession
会将SqlSessionSynchronization
注册成回调接口,这样spring事务就能管理SqlSession的生命周期了.
总结
最终,一个完整事务的流程:
- 创建事务(Creating new transaction)
- 获取连接(Acquired Connection)
- 将连接改成手动提交(Switching JDBC Connection to manual commit)
- Creating a new SqlSession
- Registering transaction synchronization for SqlSession
- 执行各种SQL
- Releasing transactional SqlSession
- Transaction synchronization committing SqlSession
- Transaction synchronization deregistering SqlSession
- Transaction synchronization closing SqlSession
- 准备事务提交(Initiating transaction commit)
- 提交事务(Committing JDBC transaction)
- 释放连接(Releasing JDBC Connection)
其他补充
如果要在spring事务下显式管理SqlSession,据我所知mapper
没有SqlSession的接口.只能通过SqlSessionTemplate
来实现. 如上所述,SqlSessionTemplate
代理了SqlSession
,而SqlSessionTemplate
是线程安全的,可以管理事务下的SqlSession
,如下所示
int age = sqlSessionTemplate.selectOne("xxx.UserMapper.getAgeByUserIdForUpdate", 1);
sqlSessionTemplate.clearCache();
int anotherAge = sqlSessionTemplate.selectOne("xxx.UserMapper.getAgeByUserIdForUpdate", 1);
通过sqlSessionTemplate.clearCache()
清空了SqlSession
的一级缓存,anotherAge
无法读取一级缓存而再次查询数据库
转载自:https://juejin.cn/post/6999145665787854856