事务的传播行为、声明式事务和编程式事务、异常失效、事务错误使用、分布式事务

Spring事务详细传播属性解释

Spring事务(Transaction)的传播(propagation)属性以及隔离(isolation)级别

SpringBoot异常处理回滚事务详解(自动回滚、手动回滚、部分回滚)

声明式事务和编程式事务

事务传播行为测试

1. Spring事务的传播行为

1. 7种传播行为

事务传播行为类型说明
PROPAGATION_REQUIRED如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。
PROPAGATION_REQUIRES_NEW新建事务,如果当前存在事务,把当前事务挂起。
PROPAGATION_NESTED如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。
PROPAGATION_SUPPORTS支持当前事务,如果当前没有事务,就以非事务方式执行。
PROPAGATION_MANDATORY使用当前的事务,如果当前没有事务,就抛出异常。
PROPAGATION_NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
PROPAGATION_NEVER以非事务方式执行,如果当前存在事务,则抛出异常。

2. 传播属性示例

2.1 PROPAGATION_REQUIRED

我们为User1Service和User2Service相应方法加上Propagation.REQUIRED属性。

User1Service方法:

@Service
public class User1ServiceImpl implements User1Service {
    //省略其他..
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void addRequired(User1 user){
        user1Mapper.insert(user);
    }
}

User2Service方法:

@Service
public class User2ServiceImpl implements User2Service {
    //省略其他...
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void addRequired(User2 user){
        user2Mapper.insert(user);
    }
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void addRequiredException(User2 user){
        user2Mapper.insert(user);
        throw new RuntimeException();
    }
}

1.1 场景一

此场景外围方法没有开启事务。

验证方法1:

// 张三 和 李四成功保存。
// 外层方法抛出异常但不受异常管理,user1Service 和 user2Service 在各自的事务内正常提交
@Override
    public void notransaction_exception_required_required(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequired(user1);
        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addRequired(user2);
        throw new RuntimeException();
    }

验证方法2:

    // 张三成功,李四失败
	// 外层方法不受异常管理,user1Service 和 user2Service 在各自的事务内提交;user1Service成功提交、user2Service回滚
	@Override
    public void notransaction_required_required_exception(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequired(user1);
        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addRequiredException(user2);
    }

分别执行验证方法,结果:

验证方法序号数据库结果结果分析
1“张三”、“李四”均插入。外围方法未开启事务,插入“张三”、“李四”方法在自己的事务中独立运行,外围方法异常不影响内部插入“张三”、“李四”方法独立的事务。
2“张三”插入,“李四”未插入。外围方法没有事务,插入“张三”、“李四”方法都在自己的事务中独立运行,所以插入“李四”方法抛出异常只会回滚插入“李四”方法,插入“张三”方法不受影响。

结论:通过这两个方法我们证明了在外围方法未开启事务的情况下Propagation.REQUIRED修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。

1.2 场景二

外围方法开启事务,这个是使用率比较高的场景。

验证方法1:

// 张三、李四均未插入。
//  Propagation.REQUIRED 传播属性的特点外层有事务就加入外层事务
// 由于user1Service、user2Service都是Propagation.REQUIRED级别,所以此时所有操作都在一个事务内;外围方法抛出异常全部回滚  
@Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void transaction_exception_required_required(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequired(user1);
        
        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addRequired(user2);
        
        throw new RuntimeException();
    }

验证方法2:

// 张三、李四均未插入。
//  Propagation.REQUIRED 传播属性的特点外层有事务就加入外层事务
// 由于user1Service、user2Service都是Propagation.REQUIRED级别,所以此时所有操作都在一个事务内;user2Service抛出异常全部回滚
@Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void transaction_required_required_exception(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequired(user1);
        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addRequiredException(user2);
    }

验证方法3:

