Spring 事务详解(事务的使用,传播机制,可能导致失效的情况)

3 篇文章 0 订阅
1 篇文章 0 订阅

前言:
事务是在开发过程中不可避免,Spring提供了完善的事务管理机制。
主要分为两种:

- 编程式事务

基于底层的API,开发人员可以自己通过代码的方式进行管理事务。如TransactionDefinition、TransactionTemplate、PlatformTransactionManager、TransactionStatus等核心接口。
编程式事务需要开发人员在代码中手动进行事务的管理,如:事务的开启,提交,回滚等操作。
使用**PlatformTransactionManager** 时代码演示
    private SysOrderMapper sysOrderMapper;
    private PlatformTransactionManager transactionManager;
    
    @Autowired
    public void setBeans(SysOrderMapper sysOrderMapper, PlatformTransactionManager transactionManager) {
        this.sysOrderMapper = sysOrderMapper;
        this.transactionManager = transactionManager;
    }
    /**
     *
     * @param orderId 主键
     * @return 1 成功 , -1 失败
     */
    public int deleteById(Integer orderId){
        TransactionDefinition transactionDefinition = new DefaultTransactionDefinition();
        TransactionStatus status = transactionManager.getTransaction(transactionDefinition);
        int isDel = 0;
        try {
            //1.事务操作
            isDel = sysOrderMapper.deleteById(orderId);
            //2.事务提交
            transactionManager.commit(status);
        }catch (DataAccessException e){
            //事务回滚
            transactionManager.rollback(status);
            isDel = -1;
            throw e;
        }
        return isDel;
    }

使用Spring中提供的TransactionTemplate也可以实现同样的效果,通过API进行控制事务
使用TransactionTemplate可以简化事务管理,它内部封装了事务的开启、提交、回滚等操作,而无需手动管理事务的状态。
如果事务操作过程中发生了异常,execute方法会捕获异常并执行事务回滚操作。所以在捕获异常的代码块中,返回-1表示删除操作失败。

    private SysOrderMapper sysOrderMapper;
    private PlatformTransactionManager transactionManager;
    private TransactionTemplate transactionTemplate;

    @Autowired
    public void setBeans(SysOrderMapper sysOrderMapper, TransactionTemplate transactionTemplate) {
        this.sysOrderMapper = sysOrderMapper;
        this.transactionTemplate = transactionTemplate;
    }

    /**
     * 根据订单ID删除订单
     * @param orderId 订单ID
     * @return 删除结果:1-成功,-1-失败
     */
    public int deleteByIdTest(Integer orderId) {
        try {
            return transactionTemplate.execute(status -> {
                // 1. 事务操作:根据订单ID删除订单
                int result = sysOrderMapper.deleteById(orderId);
                // 2. 返回结果
                return result;
            });
        } catch (Exception e) {
            // 如果事务操作抛出异常,则返回-1表示失败
            return -1;
        }
    }

- 声明式事务

声明式事务管理方法允许开发人员配置的帮助下管理事务,不依赖于底层的API进行硬编码。开发人员可以只使用注解或基于配置的XML来管理事务。
使用 @Transactional注解即可给deleteById方法增加事务控制

    @Transactional
    public int deleteById(Integer orderId){
    //事务操作
    }

声明式事务的优点
通过上面的案例,可以看的出来,声明式事务帮助我们节省了很多代码,能够自行进行事务的开启、提交和回滚操作,把开发人员从事务管理中解放出来。

声明式事务管理是基于AOP进行实现,本质就是在目标方法执行前后进行拦截。在目标方法执行前已经有事务就加入,如果没有就创建一个事务,然后再方法执行后,根据实际情况选择提交或者是回滚事务。

声明式事务带来的好处就是对代码没有侵入性,方法内只需要写业务逻辑就行了。
但是声明式事务同时也会带来一定的问题

声明式事务的粒度问题

假如我要在某一部分代码块增加事务,就需要将这部分代码抽出来作为一个方法。
所以声明式事务的局限性,就是最小粒度必须作用在方法上。

同时声明式事务是通过注解实现,有些时候也可以通过配置实现,那么这样就会带来一个问题就是 这个事务很大可能被开发人员忽略。
被忽略就会带来一些问题,假如其他开发人员没有注意到这个方法被事务嵌套的,在不知道的情况下在方法中加入了例如缓存更新,消息发送,RPC远程调用以及文件写入等操作。

