前言
前一两周,公司的业务出了重大问题。数据库的服务器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 信息
}
关键:提交操作只需要:
- 数据库连接(从 TransactionStatus 获取)
- 事务状态(是否标记为回滚)
❗明白事务传播机制以及事务的相关信息是绑定到ThreadLocal中之后,用异步的时候就要注意事务失效的问题了