// 会回滚且 报错 Transaction rolled back because it has been marked as rollback-only
// 在整个事务中 ,在user2Service已经抛出的异常此时事务被标记为rollBack;在外层方法处理异常时事务被标记为commit,出现矛盾保存Transaction rolled back because it has been marked as rollback-only
@Transactional
    @Override
    public void transaction_required_required_exception_try(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequired(user1);
        User2 user2=new User2();
        user2.setName("李四");
        try {
            user2Service.addRequiredException(user2);
        } catch (Exception e) {
            System.out.println("方法回滚");
        }
    }

验证方法4:

  1. 修改addRequiredException 为 addRequiredExceptionAndCatch
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void addRequiredExceptionAndCatch(User2 user){
        user2Mapper.insert(user);
        try {
            throw new RuntimeException();
        } catch (Exception e) {
           
        }
  
    }
  1. 测试
// 事务提交 张三、李四全部插入;插入李四的异常被catch外层无法感知提交事务。
// 总而言之只要在事务处理过程中存在直接抛异常的动作一定会回滚
@Transactional
    @Override
    public void transaction_required_required_exception_try(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequired(user1);
        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addRequiredExceptionAndCatch(user2);
    }

分别执行验证方法,结果:

验证方法序号数据库结果结果分析
1“张三”、“李四”均未插入。外围方法开启事务,内部方法加入外围方法事务,外围方法回滚,内部方法也要回滚。
2“张三”、“李四”均未插入。外围方法开启事务,内部方法加入外围方法事务,内部方法抛出异常回滚,外围方法感知异常致使整体事务回滚。
3“张三”、“李四”均未插入。外围方法开启事务,内部方法加入外围方法事务,内部方法抛出异常回滚,即使方法被catch不被外围方法感知,整个事务依然回滚。
4“张三”、“李四”均插入。外围方法开启事务,内部方法加入外围方法事务,内部方法抛出异常被catch,不能被外围方法感知,整个事务提交。

结论:以上试验结果我们证明在外围方法开启事务的情况下Propagation.REQUIRED修饰的内部方法会加入到外围方法的事务中,所有Propagation.REQUIRED修饰的内部方法和外围方法均属于同一事务,只要一个方法回滚,整个事务均回滚。

2.2 PROPAGATION_REQUIRES_NEW

我们为User1Service和User2Service相应方法加上Propagation.REQUIRES_NEW属性。
User1Service方法:

@Service
public class User1ServiceImpl implements User1Service {
    //省略其他...
    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void addRequiresNew(User1 user){
        user1Mapper.insert(user);
    }
    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void addRequired(User1 user){
      user1Mapper.insert(user);
    }
}

User2Service方法:

@Service
public class User2ServiceImpl implements User2Service {
    //省略其他...
    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void addRequiresNew(User2 user){
        user2Mapper.insert(user);
    }
    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void addRequiresNewException(User2 user){
        user2Mapper.insert(user);
        throw new RuntimeException();
    }
}

2.1 场景一

外围方法没有开启事务。

验证方法1:

    @Override
    public void notransaction_exception_requiresNew_requiresNew(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequiresNew(user1);
        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addRequiresNew(user2);
        throw new RuntimeException();
    }

验证方法2:

    @Override
    public void notransaction_requiresNew_requiresNew_exception(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequiresNew(user1);
        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addRequiresNewException(user2);
    }

分别执行验证方法,结果:

验证方法序号数据库结果结果分析
1“张三”插入,“李四”插入。外围方法没有事务,插入“张三”、“李四”方法都在自己的事务中独立运行,外围方法抛出异常回滚不会影响内部方法。
2“张三”插入,“李四”未插入外围方法没有开启事务,插入“张三”方法和插入“李四”方法分别开启自己的事务,插入“李四”方法抛出异常回滚,其他事务不受影响。

结论:通过这两个方法我们证明了在外围方法未开启事务的情况下Propagation.REQUIRES_NEW修饰的内部方法会新开启自己的事务,且开启的事务相互独立,互不干扰。

2.2 场景二

外围方法开启事务。

验证方法1:

