AOP实现事务:使用try?c/atch包裹@Transactional
注解的方法,当方法出现异常并满足一定条件时,在catch里可设置事务回滚,没有异常则直接提交事务。
“一定条件”包括:
- 只有异常传播出了标记了
@Transactional
注解的方法,事务才能回滚。在Spring的TransactionAspectSupport里有个 invokeWithinTransaction方法,里面就是处理事务的逻辑。可以看到,只有捕获到异常才能进行后续事务处理:
- 默认情况下,出现RuntimeException(非受检异常)或Error,Spring才会回滚事务。
打开Spring的DefaultTransactionAttribute
- 受检异常一般是业务异常或是类似另一种方法的返回值,出现这样的异常可能业务还能完成,所以不会主动回滚
- 而Error或RuntimeException代表非预期结果,应回滚
反面教材
注册用户:
- createUserError1会抛RuntimeException,但方法内的catch所有异常:
- createUserError2,注册用户同时会有一次
otherTask
文件读,若读文件失败,希望用户注册的DB操作回滚。这里虽无捕获异常,但因otherTask抛受检异常,createUserError2传播出去的也是受检异常,事务同样不回滚:
- readFile
createUserError1、2这俩方法的实现和调用,虽然避开了事务不生效的坑,但因异常处理不当,文件操作出现异常时依旧不回滚事务。
修复bug
以及如何通过日志来验证是否修复成功。针对这2种情况,对应的修复方法如下。
1 如果希望自己捕获异常并处理,可手动设置让当前事务处回滚态
@Transactional public void createUserRight1(String name) { try { userRepository.save(new UserEntity(name)); throw new RuntimeException("error"); } catch (Exception ex) { log.error("create user failed", ex); TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); } }
查看日志,事务确定回滚。
Transactional code has requested rollback
:手动请求回滚。
2 在注解中声明,期望遇到所有的Exception都回滚事务
以突破默认不回滚受检异常的限制。
查看日志,提示回滚:
该案例有DB操作、IO操作,在IO操作问题时期望DB事务也回滚,以确保逻辑一致性。
小结
由于异常处理不正确,导致虽然事务生效,但出现异常时没回滚。
Spring默认只对被@Transactional
注解的方法出现RuntimeException
和Error
时回滚,所以若方法捕获了异常,就需要通过手写代码处理事务回滚。
若希望Spring针对其他异常也可回滚,可相应配置@Transactional
注解的rollbackFor
和noRollbackFor
属性覆盖Spring的默认配置。
有些业务可能包含多次DB操作,不一定希望将两次操作作为一个事务,这时就需仔细考虑事务传播的配置。