• (VI)事务:Spring 事务细节


    一、@Transactional 注解

      @Transactional:对方法中所有的操作作为一个事务进行管理。
      如果在方法上使用,只对当前方法有效果;
      如果在类上使用,对类中所有的方法都有效果。
      @Transactional 中可以设置的属性:
    progpagation——Propagation:设置事务的传播行为;
    isolation——Isolation:设置事务的隔离级别;
     
    timeout——int:设置事务的超时时间,事务超出指定执行时长后自动终止并回滚;
     
    noRollbackFor——Class[]:哪些异常事务可以不回滚
    noRollbackForClassName——String[]:(String全类名)
     
    rollbackFor——Class[]:哪些异常事务需要回滚;
    rollbackForClassName——String[]:(String全类名)
     
    readOnly——boolean:设置事务中的一些列的操作是否为只读;
     
     

    二、超时属性

      语法格式:

    @Transactional(timeout = 3)//毫秒值
    

      超时事务属性:事务在强制回滚之前可以保持多久。这样可以防止长期运行的事务占用资源。

      该属性是设置在事务强制回滚前最多可执行(等待)时间;

    三、只读属性

      由于事务可以在行和表上获得锁,因此长事务会占用资源,并对整体性能产生影响。
      如果一个事务只读取数据但不做修改,数据库引擎可以对这个事务进行优化 。
      只读事务属性:表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务。
     
      设置只读属性:
    @Transactional(readOnly = true) //默认是 false
    

       readOnly 属性:

      该属性指定当前事务中一系列的操作是否为只读,能够加快查询,不再关心事务。

      默认值为 false,不设置为只读。
      若设置为只读(true),不管事务中有没有写的操作,MySQL 都会在请求访问数据的时候,不加锁,提高性能;
      如果有读操作,有写操作,MySQL不会加锁,这样就会产生脏读、幻读、不可重复读等问题。
      所以如果有写的操作的情况,建议一定不能设置为只读。
     
      扩展:MySQL 使用多线程+锁机制,如果设置不设置为只读,MySQL会加锁,这样CRUD什么也操作不了,可以防止 增删改造成的问题。

    四、触发事务回滚的异常

      1、默认情况

        异常分类

          运行时异常(非检查异常):可以不用处理,默认都回滚;

          编译时异常(检查异常):要么try-catch,要么在方法上面声明 throws 默认不回滚;

        事务的回滚:捕获到 RuntimeException 或 Error 时回滚,而捕获到编译时异常不回滚。
        rollbackFor | rollbackForClassName | noRollbackFor | noRollbackForClassName:设置事务回滚的条件

      2、rollbackFor 属性

        rollbackFor 属性:指定遇到时必须进行回滚的异常类型,可以为多个;

        rollbackForClassName 属性:String 全类名

        举例:

        @Transactional(rollbackFor = {FileNotFoundException.class})
        @Transactional(rollbackForClassName = {"java.io.FileNotFoundException"})

      3、noRollbackFor 属性

        noRollbackFor 属性:指定遇到时不回滚的异常类型,可以为多个;

        noRollbackForClassName 属性:String 全类名

        举例:

        @Transactional(noRollbackFor = {ArithmeticException.class})
        @Transactional(noRollbackForClassName = {"java.lang.ArithmeticException"})
      注意:如果没有指定异常类型,抛出异常就回滚,指定异常类型,只有指定的异常发生,才回滚

     

    五、事务的隔离级别

      1、数据库事务并发问题

       假设现在有两个事务:Transaction01 和 Transaction02 并发执行。

      (1)脏读

    【1】Transaction01 将某条记录的 AGE 值从 20 修改为 30;
    【2】Transaction02 读取了 Transaction01 更新后的值:30;
    【3】Transaction01 回滚,AGE 的值恢复到了 20;
    【4】Transaction02 读取的30 就是一个无效的值;(脏数据)

      

      (2)不可重复读

    【1】Transaction01 读取了 AGE 的值为20;
    【2】Transaction02 将 AGE 值修改为 30;
    【3】Transaction01 再次读取 AGE 值为30,和第一次读取不一致;

      

      (3)幻读

    【1】Transaction01 读取了 Student 表中的一部分数据;
    【2】Transaction02 向 Student 表中插入了新的行;
    【3】Transaction03 读取了 Student 表时,多出了一些行;(出现了幻觉)

     

      2、隔离级别

      数据库系统必须具有隔离并发运行各个事务的能力,使他们不会相互影响,避免各种并发问题。
            一个事务与其他事务隔离的程度称为隔离级别。SQL 标准中规定了多种事物隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。
      (1)读未提交:READ UNCOMMITED
                            允许 Transaction01 读取 Transaction02 未提交的修改。(脏读)
            案例:
          
      (2)读已提交:READ COMMITED
                            要求 Transaction01 只能读取 Transaction02 已提交的修改。(幻读)
            案例:
          
     
      (3)可重复读:REPEATABLE READ
                            确保 Transaction01 可以多次从一个字段中读取到相同的值,即 Transaction01 执行期间禁止其他事务对这个字段进行更新。(不可重复读,)                
            案例:
          
     
      (4)串行化:SERIALIZABLE
                            确保 Transaction01 可以多次从一个表中读取到相同的行,在 Transaction01 执行期间,禁止其他事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。
     

      3、各个隔离级别解决并发问题的能力表

        

     
    脏读
    不可重复读
    幻读
    READ UNCOMMITTED
    READ COMMITTED
    REPEATABLE READ
    SERIALIZABLE

      4、各个数据库产品对事务隔离级别的支持程度

        

     
    Oracle
    MySQL
    READ UNCOMMITTED
    ×
    READ COMMITTED
    √(默认)
    REPEATABLE READ
    ×
    √(默认)
    SERIALIZABLE

      5、在 Spring 中指定事务隔离级别

        (1)使用注解

          用 @Transaction 注解声明式地管理事务时可以在 @Transaction 的 Isolation 属性中设置隔离级别。     

        (2)XML 方式

          在Spring 2.x事务通知中,可以在< tx:method >元素中指定隔离级别

        <tx:advice id="bookShpTXAdvice"  transaction-manager="transactionManager">
             <tx:attributes>
                  <tx:method name="purchase"  propagation="REQUIRES_NEW" isolation="READ_COMMITTED"/>
             </tx:attributes>
         </tx:advice>

      6、详解隔离级别

        Isolation:事务的隔离级别,在并发的情况下,操作数据的一种规定
    读未提交:脏读                  1
    读已提交:不可重复读       2
    可重复读:幻读                  4
    串行化:性能低、消耗大    8
     
      读未提交、读已提交,只是对某一记录的字段的值进行修改
      可重复读是指对整个表中的记录的个数进行操作。
     
      串行化相当于单线程,其他操作(增删改查)什么也不可以执行。

      7、MySQL 数据设置隔离级别

    查询 MySQL 的隔离级别
    select @@global.tx_isolation;    //查询全局隔离级别
    select @@session.tx_isolation;  //查询当前会话隔离级别
    select @@tx_isolation; //默认是会话隔离级别
    
    修改MySQL隔离级别 
    set [session | global] transaction isolation level [READ UNCOMMITTED | READ COMMITTED | REPEATABLE READ | SERIALIZABLE]
    
    事务操作
    开启事务  start transaction
    提交事务  commit
    回滚事务  rollback
    

      

      8、并发修改同一个数据下的排队

        案例:

        

      9、ioc 容器中保存的是业务逻辑组件(有事务)的代理对象

    六、事务的传播行为  

      1、传播行为

        传播行为(事务的传播+事务的行为):如果有多个事务进行嵌套运行,子事务是否要和大事务共用一个事务;

        当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。
        例如:方法可能继续在现有的事务中运行,也可能开启一个新事务,并在自己的事务中运行。
        事务的传播行为可以由传播属性指定。Spring 定义了7种类传播行为

      2、Spring的7种传播行为

          

          事务传播属性可以在 @Transaction 注解的 propagation 属性中定义。        

      3、测试

        

      4、REQUIRED传播行为

        当 bookService 的 purchase() 方法被另一个事务方法 checkout() 调用时,它默认会在现有的事务内运行。
                这个默认的传播行为就是 REQUIRED
                因此在 checkout() 方法的开始和终止边界内只有一个事务。这个事务只在 checkout() 方法结束的时候被提交,结果用户一本书都买不了。
        REQUIRED:同一辆车(要翻车一起翻)

    事务的属性都是继承于大事务;自己的都失效

    只有这一条线上任何一处出现异常,整条先全崩,回滚

    将之前事务用的connection传递给这个方法使用

        

      5、REQUIRES_NEW传播行为

        表示该方法必须启动一个新事务,并在自己的事务内运行。如果有事务在运行,就应该先挂起它。

        REQUIRED_NEW:开一辆新车(你翻车不影响我)

    不继承大事务,自己用自己的

    一处事务崩,只是他崩了,跟其他没有关系,他自己回滚。

    这个方法直接使用新的connection

        

      6、详解传播行为

      Propagation:A方法和B方法都有事务,当 A方法在调用 B 方法时,会将 A 中的事务传播给 B 方法,B方法对于事务的处理方式就是事务的传播行为。
      所以要在被调用的方法 (B方法)上面设置事务的传播行为
     
      (1)Propagation.REQUIRED:必须使用调用者的事务;
      (2)Propagation.REQUIRES_NEW:将调用者的事务挂起,不使用调用者的事务,使用新的事务进行处理;
     
       注意:默认的传播行为是 REQUIRED,默认和数据库的隔离级别是一样的

      7、REQUIRED 事务属性来源于最外层的大事务属性

        如果是 REQUIRED :事务的属性都是继承于最外层的大事务属性,而 REQUIRED_NEW 是可以调整;

      8、本类事务方法之间的调用就只是一个事务(事务失效)

        举例:

    @Service
    public class BookService {
    
        @Autowired
        private BookDao bookDao;
    
        /**
         * 结账,哪个用户买了哪本书
         * @param userName
         * @param isbn
         */
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        public void checkOut(String userName, String isbn) {
            bookDao.updateStock(isbn);
    
            int price = bookDao.getPrice(isbn);
    
            int i = 10 / 0;
    
            bookDao.updateBalance(userName, price);
    
        }
        
        @Transactional(propagation = Propagation.REQUIRES_NEW)
        public void updatePrice() {
            
        }
        
        
        @Transactional
        public void mulTx() {
            checkOut("Tom", "ISBN-001");
            updatePrice();
        }
        
    }

      在上面的 mulTx() 方法中,直接调用本类的事务方法,并且事务的传播行为都是 REQUIRED_NEW,但是运行 mulTx() 方法时,事务并没有正常运行。

      注意:Spring是利用AOP来实现事务的,而且在容器中保存的代理对象,我们应该调用代理对象的事务方法,直接调用本类的事务方法,并没有代理对象进行事务控制。

      上面的 mulTx() 相当于只有一个 mulTx() 一个事务方法:

        BookServiceProxy.mulTx() {
            checkOut("Tom", "ISBN-001");
            updatePrice();
        }

      正确写法:

        @Autowired
        private BookDao bookDao; //注入的是代理对象
        
        @Transactional
        public void mulTx() {
            bookService.checkOut("Tom", "ISBN-001");
            bookService.updatePrice();
        }

        通过代理对象来调用,代理对象对原来方法做了事务控制(增强)。

        本类事务方法的嵌套调用就只有一个事务。

     
  • 相关阅读:
    1-22
    好久未更
    学习进度条 第十六周
    构建之法阅读笔记06
    返回一个整数数组中最大子数组的和(补充:输出该子数组)
    构建执法阅读笔记4
    第三周的学习进度情况
    构建之法阅读笔记03
    四则运算三
    安卓小程序之“标准体重计算器”
  • 原文地址:https://www.cnblogs.com/niujifei/p/15491467.html
Copyright © 2020-2023  润新知