// 张三未插入,李四、王五插入
// addRequired(user1) 的传播属性是Propagation.REQUIRED外围有事务就使用外围的事务一同回滚
// addRequiresNew(user2) 的传播属性为Propagation.REQUIRED_NEW在自己新建的独立事务中可以提交;
@Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void transaction_exception_required_requiresNew_requiresNew(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequired(user1) 的传播属性是;
        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addRequiresNew(user2);
        User2 user3=new User2();
        user3.setName("王五");
        user2Service.addRequiresNew(user3);
        throw new RuntimeException();
    }

验证方法2:

// 张三、王五未插入,李四插入
// 外围方法开启事务,插入“张三”方法和外围方法一个事务,
// 插入“李四”方法、插入“王五”方法分别在独立的新建事务中。插入“王五”方法抛出异常,首先插入 “王五”方法的事务被回滚,异常继续抛出被外围方法感知,
// 外围方法事务亦被回滚,故插入“张三”方法也被回滚。   
@Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void transaction_required_requiresNew_requiresNew_exception(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequired(user1);
        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addRequiresNew(user2);
        User2 user3=new User2();
        user3.setName("王五");
        user2Service.addRequiresNewException(user3);
    }

验证方法3:

// 张三、李四插入,王五未插入。
// 外围方法开启事务,插入“张三”方法和外围方法一个事务
// 插入“李四”方法、插入“王五”方法分别在独立的新建事务中。
// 插入“王五”方法抛出异常,首先插入“王五”方法的事务被回滚,异常被catch不会被外围方法感知,外围方法事务不回滚,故插入“张三”方法插入成功。   
@Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void transaction_required_requiresNew_requiresNew_exception_try(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addRequired(user1);
        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addRequiresNew(user2);
        User2 user3=new User2();
        user3.setName("王五");
        try {
            user2Service.addRequiresNewException(user3);
        } catch (Exception e) {
            System.out.println("回滚");
        }
    }

分别执行验证方法,结果:

验证方法序号数据库结果结果分析
1“张三”未插入,“李四”插入,“王五”插入。外围方法开启事务,插入“张三”方法和外围方法一个事务,插入“李四”方法、插入“王五”方法分别在独立的新建事务中,外围方法抛出异常只回滚和外围方法同一事务的方法,故插入“张三”的方法回滚。
2“张三”未插入,“李四”插入,“王五”未插入。外围方法开启事务,插入“张三”方法和外围方法一个事务,插入“李四”方法、插入“王五”方法分别在独立的新建事务中。插入“王五”方法抛出异常,首先插入 “王五”方法的事务被回滚,异常继续抛出被外围方法感知,外围方法事务亦被回滚,故插入“张三”方法也被回滚。
3“张三”插入,“李四”插入,“王五”未插入。外围方法开启事务,插入“张三”方法和外围方法一个事务,插入“李四”方法、插入“王五”方法分别在独立的新建事务中。插入“王五”方法抛出异常,首先插入“王五”方法的事务被回滚,异常被catch不会被外围方法感知,外围方法事务不回滚,故插入“张三”方法插入成功。

结论:在外围方法开启事务的情况下Propagation.REQUIRES_NEW修饰的内部方法依然会单独开启独立事务,且与外部方法事务也独立,内部方法之间、内部方法和外部方法事务均相互独立,互不干扰。

2.3.PROPAGATION_NESTED

我们为User1Service和User2Service相应方法加上Propagation.NESTED属性。
User1Service方法:

@Service
public class User1ServiceImpl implements User1Service {
    //省略其他...
    @Override
    @Transactional(propagation = Propagation.NESTED)
    public void addNested(User1 user){
        user1Mapper.insert(user);
    }
}

User2Service方法:

@Service
public class User2ServiceImpl implements User2Service {
    //省略其他...
    @Override
    @Transactional(propagation = Propagation.NESTED)
    public void addNested(User2 user){
        user2Mapper.insert(user);
    }
    @Override
    @Transactional(propagation = Propagation.NESTED)
    public void addNestedException(User2 user){
        user2Mapper.insert(user);
        throw new RuntimeException();
    }
}

3.1 场景一

