• spring-事务实现和@Transactional注解分析


    参考:

    https://www.jianshu.com/p/d23f22b5aed5

    https://www.cnblogs.com/wangfg/p/9475788.html

    https://www.cnblogs.com/alice-cj/p/10417097.html

    https://blog.csdn.net/jiangyu1013/article/details/84397366

    数据库事务和@Transaction注解

    数据库事务(Database Transaction) ,是指作为单个逻辑工作单元执行的一系列操作,要么完全地执行,要么完全地不执行。 事务处理可以确保除非事务性单元内的所有操作都成功完成,否则不会永久更新面向数据的资源。通过将一组相关操作组合为一个要么全部成功要么全部失败的单元,可以简化错误恢复并使应用程序更加可靠。

    事务的基本要素(ACID)

    • 原子性(Atomicity):事务开始后所有操作,要么全部做完,要么全部不做,不可能停滞在中间环节。事务执行过程中出错,会回滚到事务开始前的状态,所有的操作就像没有发生一样。也就是说事务是一个不可分割的整体,就像化学中学过的原子,是物质构成的基本单位。
    • 一致性(Consistency):事务开始前和结束后,数据库的完整性约束没有被破坏 。比如A向B转账,不可能A扣了钱,B却没收到。
    • 隔离性(Isolation):同一时间,只允许一个事务请求同一数据,不同的事务之间彼此没有任何干扰。比如A正在从一张银行卡中取钱,在A取钱的过程结束前,B不能向这张卡转账。
    • 持久性(Durability):事务完成后,事务对数据库的所有更新将被保存到数据库,不能回滚。

    当单进程执行时,很容易保证事务的4中基本要素,并且不会引发问题,然而当多个并发进程共同执行事务时候,可能会引发不同的问题。

    事务隔离级别

    隔离级别脏读不可重复读幻读特点
    未提交读(read-uncommitted) 优点在于并发能力高,适合那些对数据一致性没有要求而追求高并发的场景,缺点是脏读
    读写提交(read-committed) × 出现不可重复读
    可重复读(repeatable-read) × × mysql的默认事务隔离级别。会出现幻读
    串行化(serializable) × × × 并行性低,一般不用。通常消除幻读使用数据库锁的方式
    • 脏读:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据
    • 不可重复读:事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。
    • 幻读:幻读是针对多条数据库记录的。例如,系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。

    数据库锁

    乐观锁

    相对悲观锁而言,乐观锁假设认为数据一般情况下不会造成冲突,所以在数据进行提交更新的时候,才会正式对数据的冲突与否进行检测,如果发现冲突了,则让返回用户错误的信息,让用户决定如何去做。一般的实现乐观锁的方式就是记录数据版本,并且不会造成线程的阻塞。

    但是,版本的冲突会造成请求失败的概率剧增,这时往往需要通过重入的机制将请求失败的概率降低。而多次的重入又会带来过多执行SQL的问题,为了克服这个问题,可以考虑使用按时间戳或者限制重入次数的办法。

    悲观锁

    正如其名,它指的是对数据被外界修改持保守态度。因此在整个数据处理过程中,将数据处于锁定状态。 悲观锁的实现,往往依靠数据库提供的锁机制 (也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

    数据库两种锁可以实现悲观锁,排他锁(Exclusive Lock,也叫X锁)和共享锁(Shared Lock,也叫S锁)。排他锁表示对数据进行写操作,如果一个事务对对象加了排他锁,其他事务就不能再给它加任何锁了;共享锁会为操作对象加共享锁,可以允许其他共享锁的加锁操作,但是不允许排它锁加锁操作。

    锁粒度

    MySQL锁的粒度几种,从小到大分别是,行锁(Record Lock)、间隙锁(Gap Lock)、Next-Key Lock、表锁。
    1、 行锁:锁定一行记录
    2、 间隙锁:锁定一个范围,但是不包含记录本身
    3、Next-Key Lock:Record Lock和Gap Lock的组合,锁定一个区间以及对应的记录
    4、 表锁:锁定整张表

    MySQL innoDB加锁是通过索引项判断加锁的范围,即如果操作的对象上没有索引或者没有使用索引,则会导致锁的粒度扩大。因为InnoDB索引采用的BTree结构,所以,如果不是唯一索引,则如果使用查询值索引不存在,或者使用的条件查询则会引发Next-Key Lock。

    意向锁

    有了共享锁(S锁)和排它锁(X锁),能够实现数据库操作不同粒度的加锁,但是在一个操作需要给整个表加X锁的时候,需要确定整个表以及表的每一行没有被加S锁或者X锁,那么如何确定表中每一行没有被加锁,需要扫描整个表吗?如果表中有海量记录需要长时间等待,此时需要有意向锁。

    Spring的@Transaction注解

    在Spring中,为了 “擦除”令人厌烦的 try...catch...finally..语句,减少代码中数据库连接开闭和事务回滚提交的代码, Spring利用其 AOP 为我们提供了一个数据库事务的约定流程,这样开发的代码可读性就更高,也更好维护。

     
    Spring数据库事务约定

    @Transactional源码分析:

    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Inherited
    @Documented
    public @interface Transactional {
        // 通过 bean name 指定事务管理器
        @AliasFor("transactionManager")
        String value() default "";
        // 同 value 属性
        @AliasFor("value")
        String transactionManager() default "";
        // 传播行为
        Propagation propagation() default Propagation.REQUIRED;
        // 隔离级别
        Isolation isolation() default Isolation.DEFAULT;
        // 超时时间
        int timeout() default TransactionDefinition.TIMEOUT_DEFAULT;
        // 是否只读事务
        boolean readOnly() default false;
        // 方法在发生指定异常时回滚,默认是所有异常都囚滚
        Class<? extends Throwable>[] rollbackFor() default {};
        // 方法在发生指定异常名称时回滚,默认是所有异常都回滚
        String[] rollbackForClassName() default {};
        // 方法在发生指定异常时不回滚,默认是所有异常都回滚
        Class<? extends Throwable>[] noRollbackFor() default {};
        // 方法在发生指定异常名称时不回滚,默认是所有异常都回滚
        String[] noRollbackForClassName() default {};
    }
    

    事务管理器

    事务的打开、回滚和提交是由事务管理器来完成的。在Spring中,事务管理器的顶层接口为PlatformTransactionManager,Spring也定义了一些其他的接口和类。

    在Spring Boot中,当你依赖于mybatis-spring-boot-starter之后,它会自动创建一个DataSource­ TransactionManager对象作为事务管理器;如果依赖于spring-boot-starter-data-jpa,则它会自动创建JpaTransactionManager对象作为事务管理器,所以我们一般不需要自己创建事务管理器而直接使用它们即可。

    传播行为

    传播行为是方法之间调用事务采取的策略。在大部分的情况下,我们会认为数据库事务要么全部成功,要么全部失败。但当执行批量任务时,我们有时不希望因为极少数的任务不能完成而回滚所有的批量任务 。

    在Spring中,当一个方法调用另外一个方法时,可以让事务采取不同的策略工作,如新建事务或者挂起当前事务等,这便是事务的传播行为。例如,批量任务我们称之为当前方法,当它调用单个任务时,称单个任务为子方法。当前方法调用子方法的时候,让每一个子方法不在当前事务中执行,而是创建一个新的事务,我们就说当前方法调用子方法的传播行为为新建事务。此外,还可能让方法在无事务、独立事务中执行,这些完全取决于业务需求。

    源码分析:

    public enum Propagation {
        // 需要事务。它是默认传播行为,如果当前存在事务,就沿用当前事务,否则新建一个事务运行子方法
        REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
        // 支持事务,如果当前存在事务,就沿用当前事务,如果不存在 ,则继续采用无事务的方式运行子方法
        SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
        // 必须使用事务,如果当前没有事务,则会抛出异常,如果存在当前事务 ,就沿用当前事务
        MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
        // 无论当前事务是否存在,都会创建新事务运行方法,这样新事务就可以拥有新的锁和隔离级别等特性,与当前事务相互独立
        REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
        // 不支持事务,当前存在事务时,将挂起事务,运行方法
        NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
        // 不支持事务,如果当前方法存在事务,则抛出异常,否则继续使用无事务机制运行
        NEVER(TransactionDefinition.PROPAGATION_NEVER),
        // 在当前方法调用子方法时,如果子方法发生异常,只因滚子方法执行过的 SQL,而不回滚当前方法的事务
        NESTED(TransactionDefinition.PROPAGATION_NESTED);
    
        private final int value;
    
        Propagation(int value) { this.value = value; }
    
        public int value() { return this.value; }
    
    }
    

    常用的传播行为是REQUIRED、REQUIRES_NEW、NESTED三种。

    @Transactional自调用失效问题

    Spring数据库事务的实现原理是AOP,而AOP的原理是动态代理。在事务自调用的过程中,是类自身的调用,而不是代理对象去调用,那么就不会产生AOP,这样Spring就不能把你的代码织入到约定的流程中,于是就产生了@Transactional自调用失效的场景。

    有两种解决方法:用一个Service去调用另一个Service,这样就是代理对象的调用,Spring就会将代码织入事务流程;或者从Spring IoC容器中获取代理对象去启用AOP。

    参考资料

    • 《深入浅出Spring Boot 2.X》
    • 《mysql数据库事务与锁》--尧亦
    作者:斯文遮阳
    链接:https://www.jianshu.com/p/d23f22b5aed5
    来源:简书
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
     
     
     
     
     
     
     
     
     
     
     

    spring的@Transactional注解详细用法

    概述

    事务管理对于企业应用来说是至关重要的,即使出现异常情况,它也可以保证数据的一致性。
    Spring Framework对事务管理提供了一致的抽象,其特点如下:

    • 为不同的事务API提供一致的编程模型,比如JTA(Java Transaction API), JDBC, Hibernate, JPA(Java Persistence API和JDO(Java Data Objects)
    • 支持声明式事务管理,特别是基于注解的声明式事务管理,简单易用
    • 提供比其他事务API如JTA更简单的编程式事务管理API
    • 与spring数据访问抽象的完美集成

    事务管理方式

    spring支持编程式事务管理和声明式事务管理两种方式。

    编程式事务管理使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。

    声明式事务管理建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。

    显然声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要加上注解就可以获得完全的事务支持。和编程式事务相比,声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。

    声明式事务管理也有两种常用的方式,一种是基于tx和aop名字空间的xml配置文件,另一种就是基于@Transactional注解。显然基于注解的方式更简单易用,更清爽。

    自动提交(AutoCommit)与连接关闭时的是否自动提交

    自动提交

    默认情况下,数据库处于自动提交模式。每一条语句处于一个单独的事务中,在这条语句执行完毕时,如果执行成功则隐式的提交事务,如果
    执行失败则隐式的回滚事务。

    有些数据连接池提供了关闭事务自动提交的设置,最好在设置连接池时就将其关闭。但C3P0没有提供这一特性,只能依靠spring来设置。
    因为JDBC规范规定,当连接对象建立时应该处于自动提交模式,这是跨DBMS的缺省值,如果需要,必须显式的关闭自动提交。C3P0遵守这一规范,让客户代码来显式的设置需要的提交模式。

    连接关闭时的是否自动提交

    当一个连接关闭时,如果有未提交的事务应该如何处理?JDBC规范没有提及,C3P0默认的策略是回滚任何未提交的事务。这是一个正确的策略,但JDBC驱动提供商之间对此问题并没有达成一致。
    C3P0的autoCommitOnClose属性默认是false,没有十分必要不要动它。或者可以显式的设置此属性为false,这样会更明确。

    基于注解的声明式事务管理配置
    spring-servlet.xml

    复制代码
    复制代码
    1 <!-- transaction support-->
    2 <!-- PlatformTransactionMnager -->
    3 <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    4     <property name="dataSource" ref="dataSource" />
    5 </bean>
    6 <!-- enable transaction annotation support -->
    7 <tx:annotation-driven transaction-manager="txManager" />
    复制代码
    复制代码

    还要在spring-servlet.xml中添加tx名字空间

    复制代码
    复制代码
     1 ...
     2     xmlns:tx="http://www.springframework.org/schema/tx"
     3     xmlns:aop="http://www.springframework.org/schema/aop"
     4     xsi:schemaLocation="
     5     ...
     6  
     7 http://www.springframework.org/schema/tx
     8  
     9  
    10 http://www.springframework.org/schema/tx/spring-tx.xsd
    11  
    12     ...
    复制代码
    复制代码

    MyBatis自动参与到spring事务管理中,无需额外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的数据源与DataSourceTransactionManager引用的数据源一致即可,否则事务管理会不起作用。

    另外需要下载依赖包aopalliance.jar放置到WEB-INF/lib目录下。否则spring初始化时会报异常
    java.lang.NoClassDefFoundError: org/aopalliance/intercept/MethodInterceptor

    spring事务特性

    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:以非事务方式运行,如果当前存在事务,则把当前事务挂起。
    • TransactionDefinition.PROPAGATION_NEVER:以非事务方式运行,如果当前存在事务,则抛出异常。
    • TransactionDefinition.PROPAGATION_MANDATORY:如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
    • TransactionDefinition.PROPAGATION_NESTED:如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。

    事务超时

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

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

    事务只读属性

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

    spring事务回滚规则

    指示spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。

    默认配置下,spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。
    可以明确的配置在抛出那些异常时回滚事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。

    还可以编程性的通过setRollbackOnly()方法来指示一个事务必须回滚,在调用完setRollbackOnly()后你所能执行的唯一操作就是回滚。

    @Transactional注解

    @Transactional属性

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

    用法

    1. 在需要事务管理的地方加@Transactional 注解。@Transactional 注解可以被应用于接口定义和接口方法、类定义和类的 public 方法上。

    2. @Transactional 注解只能应用到 public 可见度的方法上。 如果你在 protected、private 或者 package-visible 的方法上使用 @Transactional 注解,它也不会报错, 但是这个被注解的方法将不会展示已配置的事务设置。

    3. 注意仅仅 @Transactional 注解的出现不足于开启事务行为,它仅仅 是一种元数据。必须在配置文件中使用配置元素,才真正开启了事务行为。

    4. 通过 元素的 "proxy-target-class" 属性值来控制是基于接口的还是基于类的代理被创建。如果 "proxy-target-class" 属值被设置为 "true",那么基于类的代理将起作用(这时需要CGLIB库cglib.jar在CLASSPATH中)。如果 "proxy-target-class" 属值被设置为 "false" 或者这个属性被省略,那么标准的JDK基于接口的代理将起作用。


    标准的JDK基于接口的代理将起作用-->
    proxy-target-class="false"/>

    基于类的代理将起作用 ,同时 cglib.jar必须在CLASSPATH中
    proxy-target-class="true"/>
    -->


    非JTA事务(即非分布式事务), 事务配置的时候 ,需要指定dataSource属性(非分布式事务,事务是在数据库创建的链接上开启。)-->


    JTA事务(非分布式事务), 事务配置的时候 ,不能指定dataSource属性(分布式事务,是有全局事务来管理数据库链接的)-->

    注解@Transactional cglib与java动态代理最大区别是代理目标对象不用实现接口,那么注解要是写到接口方法上,要是使用cglib代理,这是注解事物就失效了,为了保持兼容注解最好都写到实现类方法上。

    5. Spring团队建议在具体的类(或类的方法)上使用 @Transactional 注解,而不要使用在类所要实现的任何接口上。在接口上使用 @Transactional 注解,只能当你设置了基于接口的代理时它才生效。因为注解是 不能继承 的,这就意味着如果正在使用基于类的代理时,那么事务的设置将不能被基于类的代理所识别,而且对象也将不会被事务代理所包装。

    6. @Transactional 的事务开启 ,或者是基于接口的 或者是基于类的代理被创建。所以在同一个类中一个方法调用另一个方法有事务的方法,事务是不会起作用的。

    public interface PersonService {
    //删除指定id的person
    public void delete(Integer personid) ;

    //删除指定id的person,flag
    public void delete(Integer personid,boolean flag) ;
    }

    public class PersonServiceBean implements PersonService {
    private JdbcTemplate jdbcTemplate;

    public void delete(Integer personid){
    try{
    this.delete(personid,true)
    System.out.println("delete success");
    }catch(Exception e){
    System.out.println("delete failed");
    }
    }

    @Transactional
    //此时,事务根本就没有开启, 即数据库会默认提交该操作,即记录别删除掉 public void delete(Integer personid,boolean flag){
    if(flag == ture){
    jdbcTemplate.update("delete from person where id=?", new Object[]{personid},
    new int[]{java.sql.Types.INTEGER});
    throw new RuntimeException("运行期例外");
    }
    }
    }

    public class PersonServiceBeanTest{
    PersonService ps = new PersonServiceBean ();
    ps.delete(5);
    }

    7. Spring使用声明式事务处理,默认情况下,如果被注解的数据库操作方法中发生了unchecked异常,所有的数据库操作将rollback;如果发生的异常是checked异常,默认情况下数据库操作还是会提交的。

    -----------------------------------------------------------------------------------------------------------------------------------------------
    public interface PersonService {
    //删除指定id的person
    public void delete(Integer personid) ;

    //获取person
    public Person getPerson(Integer personid);
    }

    //PersonServiceBean 实现了PersonService 接口,则基于接口的还是基于类的代理 都可以实现事务
    @Transactional public class PersonServiceBean implements PersonService {
    private JdbcTemplate jdbcTemplate;

    //发生了unchecked异常,事务回滚, @Transactional
    public void delete(Integer personid){
    jdbcTemplate.update("delete from person where id=?", new Object[]{personid},
    new int[]{java.sql.Types.INTEGER});
    throw new RuntimeException("运行期例外");
    }
    }

    ---------------------------------------------------------------------------------------------------------------------------------------------------
    public interface PersonService {
    //删除指定id的person
    public void delete(Integer personid) throws Exception;

    //获取person
    public Person getPerson(Integer personid);
    }

    @Transactional
    public class PersonServiceBean implements PersonService {

    //发生了checked异常,事务不回滚,即数据库记录仍能被删除,
    //checked的例外,需要我们在外部用try/catch语法对调用该方法的地方进行包含 @Transactional
    public void delete(Integer personid) throws Exception{
    jdbcTemplate.update("delete from person where id=?", new Object[]{personid},
    new int[]{java.sql.Types.INTEGER});
    throw new Exception("运行期例外");
    }

    }
    ---------------------------------------------------------------------------------------------------------------------------------------------------
    但是,对于checked这种例外,默认情况下它是不会进行事务回滚的,但是如果我们需要它进行事务回滚,这时候可以在delete方法上通过@Transaction这个注解来修改它的行为。

    @Transactional
    public class PersonServiceBean implements PersonService {

    @Transactional(rollbackFor=Exception.class)
    //rollbackFor这属性指定了,既使你出现了checked这种例外,那么它也会对事务进行回滚
    public void delete(Integer personid) throws Exception{
    jdbcTemplate.update("delete from person where id=?", new Object[]{personid},
    new int[]{java.sql.Types.INTEGER});
    throw new Exception("运行期例外");
    }
    }
    ---------------------------------------------------------------------------------------------------------------------------------------------------

    在PersonServiceBean这个业务bean里面,有一些事务是不需要事务管理的,好比说获取数据的getPersons方法,getPerson方法。因为@Transactional 放在了类的上面。


    此时,可以采用propagation这个事务属性@Transactional(propagation=Propagation.NOT_SUPPORTED),propagation这个属性指定了事务传播行为,我们可以指定它不支持事务,当我们这么写了之后,Spring容器在getPersons方法执行前就不会开启事务.

    @Transactional
    public class PersonServiceBean implements PersonService {

    @Transactional(propagation=Propagation.NOT_SUPPORTED)
    //则此方法 就不会开启事务了
    public Person getPerson(Integer personid)
    {
    }
    }

    transaction注解分析

    1. Spring事务的基本原理

    事务管理是应用系统开发中必不可少的一部分。Spring 为事务管理提供了丰富的功能支持。Spring 事务管理分为编码式和声明式的两种方式。编程式事务指的是通过编码方式实现事务;声明式事务基于 AOP,将具体业务逻辑与事务处理解耦。声明式事务管理使业务代码逻辑不受污染, 因此在实际使用中声明式事务用的比较多。声明式事务有两种方式,一种是在配置文件(xml)中做相关的事务规则声明,另一种是基于@Transactional 注解的方式。注释配置是目前流行的使用方式,因此本文将着重介绍基于@Transactional 注解的事务管理。
    使用@Transactional的相比传统的我们需要手动开启事务,然后提交事务来说。它提供如下方便

    • 根据你的配置,设置是否自动开启事务
    • 自动提交事务或者遇到异常自动回滚

    声明式事务(@Transactional)基本原理如下:

    1. 配置文件开启注解驱动,在相关的类和方法上通过注解@Transactional标识。
    2. spring 在启动的时候会去解析生成相关的bean,这时候会查看拥有相关注解的类和方法,并且为这些类和方法生成代理,并根据@Transaction的相关参数进行相关配置注入,这样就在代理中为我们把相关的事务处理掉了(开启正常提交事务,异常回滚事务)。
    3. 真正的数据库层的事务提交和回滚是通过binlog或者redo log实现的。
    2. @Transactional基本配置解析
    @Transactional
        public void saveUser(){
    
            User user = new User();
            user.setAge(22);
            user.setName("maskwang");
            logger.info("save the user{}",user);
            userRepository.save(user);
        }
    

    如上面这个例子一样,很轻松的就能应用事务。只需要在方法上加入@Transactional注解。当@Transactional加在方法上,表示对该方法应用事务。当加在类上,表示对该类里面所有的方法都应用相同配置的事务。接下来对@Transactional的参数解析。

    public @interface Transactional {
        @AliasFor("transactionManager")
        String value() default "";
    
        @AliasFor("value")
        String transactionManager() default "";
    
        Propagation propagation() default Propagation.REQUIRED;
    
        Isolation isolation() default Isolation.DEFAULT;
    
        int timeout() default -1;
    
        boolean readOnly() default false;
    
        Class<? extends Throwable>[] rollbackFor() default {};
    
        String[] rollbackForClassName() default {};
    
        Class<? extends Throwable>[] noRollbackFor() default {};
    
        String[] noRollbackForClassName() default {};
    }
    
    • transactionManager()表示应用哪个TransactionManager.常用的有如下的事务管理器
     
    image.png
    • isolation()表示隔离级别
     
    image.png

    脏读:一事务对数据进行了增删改,但未提交,另一事务可以读取到未提交的数据。如果第一个事务这时候回滚了,那么第二个事务就读到了脏数据。
    不可重复读:一个事务中发生了两次读操作,第一次读操作和第二次操作之间,另外一个事务对数据进行了修改,这时候两次读取的数据是不一致的。
    幻读:第一个事务对一定范围的数据进行批量修改,第二个事务在这个范围增加一条数据,这时候第一个事务就会丢失对新增数据的修改。

    不可重复读的重点是修改 :同样的条件, 你读取过的数据,再次读取出来发现值不一样了幻读的重点在于新增或者删除:同样的条件, 第 1 次和第 2 次读出来的记录数不一样

    • propagation()表示事务的传播属性
      事务的传播是指事务的嵌套的时候,它们的事务属性。常见的传播属性有如下几个
    1. PROPAGATION_REQUIRED(spring 默认)
      假设外层事务 Service A 的 Method A() 调用 内层Service B 的 Method B()。如果ServiceB.methodB() 的事务级别定义为 PROPAGATION_REQUIRED,那么执行 ServiceA.methodA() 的时候spring已经起了事务,这时调用 ServiceB.methodB(),ServiceB.methodB() 看到自己已经运行在 ServiceA.methodA() 的事务内部,就不再起新的事务。假如 ServiceB.methodB() 运行的时候发现自己没有在事务中,他就会为自己分配一个事务。不管如何,ServiceB.methodB()都会在事务中。

    2. PROPAGATION_REQUIRES_NEW
      比如我们设计 ServiceA.methodA() 的事务级别为 PROPAGATION_REQUIRED,ServiceB.methodB() 的事务级别为 PROPAGATION_REQUIRES_NEW。那么当执行到 ServiceB.methodB() 的时候,ServiceA.methodA() 所在的事务就会挂起,ServiceB.methodB() 会起一个新的事务,等待 ServiceB.methodB() 的事务完成以后,它才继续执行。它与1中的区别在于ServiceB.methodB() 新起了一个事务。如过ServiceA.methodA() 发生异常,ServiceB.methodB() 已经提交的事务是不会回滚的。

    3. PROPAGATION_SUPPORTS
      假设ServiceB.methodB() 的事务级别为 PROPAGATION_SUPPORTS,那么当执行到ServiceB.methodB()时,如果发现ServiceA.methodA()已经开启了一个事务,则加入当前的事务,如果发现ServiceA.methodA()没有开启事务,则自己也不开启事务。这种时候,内部方法的事务性完全依赖于最外层的事务。
      剩下几种就不多介绍,可以参考这篇文章。https://www.jianshu.com/p/249f2cd42692

    • readOnly() 事务超时设置.超过这个时间,发生回滚

    • readOnly() 只读事务
      从这一点设置的时间点开始(时间点a)到这个事务结束的过程中,其他事务所提交的数据,该事务将看不见!(查询中不会出现别人在时间点a之后提交的数据)。
      注意是一次执行多次查询来统计某些信息,这时为了保证数据整体的一致性,要用只读事务

    • rollbackFor()导致事务回滚的异常类数组.

    • rollbackForClassName() 导致事务回滚的异常类名字数组

    • noRollbackFor 不会导致事务回滚的异常类数组

    • noRollbackForClassName 不会导致事务回滚的异常类名字数组

    3. @Transactional 使用应该注意的地方
    3.1 默认情况下,如果在事务中抛出了未检查异常(继承自 RuntimeException 的异常)或者 Error,则 Spring 将回滚事务;除此之外,Spring 不会回滚事务。你如果想要在特定的异常回滚可以考虑rollbackFor()等属性
    3.2 @Transactional 只能应用到 public 方法才有效。

    这是因为在使用 Spring AOP 代理时,Spring 会调用 TransactionInterceptor在目标方法执行前后进行拦截之前,DynamicAdvisedInterceptorCglibAopProxy的内部类)的的 intercept方法或 JdkDynamicAopProxy 的 invoke 方法会间接调用 AbstractFallbackTransactionAttributeSource(Spring 通过这个类获取@Transactional 注解的事务属性配置属性信息)的 computeTransactionAttribute 方法。

    @Nullable
        protected TransactionAttribute computeTransactionAttribute(Method method, @Nullable Class<?> targetClass) {
           //这里判断是否是public方法
            if(this.allowPublicMethodsOnly() && !Modifier.isPublic(method.getModifiers())) {
                return null;
            } 
    //省略其他代码
    

    若不是 public,就不会获取@Transactional 的属性配置信息,最终会造成不会用 TransactionInterceptor 来拦截该目标方法进行事务管理。整个事务执行的时序图如下。


     
    image.png
    3.3 Spring 的 AOP 的自调用问题

    在 Spring 的 AOP 代理下,只有目标方法由外部调用,目标方法才由 Spring 生成的代理对象来管理,这会造成自调用问题。若同一类中的其他没有@Transactional注解的方法内部调用有@Transactional注解的方法,有@Transactional注解的方法的事务被忽略,不会发生回滚。这个问题是由于Spring AOP 代理造成的(如下面代码所示)。之所以没有应用事务,是因为在内部调用,而代理后的类(把目标类作为成员变量静态代理)只是调用成员变量中的对应方法,自然也就没有aop中的advice,造成只能调用父类的方法。另外一个问题是只能应用在public方法上。为解决这两个问题,使用 AspectJ 取代 Spring AOP 代理。

    @Transactional
     public void saveUser(){
            User user = new User();
            user.setAge(22);
            user.setName("mask");
            logger.info("save the user{}",user);
            userRepository.save(user);
           // throw new RuntimeException("exception");
        }
     public void saveUserBack(){
            saveUser();   //自调用发生
        }
    
    3.4 自注入解决办法
    @Service
    public class UserService {
    
        Logger logger = LoggerFactory.getLogger(UserService.class);
    
        @Autowired
        UserRepository userRepository;
        
        @Autowired 
        UserService userService; //自注入来解决
    
        @Transactional
        public void saveUser(){
    
            User user = new User();
            user.setAge(22);
            user.setName("mask");
            logger.info("save the user{}",user);
            userRepository.save(user);
           // throw new RuntimeException("exception");
        }
     public void saveUserBack(){
            saveUser();
        }
    }
    

    另外也可以把注解加到类上来解决。

    3.4注意事项

    • @Transactional注解不支持多数据源的情况
    • 如果存在多个数据源且未指定具体的事务管理器,那么实际上启用的事务管理器是最先在配置文件中指定的(即先加载的)
    •  Spring使用声明式事务处理,默认情况下,如果被注解的数据库操作方法中发生了unchecked异常,所有的数据库操作将rollback;如果发生的异常是checked异常,默认情况下数据库操作还是会提交的。

    4.spring事务回滚规则

    指示spring事务管理器回滚一个事务的推荐方法是在当前事务的上下文内抛出异常。spring事务管理器会捕捉任何未处理的异常,然后依据规则决定是否回滚抛出异常的事务。

    默认配置下,spring只有在抛出的异常为运行时unchecked异常时才回滚该事务,也就是抛出的异常为RuntimeException的子类(Errors也会导致事务回滚),而抛出checked异常则不会导致事务回滚。
    可以明确的配置在抛出那些异常时回滚事务,包括checked异常。也可以明确定义那些异常抛出时不回滚事务。

    还可以编程性的通过setRollbackOnly()方法来指示一个事务必须回滚,在调用完setRollbackOnly()后你所能执行的唯一操作就是回滚。

    4. 总结

    @Transactional用起来是方便,但是我们需要明白它背后的原理,避免入坑。另外@Transactional不建议用在处理时间过长的事务。因为,它会一直持有数据库线程池的连接,造成不能及时返回。就是尽量是的事务的处理时间短。

    参考文章:

    Spring @Transactional的使用及原理
    深入理解 Spring 事务原理
    透彻的掌握 Spring 中@transactional 的使用
    在同一个类中,一个方法调用另外一个有注解(比如@Async,@Transational)的方法,注解失效的原因和解决方法

    https://www.cnblogs.com/yepei/p/4716112.html


    作者:maskwang520
    链接:https://www.jianshu.com/p/5687e2a38fbc
    來源:简书

    @Transactional 详解

    @Transactional 是声明式事务管理 编程中使用的注解

    1 .添加位置

    1)接口实现类或接口实现方法上,而不是接口类中。
    2)访问权限:public 的方法才起作用。@Transactional 注解应该只被应用到 public 方法上,这是由 Spring AOP 的本质决定的。
    系统设计:将标签放置在需要进行事务管理的方法上,而不是放在所有接口实现类上:只读的接口就不需要事务管理,由于配置了@Transactional就需要AOP拦截及事务的处理,可能影响系统性能。

    3)错误使用:

    1.接口中A、B两个方法,A无@Transactional标签,B有,上层通过A间接调用B,此时事务不生效。
     
    2.接口中异常(运行时异常)被捕获而没有被抛出。
      默认配置下,spring 只有在抛出的异常为运行时 unchecked 异常时才回滚该事务,
      也就是抛出的异常为RuntimeException 的子类(Errors也会导致事务回滚),
      而抛出 checked 异常则不会导致事务回滚 。可通过 @Transactional rollbackFor进行配置。
     
    3.多线程下事务管理因为线程不属于 spring 托管,故线程不能够默认使用 spring 的事务,
      也不能获取spring 注入的 bean 。
      在被 spring 声明式事务管理的方法内开启多线程,多线程内的方法不被事务控制。
      一个使用了@Transactional 的方法,如果方法内包含多线程的使用,方法内部出现异常,
      不会回滚线程中调用方法的事务。
     


    2.声明式事务管理实现方式:
    基于 tx 和 aop 名字空间的 xml 配置文件

    // 基本配置
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
           xmlns:mvc="http://www.springframework.org/schema/mvc" xmlns:aop="http://www.springframework.org/schema/aop"
           xmlns:task="http://www.springframework.org/schema/task" xmlns:jms="http://www.springframework.org/schema/jms"
           xmlns:tx="http://www.springframework.org/schema/tx"
           xsi:schemaLocation="http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd
                              http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd
                              http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
                              http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
                              http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.1.xsd
                              http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
                              http://www.springframework.org/schema/jms http://www.springframework.org/schema/jms/spring-jms-4.1.xsd">
    <bean name="transactionManager"
            class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="shardingDataSource"></property>
        </bean>
    <tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true" />
    // MyBatis 自动参与到 spring 事务管理中,无需额外配置,
    只要 org.mybatis.spring.SqlSessionFactoryBean 引用的数据源与
    DataSourceTransactionManager 引用的数据源一致即可,否则事务管理会不起作用。
    // <annotation-driven> 标签的声明,
    是在 Spring 内部启用 @Transactional 来进行事务管理,使用 @Transactional 前需要配置。
    1.  


    3. @Transactional注解
    @Transactional 实质是使用了 JDBC 的事务来进行事务控制的
    @Transactional 基于 Spring 的动态代理的机制

    @Transactional 实现原理:
     
    1) 事务开始时,通过AOP机制,生成一个代理connection对象,
       并将其放入 DataSource 实例的某个与 DataSourceTransactionManager 相关的某处容器中。
       在接下来的整个事务中,客户代码都应该使用该 connection 连接数据库,
       执行所有数据库命令。
       [不使用该 connection 连接数据库执行的数据库命令,在本事务回滚的时候得不到回滚]
      (物理连接 connection 逻辑上新建一个会话session;
       DataSource 与 TransactionManager 配置相同的数据源)
     
    2) 事务结束时,回滚在第1步骤中得到的代理 connection 对象上执行的数据库命令,
       然后关闭该代理 connection 对象。
      (事务结束后,回滚操作不会对已执行完毕的SQL操作命令起作用)

    4.声明式事务的管理实现本质:
    事务的两种开启方式:
          显示开启 start transaction | begin,通过 commit | rollback 结束事务
          关闭数据库中自动提交 autocommit set autocommit = 0;MySQL 默认开启自动提交;通过手动提交或执行回滚操作来结束事务


    Spring 关闭数据库中自动提交:在方法执行前关闭自动提交,方法执行完毕后再开启自动提交

     // org.springframework.jdbc.datasource.DataSourceTransactionManager.java 源码实现
     // switch to manual commit if necessary. this is very expensive in some jdbc drivers,
     // so we don't want to do it unnecessarily (for example if we've explicitly
     // configured the connection pool to set it already).
     if (con.getautocommit()) {
         txobject.setmustrestoreautocommit(true);
         if (logger.isdebugenabled()) {
             logger.debug("switching jdbc connection [" + con + "] to manual commit");
         }
         con.setautocommit(false);
     }
     
    1.  

    问题:

    关闭自动提交后,若事务一直未完成,即未手动执行 commit 或 rollback 时如何处理已经执行过的SQL操作?

    C3P0 默认的策略是回滚任何未提交的事务
    C3P0 是一个开源的JDBC连接池,它实现了数据源和 JNDI 绑定,支持 JDBC3 规范和 JDBC2 的标准扩展。目前使用它的开源项目有 Hibernate,Spring等
    JNDI(Java Naming and Directory Interface,Java命名和目录接口)是SUN公司提供的一种标准的Java命名系统接口,JNDI提供统一的客户端API,通过不同的访问提供者接口JNDI服务供应接口(SPI)的实现,由管理者将JNDI API映射为特定的命名服务和目录系统,使得Java应用程序可以和这些命名服务和目录服务之间进行交互

    -------------------------------------------------------------------------------------------------------------------------------
    5. spring 事务特性
    spring 所有的事务管理策略类都继承自 org.springframework.transaction.PlatformTransactionManager 接口

    public interface PlatformTransactionManager {
      TransactionStatus getTransaction(TransactionDefinition definition)
      throws TransactionException;
      void commit(TransactionStatus status) throws TransactionException;
      void rollback(TransactionStatus status) throws TransactionException;
    }

    事务的隔离级别:是指若干个并发的事务之间的隔离程度

    1. @Transactional(isolation = Isolation.READ_UNCOMMITTED):读取未提交数据(会出现脏读,
     不可重复读) 基本不使用
     
    2. @Transactional(isolation = Isolation.READ_COMMITTED):读取已提交数据(会出现不可重复读和幻读)
     
    3. @Transactional(isolation = Isolation.REPEATABLE_READ):可重复读(会出现幻读)
     
    4. @Transactional(isolation = Isolation.SERIALIZABLE):串行化

    事务传播行为:如果在开始当前事务之前,一个事务上下文已经存在,此时有若干选项可以指定一个事务性方法的执行行为

    1. TransactionDefinition.PROPAGATION_REQUIRED:
       如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值。
     
    2. TransactionDefinition.PROPAGATION_REQUIRES_NEW:
       创建一个新的事务,如果当前存在事务,则把当前事务挂起。
     
    3. TransactionDefinition.PROPAGATION_SUPPORTS:
       如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
     
    4. TransactionDefinition.PROPAGATION_NOT_SUPPORTED:
       以非事务方式运行,如果当前存在事务,则把当前事务挂起。
     
    5. TransactionDefinition.PROPAGATION_NEVER:
       以非事务方式运行,如果当前存在事务,则抛出异常。
     
    6. TransactionDefinition.PROPAGATION_MANDATORY:
       如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。
     
    7. TransactionDefinition.PROPAGATION_NESTED:
       如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;
       如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。



    上表字段说明:

    1. value :主要用来指定不同的事务管理器;
       主要用来满足在同一个系统中,存在不同的事务管理器。
       比如在Spring中,声明了两种事务管理器txManager1, txManager2.然后,
       用户可以根据这个参数来根据需要指定特定的txManager.
     
    2. value 适用场景:在一个系统中,需要访问多个数据源或者多个数据库,
       则必然会配置多个事务管理器的
     
    3. REQUIRED_NEW:内部的事务独立运行,在各自的作用域中,可以独立的回滚或者提交;
       而外部的事务将不受内部事务的回滚状态影响。
     
    4. ESTED 的事务,基于单一的事务来管理,提供了多个保存点。
       这种多个保存点的机制允许内部事务的变更触发外部事务的回滚。
       而外部事务在混滚之后,仍能继续进行事务处理,即使部分操作已经被混滚。 
       由于这个设置基于 JDBC 的保存点,所以只能工作在 JDB C的机制。
     
    5. rollbackFor:让受检查异常回滚;即让本来不应该回滚的进行回滚操作。
     
    6. noRollbackFor:忽略非检查异常;即让本来应该回滚的不进行回滚操作。
     

    6.其他:

    1. 事务方法的嵌套调用会产生事务传播。
    2. spring 的事务管理是线程安全的
    3. 父类的声明的 @Transactional 会对子类的所有方法进行事务增强;
       子类覆盖重写父类方式可覆盖其 @Transactional 中的声明配置。
     
    4. 类名上方使用 @Transactional,类中方法可通过属性配置来覆盖类上的 @Transactional 配置;
       比如:类上配置全局是可读写,可在某个方法上改为只读。

     

    如果不对运行时异常进行处理,那么出现运行时异常之后,要么是线程中止,要么是主程序终止。 
    如果不想终止,则必须捕获所有的运行时异常,决不让这个处理线程退出。队列里面出现异常数据了,正常的处理应该是把异常数据舍弃,然后记录日志。不应该由于异常数据而影响下面对正常数据的处理。


    非运行时异常是RuntimeException以外的异常,类型上都属于Exception类及其子类。如IOException、SQLException等以及用户自定义的Exception异常。对于这种异常,JAVA编译器强制要求我们必需对出现的这些异常进行catch并处理,否则程序就不能编译通过。所以,面对这种异常不管我们是否愿意,只能自己去写一大堆catch块去处理可能的异常。


    --------------------- 

    转自:https://blog.csdn.net/mingyundezuoan/article/details/79017659 

    https://www.cnblogs.com/clwydjgs/p/9317849.html

     
     
     
  • 相关阅读:
    CSS禁止换行
    oracle时间转换:12小时24小时制
    三层架构各层次的职责
    Oracle截取字符串和查找字符串
    "......"的类型初始值设定项引发异常
    Oracle中对现有表增加列
    CSS 设置table 样式
    Aspose.Cells 根据Excel模板导出数据统计
    利用正则表达式限制网页表单里的文本框输入内容
    Table 边框 css设置
  • 原文地址:https://www.cnblogs.com/xuwc/p/13986565.html
Copyright © 2020-2023  润新知