前言
近段时间在重构代码的时候,发现一个严重的bug:事务方法报错之后,竟然不回滚,原因是我把异常捕获了,具体原因下文会讲。细思恐极,整个项目都是那种写法。于是,开始对@Transactional注解进行研究和测试。
@Transactional能够帮我们自动管理事务。一般在service层的方法中加上该注解。但是有很多坑!以下的代码均为不成形的代码,俗称伪代码。
坑一:只对外部调用有效
内部调用事务方法会导致事务无效,原因是spring使用动态代理进行事务管理,只能对外部调用进行拦截,内部调用的管不了。
class UserController {
// 外部调用,受事务管理
userService.saveUser();
}
class UserService {
@Transation
public void saveUser() {}
public void saveUserInternal() {
// 内部调用,不受事务管理
saveUser();
}
}
解决办法:获取代理对象,并通过代理对象去调用
第一步,开启暴露代理
<aop:aspectj-autoproxy expose-proxy="true" />
第二步,通过代理对象去调用事务方法
((UserService)AopContext.getProxy()).saveUser();
坑二:只对public方法有效
因为事务注解只对外部调用有效(参考坑一),private方法只能内部调用,所以private无效很好理解。但是protected为什么无效。stackoverflow的这个回答解释了我的困惑。
意思是说:JDK代理不支持对protected方法的拦截,但是CGLIB是可以的,但是不推荐使用,然后Spring那帮家伙为了统一,直接砍掉了protected方法的支持。
坑三:只对非受检异常有效
什么是受检异常和非受检异常?简单来讲,受检异常就是如果不捕获异常或抛出异常,那么编译会报错,常见的如FileInputStream,你要么捕获异常,要么抛出异常。
解决方法:@Transactional(rollbackFor = Exception.class)
@Transactional(rollbackFor = Exception.class)
public void saveFile() throws FileNotFoundException {
// 数据操作
userDao.saveUser();
// 会抛出异常,因为设置了rollbackFor属性,会回滚
InputStream in = new FileInputStream(new File("xxx.xxx");
}
坑四:只对抛出异常有效
异常捕获了,相当于隐藏了异常,出现异常不会自动回滚。如果一定要捕获,请在捕获代码块里手动回滚
@Transactional
public void saveUser() {
try {
saveUser();
} catch(Exception e) {
// 手动关闭事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
悬而未决
鄙人在调试的时候遇到一个问题,在事务方法里,先删除后查询,是正常的;但是先保存后查询,就查询不到保存的数据。各位大佬,如果知道是什么原因可以点播下。