🔍 为什么我的日志在事务回滚后也没了?——揭秘 REQUIRES_NEW 的陷阱

你有没有遇到过这样的场景?

你正在开发一个用户注册功能:先保存用户信息,再记录一条操作日志。为了保证数据一致性,你在主方法上加了 @Transactional 注解。但突然有一天,用户注册失败了,事务回滚了------这没问题。可你回头一看日志表,发现那条"注册失败"的审计日志居然也跟着没了

你一脸困惑:

"我明明是在事务里写入的日志,为什么回滚后日志也没了?"

"我不是用了 REQUIRES_NEW 吗?它难道不是独立事务?"

这就是我们今天要揭开的 Spring 事务中一个极其隐蔽却又高频踩坑的问题: @Transactional(propagation = Propagation.REQUIRES_NEW) 的"伪独立"陷阱

表面上看,REQUIRES_NEW 会挂起当前事务,开启一个全新的事务。理论上,即使外层事务回滚,这个新事务也应该已经提交、数据保留下来。但现实却常常打脸------尤其是在你没有真正理解 Spring 事务底层机制的情况下。

比如,当你在一个 @Transactional 方法中调用另一个标记为 REQUIRES_NEW 的方法时,你以为它运行在独立事务中,但实际上,如果这个方法调用发生在同一个类内(自调用),或者异常被捕获、传播配置不当,那么所谓的"新事务"可能根本没生效,日志自然也就随着主事务一起回滚了。

更让人头疼的是,这种问题在开发和测试阶段往往难以发现,只有在线上出现异常时才暴露出来,导致问题追溯困难、数据对账混乱。

这正是我们今天深入探讨的起点。

✅ 正常流程(理想情况):

java 复制代码
@Transactional
public void registerUser(User user) {
    saveUser(user); // 主事务中的操作
    
    logService.log("User registration attempt"); 
    // ↑ 调用外部 Bean,触发 REQUIRES_NEW,独立事务提交
}

@Service
public class LogService {
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void log(String message) {
        logMapper.insert(new Log(message));
    }
}

此时:即使 registerUser 后续抛出异常导致主事务回滚,log() 方法由于运行在独立事务中,且已提交,日志依然存在

为什么日志还是消失了?有以下几种可能

🛑 1. rollbackFor 未正确设置

默认情况下,Spring 事务只会回滚 RuntimeExceptionError。如果在方法内抛出了其他类型的异常(例如 IOException),而没有设置 rollbackFor,事务就不会回滚。

示例:

java 复制代码
@Service
public class LogService {
    
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void log(String message){
       try{
           logMapper.insert(new Log(message));
       }catch(Exception e) {
           throw new AuditLogException("审计日志记录失败", e);
       } 
    }
}

在这个例子中,AuditLogException 被显式列为需要回滚的异常类型。

⚠️ 2. 异常被捕获后没有重新抛出

当异常被 catch 捕获并且没有重新抛出时,Spring 事务管理器会认为方法执行没有问题,从而不会触发回滚。为了确保事务能够回滚,捕获的异常应该被重新抛出,或者标记为需要回滚的异常。

示例:

java 复制代码
@Service
public class LogService {
    
    @Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
    public void log(String message){
       try{
           logMapper.insert(new Log(message));
       }catch(Exception e) {
           log.error("Error occurred: ", e); // 捕获异常但不抛出,事务不会回滚
       } 
    }
}

在此代码中,异常被捕获但没有重新抛出,导致事务无法回滚。

🚫 3. 同一类中的方法调用

Spring 的事务管理基于动态代理实现,但如果同一类中的方法相互调用(自调用),事务管理就不会生效。因为自调用不会触发代理方法,事务管理也无法插入到执行过程中。

示例:

java 复制代码
@Transactional
public void registerUser(User user) {
    saveUser(user); // 主事务中的操作
    
    log("User registration attempt"); 

}

@Transactional(propagation = Propagation.REQUIRES_NEW)
 public void log(String message) {
     logMapper.insert(new Log(message));
 }

解决办法:

可以通过将方法提取到不同的类中,或通过外部调用来触发代理。

🔒 4. @Transactional 应用在final 或非 public方法上

Spring 默认使用动态代理(JDK 动态代理或 CGLIB)来实现声明式事务管理(@Transactional 注解)。JDK 动态代理只能代理接口的 public 方法,CGLIB 可以代理非 public 方法,无法代理 final 方法,因此这些方法上的事务也会失效。

示例:

java 复制代码
@Service
public class LogService {
    
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    private void log(String message) {
        logMapper.insert(new Log(message));
    }
    或者
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public final void log(String message) {
        logMapper.insert(new Log(message));
    }
}

🔄 5. 事务传播机制配置错误

如果配置了错误的事务传播行为,误将Propagation.REQUIRES_NEW配置为Propagation.REQUIRES

示例:

java 复制代码
@Service
public class LogService {
    
    @Transactional(propagation = Propagation.REQUIRES)
    public void log(String message) {
        logMapper.insert(new Log(message));
    }
}
相关推荐
qq_54702617913 小时前
Flowable 工作流引擎
java·服务器·前端
鼓掌MVP14 小时前
Java框架的发展历程体现了软件工程思想的持续进化
java·spring·架构
编程爱好者熊浪14 小时前
两次连接池泄露的BUG
java·数据库
lllsure14 小时前
【Spring Cloud】Spring Cloud Config
java·spring·spring cloud
拽着尾巴的鱼儿14 小时前
fixed-bug:JPA 关联关系的对象序列化循环引用问题
spring·bug·jpa
鬼火儿15 小时前
SpringBoot】Spring Boot 项目的打包配置
java·后端
NON-JUDGMENTAL15 小时前
Tomcat 新手避坑指南:环境配置 + 启动问题 + 乱码解决全流程
java·tomcat
cr7xin15 小时前
缓存三大问题及解决方案
redis·后端·缓存
chxii16 小时前
Maven 详解(上)
java·maven
李少兄16 小时前
IntelliJ IDEA 远程调试(Remote Debugging)教程
java·ide·intellij-idea