不是所有的this调用会导致事务失效

前言

前一两周,公司的业务出了重大问题。数据库的服务器CPU 100%然后一些重要的抽奖业务竟然出现物品超领的情况。

同事的反馈就是 一个事务里面 只执行了后半部分,也就是领取记录插入(B操作)成功了,库存扣减(A操作)是没有问题的。

核心代码如下(大家看一下事务是否失效🤶):

java 复制代码
//----------------------------------------controller代码---------------------------------------
@RedisRateLimiter(value = 100,limit = 1)
@PostMapping(value = "/grabCoupon")
public Res<?> equityClaim(@RequestBody GrabCouponReqEx req) {
    .........service 使用@Resource 注入..... 
    return service.grabCouponTrans(req);
}
    
    
---------------------------------------service impl代码----------------------------------------
@Transactional(rollbackFor = RuntimeException.class)
@Override
public Result<?> grabCouponTrans(GrabCouponReqEx req) {
       // 对所有代码进行 try,然后抛出RuntimException 让框架回滚
       try{
       .............................
        //✅ A操作:库存扣减,这个地方对库存-1,如果更新行数大与0返回true 
        //UPDATE t SET num =  num -1  WHERE id = #{id} AND num > 0;
        boolean isok = reduceInventory(id);
        if (isok) {
            // ✅B操作:插入领取记录,插入成功返回 true
            Boolean insetSuc = insertRecord(record);
            if (!insetSuc) {
               throw new RuntimeException("领取失败!");
            }
            return result;
        } 
    }catch (Exception e){
        throw new RuntimeException("xxxx");
    }
}

🧔:很多网友都说this调用了reduceInventory、insertRecord方法事物不会生效

✔这种理解肯定是不对的,this调用的外层方法是加了事物注解,并且是@Resource注入调用,是能被代理的。外层方法都被拦截了代理了,方法内部再this调用也是没有问题的,默认就加入当前事务了,如果内部方法通过容器对象获取,并且有事物注解这时候就是看事物的传播机制了。

事务源码分析

下面就是事务拦截器主要的代码逻辑 TransactionAspectSupport.invokeWithinTransaction:

  • 创建或获取事务
  • 执行业务方法
  • 捕捉异常:有异常处理回滚、没有匹配到回滚异常则提交事务
  • 清理事务信息
  • 提交事务
java 复制代码
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);
    
    // 处理响应式事务(Spring 5.2+)
    if (this.reactiveAdapterRegistry != null && tm instanceof ReactiveTransactionManager) {
       ............................
       return result;
    }

    PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
    final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);

    if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
        
       // ✅关键步骤1:创建或获取事务(创建新事务还是沿用使用就看传播机制了)
       // Standard transaction demarcation with getTransaction and commit/rollback calls.
       TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);

       Object retVal;
       try {
          // ✅关键步骤2:执行业务方法(AOP链的下一个拦截器或目标方法) 
          (执行目标方法:内部调用的方法使用this指向默认也是被拦截的,
          如果有事务注解并且通过容器对象调用,执行的时候就会再次被拦截,此时事务的传播机制的作用就体现出来了)
          // 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) {
          // ✅关键步骤3:异常处理回滚
          // target invocation exception
          completeTransactionAfterThrowing(txInfo, ex);
          throw ex;
       }
       finally {
          //✅关键步骤4:清理事务信息
          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);
          }
       }
       // ✅关键步骤5:提交事务
       commitTransactionAfterReturning(txInfo);
       return retVal;
    }

    else {
      //**JTA(Java Transaction API)**  或需要回调机制的事务管理器
      //...........................................................
    }
}

一、创建获取事务

创建事务信息createTransactionIfNecessary,如果之前已经存在事务就走需要判断事务的传播机制了

java 复制代码
protected TransactionInfo createTransactionIfNecessary(@Nullable PlatformTransactionManager tm,
        @Nullable TransactionAttribute txAttr, final String joinpointIdentification) {
    
    // 如果没有指定名称,使用方法标识作为事务名称
    if (txAttr != null && txAttr.getName() == null) {
        txAttr = new DelegatingTransactionAttribute(txAttr) {
            @Override
            public String getName() {
                return joinpointIdentification;
            }
        };
    }
    
    TransactionStatus status = null;
    if (txAttr != null) {
        // ✅关键:根据传播行为获取事务状态
        if (tm != null) {
            status = tm.getTransaction(txAttr);  // 这里处理PROPAGATION_REQUIRED等
        }
        // ...
    }
    
    // ✅准备事务信息
    return prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
}

这是核心中的核心,在 AbstractPlatformTransactionManager.getTransaction() 中:

