zoukankan      html  css  js  c++  java
  • @Transactional注解使用心得

    配置基于注解的声明式事务:

    ...配置tx,aop的命名空间
    xmlns:tx="http://www.springframework.org/schema/tx"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xsi:schemaLocation="
    ...

    http://www.springframework.org/schema/tx


    http://www.springframework.org/schema/tx/spring-tx.xsd

    ...

    <!-- transaction support-->
    <!-- PlatformTransactionMnager -->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>
    <!-- enable transaction annotation support -->
    <tx:annotation-driven transaction-manager="txManager" />

    其中TransactionDefinition接口定义以下特性:

    事务隔离级别

    隔离级别是指若干个并发的事务之间的隔离程度。TransactionDefinition 接口中定义了五个表示隔离级别的常量:

    • TransactionDefinition.ISOLATION_DEFAULT:这是默认值,表示使用底层数据库的默认隔离级别(读已提交)。对大部分数据库而言,通常这值就是TransactionDefinition.ISOLATION_READ_COMMITTED。
    • TransactionDefinition.ISOLATION_READ_UNCOMMITTED:该隔离级别表示一个事务可以读取另一个事务修改但还没有提交的数据。该级别不能防止脏读,不可重复读和幻读,因此很少使用该隔离级别。比如PostgreSQL实际上并没有此级别。
    • TransactionDefinition.ISOLATION_READ_COMMITTED:该隔离级别表示一个事务只能读取另一个事务已经提交的数据。该级别可以防止脏读,这也是大多数情况下的推荐值。
    • TransactionDefinition.ISOLATION_REPEATABLE_READ:该隔离级别表示一个事务在整个过程中可以多次重复执行某个查询,并且每次返回的记录都相同。该级别可以防止脏读和不可重复读。
    • TransactionDefinition.ISOLATION_SERIALIZABLE:所有的事务依次逐个执行,这样事务之间就完全不可能产生干扰,也就是说,该级别可以防止脏读、不可重复读以及幻读。但是这将严重影响程序的性能。通常情况下也不会用到该级别。

    事务传播行为

    所谓事务的传播行为是指,如果在开始当前事务之前,一个事务上下文已经存在(一个事务方法里,调用另一个事务方法,牵扯到的传播行为

    在TransactionDefinition定义了如下传播行为常量:

    • TransactionDefinition.PROPAGATION_REQUIRED:如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值
    • TransactionDefinition.PROPAGATION_REQUIRES_NEW:创建一个新的事务,如果当前存在事务,则把当前事务挂起
    • TransactionDefinition.PROPAGATION_SUPPORTS:如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
    • TransactionDefinition.PROPAGATION_NOT_SUPPORTED:以非事务方式运行,如果当前存在事务,则把当前事务挂起。就像是一个事务方法里调用了含not-supported的方法,或者反过来,都不支持事务
    • TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
    • TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
    • TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行(在前一个事务的基础上,不能算真正的独立事务);如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。感觉很像required,不过required是一个大事务,而nested是内嵌事务(基于save point),内嵌的事务成功后不会立即提交,等外层事务提交之后才会提交,外层可以对内层事物做try-catch,决定是否提交,还是整体回滚,只有外层事物提交了,内层才跟着提交。

    tip:将事务挂起,意味着本条数据所在排他锁不会释放,只有requires_new,算是真正独立的内层事务,其他都是加入事务

    只有requires_new和not supported将当前事务挂起(容易死锁)

    REQUIRES_NEW和PROPAGATION_NESTED的区别:

    PROPAGATION_REQUIRES_NEW 启动一个新的, 不依赖于环境的 “内部” 事务. 这个事务将被完全 commited 或 rolled back (不依赖于外部事务), 它拥有自己的隔离范围, 自己的锁, 等等. 当内部事务开始执行时, 外部事务将被挂起, 内务事务结束时, 外部事务将继续执行.

    PROPAGATION_NESTED 开始一个 “嵌套的” 事务, 它是已经存在事务的一个真正的子事务(依赖外部事务). 潜套事务开始执行时, 它将取得一个 savepoint. 如果这个嵌套事务失败, 我们将回滚到此 savepoint(子事务开始的地方). 潜套事务是外部事务的一部分, 只有外部事务结束后它才会被提交.

    最大区别在于, PROPAGATION_REQUIRES_NEW 完全是一个新的事务, 而 PROPAGATION_NESTED 则是外部事务的子事务, 如果外部事务 commit, 潜套事务也会被 commit

    事务超时

    所谓事务超时,就是指一个事务所允许执行的最长时间,如果超过该时间限制但事务还没有完成,则自动回滚事务。在 TransactionDefinition 中以 int 的值来表示超时时间,其单位是秒。

    默认设置为底层事务系统的超时值,如果底层数据库事务系统没有设置超时值,那么就是none,没有超时限制。

    事务只读属性

    只读事务用于客户代码只读但不修改数据的情形,只读事务用于特定情景下的优化,比如使用Hibernate的时候。
    默认为读写事务。

    @Transactional 使用:

    @Transactional 可以作用于接口、接口方法、类以及类方法上。当作用于类上时,该类的所有 public 方法将都具有该类型的事务属性,同时,我们也可以在方法级别使用该标注来覆盖类级别的定义。方法上注解属性会覆盖类注解上的相同属性

    虽然 @Transactional 注解可以作用于接口、接口方法、类以及类方法上,但是 Spring 建议不要在接口或者接口方法上使用该注解,因为这只有在使用基于接口的代理时它才会生效。另外, @Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。如果你在 protected、private 或者默认可见性的方法上使用 @Transactional 注解,这将被忽略,也不会抛出任何异常。

    默认情况下,只有来自外部的方法调用才会被AOP代理捕获,也就是,本类内部方法调用本类内部的其他方法并不会引起事务行为,即使被调用方法使用@Transactional注解进行修饰。

    //xxService里可以调用xxDao,因为xxService是一个类,xxDao又是一个类

                                   @Transactional属性值

    value String 可选的限定描述符,指定使用的事务管理器
    propagation enum: Propagation 可选的事务传播行为设置
    isolation enum: Isolation 可选的事务隔离级别设置
    readOnly boolean 读写或只读事务,默认读写
    timeout int (in seconds granularity) 事务超时时间设置
    rollbackFor(类) Class对象数组,必须继承自Throwable 导致事务回滚的异常类数组
    rollbackForClassName(类名) 类名数组,必须继承自Throwable 导致事务回滚的异常类名字数组
    noRollbackFor Class对象数组,必须继承自Throwable 不会导致事务回滚的异常类数组

    @Transactional实战:

    @Service
    public class TxService {
    
        @Autowired
        GoodsDao goodsDao;
    
        @Autowired
        TxService2 txService2;
    
        @Transactional
        public void x(){
            MiaoshaGoods good = new MiaoshaGoods();
            good.setGoodsId(1l);
            goodsDao.reduceStock(good);
            GoodsVo vo = goodsDao.getGoodsVoByGoodsId(1l);
            System.out.println("当前库存x:"+vo.getStockCount());
            txService2.y();// y方法事务有效
         //y(); 本类中,y方法事务失效,但是y方法加入了x中,作为了一个整体
    throw new RuntimeException("brx!!!!!!!!!!!!");

      //这种情况,事务失效
      @Transactional(propagation = Propagation.REQUIRES_NEW,isolation = Isolation.READ_COMMITTED)
      public void y(){
    MiaoshaGoods good = new MiaoshaGoods();
    good.setGoodsId(1L);
    goodsDao.reduceStock(good);
    GoodsVo vo = goodsDao.getGoodsVoByGoodsId(1L);
    System.out.println("当前库存y:"+vo.getStockCount());

    }
    
    }
    
    
    @Service
    public class TxService2 {
    
        @Autowired
        GoodsDao goodsDao;
    
        //@Transactional(propagation = Propagation.REQUIRES_NEW)   设置tx传播级别为requires_new,会造成CannotAcquireLockException(死锁)
    //因为dml操作会抢占排它锁,对同一条数据在外层事务进行dml,那么内层事务会将外层事务挂起,去请求该行锁,直到超时,,但是nest,required不会
      @Transactional
    public void y(){ MiaoshaGoods good = new MiaoshaGoods(); good.setGoodsId(1L); goodsDao.reduceStock(good); GoodsVo vo = goodsDao.getGoodsVoByGoodsId(1L); // 如果修改的是2L,那么Propagation.REQUIRES_NEW不会死锁
      System.out.println("当前库存y:"+vo.getStockCount()); } }

    如果
    TxService1的x方法加@Trxxx注解,那么调用自身的y方法,不管y上有没有@Trxxx,当外界调用x方法时,x包含y做统一事务处理

    如果TxService1的x方法不加@Trxxx注解,自身的y方法加注解,那么当外界调用x方法时,y事务失效,但是,当外界单独去掉y方法时,事务有效
    如果TxService1的x方法不加@Trxxx注解,TxService2的y方法加@Trxxx注解,那么当外界调用x方法时,y事务有效,但是x事务无效
    如果TxService1的x方法加@Trxxx注解,传播级别为required,TxService2的y方法加@Trxxx注解,传播级别为required,那么会作为一个整体进行回滚,提交
    如果TxService1的x方法加@Trxxx注解,传播级别为required,TxService2的y方法加@Trxxx注解,传播级别为requires_new,那么内层事务是独立的,
    外层(抛异常)影响不了内层,但是内层可以影响外层(内层抛异常,会导致整体事务回滚),但是,如果外层对内层的异常做捕获,那么外层可以提交事务,内层回滚

    如果TxService1的x方法加@Trxxx注解,传播级别为required,TxService2的y方法加@Trxxx注解,传播级别为nested,那么外层会影响内层,内层
    可以影响外层(内层抛异常,会导致整体事务回滚),但是,如果外层对内层的异常做捕获,那么外层可以提交事务,内层回滚;
    (跟required不同的是,required只要抛出异常,不管是外层抛出还是内层抛出,整体回滚,捕获无效)
    严格级别:required > nested > requires_new

    补充(dml操作会抢占排它锁):

    通过DML语句对一张表的某一行数据进行修改,一个事务开始,背后的步骤是:
    1.对这张表(表结构)加一个共享锁。防止其他事务DDL语句(数据定义语言)修改这张表的表结构。注意:读锁和写锁是互斥的,但是一个是针对表结构没,一个是针对行数据
    2.对修改的那一行加一个排他锁,别的会话不能修改这一行。但是我对整张表加的是共享锁而不是排他锁,所以别的会话还是可以修改其他行(也经历1、2两个步骤)

    当锁的持有时间较长并且大部分操作都不会修改被守护的资源时,读-写锁可以提高并发性。

  • 相关阅读:
    7.分类与预测
    6.图标绘制入门
    5.Python使用模块
    4.面向对象编程
    2.函数
    1,python基础入门
    (6)访问静态资源
    (5)操作数据库
    (2)快速创建springboot
    (1)IDEA 创建springboot
  • 原文地址:https://www.cnblogs.com/brxHqs/p/9761633.html
Copyright © 2011-2022 走看看