Spring的事务管理机制默认基于AOP代理实现,当我们在同一个类中调用标注了@Transactional的方法时,很容易出现事务不生效的情况,这是Spring事务失效的常见诱因之一。

问题产生的核心原理
Spring的@Transactional注解是通过AOP代理来生效的,当我们从外部调用一个Bean的事务方法时,实际调用的是该Bean的代理对象的方法,代理对象会在方法执行前后添加事务管理的逻辑,比如开启事务、提交事务或者回滚事务。
但如果是在同一个类的内部,一个方法直接调用另一个标注了@Transactional的方法,此时调用的是目标对象本身的方法,而不是代理对象的方法,因此事务增强的逻辑不会被执行,自然就会出现事务失效的情况。
具体失效场景示例
下面通过一个简单的业务场景来展示这个问题,我们有一个用户服务类,包含保存用户和保存用户日志两个方法,其中保存用户日志的方法标注了事务注解。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
// 保存用户的主方法
public void saveUser(String username) {
// 同类内直接调用标注了@Transactional的方法
this.saveUserLog(username);
// 模拟用户保存逻辑,这里故意制造异常
jdbcTemplate.update("insert into user(name) values(?)", username);
int i = 1 / 0; // 制造除零异常,期望事务回滚
}
// 标注了事务注解的保存日志方法
@Transactional
public void saveUserLog(String username) {
jdbcTemplate.update("insert into user_log(content) values(?)", "保存用户:" + username);
}
}
上面的代码中,saveUser方法调用了同一个类中的saveUserLog方法,即使saveUserLog标注了@Transactional,当saveUser方法执行到除零异常时,user_log表中的插入记录并不会回滚,因为saveUserLog的事务增强没有生效。
验证事务是否生效的方式
我们可以在调用方法前后查询数据库对应表的数据,也可以在saveUserLog方法中主动抛出异常,观察数据是否回滚。如果要确认是否是同类调用导致的问题,可以通过下面的方式获取当前类的代理对象来验证。
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
public void saveUser(String username) {
// 通过AopContext获取当前类的代理对象,再调用事务方法
UserService proxy = (UserService) AopContext.currentProxy();
proxy.saveUserLog(username);
jdbcTemplate.update("insert into user(name) values(?)", username);
int i = 1 / 0;
}
@Transactional
public void saveUserLog(String username) {
jdbcTemplate.update("insert into user_log(content) values(?)", "保存用户:" + username);
}
}
注意使用AopContext获取代理对象需要在启动类或者配置类上添加@EnableAspectJAutoProxy(exposeProxy = true)注解,否则会抛出异常。
常见的解决方案
方案一:将事务方法拆分到不同的类中
这是最简单也最推荐的方式,把saveUserLog方法放到另一个服务类中,比如UserLogService,然后在UserService中注入UserLogService,通过注入的Bean来调用事务方法,这样调用的是代理对象的方法,事务就会生效。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private UserLogService userLogService;
public void saveUser(String username) {
userLogService.saveUserLog(username);
jdbcTemplate.update("insert into user(name) values(?)", username);
int i = 1 / 0;
}
}
@Service
public class UserLogService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Transactional
public void saveUserLog(String username) {
jdbcTemplate.update("insert into user_log(content) values(?)", "保存用户:" + username);
}
}
方案二:使用自注入的方式获取代理对象
在同一个类中注入自身的代理对象,然后通过注入的代理对象调用事务方法,这种方式不需要拆分类,但是会让代码有一定的耦合性。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
// 自注入当前类的代理对象
@Autowired
private UserService userService;
public void saveUser(String username) {
userService.saveUserLog(username);
jdbcTemplate.update("insert into user(name) values(?)", username);
int i = 1 / 0;
}
@Transactional
public void saveUserLog(String username) {
jdbcTemplate.update("insert into user_log(content) values(?)", "保存用户:" + username);
}
}
方案三:使用编程式事务
如果确实需要在同一个类中处理,也可以使用编程式事务,直接通过TransactionTemplate来手动管理事务,这种方式不依赖AOP代理,因此不存在同类调用失效的问题。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.support.TransactionTemplate;
@Service
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
@Autowired
private TransactionTemplate transactionTemplate;
public void saveUser(String username) {
transactionTemplate.execute(status -> {
jdbcTemplate.update("insert into user_log(content) values(?)", "保存用户:" + username);
return null;
});
jdbcTemplate.update("insert into user(name) values(?)", username);
int i = 1 / 0;
}
}
注意事项
- 除了同类方法调用之外,还有非public方法标注@Transactional、异常类型不匹配、数据源没有配置事务管理器等情况也会导致事务失效,需要逐一排查。
- 自注入的方式在某些Spring版本中可能需要配置允许循环依赖,否则会启动失败。
- 编程式事务需要手动处理事务的提交和回滚逻辑,相对注解式事务更繁琐,适合需要精细控制事务的场景。