Spring 事务管理 Transaction rolled back because it has been marked as rollback-only

举个例子:

假设我们有一个服务类的方法A,它调用了方法B。方法B抛出了一个异常,但是方法A捕获了这个异常并没有重新抛出。但是,方法B的事务传播行为可能是REQUIRED,所以方法B会在同一个事务中执行。当方法B抛出异常时,事务被标记为回滚。然后方法A捕获了异常,没有重新抛出,那么方法A的事务拦截器在退出时会尝试提交事务,但是发现事务已经被标记为回滚,于是就会回滚事务并输出上述信息。

解决:

检查代码中是否有在事务方法中捕获了异常但没有正确处理的情况。如果希望事务回滚,通常应该在捕获异常后,将异常重新抛出(或者不再捕获异常,让异常自动抛出)。

如果确实需要捕获异常并且不想回滚事务,那么可以考虑将可能抛出异常的方法的事务传播行为设置为REQUIRES_NEW,这样它会在一个新的事务中执行,不会影响当前事务。

但是,请注意,REQUIRES_NEW会开启一个新事务,如果原事务已经存在,则会挂起原事务,这样方法B的异常就不会导致原事务回滚。

另一种情况是,可能你并不想在一个事务中运行整个方法链,那么可以考虑调整事务的边界,将不需要事务的方法排除,或者将事务的传播行为设置为NOT_SUPPORTED等。

典型代码场景

java 复制代码
@Service
public class UserService {
    
    @Transactional
    public void outerMethod() {
        try {
            innerMethod();  // 内层方法抛出异常
            // 其他业务逻辑...
        } catch (Exception e) {
            // 捕获异常,希望继续执行
            log.error("发生错误", e);
        }
        // 这里Spring会尝试提交事务,但事务已被标记为rollback-only
    }
    
    @Transactional(propagation = Propagation.REQUIRED)
    public void innerMethod() {
        // 业务操作...
        throw new RuntimeException("业务异常"); // 导致事务被标记为rollback-only
    }
}

解决方案

方案1:重新抛出异常(推荐)

java 复制代码
@Transactional
public void outerMethod() {
    try {
        innerMethod();
    } catch (Exception e) {
        log.error("发生错误", e);
        throw e; // 重新抛出异常,让事务正常回滚
    }
}

方案2:使用不同的事务传播机制

java 复制代码
@Service
public class UserService {
    
    @Transactional
    public void outerMethod() {
        try {
            // 使用REQUIRES_NEW创建新事务
            innerMethodWithNewTransaction();
        } catch (Exception e) {
            log.error("内层方法失败,但外层事务继续", e);
        }
        // 外层事务可以正常提交
    }
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void innerMethodWithNewTransaction() {
        // 这个方法的异常不会影响外层事务
        throw new RuntimeException("业务异常");
    }
}

方案3:手动控制事务边界

java 复制代码
@Service
public class UserService {
    
    @Autowired
    private TransactionTemplate transactionTemplate;
    
    public void outerMethod() {
        // 手动控制事务
        transactionTemplate.execute(status -> {
            try {
                innerMethod();
                return true;
            } catch (Exception e) {
                status.setRollbackOnly(); // 明确标记回滚
                log.error("事务回滚", e);
                return false;
            }
        });
    }
}

方案4:使用声明式事务控制

java 复制代码
@Service
public class UserService {
    
    @Transactional
    public void outerMethod() {
        innerMethod();
        // 让异常自然传播,不要捕获
    }
    
    // 指定回滚的异常类型
    @Transactional(rollbackFor = Exception.class)
    public void innerMethod() {
        // 业务逻辑
    }
}

预防措施

  1. 统一异常处理策略:在事务边界明确异常处理方式
  2. 合理使用传播机制
    • REQUIRED(默认):加入现有事务
    • REQUIRES_NEW:创建新事务
    • NESTED:嵌套事务
  3. 避免在事务方法中静默捕获异常
  4. 使用事务监听器:监控事务状态
java 复制代码
// 事务事件监听
@Component
public class TransactionEventListener {
    
    @TransactionalEventListener(phase = TransactionPhase.AFTER_ROLLBACK)
    public void handleRollback(TransactionRolledBackEvent event) {
        log.warn("事务回滚: {}", event.getTransactionName());
    }
}
相关推荐
JIngJaneIL11 分钟前
社区互助|社区交易|基于springboot+vue的社区互助交易系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·社区互助
晚风吹人醒.17 分钟前
缓存中间件Redis安装及功能演示、企业案例
linux·数据库·redis·ubuntu·缓存·中间件
Y***985136 分钟前
DVWA靶场通关——SQL Injection篇
数据库·sql
Yawesh_best38 分钟前
告别系统壁垒!WSL+cpolar 让跨平台开发效率翻倍
运维·服务器·数据库·笔记·web安全
蒋士峰DBA修行之路40 分钟前
实验二十八 SQL PATCH调优
数据库·sql·gaussdb
这是程序猿1 小时前
基于java的ssm框架旅游在线平台
java·开发语言·spring boot·spring·旅游·旅游在线平台
I***t7161 小时前
一条sql 在MySQL中是如何执行的
数据库·sql·mysql
i***t9191 小时前
基于SpringBoot和PostGIS的云南与缅甸的千里边境线实战
java·spring boot·spring
k***08291 小时前
【监控】spring actuator源码速读
java·spring boot·spring
一 乐1 小时前
应急知识学习|基于springboot+vue的应急知识学习系统(源码+数据库+文档)
数据库·vue.js·spring boot