Spring Boot 开发笔记 - @Transactional 注解使用浅析
Spring Boot 中对方法开启事务管理非常轻松,只需要 @Transactional 注解就可以让方法开启事务,然而不了解其机制时使用可能会导致其失效。
@Transactional 本质是一个 AOP 动态代理,不需要开发者干预。下面来一个代码例子:
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
@Transactional(propagation=Propagation.REQUIRED,isolation=Isolation.DEFAULT,readOnly=false)
public void insert01(User u){
this.userMapper.insert(u);
throw new RuntimeException("测试插入事务");
}
public void insert02(User u){
this.insert01(u);
throw new RuntimeException("测试插入事务");
}
在这个例子中,调用 insert01 时,事务的执行不会有任何问题,但是调用 insert02 时,异常被正常抛出,但是数据依然被插入了数据库,说明事务没有被正常执行。
在外部调用 insert01 时,调用的就是被动态代理的 insert01,但是如果在一个类里自调用时,这样是无法调用到代理对象的,所以 insert02 中调用的不是代理对象 insert01,而是原本的方法。当然,原本的对象中是没有切片做事务增强的,自然也不能进行事务回滚。
一般的事务代理机制:

自调用时,通过 this 只能取到原本的方法:

那么如何解决自调用失效的问题呢?
-
最佳实践自然是不要产生自调用。
-
如果无法避免的话,那么可以只在
insert02上注解事务。(如果两个方法都要被外部调用,那就两个都写上) -
insert01开启事务,并且在insert02中不使用this调用,而是获取代理public void insert02(User u){ getService().insert01(u); } private UserService getService(){ // 采取这种方式的话 // @EnableAspectJAutoProxy(exposeProxy=true,proxyTargetClass=true) // 必须设置为true return AopContext.currentProxy() != null ? (UserService)AopContext.currentProxy() : this; } -
从
beanFactory中获取对象。Controller 中的UserService是代理对象,它是从beanFactory中得来的,那么 Service 类内调用其他方法时,也先从beanFacotry中拿出来就 OK 了。public void insert02(User u){ getService().insert01(u); } private UserService getService(){ return SpringContextUtil.getBean(this.getClass()); }
有关 AOP 和代理的问题可以看 从代理机制到 Spring AOP 和 Spring AOP 就是这么简单啦。这两篇文章讲得很清楚了。
除了代理导致的自调用失效,还有一个问题是方法抛出异常时,事务也没有回滚。这个则是因为 @Transactional 中捕获的异常只有 RuntimeException。
DefaultTransactionAttribute.java 源码中写的是:
@Override
public boolean rollbackOn(Throwable ex) {
return (ex instanceof RuntimeException || ex instanceof Error);
}
所以需要捕获其他异常时,必须把注解写成
@Transactional( rollbackFor = Exception.class )
更详细的使用要点可以看来自 IBM Developer 的 透彻的掌握 Spring 中@transactional 的使用