此场景外围方法没有开启事务。

验证方法1:

    @Override
    public void notransaction_exception_nested_nested(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addNested(user1);
        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addNested(user2);
        throw new RuntimeException();
    }

验证方法2:

    @Override
    public void notransaction_nested_nested_exception(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addNested(user1);
        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addNestedException(user2);
    }

分别执行验证方法,结果:

验证方法序号数据库结果结果分析
1“张三”、“李四”均插入。外围方法未开启事务,插入“张三”、“李四”方法在自己的事务中独立运行,外围方法异常不影响内部插入“张三”、“李四”方法独立的事务。
2“张三”插入,“李四”未插入。外围方法没有事务,插入“张三”、“李四”方法都在自己的事务中独立运行,所以插入“李四”方法抛出异常只会回滚插入“李四”方法,插入“张三”方法不受影响。

结论:通过这两个方法我们证明了在外围方法未开启事务的情况下Propagation.NESTEDPropagation.REQUIRED作用相同,修饰的内部方法都会新开启自己的事务,且开启的事务相互独立,互不干扰。

3.2 场景二

外围方法开启事务。

验证方法1:

 // 张三、李四均未插入。
// 外围方法开启事务,内部事务为外围事务的子事务,外围方法回滚,内部方法也要回滚。
@Transactional
    @Override
    public void transaction_exception_nested_nested(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addNested(user1);
        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addNested(user2);
        throw new RuntimeException();
    }

验证方法2:

 // 张三、李四均未插入。
// 外围方法开启事务,内部事务为外围事务的子事务,内部方法抛出异常回滚,且外围方法感知异常致使整体事务回滚。
@Transactional
    @Override
    public void transaction_nested_nested_exception(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addNested(user1);
        User2 user2=new User2();
        user2.setName("李四");
        user2Service.addNestedException(user2);
    }

验证方法3:

// 张三插入、李四未插入。
// 外围方法开启事务,内部事务为外围事务的子事务,插入“李四”内部方法抛出异常,可以单独对子事务回滚。
@Transactional
    @Override
    public void transaction_nested_nested_exception_try(){
        User1 user1=new User1();
        user1.setName("张三");
        user1Service.addNested(user1);
        User2 user2=new User2();
        user2.setName("李四");
        try {
            user2Service.addNestedException(user2);
        } catch (Exception e) {
            System.out.println("方法回滚");
        }
    }

分别执行验证方法,结果:

验证方法序号数据库结果结果分析
1“张三”、“李四”均未插入。外围方法开启事务,内部事务为外围事务的子事务,外围方法回滚,内部方法也要回滚。
2“张三”、“李四”均未插入。外围方法开启事务,内部事务为外围事务的子事务,内部方法抛出异常回滚,且外围方法感知异常致使整体事务回滚。
3“张三”插入、“李四”未插入。外围方法开启事务,内部事务为外围事务的子事务,插入“李四”内部方法抛出异常,可以单独对子事务回滚。

结论:以上试验结果我们证明在外围方法开启事务的情况下Propagation.NESTED修饰的内部方法属于外部事务的子事务,外围主事务回滚,子事务一定回滚,而内部子事务可以单独回滚而不影响外围主事务和其他子事务

2.4 PROPAGATION_SUPPORTS

支持当前事务,如果当前没有事务就以非事务的方法执行

//  外围方法无事务,  otherClassMethod()也以无事务的方式执行;事务提交 13/14/15/16全被删除
@Override
    public void transactionalPropagationOfSupport() {
        userMapper.deleteById(15);
        userMapper.deleteById(16);
        orderService.otherClassMethod();
    }

    @Override
    @Transactional(propagation = Propagation.SUPPORTS)
    public User otherClassMethod() {
        userMapper.deleteById(13);
        userMapper.deleteById(14);
        System.out.println(1/0);
        return null;
    }
//  外围方法有事务,  otherClassMethod()以事务的方式执行;全部回滚
 @Transactional
