Spring Boot下@Transactional异常回滚同时写入错误到数据库的问题
核心问题原因
- 异常被内部捕获未向上抛出:
@Transactional默认只有目标方法抛出未捕获的异常时,才会触发事务回滚。你的代码把异常在try-catch里吞了,事务切面感知不到异常发生,会默认把主事务正常提交,导致processAndPersist的业务数据被写入数据库。 - 同类内部调用事务方法不生效:Spring的
@Transactional是基于动态代理实现的,只有外部调用代理类的事务方法才会触发切面逻辑。你在同一个类的updateData里直接调用本类的persistError,本质是this调用,走的是原对象而非代理对象,REQUIRES_NEW的传播级别完全不生效,两个方法共用同一个主事务。
解决方案
要实现主事务回滚、错误日志独立提交,需要解决上述两个问题,推荐方案如下:
方案1:错误日志方法抽离到独立类(更推荐,职责分离更清晰)
- 新建单独的错误日志服务类,专门处理错误日志存储,保证它的事务是独立的新事务:
@Service public class ErrorLogService { // 新开启独立事务,不受调用方事务影响 @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) public void persistError(String message) { persistError2Db(message); } }
- 改造原业务服务类,注入独立的日志服务,异常处理完成后手动标记主事务回滚:
@Service public class MyService{ // 注入独立的日志服务 @Resource private ErrorLogService errorLogService; @Transactional(rollbackFor = Exception.class) public void updateData() { try{ processAndPersist(); // 业务数据插入操作 int i = 1/0; // 模拟运行时异常 }catch (Exception e){ // 调用独立服务存错误,日志事务独立提交,不会跟着主事务回滚 errorLogService.persistError(e.getMessage()); trackReportError(filename, e.getMessage()); // 手动标记当前主事务需要回滚,也可以直接抛出异常交由切面自动回滚 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } } }
方案2:注入自身代理解决同类调用问题
如果不想拆分类,可以在当前类中注入自身的代理对象,通过代理对象调用事务方法:
@Service public class MyService{ // 加@Lazy避免循环依赖问题,注入自身的代理对象 @Lazy @Resource private MyService self; @Transactional(rollbackFor = Exception.class) public void updateData() { try{ processAndPersist(); int i = 1/0; }catch (Exception e){ // 用代理对象调用,事务注解生效 self.persistError(e.getMessage()); trackReportError(filename, e.getMessage()); // 标记主事务回滚 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } } @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class) public void persistError(String message) { persistError2Db(message); } }
注意事项
- 错误日志的独立事务一定要保证本身不会抛出未捕获的异常,否则会影响主事务逻辑,必要时可以在
persistError方法内部加try-catch处理。 - 如果选择抛出异常触发主事务回滚,要注意上层调用方是否会处理该异常,避免出现不必要的报错。
内容的提问来源于stack exchange,提问作者mcfly soft
相关产品推荐
相关产品推荐

