Spring 事务传播实践分析

事务管理是应用系统开发中必不可少的一部分。事物将若干的数据库操作作为一个整体控制,一起成功或一起失败。事物有以下特性

  原子性:指事务是一个不可分割的工作单位,事务中的操作要么都发生,要么都不发生。
  一致性:指事务前后数据的完整性必须保持一致。
  隔离性:指多个用户并发访问数据库时,一个用户的事务不能被其他用户的事务所干扰,多个并发事务之间数据要相互隔离。
  持久性:指一个事务一旦被提交,它对数据库中数据的改变就是永久性的,即时数据库发生故障也不应该对其有任何影响。

Spring 为事务管理提供了丰富的功能支持。Spring 事务管理分为编码式和声明式的两种方式。编程式事务指的是通过编码方式实现事务;声明式事务基于 AOP,将具体业务逻辑与事务处理解耦。声明式事务管理使业务代码逻辑不受污染, 因此在实际使用中声明式事务用的比较多。声明式事务有两种方式,一种是在配置文件(xml)中做相关的事务规则声明,另一种是基于@Transactional 注解的方式。注释配置是目前流行的使用方式,因此本文将着重介绍基于@Transactional 注解的事务管理。

@Transactional 注解的属性信息
属性名 说明
name 当在配置文件中有多个 TransactionManager , 可以用该属性指定选择哪个事务管理器。
propagation 事务的传播行为,默认值为 REQUIRED。
isolation 事务的隔离度,默认值采用 DEFAULT。
timeout 事务的超时时间,默认值为-1。如果超过该时间限制但事务还没有完成,则自动回滚事务。
read-only 指定事务是否为只读事务,默认值为 false;为了忽略那些不需要事务的方法,比如读取数据,可以设置 read-only 为 true。
rollback-for 用于指定能够触发事务回滚的异常类型,如果有多个异常类型需要指定,各类型之间可以通过逗号分隔。
no-rollback- for 抛出 no-rollback-for 指定的异常类型,不回滚事务。

事物传播行为:


  •     REQUIRED--支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
  •     SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。
  •     MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。
  •     REQUIRES_NEW--新建事务,如果当前存在事务,把当前事务挂起。
  •     NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
  •     NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。
  •     NESTED--如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与REQUIRED类似的操作。拥有多个可以回滚的保存点,内部回滚不会对外部事务产生影响。只对DataSourceTransactionManager有效

事物隔离级别


  •   DEFAULT--使用后端数据库默认的隔离级别(Spring中的选择项)
  •     READ_UNCOMMITED--允许你读取还未提交的改变了的数据。可能导致脏、幻、不可重复读
  •     READ_COMMITTED--允许在并发事务已经提交后读取。可防止脏读,但幻读和不可重复读仍可发生(锁定正在读取的行)
  •     REPEATABLE_READ--对相同字段的多次读取是一致的,除非数据被事务本身改变。可防止脏、不可重复读,但幻读仍可能发生(锁定所读取的所有行)
  •     SERIALIZABLE--完全服从ACID的隔离级别,确保不发生脏、幻、不可重复读。这在所有的隔离级别中是最慢的,它是典型的通过完全锁定在事务中涉及的数据表来完成的 (锁表

    其中,MySQL默认采用REPEATABLE_READ隔离级别;Oracle默认采用READ_COMMITTED隔离级别

依据事务的隔离级别将会有三种情况发生。

  脏读:一个事务会读进还没有被另一个事务提交的数据,所以你会看到一些最后被另一个事务回滚掉的数据。

  不可重复读:一个事务读进一条记录,另一个事务更改了这条记录并提交完毕,这时候第一个事务再次读这条记录时,它已经改变了

  幻影读:一个事务用Where子句来检索一个表的数据,另一个事务插入一条新的记录,并且符合Where条件,这样,第一个事务用同一个where条件来检索数据后,就会多出一条记录

我们首先先实验事务的传播

注意:所有的测试都是调用method1

public void method1() {
        TicketEntity ticketEntity = new TicketEntity();
        ticketEntity.setF_apply_service("测试");
        ticketEntityMapper.insertSelective(ticketEntity);
        method2(ticketEntity.getF_ticket_id());
        throw new RuntimeException("事务需要回滚");

    }

    @Transactional(propagation = Propagation.REQUIRED)
    public void method2(Integer id) {
        TicketEntity ticketEntity = new TicketEntity();
        ticketEntity.setF_apply_service("测试2");
        ticketEntityMapper.insertSelective(ticketEntity);
    }

结果:都不回滚

public void method1() {
        TicketEntity ticketEntity = new TicketEntity();
        ticketEntity.setF_apply_service("测试");
        ticketEntityMapper.insertSelective(ticketEntity);
        method2();
        // throw new RuntimeException("事务需要回滚");

    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void method2() {
        TicketEntity ticketEntity = new TicketEntity();
        ticketEntity.setF_apply_service("测试2");
        ticketEntityMapper.insertSelective(ticketEntity);
        throw new RuntimeException("事务需要回滚");
    }

结果:都不回滚

// @Transactional(propagation = Propagation.REQUIRED)
    public void method1() {
        TicketEntity ticketEntity = new TicketEntity();
        ticketEntity.setF_apply_service("测试");
        ticketEntityMapper.insertSelective(ticketEntity);
        method2();
        // throw new RuntimeException("事务需要回滚");

    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void method2() {
        TicketEntity ticketEntity = new TicketEntity();
        ticketEntity.setF_apply_service("测试2");
        ticketEntityMapper.insertSelective(ticketEntity);
        throw new RuntimeException("事务需要回滚");
    }

  结果:都不回滚

@Transactional(propagation = Propagation.REQUIRED)
    public void method1() {
        TicketEntity ticketEntity = new TicketEntity();
        ticketEntity.setF_apply_service("测试");
        ticketEntityMapper.insertSelective(ticketEntity);
        method2();
        // throw new RuntimeException("事务需要回滚");

    }

    @Override
    // @Transactional(propagation = Propagation.REQUIRED)
    public void method2() {
        TicketEntity ticketEntity = new TicketEntity();
        ticketEntity.setF_apply_service("测试2");
        ticketEntityMapper.insertSelective(ticketEntity);
        throw new RuntimeException("事务需要回滚");
    }

结果:回滚

@Transactional(propagation = Propagation.REQUIRED)
    public void method1() {

        TicketEntity ticketEntity = new TicketEntity();
        ticketEntity.setF_apply_service("测试");
        ticketEntityMapper.insertSelective(ticketEntity);
        method2();
        // throw new RuntimeException("事务需要回滚1");
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRES_NEW, readOnly = true)
    public void method2() {
        TicketEntity ticketEntity = new TicketEntity();
        ticketEntity.setF_apply_service("测试2");
        ticketEntityMapper.insertSelective(ticketEntity);
        // throw new RuntimeException("事务需要回滚2");
    }

结果:不回滚,如果直接调用method2,则会报错,说明自调用时注解时没有效果的。

在 Spring 的 AOP 代理下,只有目标方法由外部调用,目标方法才由 Spring 生成的代理对象来管理,这会造成自调用问题。若同一类中的其他没有@Transactional 注解的方法内部调用有@Transactional 注解的方法,有@Transactional 注解的方法的事务被忽略,不会发生回滚。

这是由于使用 Spring AOP 代理造成的。为解决这两个问题,使用 AspectJ 取代 Spring AOP 代理。

原文地址:https://www.cnblogs.com/xmzJava/p/7474734.html