@Override
    public void transactionalPropagationOfSupport() {
        userMapper.deleteById(15);
        userMapper.deleteById(16);
        orderService.otherClassMethod();
    }

    @Override
    @Transactional(propagation = Propagation.SUPPORTS)
    public User otherClassMethod() {
        userMapper.deleteById(13);
        userMapper.deleteById(14);
        System.out.println(1/0);
        return null;
    }

3. REQUIRED,REQUIRES_NEW,NESTED异同

由“1.2 场景二”和“3.2 场景二”对比,我们可知:
NESTED和REQUIRED修饰的内部方法都属于外围方法事务,如果外围方法抛出异常,这两种方法的事务都会被回滚。但是REQUIRED是加入外围方法事务,所以和外围事务同属于一个事务,一旦REQUIRED事务抛出异常被回滚,外围方法事务也将被回滚。而NESTED是外围方法的子事务,有单独的保存点,所以NESTED方法抛出异常被回滚,不会影响到外围方法的事务。

由“2.2 场景二”和“3.2 场景二”对比,我们可知:
NESTED和REQUIRES_NEW都可以做到内部方法事务回滚而不影响外围方法事务。但是因为NESTED是嵌套事务,所以外围方法回滚之后,作为外围方法事务的子事务也会被回滚。而REQUIRES_NEW是通过开启新的事务实现的,内部事务和外围事务是两个事务,外围事务回滚不会影响内部事务。

-2 声明式事务和编程式事务

-2.1 Spring事务支持

Spring 支持两种事务方式,分别是编程式事务和声明式事务,后者最常见,通常情况下只需要一个 **@Transactional **就搞定了(代码侵入性降到了最低)。

- 2.2 编程式事务

编程式事务是指将事务管理代码嵌入嵌入到业务代码中,来控制事务的提交和回滚。比如说,使用 TransactionTemplate 来管理事务:

@Autowired
private TransactionTemplate transactionTemplate;
public void testTransaction() {

        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {

                try {

                    // ....  业务代码
                } catch (Exception e){
                    //回滚
                    transactionStatus.setRollbackOnly();
                }

            }
        });
}

再比如说,使用 TransactionManager 来管理事务:

@Autowired
private PlatformTransactionManager transactionManager;

public void testTransaction() {

  TransactionStatus status = transactionManager.getTransaction(new DefaultTransactionDefinition());
          try {
               // ....  业务代码
              transactionManager.commit(status);
          } catch (Exception e) {
              transactionManager.rollback(status);
          }
}

就编程式事务管理而言,Spring 更推荐使用 TransactionTemplate。
在编程式事务中,必须在每个业务操作中包含额外的事务管理代码,就导致代码看起来非常的臃肿,但对理解 Spring 的事务管理模型非常有帮助。

- 2.2 声明式事务

声明式事务将事务管理代码从业务方法中抽离了出来,以声明式的方式来实现事务管理,对于开发者来说,声明式事务显然比编程式事务更易用、更好用。
当然了,要想实现事务管理和业务代码的抽离,就必须得用到 Spring 当中的AOP,其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,执行完目标方法之后根据执行的情况提交或者回滚。

声明式事务虽然优于编程式事务,但也有不足,声明式事务管理的粒度是方法级别,而编程式事务是可以精确到代码块级别的。

-2.3 事务管理模型

Spring 将事务管理的核心抽象为一个事务管理器(TransactionManager),它的源码只有一个简单的接口定义,属于一个标记接口:

public interface TransactionManager {

}

该接口有两个子接口,分别是编程式事务接口 ReactiveTransactionManager 和声明式事务接口 PlatformTransactionManager。我们来重点说说 PlatformTransactionManager,该接口定义了 3 个接口方法:

interface PlatformTransactionManager extends TransactionManager{
    // 根据事务定义获取事务状态
    TransactionStatus getTransaction(TransactionDefinition definition)
            throws TransactionException;

    // 提交事务
    void commit(TransactionStatus status) throws TransactionException;

    // 事务回滚
    void rollback(TransactionStatus status) throws TransactionException;
}