java 复制代码
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException {
    // 1. 获取事务定义
    TransactionDefinition def = (definition != null ? definition : TransactionDefinition.withDefaults());
    
    // 2. 获取现有事务(处理传播行为)
    Object transaction = doGetTransaction();
    
    // 3. 检查当前是否存在事务
    if (isExistingTransaction(transaction)) {
        // 已存在事务:根据传播行为处理
        return handleExistingTransaction(def, transaction, debugEnabled);
    }
    
    // 4. 没有现有事务:检查超时等设置
    if (def.getTimeout() < TransactionDefinition.TIMEOUT_DEFAULT) {
        throw new InvalidTimeoutException("Invalid transaction timeout", def.getTimeout());
    }
    
    // 5. 需要新事务:PROPAGATION_REQUIRED, PROPAGATION_REQUIRES_NEW, PROPAGATION_NESTED
    if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_MANDATORY) {
        throw new IllegalTransactionStateException("No existing transaction found for transaction marked with propagation 'mandatory'");
    }
    else if (def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRED ||
             def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_REQUIRES_NEW ||
             def.getPropagationBehavior() == TransactionDefinition.PROPAGATION_NESTED) {
        
        // 挂起现有资源(如果有)
        SuspendedResourcesHolder suspendedResources = suspend(null);
        
        try {
            boolean newSynchronization = (getTransactionSynchronization() != SYNCHRONIZATION_NEVER);
            DefaultTransactionStatus status = newTransactionStatus(
                def, transaction, true, newSynchronization, debugEnabled, suspendedResources);
            
            // 关键:开始新事务
            doBegin(transaction, def);
            prepareSynchronization(status, def);
            return status;
        }
        catch (RuntimeException | Error ex) {
            resume(null, suspendedResources);
            throw ex;
        }
    }
    else {
        // 6. 空事务:PROPAGATION_SUPPORTS, PROPAGATION_NOT_SUPPORTED, PROPAGATION_NEVER
        boolean newSynchronization = (getTransactionSynchronization() == SYNCHRONIZATION_ALWAYS);
        return prepareTransactionStatus(def, null, true, newSynchronization, debugEnabled, null);
    }
}

二、异常回滚

执行回滚 : 匹配指定设置的异常,匹配不到就匹配 RuntimeException 或 Error

java 复制代码
protected void completeTransactionAfterThrowing(@Nullable TransactionInfo txInfo, Throwable ex) {
    if (txInfo != null && txInfo.getTransactionStatus() != null) {
        // 关键:根据回滚规则决定是否回滚
        if (txInfo.transactionAttribute != null && txInfo.transactionAttribute.rollbackOn(ex)) {
            try {
                // 执行回滚 : 匹配指定设置的异常,匹配不到就匹配 RuntimeException 或 Error
                txInfo.getTransactionManager().rollback(txInfo.getTransactionStatus());
            }
            catch (TransactionSystemException ex2) {
                // 记录错误但不抛出
                logger.error("Application exception overridden by rollback exception", ex);
                ex2.initApplicationException(ex);
                throw ex2;
            }
            catch (RuntimeException | Error ex2) {
                logger.error("Application exception overridden by rollback exception", ex);
                throw ex2;
            }
        }
        else {
            // 不满足回滚条件,提交事务
            try {
                txInfo.getTransactionManager().commit(txInfo.getTransactionStatus());
            }
            catch (TransactionSystemException ex2) {
                // ... 异常处理
            }
        }
    }
}

三、为什么清理事务信息后才提交事务

在标准事务分支执行逻辑如下,没有发生异常的情况就是先清理事务信息然后提交事务:

java 复制代码
try {
    retVal = invocation.proceedWithInvocation();  // 执行业务方法
}
catch (Throwable ex) {
    completeTransactionAfterThrowing(txInfo, ex);  // 异常回滚
    throw ex;
}
finally {
    cleanupTransactionInfo(txInfo);  // 清理事务信息
}

commitTransactionAfterReturning(txInfo);  // 提交事务

1.事务信息与事务状态分离

首先理解两个关键概念:

  • TransactionInfo:线程绑定的事务上下文信息
  • TransactionStatus:实际的事务状态(包含数据库连接、回滚标记等)
java 复制代码
public class TransactionInfo {
    private final PlatformTransactionManager transactionManager;
    private final TransactionAttribute transactionAttribute;
    private final String joinpointIdentification;
    private TransactionStatus transactionStatus;  // 实际事务状态
    private TransactionInfo oldTransactionInfo;   // 旧的事务信息(用于嵌套事务)
}

关键点 :提交事务需要的是 TransactionStatus,而不是 TransactionInfo。清理的是线程绑定的上下文信息,而不是实际的事务状态。

2. cleanupTransactionInfo 实际做了什么?

java 复制代码
protected void cleanupTransactionInfo(@Nullable TransactionInfo txInfo) {
    if (txInfo != null) {
        // 恢复线程的事务状态到之前的状态
        txInfo.restoreThreadLocalStatus();
    }
}

