zoukankan      html  css  js  c++  java
  • 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 代理。

  • 相关阅读:
    javascript中获取dom元素高度和宽度
    $.ajax()方法详解
    JQ中$(window).load和$(document).ready区别与执行顺序
    第几个幸运数
    乘积尾零
    星期一
    分数
    卡片换位
    冰雹数
    打印方格
  • 原文地址:https://www.cnblogs.com/xmzJava/p/7474734.html
Copyright © 2011-2022 走看看