Transaction rolled back because it has been marked as rollback-only 解决办法

本文探讨了Spring框架中一个常见的事务回滚问题:“Transaction rolled back because it has been marked as rollback-only”。通过分析源码,解释了问题产生的根本原因,并提供了解决方案,即将事务传播行为从REQUIRED更改为SUPPORTS。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

       今天早上高高兴兴上班,居然收到一大堆报警信息,仔细看了一下具体内容,都在提示这个错误

:"Transaction rolled back because it has been marked as rollback-only"

我一看就觉得奇怪了,为什么会有rollback呢,出现问题的地方,就是一个get查询,而且已经标记为readonly=true了。

下面是项目的事务配置:

 <tx:advice id="txAdvice" transaction-manager="txManager">
        <tx:attributes>
            <tx:method name="get*" read-only="true" />
            <tx:method name="query*" read-only="true" />
            <tx:method name="is*" read-only="true" />
            <tx:method name="format*" read-only="true" />
            <tx:method name="fill*" read-only="true" />
            <tx:method name="set*" read-only="true" />
            <tx:method name="*" propagation="REQUIRED" rollback-for="Exception" />
        </tx:attributes>
    </tx:advice>
 

没毛病啊,根据事务的传播性默认为REQUIRED,就是“没有则新建一个事务,存在则加入当前事务”。但是一个只读的事务,为啥会有回滚呢?

上网查了一下,返现很多有人都有遇到类似的问题。

 

原因:

如果getApply()的方法里面调用了一个load()的方法,同时getApply()捕捉了load()方法的异常而没有抛出,则就会报这个错误“”,如果继续往外抛出这个错误就没有问题了。

这是因为事务默认属性配置都为PROPAGATION_REQUIRED,

所以两方法使用的是同一事务,如果load方法出现异常,则org.springframework.transaction.interceptor.

TransactionInterceptor的invoke处理后则会抛出异常.执行completeTransactionAfterThrowing(txInfo, ex);

 

事务抛出的源码如下:

public final void commit(TransactionStatus status) throws TransactionException {
        if (status.isCompleted()) {
            throw new IllegalTransactionStateException("Transaction is already completed - do not call commit or rollback more than once per transaction");
        }

        DefaultTransactionStatus defStatus = (DefaultTransactionStatus) status;
        if (defStatus.isLocalRollbackOnly()) {
            if (defStatus.isDebug()) {
                this.logger.debug("Transactional code has requested rollback");
            }
            processRollback(defStatus);
            return;
        }
        if ((!(shouldCommitOnGlobalRollbackOnly())) && (defStatus.isGlobalRollbackOnly())) {
            if (defStatus.isDebug()) {
                this.logger.debug("Global transaction is marked as rollback-only but transactional code requested commit");
            }
            processRollback(defStatus);

            if ((status.isNewTransaction()) || (isFailEarlyOnGlobalRollbackOnly())) {
                throw new UnexpectedRollbackException("Transaction rolled back because it has been marked as rollback-only");
            }

            return;
        }

        processCommit(defStatus);
    }

 

 

纠其原理其实很简单,在locad()返回的时候,transaction被设置为rollback-only了,但是getApply()正常消化掉,没有继续向外抛。那么getApply()结束的时候,transaction会执commit操作,但是transaction已经被设置为rollback-only了。

所以会出现这个错误。

调整好问题,找解决方案,问题就出现在propagation="REQUIRED"这个属性上。

标准文档上这样写:

MANDATORY 
          Support a current transaction, throw an exception if none exists.
NESTED 
          Execute within a nested transaction if a current transaction exists, behave like PROPAGATION_REQUIRED else.
NEVER 
          Execute non-transactionally, throw an exception if a transaction exists.
NOT_SUPPORTED 
          Execute non-transactionally, suspend the current transaction if one exists.
REQUIRED 
          Support a current transaction, create a new one if none exists.
REQUIRES_NEW 
          Create a new transaction, suspend the current transaction if one exists.
SUPPORTS 
          Support a current transaction, execute non-transactionally if none exists.

事务配置参考地址: https://blog.youkuaiyun.com/bao19901210/article/details/41724355

怎么办?改成supports:

        <tx:attributes>

            <tx:method name="*" read-only="true" propagation="SUPPORTS"/>

        </tx:attributes>

这个状态用一句话概括就是“有则加入事物,无也不创建事物”。对于只读的方法来说,其实不启用事务,也是没有关系的。

 

参考博客:

https://blog.youkuaiyun.com/caolaosanahnu/article/details/6890107

 

### 解决方案:Transaction Rolled Back Because It Has Been Marked As Rollback-Only 当遇到 `transaction rolled back because it has been marked as rollback-only` 错误时,通常意味着当前事务已经被标记为只回滚状态。这种情况可能发生在多种场景下,特别是在分布式事务管理环境中,如Java EE中的JTA或EJB应用。 #### 1. 理解原因 此错误的根本原因是某个操作已经触发了事务的回滚机制,使得整个事务无法再继续提交。这可能是由于显式的调用了 `setRollbackOnly()` 方法,或者是某些未被捕获的异常导致[^1]。 #### 2. 检查并调整配置 对于基于Spring的应用程序来说,确保正确设置了 `@Transactional` 注解的相关参数非常重要。特别是要关注 `propagation` 和 `isolation` 属性以及 `rollbackFor` 参数: - **Propagation Behavior**: 设置合适的传播行为可以有效避免不必要的嵌套事务问题。 - **Isolation Level**: 调整隔离级别有助于减少并发冲突的可能性。 - **Exception Handling with `rollbackFor`**: 明确指定哪些类型的异常应该触发事务回滚是非常重要的。默认情况下,只有运行时异常才会引起回滚;因此,如果业务逻辑中有特定的受检异常也需要回滚,则需特别指明这些异常类型[^3]。 ```java @Transactional(propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, rollbackFor = {CustomCheckedException.class}) public void performOperation() { // Business logic here... } ``` #### 3. 处理嵌套事务 在涉及多个服务层方法的情况下,注意区分不同层次之间的事务边界。虽然大多数时候推荐使用声明式事务管理方式(即通过注解),但在复杂场景中适当采用编程式事务控制也可能有所帮助。此外,在JTA环境下要注意同步机制的影响[^2]。 #### 4. 日志记录与调试 启用详细的日志输出可以帮助定位具体哪个部分的操作引发了事务的状态变化。查看应用程序的日志文件,寻找任何有关事务管理和异常处理的信息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值