// TransactionInfo.restoreThreadLocalStatus()
public void restoreThreadLocalStatus() {
    // 将线程绑定恢复到旧的事务信息(如果有的话)
    TransactionSynchronizationManager.bindResource(
        this.transactionManager, this.oldTransactionInfo);
}

重点cleanupTransactionInfo 只是恢复线程的 ThreadLocal 状态,并不影响实际事务的连接和状态

3. 详细执行时序分析

可以看一下面事务嵌套的案例

java 复制代码
@Service
public class UserService {
    
    @Transactional
    public void methodA() {
        // 事务A开始
        userRepository.updateA();
        
        // 调用 methodB(REQUIRES_NEW)
        userServiceProxy.methodB();  // 通过代理调用
        
        // 事务A继续
        userRepository.updateA2();
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void methodB() {
        // 事务B开始(挂起事务A)
        logRepository.save();
        // 事务B提交
    }
}

执行时序: 大概的逻辑就是,执行完一个事务就需要立即把当前事务方法的外层方法的事务给恢复(存在的话)。

text 复制代码
时间轴:
│
├─ 进入 methodA()
│   ├─ 创建 TransactionInfo_A(绑定到 ThreadLocal)
│   ├─ 开始事务A
│   │
│   ├─ 调用 methodB()(通过代理)
│   │   ├─ 创建 TransactionInfo_B(绑定到 ThreadLocal,保存旧的 TransactionInfo_A)
│   │   ├─ 挂起事务A
│   │   ├─ 开始事务B
│   │   ├─ 执行业务逻辑
│   │   │
│   │   ├─ 清理阶段:
│   │   │   ├─ cleanupTransactionInfo(TransactionInfo_B) 
│   │   │   │   └─ 恢复 ThreadLocal 到 TransactionInfo_A ✓
│   │   │   │
│   │   │   ├─ 提交事务B ✓
│   │   │   │   └─ 需要事务B的 TransactionStatus,但不需要 ThreadLocal 的 TransactionInfo_B
│   │   │   │
│   │   │   └─ 恢复事务A
│   │   └─ 返回
│   │
│   └─ methodA 继续执行
│       ├─ 使用 TransactionInfo_A(已恢复)
│       ├─ 提交事务A
│       └─ 清理 TransactionInfo_A
└─ 结束

4. 提交事务不需要 ThreadLocal 绑定

java 复制代码
// AbstractPlatformTransactionManager.commit()
public final void commit(TransactionStatus status) throws TransactionException {
    // 直接从 TransactionStatus 获取实际事务对象
    DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
    
    // 检查是否需要回滚
    if (defStatus.isLocalRollbackOnly() || defStatus.isGlobalRollbackOnly()) {
        processRollback(defStatus, false);
        return;
    }
    
    // 实际提交
    processCommit(defStatus);
}

// DataSourceTransactionManager.doCommit()
protected void doCommit(DefaultTransactionStatus status) {
    DataSourceTransactionObject txObject = (DataSourceTransactionObject) status.getTransaction();
    Connection con = txObject.getConnectionHolder().getConnection();
    
    // 直接使用连接提交
    con.commit();  // 不需要 ThreadLocal 信息
}

关键:提交操作只需要:

  1. 数据库连接(从 TransactionStatus 获取)
  2. 事务状态(是否标记为回滚)

❗明白事务传播机制以及事务的相关信息是绑定到ThreadLocal中之后,用异步的时候就要注意事务失效的问题了

相关推荐
野生的码农4 小时前
码农的妇产科实习记录
android·java·人工智能
盖世英雄酱581365 小时前
Java 组长年终总结:靠 AI 提效 50%,25 年搞副业只赚 4k?
后端·程序员·trae
毕设源码-赖学姐5 小时前
【开题答辩全过程】以 高校人才培养方案管理系统的设计与实现为例,包含答辩的问题和答案
java
+VX:Fegn08955 小时前
计算机毕业设计|基于springboot + vue在线音乐播放系统(源码+数据库+文档)
数据库·vue.js·spring boot·后端·课程设计
一起努力啊~5 小时前
算法刷题-二分查找
java·数据结构·算法
小途软件6 小时前
高校宿舍访客预约管理平台开发
java·人工智能·pytorch·python·深度学习·语言模型
J_liaty6 小时前
Java版本演进:从JDK 8到JDK 21的特性革命与对比分析
java·开发语言·jdk
code bean6 小时前
Flask图片服务在不同网络接口下的路径解析问题及解决方案
后端·python·flask
+VX:Fegn08956 小时前
计算机毕业设计|基于springboot + vue律师咨询系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·后端·课程设计
努力的小郑6 小时前
2025年度总结:当我在 Cursor 里敲下 Tab 的那一刻,我知道时代变了
前端·后端·ai编程