在事务注解@Transactional的方法中如何做到在事务提交/回滚后执行业务逻辑

需求描述

在某些情况下 我们需要在事务注解的方法中,在事务提交/回滚的前后,去执行一些业务逻辑
如:在事务提交后执行一些异步任务


解决方案:

案例:

public void insert(XxxEntity entity){
        XxxMapper.delete(entity);
       // 在删除数据后发送异步消息
        TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
            @Override
            public void afterCommit() {
                mqProducer.sendMessage("发送删除后消息");
            }
        }
       );
    }

我们可以通过Spring为我们提供的 TransactionSynchronizationManager.registerSynchronization(TransactionSynchronization synchronization)方法来实现在事务提交/回滚后插入业务逻辑
在这里插入图片描述
其中我们查看源码注释的第一行Register a new transaction synchronization for the current thread.可以发现,该方法是将当前线程注册一个事务synchronization 也就是将TransactionSynchronization注册到当前线程的synchronizations。
而TransactionSynchronization 则使用了TransactionSynchronizationAdapter抽象类 可以发现该类中所有的方法都是空方法,我们可以根据需要重写里面的方法,可以发现这个类使用了适配器模式
在这里插入图片描述
通过这个类 我们可以再事务提交前/后执行一些业务逻辑

那么spring的事务是怎么调用这个方法的呢?

看过Spring源码的同学一定非常熟悉@Transactional注解是怎么将方法加入到事务中的

没错就是通过TransactionInterceptor类的invoke()方法里
在这里插入图片描述
invokeWithinTransaction方法