通过 PlatformTransactionManager 这个接口,Spring 为各个平台如 JDBC(DataSourceTransactionManager)、Hibernate(HibernateTransactionManager)、JPA(JpaTransactionManager)等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。
参数 TransactionDefinition 和 @Transactional 注解是对应的,比如说 @Transactional 注解中定义的事务传播行为、隔离级别、事务超时时间、事务是否只读等属性,在 TransactionDefinition 都可以找得到。
返回类型 TransactionStatus 主要用来存储当前事务的一些状态和数据,比如说事务资源(connection)、回滚状态等。
TransactionDefinition如下:

public interface TransactionDefinition {
 // 事务的传播行为
 default int getPropagationBehavior() {
  return PROPAGATION_REQUIRED;
 }
 // 事务的隔离级别
 default int getIsolationLevel() {
  return ISOLATION_DEFAULT;
 }
  // 事务超时时间
  default int getTimeout() {
  return TIMEOUT_DEFAULT;
 }
  // 事务是否只读
  default boolean isReadOnly() {
  return false;
 }
}

Transactional注解如下:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {

 Propagation propagation() default Propagation.REQUIRED;
 Isolation isolation() default Isolation.DEFAULT;
  int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
  boolean readOnly() default false;

}

  • @Transactional 注解中的 propagation 对应 TransactionDefinition 中的 getPropagationBehavior,默认值为 Propagation.REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED)。

  • @Transactional 注解中的 isolation 对应 TransactionDefinition 中的 getIsolationLevel,默认值为 DEFAULT(TransactionDefinition.ISOLATION_DEFAULT)。

  • @Transactional 注解中的 timeout 对应 TransactionDefinition 中的 getTimeout,默认值为TransactionDefinition.TIMEOUT_DEFAULT。

  • @Transactional 注解中的 readOnly 对应 TransactionDefinition 中的 isReadOnly,默认值为 false。

2. 手动回滚事务(编程式事务)

2.1 TransactionAspectSupport

TransactionAspectSupport是Spring提供的事务切面支持类。

    public void updateUser2(Integer id, String name) {
        // 操作未回滚
        userMapper.deleteUser(2);
        try {
            // 操作回滚
            userMapper.updateUser(id, name);
            System.out.println(1 / 0);
        } catch (Exception e) {
            e.printStackTrace();
            // 手动回滚事务,并抛出异常
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        }
    }

2.2 PlatformTransactionManager

    @Autowired
	// (PlatformTransactionManager和DataSourceTransactionManager都是TransactionManager的子类使用方法一样)
    private PlatformTransactionManager platformTransactionManager;
    @Autowired
    private TransactionDefinition transactionDefinition;    

public void updateUser1(Integer id, String name) {
        // 操作未回滚
        userMapper.deleteUser(2);
        TransactionStatus transaction = platformTransactionManager.getTransaction(transactionDefinition);
        try {
            // 操作回滚
            userMapper.updateUser(id, name);
            System.out.println(1 / 0);
            platformTransactionManager.commit(transaction);
        } catch (Exception e) {
            e.printStackTrace();
            platformTransactionManager.rollback(transaction);
        }

    }

2.3 回滚部分异常

使用【Object savePoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint(); 】设置回滚点。

使用【TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savePoint);】回滚到savePoint。

    public void updateUser3(Integer id, String name) {
        // 操作未回滚
        userMapper.deleteUser(2);
        // 设置回滚点,回滚点以下的数据库操作回滚
        Object savepoint = TransactionAspectSupport.currentTransactionStatus().createSavepoint();
        try {
            // 操作回滚
            userMapper.updateUser(id, name);
            System.out.println(1 / 0);
        } catch (Exception e) {
            e.printStackTrace();
            // 手动回滚事务,并抛出异常
            TransactionAspectSupport.currentTransactionStatus().rollbackToSavepoint(savepoint);
        }
    }

3. 异常失效

3.1 try和@Transactional

有事务注解的方法 调用 不同类的无事务注解的方法;

