这是我参与11月更文挑战的第8天,活动详情查看:2021最后一次更文挑战
前言
继上篇博客之后,本文主要是详解 REQUIRED这个事务传播机制
最主要是回答如下问题
-
子事务提交,父事务会提交么?
-
父事务提交,子事务会提交么?
-
子事务回滚,父事务会回滚么?
-
父事务会回滚,子事务会回滚么?
答案可能大家都知道,但是本文主要是从源码的细节来解析
伪代码
最外层的事务
@Transactional
method_a(){
method_b();
}
调用的子事务
@Transactional
method_b(){
}
源码分析
从头到尾在梳理一下,如下是TransactionAspectSupport#invokeWithinTransaction
也就是事务注解切面大致的逻辑
if (txAttr == null || !(tm instanceof CallbackPreferringPlatformTransactionManager)) {
// 获取事务信息
TransactionInfo txInfo = createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
Object retVal = null;
try {
// 执行dbio
retVal = invocation.proceedWithInvocation();
}
catch (Throwable ex) {
// 处理异常
completeTransactionAfterThrowing(txInfo, ex);
throw ex;
}
finally {
// 清除当前线程上下文的事务信息
cleanupTransactionInfo(txInfo);
}
// 提交事务(这里的提交事务包含了线程中spring 事务信息的处理,不一定是db层次的commit)
commitTransactionAfterReturning(txInfo);
return retVal;
}
获取事务信息
createTransactionIfNecessary
这方法也很简单,核心就是调用两个方法getTransaction、prepareTransactionInfo
getTransaction
AbstractPlatformTransactionManager#getTransaction
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
// 获取当前线程上下文中的事务
Object transaction = doGetTransaction();
if (isExistingTransaction(transaction)) {
// 如果是method_b 方法的切面会执行到这里
// 如果当前线程上下文已经存在事务,则根据事务注解的隔离级别处理
return handleExistingTransaction(definition, transaction, debugEnabled);
}
......
if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
.....
}
else if (definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
definition.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
// 如果是method_a 方法的切面会执行到这里
SuspendedResourcesHolder suspendedResources = suspend(null);
try {
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
DefaultTransactionStatus status = newTransactionStatus(
definition, transaction, true, newSynchronization, debugEnabled, suspendedResources);
doBegin(transaction, definition);
prepareSynchronization(status, definition);
return status;
}
catch (RuntimeException | Error ex) {
resume(null, suspendedResources);
throw ex;
}
}
else {
....
}
}
handleExistingTransaction
private TransactionStatus handleExistingTransaction(
TransactionDefinition definition, Object transaction, boolean debugEnabled)
throws TransactionException {
.....
boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
return prepareTransactionStatus(definition, transaction, false, newSynchronization, debugEnabled, null);
}
prepareTransactionStatus
其实就是封装一个TransactionStatus对象
prepareTransactionInfo
protected TransactionInfo prepareTransactionInfo(@Nullable PlatformTransactionManager tm,
@Nullable TransactionAttribute txAttr, String joinpointIdentification,
@Nullable TransactionStatus status) {
TransactionInfo txInfo = new TransactionInfo(tm, txAttr, joinpointIdentification);
if (txAttr != null) {
// 将上文中创建的TransactionStatus 赋值到事务信息中
txInfo.newTransactionStatus(status);
}
else {
}
// 绑定 TransactionInfo 到当前线程上下文
txInfo.bindToThread();
return txInfo;
}
所以这里实际上是有两个TransactionInfo,当时我还是很疑惑,不是说 REQUIRED实际上只有一个事务么
现在这里明显是不同的TransactionInfo对象
后来再看获取sqlsession的代码时才发现自己概念搞混了
执行业务sql
之前的文章我也详细说过这个方法,任何一个mybatis业务层 在执行sql之前都会获取SqlSession
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
// 当前线程上下文 sqlsession 持有者
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
// sqlsession
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
// 如果已经存在则直接返回
return session;
}
// 创建session
session = sessionFactory.openSession(executorType);
// 将session 注册到当前线程上下文
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
这里就回答了上面的问题,虽然TransactionInfo在两个@Transactional注解的方法中是不一样的,但是只要是在同一个线程上下文中,他们实际和db进行io通讯的session是一样的,那对于db来说就是一个事务
换句话说,这里实际上是有两个概念,一个数据库的事务,一个是spring 的事务,一个可以看做是实际的,一个看做是逻辑上的。
从代码来看只要方法是被Transactional注解了,并且切面是解析到了,那么spring 就会创建TransactionStatus和TransactionInfo这算是spring 自己的机制
提交事务
AbstractPlatformTransactionManager#commit
processCommit
省略与本文无关的代码部分
private void processCommit(DefaultTransactionStatus status) throws TransactionException {
try {
.....
try {
.....
if (status.hasSavepoint()) {
......
}
else if (status.isNewTransaction()) {
//大家打个断点就知道了,如果是 method_b() 提交 这个判断是是进不来的
....
// 执行事务提交
doCommit(status);
}
.....
}
catch (UnexpectedRollbackException ex) {
.....
}
catch (TransactionException ex) {
.....
}
catch (RuntimeException | Error ex) {
....
}
}
finally {
cleanupAfterCompletion(status);
}
}
其中关键的判断点进不去是因为handleExistingTransaction中newTransaction这个属性为空
而当method_a方法执行结束需要提交时,他的TransactionInfo中newTransaction属性是true,所以可以提交
回滚
回滚代码最核心的部分是
AbstractPlatformTransactionManager#rollback
又调用了 processRollback
private void processRollback(DefaultTransactionStatus status, boolean unexpected) {
try {
try {
if (status.hasSavepoint()) {
...
}
else if (status.isNewTransaction()) {
// 如果是method_a 方法的切面会执行到这里,在上文中的getTransaction 有介绍
doRollback(status);
}
else {
if (status.hasTransaction()) {
// 如果是method_b 方法的切面会执行到这里
// globalRollbackOnParticipationFailure 默认是true
if (status.isLocalRollbackOnly() || isGlobalRollbackOnParticipationFailure()) {
....
doSetRollbackOnly(status);
}
else {
....
}
}
else {
....
}
...
}
}
catch (RuntimeException | Error ex) {
....
}
...
}
finally {
cleanupAfterCompletion(status);
}
}
这只是逻辑上回滚,也就是method_b 方法事务最终回滚的逻辑
那么method_a 呢
上文中 AbstractPlatformTransactionManager#commit
public final void commit(TransactionStatus status) throws TransactionException {
DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
........
// 如果当前线程中 connect holder 设置了回滚标记
if (!shouldCommitOnGlobalRollbackOnly() && defStatus.isGlobalRollbackOnly()) {
// 执行回滚
processRollback(defStatus, true);
return;
}
// 执行提交
processCommit(defStatus);
}
这是实际上的db回滚最终调用方法
回答前言问题
业务代码大致其实就两种
@Transactional(rollbackFor = Exception.class)
method_a(){
method_b();
}
@Transactional(rollbackFor = Exception.class)
method_b(){
}
@Transactional(rollbackFor = Exception.class)
method_a(){
try{
method_b();
}catch(){
}
}
@Transactional(rollbackFor = Exception.class)
method_b(){
}
那么其实无论是哪种情况只要任何一个事务回滚,最终都会全部一起回滚
如果是这样
@Transactional(rollbackFor = Exception.class)
method_a(){
method_b();
}
@Transactional(rollbackFor = Exception.class)
method_b(){
try{
}catch(){
}
}
那么在b中就算异常也不会
总结
简单说这种事务传播机制的话,spring 级别的事务不是同一个,但是他们的db级别的事务是同一个