如果这些操作被嵌套在事务中带来的问题:
1、如果这些操作自身是无法回滚的,就会导致数据的不一致。例如消息发送成功了,RPC远程调用成功了,但是因为一些原因本地事务回滚了。那么就导致 消息发送和RPC调用都无法进行回滚。

2、在事务中进行远程调用,就会拉长整个事务。就会导致本事务的数据库连接一直被占用,类似的操作过多的情况下,就会导致数据库连接池耗尽。即使代码中没有进行RPC远程调用,但是进行耗时操作同样会导致事务时间线拉长。

而使用编程式事务,代码中就能看到什么地方开启了事务,什么地方进行了提交事务,什么地方发行异常进行事务回滚。这样在其他开发人员进行修改维护代码的时候就会考虑到追加的代码是否应该在事务内,还是事务外。

Spring中可能导致事务失效的原因有哪些

1、代理失效的情况

Spring的AOP是通过动态代理实现的,所以,AOP生效的前提是动态代理必须生效,并且可以调用代理对象的方法。
有以下几种情况会导致代理失效,AOP不起作用:

1、@Transactional 应用在非public修饰的方发上

**private 、static、final **修饰的方法 @Transactional 都不生效

    // Idea 都会提示Methods annotated with '@Transactional' must be overridable 
    // 带` @Transactional `注解的方法必须是可重写的
    @Transactional(rollbackFor = Exception.class)
    private void checkPermissions(){
        System.out.println("作用在非public,不生效");
    }
    
    @Transactional()
    public static void checkPermissions(){
        System.out.println("作用在非public,不生效");
    }

    @Transactional()
    public final void checkPermissions(){
        System.out.println("作用在非public,不生效");
    }
2、同一个类中方法调用,导致@Transactional 失效

下列都是在对象内部直接调用的其他方法,用的原始对象直接调用,不会走代理对象调用,所以代理失效
Spring的事务功能是通过动态代理来实现的,只有通过代理对象调用的方法才会被事务管理器所拦截并应用事务。

    //1、方法自调用
    public void checkUser(){
        checkPermissions();
    }

    @Transactional()
    public void checkPermissions(){
        System.out.println("类中方法自调用,不生效");
    }

    //2、调用私有方法
    public void Test(){
        Test1();
    }

    @Transactional()
    private void Test1(){
        System.out.println("作用在非public修饰的方法,@Transactional()不生效");
    }

    //3、调用内部类方法
    private class TestClass{
     @Transactional()
     public void innerTest(){
         System.out.println("调用了内部类方法。。。");
     }
    }


    private void Test3(){
        TestClass testClass = new TestClass();
        testClass.innerTest();
    }

同时 被 static 和 final 修饰的方法也不会生效
static方法虽然不是对象自调用,但是 static 方法是属于类的,不是对象的,所以不能被AOP
final 由于AOP是通过创建代理对象进行实现的,所以无法对final修饰的方法进行类化和覆盖,从而无法进行拦截。

总结

导致代理失效,AOP不起作用的有:
1、@Transactional() 标记私有方法
2、@Transactional() 标记静态方法
3、@Transactional() 标记final修饰的方法
4、@Transactional() 标记类内部自调用的方法
5、@Transactional() 标记内部类方法

2、@Transactional 应用在非public修饰的方发上

作用在 private修饰的方法,只会通过当前对象的其他方法调用,也就是this自调用,并不会使用代理对象调用。Transactional 是基于动态代理实现的,所以不生效

    @Transactional()
    private void Test1(){
        System.out.println("作用在非public修饰的方法,@Transactional()不生效");
    }

3、@Transactional 注解属性 rollbackFor、 propagation 设置错误

propagation
Spring事务中规定的有有七种传播级别,默认的机制是REQUIRED
1、REQUIRED(默认): 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是最常见的传播级别。

2、SUPPORTS: 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式执行。

3、MANDATORY: 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。

4、REQUIRES_NEW: 创建一个新的事务,如果当前存在事务,则将当前事务挂起。

5、NOT_SUPPORTED: 以非事务的方式执行操作,如果当前存在事务,则将当前事务挂起。

6、NEVER: 以非事务的方式执行操作,如果当前存在事务,则抛出异常。

7、NESTED: 如果当前存在事务,则在嵌套事务中执行;如果当前没有事务,则创建一个新的事务。如果嵌套事务中的方法抛出异常,则回滚该嵌套事务,但不会影响外部事务的提交。但是外部事务倾向嵌套事务。