// 以下代码如果“ throw e ”被注释,执行结果,事务会提交;id=13\14\15\16的用户都会被删除    
// 以下代码如果“ throw e ”不被注释,执行结果,事务会回滚;id=13\14\15\16的用户都不会被删除  
// 总而言之只要异常被catch,事务就会提交;
@Transactional
    @Override
    public void transactionalAndTry() {
        try {
            userMapper.deleteById(13);
            userMapper.deleteById(14);
            System.out.println(1 / 0);
        } catch (Exception e) {
            e.printStackTrace();
//            throw e;
        }
        orderService.otherClassMethod();
    }

    @Override
    public User otherClassMethod() {
        userMapper.deleteById(15);
        userMapper.deleteById(16);
        return null;
    }

// =================以下写法和上面是一样的==========
@Transactional
    @Override
    public void transactionalAndTry() {
        userMapper.deleteById(13);
        userMapper.deleteById(14);
        try {
           orderService.otherClassMethod();  
        } catch (Exception e) {
            e.printStackTrace();
//            throw e;
        }
    }

    @Override
    public User otherClassMethod() {
        userMapper.deleteById(15);
        userMapper.deleteById(16);
         System.out.println(1 / 0);
        return null;
    }

// =================以下写法和上面是一样的==========
@Transactional
    @Override
    public void transactionalAndTry() {
        userMapper.deleteById(13);
        userMapper.deleteById(14);
        orderService.otherClassMethod();  
    }

    @Override
    public User otherClassMethod() {
      try {
        userMapper.deleteById(15);
        userMapper.deleteById(16);
         System.out.println(1 / 0);
     } catch (Exception e) {
            e.printStackTrace();
//            throw e;
        }    
        return null;
    }

// =================以下写法和上面是一样的 transactionalAndTry() 无法监测到otherClassMethod() catch的异常==========
@Transactional
    @Override
    public void transactionalAndTry() {
        userMapper.deleteById(13);
        userMapper.deleteById(14);
        orderService.otherClassMethod();  
    }

    @Override
    @Transactional
    public User otherClassMethod() {
      try {
        userMapper.deleteById(15);
        userMapper.deleteById(16);
         System.out.println(1 / 0);
     } catch (Exception e) {
            e.printStackTrace();
//            throw e;
        }    
        return null;
    }

// =================以下写法和上面是**不一样** transactionalAndTry() 监测到otherClassMethod() catch的异常;全部回滚==========
@Transactional
    @Override
    public void transactionalAndTry() {
        userMapper.deleteById(13);
        userMapper.deleteById(14);
        try{
          orderService.otherClassMethod();    
        }carch(Exception e){
              e.printStackTrace();
        }
        
    }

    @Override
    @Transactional
    public User otherClassMethod() {
        userMapper.deleteById(15);
        userMapper.deleteById(16);
        System.out.println(1 / 0);
        return null;
    }

无事务注解的方法 调用 不同类的有事务注解的方法;

  • 有事务注解的方法被事务管理,无事务注解的方法不受事务约束
// 以下代码id=13\14一定不会被事务管理 如果“ throw e ”被注释,执行结果,事务会提交;id=\15\16的用户都会被删除    
// 以下代码id=13\14一定不会被事务管理 如果“ throw e ”不被注释,执行结果,事务会回滚;id=\15\16的用户不会被删除    
    @Override
    public void transactionalAndTry() {
        userMapper.deleteById(13);
        try {
            userMapper.deleteById(14);
        } catch (Exception e) {
            e.printStackTrace();
        }
        orderService.deleteUserById();
    }

    @Transactional
    @Override
    public User deleteUserById() {
        userMapper.deleteById(15);
         System.out.println(1 / 0);
        try {
            userMapper.deleteById(16);
        } catch (Exception e) {
            e.printStackTrace();
         //   throw e;
        }
        return null;
    }

方案1:例如service层处理事务,那么service中的方法中不做异常捕获,或者在catch语句中最后增加

