Skip to content

记一次 @Transactional 事务未回滚的调查

🏷️ Spring Boot

前几天维护公司的一个项目,发现 Controller 的方法上虽然加了 @Transactional(rollbackFor = Exception.class) 注解,这个方法中也没有加 try catch 处理,但是发生异常时事务并没有回滚。

常见的事务未起作用多是因为方法是内部调用的、方法被定义为 private 或者异常被捕捉了但没有 throw 出去。

排查时在方法中添加了 try catch 处理,并在 catchthrow 了捕捉的异常。仔细查看日志发现,事务确实开启了,异常也确实被捕捉并 throw 了出去。但是,事务也确实没有 rollback ,而是在最后正常 commit 了。

java
Transaction synchronization committing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@334e353]
Transaction synchronization deregistering SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@334e353]
Transaction synchronization closing SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@334e353]

通过 debug 发现,异常发生后,代码运行到了使用 AOP 创建的 around 处理中,而在这个处理中,就有 try catch 处理。这是才想起来, AOP 本质上就是通过创建代理类实现的,而 @Transactional 也是基于 AOP 实现的。

这是一个用于记录请求日志的 AOP ,其代码如下:

java
@Pointcut("execution(public * com.example.app.controller.*.*(..))")
public void webLog() {
}

@Around("webLog()")
public Object arround(ProceedingJoinPoint pjp) {
    try {
        // 业务处理
        Object obj = pjp.proceed();
        // ... 一些日志处理
        return obj;
    } catch (Throwable t) {
        logger.error(t.getMessage(), t);
        return ResponseInfo.buildError("请求错误");
    }
}

在这里 @Transactional 注解相当于添加到了 around 方法上,异常被捕获然后返回了一个业务的错误响应。没有 throw 异常,所以也就没有触发 rollback ,最终事务正常提交了。

知道原因就好处理了,记录异常日志后,将异常再抛出去就可以了,而原本的返回的业务错误,可以通过 @ControllerAdvice + @ExceptionHandler 的全局异常处理来实现。

java
@Around("webLog()")
public Object arround(ProceedingJoinPoint pjp) throws Throwable {
    try {
        //业务处理
        Object obj = pjp.proceed();
        // ... 一些日志处理
        return obj;
    } catch (Throwable t) {
        logger.error(t.getMessage(), t);
        throw t;
    }
}