@Nullable
protected Object invokeWithinTransaction(Method method, @Nullable Class<?> targetClass,
		final InvocationCallback invocation) throws Throwable {
	// 如果 transaction attribute 是null, 那么该方法不需要事务.
	TransactionAttributeSource tas = getTransactionAttributeSource();
	final TransactionAttribute txAttr = (tas != null ? tas.getTransactionAttribute(method, targetClass) : null);
	PlatformTransactionManager ptm = asPlatformTransactionManager(tm);
	final String joinpointIdentification = methodIdentification(method, targetClass, txAttr);
	...
	
	if (txAttr == null || !(ptm instanceof CallbackPreferringPlatformTransactionManager)) {
		// 根据传播级别配置,看是否需要新建事务.
		TransactionInfo txInfo = createTransactionIfNecessary(ptm, txAttr, joinpointIdentification);
		Object retVal;
		try {
		   // 调用真正的dao层逻辑
	       retVal = invocation.proceedWithInvocation();
	    } catch (Throwable var18) {
		   // 根据@Transactional配置的异常来决定是否回滚
	       this.completeTransactionAfterThrowing(txInfo, var18);
	       throw var18;
	    } finally {
  		    // 结束当前的事务,信息是保存在ThreadLocal里
	        this.cleanupTransactionInfo(txInfo);
	    }
	    if (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;
	}
}

在上面的代码中 commitTransactionAfterReturning(txtexInfo); 这行代码 可以发现 TransactionInfo txtexInfo 该实例是TransactionAspectSupportd的一个内部类,最终是调用AbstractPlatformTransactionManager.commit()方法
在这里插入图片描述

在这里插入图片描述
在该方法中 可以发现最终调用processCommit()方法

private void processCommit(DefaultTransactionStatus status) throws TransactionException {
        try {
            boolean beforeCompletionInvoked = false;

            try {
                boolean unexpectedRollback = false;
                prepareForCommit(status);
                triggerBeforeCommit(status);
                triggerBeforeCompletion(status);
                beforeCompletionInvoked = true;

                if (status.hasSavepoint()) {
                    if (status.isDebug()) {
                        logger.debug("Releasing transaction savepoint");
                    }
                    unexpectedRollback = status.isGlobalRollbackOnly();
                    status.releaseHeldSavepoint();
                }
                else if (status.isNewTransaction()) {
                    if (status.isDebug()) {
                        logger.debug("Initiating transaction commit");
                    }
                    unexpectedRollback = status.isGlobalRollbackOnly();
                    doCommit(status);
                }
                else if (isFailEarlyOnGlobalRollbackOnly()) {
                    unexpectedRollback = status.isGlobalRollbackOnly();
                }

                // Throw UnexpectedRollbackException if we have a global rollback-only
                // marker but still didn't get a corresponding exception from commit.
                if (unexpectedRollback) {
                    throw new UnexpectedRollbackException(
                            "Transaction silently rolled back because it has been marked as rollback-only");
                }
            }
            catch (UnexpectedRollbackException ex) {
                // can only be caused by doCommit
                triggerAfterCompletion(status, TransactionSynchronization.STATUS_ROLLED_BACK);
                throw ex;
            }
            catch (TransactionException ex) {
                // can only be caused by doCommit
                if (isRollbackOnCommitFailure()) {
                    doRollbackOnCommitException(status, ex);
                }
                else {
                    triggerAfterCompletion(status, TransactionSynchronization.STATUS_UNKNOWN);
                }
                throw ex;
            }
            catch (RuntimeException | Error ex) {
                if (!beforeCompletionInvoked) {
                    triggerBeforeCompletion(status);
                }
                doRollbackOnCommitException(status, ex);
                throw ex;
            }

            // Trigger afterCommit callbacks, with an exception thrown there
            // propagated to callers but the transaction still considered as committed.
            try {
                triggerAfterCommit(status);
            }
            finally {
                triggerAfterCompletion(status, TransactionSynchronization.STATUS_COMMITTED);
            }

        }
        finally {
            cleanupAfterCompletion(status);
        }
    }

    private void triggerAfterCommit(DefaultTransactionStatus status) {
        if (status.isNewSynchronization()) {
            if (status.isDebug()) {
                logger.trace("Triggering afterCommit synchronization");
            }
            TransactionSynchronizationUtils.triggerAfterCommit();
        }
    }

在提交成功之后触发triggerAfterCommit,这里调用了TransactionSynchronizationUtils.triggerAfterCommit()。
注意这里没有try catch,说明triggerAfterCommit的异常最终会抛给调用方


小结

使用TransactionSynchronizationManager.registerSynchronization可以在当前线程的事务注册一个TransactionSynchronizationAdapter,可以在afterCommit方法也就是事务提交成功之后执行一些额外业务逻辑;
注意这里抛出的异常不影响事务提交,但是异常不会被catch需要由调用方处理,对于afterCommit有数据库相关操作的建议使用PROPAGATION_REQUIRES_NEW这个事务传播级别,不然afterCommit的db操作可能不会生效。

参考

https://www.jianshu.com/p/dcfad3f80e3b
https://juejin.cn/post/7003614270877335560

<think>首先,用户的问题是:“在使用@Transactional注解时,如果方法捕获异常是否还会触发事务回滚。请问@Transactional注解事务捕获异常后是否回滚”我需要根据提供的引用和系统指令来回答。系统指令包括:使用中文回答、正确格式化数学表达式(但这里可能不涉及数学)、生成相关问题、在引用段落末尾添加引用标识。回顾引用内容:-引用[1]:提到@Transactional注解默认只对未被捕获的RuntimeException及其子类异常回滚。如果捕获异常,Spring无法感知,不会触发回滚。-引用[2]:展示了使用@Transactional(rollbackFor=Exception.class)和手动回滚TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()来处理捕获的异常。-引用[3]:解释默认规则,遇到运行异常才回滚,可以使用rollbackFor指定其他异常。-引用[4]:讨论在catch块中使用手动回滚来处理异常而不抛出。-引用[5]:示例代码显示在捕获异常后手动调用setRollbackOnly()来确保回滚。关键点:默认情况下,如果方法捕获了异常,@Transactional不会触发回滚,因为Spring不知道异常发生了。需要通过配置rollbackFor或手动回滚来处理。我的回答结构应该清晰,逐步解决问题:1.**解释默认行为**:说明默认只对未被捕获的RuntimeException回滚。2.**捕获异常的影响**:如果捕获异常,事务可能不回滚。3.**解决方法**:提供选项,如使用rollbackFor属性或手动回滚。4.**示例**:给出代码示例来演示。5.**生成相关问题**:根据系统指令,在最后添加相关问题。使用中文回答。引用段落末尾自然添加引用标识,如[^1]。回答结构:-开头直接回答问题。-详细解释原因和机制。-提供解决方案。-结束前生成相关问题。相关问题:基于主题,如事务管理、异常处理等。例如:1.如何配置@Transactional回滚特定异常?2.在Spring中手动回滚事务的最佳实践是什么?3.@Transactional与其他事务管理方式的比较?最后,确保回答真实可靠,基于引用。</think>### @Transactional 注解捕获异常后的事务回滚机制 在使用 Spring 的 `@Transactional` 注解时,**如果方法内部捕获了异常,默认情况下不会触发事务回滚**。这是因为 Spring事务管理基于 AOP 实现,它依赖未被捕获的异常来触发回滚逻辑。下面我将逐步解释原因、影响及解决方案,帮助您避免常见问题。 #### 1. **默认行为分析** - **触发回滚的条件**:`@Transactional` 注解默认只对未被捕获的 `RuntimeException` 及其子类异常(如 `NullPointerException`、`IllegalArgumentException`)进行回滚。如果方法中抛出这些异常但未被捕获,Spring 会感知到异常并自动回滚事务[^1][^3]。 - **捕获异常的影响**:如果在方法内部使用 `try-catch` 块捕获异常(例如捕获 `Exception` 或 `RuntimeException`),Spring 无法检测到异常的抛出,因此不会触发回滚逻辑。这会导致事务继续提交,数据可能不一致[^1][^4]。 - 示例代码说明: ```java @Transactional public void updateData() { try { // 业务逻辑,可能抛出异常 userService.update(user); // 假设此方法抛出 RuntimeException } catch (Exception e) { e.printStackTrace(); // 捕获异常后,事务不会回滚! } } ``` 在上述代码中,即使 `update` 方法抛出异常,由于被 `catch` 块捕获,事务仍会提交,造成数据错误。 #### 2. **为什么捕获异常会导致不回滚?** - **AOP 机制限制**:Spring事务拦截器在方法执行后检查异常。如果异常在方法内部被捕获,拦截器无法获取异常信号,因此不会启动回滚流程[^1][^3]。 - **默认异常类型**:Spring 设计为仅处理“未检查异常”(即 `RuntimeException`),而检查异常(如 `IOException`)默认不回滚。捕获任何异常都会破坏这个机制[^3][^4]。 #### 3. **解决方案:确保捕获异常时也能回滚** 如果您需要在捕获异常后仍回滚事务,有以下两种推荐方法: - **方法一:配置 `rollbackFor` 属性并重新抛出异常** 通过 `@Transactional(rollbackFor = Exception.class)` 指定回滚的异常类型,并在 `catch` 块中重新抛出异常,让 Spring 感知。 ```java @Transactional(rollbackFor = Exception.class) // 指定所有异常都触发回滚 public void updateData() { try { userService.update(user); } catch (Exception e) { e.printStackTrace(); throw e; // 重新抛出异常,Spring 会触发回滚 } } ``` 优点:简单高效,符合 Spring 原生机制[^3][^5]。 - **方法二:手动回滚事务** 如果不想重新抛出异常(例如需要返回自定义错误信息),可以在 `catch` 块中调用 `TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()` 强制回滚。 ```java @Transactional public String updateData() { try { userService.update(user); return "Success"; } catch (Exception e) { e.printStackTrace(); // 手动标记回滚事务回滚方法继续执行 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); return "Error: " + e.getMessage(); // 可返回友好错误信息 } } ``` 优点:灵活控制业务逻辑,适用于需要捕获异常并处理返回值的场景[^2][^4][^5]。 #### 4. **最佳实践建议** - **优先使用 `rollbackFor`**:在需要回滚非 `RuntimeException`(如自定义异常)时,显式声明 `rollbackFor` 属性[^3][^5]。 - **避免过度捕获**:仅在必要时捕获异常。如果仅需记录日志,考虑使用 AOP 切面处理,而不是在事务方法内 `try-catch`。 - **测试验证**:使用单元测试(如 JUnit + Spring Test)模拟异常,确保回滚行为符合预期。示例项目可参考 [SpringBoot-MyBatis-Demo](https://gitee.com/tcyj/springboot-mybatis-demo)[^5]。 总之,**捕获异常默认会阻止事务回滚**,必须通过配置或手动干预来修复。正确使用 `rollbackFor` 或手动回滚,能保证数据一致性[^1][^4]。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

摸鱼的小张同学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值