throw new RuntimeException(); 语句,以便让aop捕获异常再去回滚

方案2:在service层方法的catch语句中进行手动回滚,这样上层就无需去处理异常。

3.2 自调用导致事务失效

问题描述及原因

在 Spring 的 AOP 代理下,只有目标方法由外部调用,目标方法才由 Spring 生成的代理对象来管理,否则会造成自调用问题。

若同一类中的 没有@Transactional 注解的方法 内部调用 有@Transactional 注解的方法,有@Transactional 注解的方法的事务被忽略,不会发生回滚。见 示例代码展示。

// 2个方法在同一个类;但事务注解在外层方法,事务生效    
@Transactional
    @Override
    public void transactionalSameClass() {
        userMapper.deleteById(13);
        userMapper.deleteById(14);
        this.deleteUserById();
    }

    public void deleteUserById() {
        userMapper.deleteById(15);
        userMapper.deleteById(16);
        System.out.println(1 / 0);
    }
// 2个方法在同一个类;但事务注解在内层方法,事务失效  
// id=13\14\15\16的用户都会被删除    
    @Override
    public void transactionalSameClass() {
        userMapper.deleteById(13);
        userMapper.deleteById(14);
        this.deleteUserById();
    }

	@Transactional
    public void deleteUserById() {
        userMapper.deleteById(15);
        userMapper.deleteById(16);
        System.out.println(1 / 0);
    }

自调用失效原因:

spring里事务是用注解配置的,当一个方法没有接口,单单只是一个内部方法时,事务的注解是不起作用的,需要回滚时就会报错。

出现这个问题的根本原因是:

@Transactional 的实现原理是AOP,AOP的实现原理是动态代理,而自调用时并不存在代理对象的调用,也就不会产生基于AOP 的事务回滚操作

虽然可以直接从容器中获取代理对象,但这样有侵入之嫌,不推荐。

3.3 @Transactional 应用在非 public 修饰的方法上

3.4 @Transactional 注解属性 rollbackFor 设置

默认只对非检查型异常(RuntimeException及其子类 或者是 Error)回滚。让所有异常都会让事务启动可以将 @Transactional配置为 @Transactional(rollbackFor = Exception.class)

4. 事务错误使用

4.1 事务的嵌套

事务嵌套导致 org.springframework.transaction.UnexpectedRollbackException: Transaction rolled back because it has been marked as rollback-only

// 以下2个方法在不同的类   
@Transactional
    @Override
    public void transactionalAndTry() {
        userMapper.deleteById(13);
        userMapper.deleteById(14);
        try {
            orderService.deleteUserById();
        } catch (Exception e) {
            e.printStackTrace();
            System.out.println("user服务异常");
//            throw e;
        }
    }

    @Transactional
    @Override
    public User deleteUserById() {
        userMapper.deleteById(15);
        userMapper.deleteById(16);
        System.out.println(1 / 0);
        return null;
    }

transactionalAndTry() =>以下称为A方法 ; 方法中调用了deleteUserById() => 以下称为B方法.

上述代码可以触发回滚异常的报错

两个方法都加了事务注解,并且两个方法都会受到到事务管理的拦截器增强,并且事务传播的方式都是默认的,也就是REQUIRED,当已经存在事务的时候就加入事务,没有就创建事务。这里A和B都受事务控制,并且是处于同一个事务的。

A调用B,A中抓了B的异常,当B发生异常的时候,B的操作应该回滚,但是A吃了异常,A方法中没有产生异常,所以A的操作又应该提交,二者是相互矛盾的。

spring的事务关联拦截器在抓到B的异常后就会标记rollback-only为true,当A执行完准备提交后,发现rollback-only为true,也会回滚,并抛出异常告诉调用者。

  • 处理方式:且不可直接把方法B的事务去掉,因为方法B可能被其他controller直接调用,即使没调用把项目中的事务去掉都是危险的。

    解决还是根据业务处理,具体是要回滚还是处理异常是A和B保持一致的回滚和提交标记。

5. 分布式事务

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值