通过代码来判断事务是否生效?

事务是否生效

1.异常被吃了

  1. @Transactional
  2. public void createUserWrong1(String name) {
  3. try {
  4. userRepository.save(new UserEntity(name));
  5. throw new RuntimeException("error");
  6. } catch (Exception ex) {
  7. log.error("create user failed", ex);
  8. }
  9. }

解决思路:
去掉try catch
在catch中手动TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();

2.默认rollbackFor= RuntimeException and Error

由于这里是IOException extends Exception所以不生效

  1. @Transactional
  2. public void createUserWrong2(String name) throws IOException {
  3. userRepository.save(new UserEntity(name));
  4. otherTask();
  5. }
  6. private void otherTask() throws IOException {
  7. Files.readAllLines(Paths.get("file-that-not-exist"));
  8. }

解决思路:@Transactional(rollbackFor = Exception.class)

3.this没有用到代理对象 & @Transactional的方法必须搭配public

  1. public int createUserWrong1(String name) {
  2. try {
  3. this.createUserPrivate(new UserEntity(name));
  4. } catch (Exception ex) {
  5. log.error("create user failed because {}", ex.getMessage());
  6. }
  7. return userRepository.findByName(name).size();
  8. }
  9. @Transactional
  10. private void createUserPrivate(UserEntity entity) {
  11. userRepository.save(entity);
  12. if (entity.getName().contains("test"))
  13. throw new RuntimeException("invalid username!");
  14. }

解决思路:
this改成自动注入自己,(spring会解决依赖循环)
private改成public

4,异常被吃了

  1. @Transactional
  2. public int createUserWrong3(String name) {
  3. try {
  4. this.createUserPublic(new UserEntity(name));
  5. } catch (Exception ex) {
  6. log.error("create user failed because {}", ex.getMessage());
  7. }
  8. return userRepository.findByName(name).size();
  9. }
  10. @Transactional
  11. public void createUserPublic(UserEntity entity) {
  12. userRepository.save(entity);
  13. if (entity.getName().contains("test"))
  14. throw new RuntimeException("invalid username!");
  15. }

解决思路:去掉catch异常

5.事务传播

两个事务各自独立

期待:
副逻辑subUserService.createSubUserWithExceptionWrong不影响主逻辑createMainUser的事务
事务不要互相影响

  1. @Transactional
  2. public void createUserWrong(UserEntity entity) {
  3. createMainUser(entity);
  4. subUserService.createSubUserWithExceptionWrong(entity);
  5. }
  6. private void createMainUser(UserEntity entity) {
  7. userRepository.save(entity);
  8. log.info("createMainUser finish");
  9. }
  10. @Service
  11. @Slf4j
  12. public class SubUserService {
  13. @Transactional
  14. public void createSubUserWithExceptionWrong(UserEntity entity) {
  15. log.info("createSubUserWithExceptionWrong start");
  16. userRepository.save(entity);
  17. throw new RuntimeException("invalid status");
  18. }
  19. }

失败原因:createUserWrong有异常就会回滚

只收集异常也不行

期待:副逻辑不影响主逻辑的事务

  1. @Transactional
  2. public void createUserWrong2(UserEntity entity) {
  3. createMainUser(entity);
  4. try {
  5. subUserService.createSubUserWithExceptionWrong(entity);
  6. } catch (Exception ex) {
  7. // 虽然捕获了异常,但是因为没有开启新事务,而当前事务因为异常已经被标记为rollback了,所以最终还是会回滚。
  8. log.error("create sub user error:{}", ex.getMessage());
  9. }
  10. }

失败原因:虽然捕获了异常,但是因为没有开启新事务,而当前事务因为异常已经被标记为rollback了,所以最终还是会回滚
@Transactional默认是Propagation.REQUIRED:如果当前存在事务,则加入该事务,如果当前不存在事务,则创建一个新的事务
当前情况就是加入了之前的事务,这个事务的回滚字段设置为true,哪怕catch住了也会回滚

解决思路:

副逻辑弄一个新的事务,主逻辑catch副逻辑的异常

  1. @Transactional
  2. public void createUserWrong2(UserEntity entity) {
  3. createMainUser(entity);
  4. try {
  5. subUserService.createSubUserWithExceptionWrong(entity);
  6. } catch (Exception ex) {
  7. // 虽然捕获了异常,但是因为没有开启新事务,而当前事务因为异常已经被标记为rollback了,所以最终还是会回滚。
  8. log.error("create sub user error:{}", ex.getMessage());
  9. }
  10. }
  11. @Transactional(propagation = Propagation.REQUIRES_NEW)
  12. public void createSubUserWithExceptionRight(UserEntity entity) {}

事务传播分类

小册里面总结到:声明式事务注解@Transactional是写给调用者看的
每种传播机制最好在代码里面全部用一遍,就有一定的了解了,抽手打一下自己的理解

REQUIRED

默认的策略,有事务就加入,没有就新建

SUPPORTS

有事务就加入,没有就没有

MANDATORY

有事务就加入,没有就报错

REQUIRES_NEW

新建独立的事务,跟之前不影响

NOT_SUPPORTED

以非事务的方式运行,无论调用者是否存在事务,自己都不受其影响

NEVER

以非事务的方式运行,如果当前存在事务,则抛出异常

NESTED

跟默认的一样

练手

  1. @Transactional
  2. public void method1(UserEntity userEntity){
  3. userRepository.save(userEntity);
  4. self.a(userEntity);
  5. throw new RuntimeException("m1");
  6. }
  7. @Transactional(propagation = Propagation.NOT_SUPPORTED)
  8. public void a(UserEntity userEntity){
  9. UserEntity aaa = new UserEntity("NOT_SUPPORTED");
  10. userRepository.save(aaa);
  11. }
  12. 结果1事务不生效,a的事务生效
  1. @Transactional
  2. public void method2(UserEntity userEntity){
  3. userRepository.save(userEntity);
  4. self.b(userEntity);
  5. }
  6. @Transactional(propagation = Propagation.NEVER)
  7. public void b(UserEntity userEntity){
  8. UserEntity aaa = new UserEntity("NEVER");
  9. userRepository.save(aaa);
  10. }
  11. 发现调用者有事务,直接回滚报错,2也不生效
  1. public void method3(UserEntity userEntity){
  2. userRepository.save(userEntity);
  3. self.b(userEntity);
  4. throw new RuntimeException("m1");
  5. }
  6. @Transactional(propagation = Propagation.NEVER)
  7. public void b(UserEntity userEntity){
  8. UserEntity aaa = new UserEntity("NEVER");
  9. userRepository.save(aaa);
  10. }
  11. 发现调用者没事务,无事务执行b,然后无事务执行3
  12. 插入2条数据
  13. 哪怕b报错也插入

源码分析

总结

编程式事务,controller里面写if else
声明式事务,service里面用注解@Transactional
注意事项:

  1. 必须是动态代理对象调方法,不能是普通的this
  2. 异常不能吃掉
  3. 异常的类型要留意Exception
  4. 要根据业务判断事务是一个还是多个,关系是什么,谁成功,谁失败
  5. 搞不定就用编程式事务if else