rollbackFor
rollbackFor 是 Spring事务中的一个属性,用于指定那些异常会触发事务回滚。
如果指定rollbackFor,发生了指定异常或其子类异常,事务会回滚。
如果不指定rollbackFor ,则默认情况下只有RuntimeException和Error 会触发事务回滚。

4、在同一个类中进行自调用,导致@Transactional 失效

    //1、方法自调用
    public void checkUser(){
        checkPermissions();
    }
    @Transactional()
    public void checkPermissions(){
        System.out.println("类中方法自调用,不生效");
    }

5、异常被catch捕获没有进行抛出,导致@Transactional 失效

下列代码在不正确使用@Transactional事务时,异常被catch捕获导致@Transactional 事务失效

    @Transactional(rollbackFor = Exception.class)
    public int deleteByIntegerId(Integer containerAbnormalSeq) {
        int i = 0;
        try {
            tbContainerAbnormalDao.deleteById(containerAbnormalSeq);
            i = 1;
        } catch (Exception e){
            i = -1;
        }
        return i;
    }

//tbContainerAbnormalDao.deleteById
@Transactional
public void deleteById(ID id) {
    Assert.notNull(id, "The given id must not be null!");
    this.delete(this.findById(id).orElseThrow(() -> {
        return new EmptyResultDataAccessException(String.format("No %s entity with id %s exists!", this.entityInformation.getJavaType(), id), 1);
    }));
}

上述使用嵌套事务时,抛出以下错误,

org.springframework.transaction.UnexpectedRollbackException: 
Transaction silently rolled back because it has been marked as rollback-only

这个错误意味着一个数据库事务已经被标记为只能回滚(rollback-only),因此当这个事务试图提交时,它会被静默地(silently)回滚,而不是正常地提交。

分析: 因为 Spring Data Jpa 中 deleteById 的实现也标记了开启事务,在执行deleteById的时候发生了异常,deleteByIntegerId方法中进行了 try catch, 异常被catch捕获了,导致deleteByIntegerId 的事务失效了, 但是deleteById方法没有捕获异常,异常会被传播出去,从而导致事务被标记为“rollback-only”,并最终回滚 。

有两种办法可以解决这个问题
第一种解决办法:将catch到的异常向上抛出即可

@Transactional(rollbackFor = Exception.class)
  public int deleteById(Integer containerAbnormalSeq) {
      int i = 0;
      try {
          tbContainerAbnormalDao.deleteById(containerAbnormalSeq);
          i = 1;
      } catch (Exception e){
          i = -1;
          throw e;
      }
      return i;
  }

第二种:直接向上抛出异常

    @Transactional(rollbackFor = Exception.class)
    public void deleteById(Integer containerAbnormalSeq) throws Exception{
        tbContainerAbnormalDao.deleteById(containerAbnormalSeq);
    }

6、数据库不支持事务

例如:Mysql的 MyISAM 不支持事务

7、使用了非Spring支持的@Transactional 注解

最后

关于@Transactional的用法,阿里巴巴Java开发规约中也提到过。
在这里插入图片描述

  • 16
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Spring事务传播机制是指在多个事务方法调用的场景下,如何管理这些事务的提交和回滚。Spring提供了多种事务传播行为,包括: 1. REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是最常用的传播行为。 2. REQUIRES_NEW:每次都创建一个新的事务,如果当前存在事务,则将其挂起。 3. SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务方式执行。 4. NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,则将其挂起。 5. MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。 6. NEVER:以非事务方式执行操作,如果当前存在事务,则抛出异常。 7. NESTED:如果当前存在事务,则在嵌套事务内执行;如果当前没有事务,则创建一个新的事务。嵌套事务是外部事务的一部分,它有自己的保存点和回滚范围。 事务失效的场景包括: 1. 异常未被捕获并处理,导致事务没有正常回滚。 2. 在没有开启事务情况下调用带有@Transactional注解的方法,导致方法执行时没有开启事务。 3. 在同一个类中的方法互相调用,而没有通过代理对象进行调用,导致事务失效。 4. 在事务方法中使用了try-catch块并捕获了异常,没有主动抛出异常或调用setRollbackOnly方法,导致事务无法回滚。 需要注意的是,事务失效可能与具体的配置和使用方式有关,详细的分析和排查需要根据具体的代码和配置进行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

旧歌*

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值