@Transactional及Spring事务传播[零零散散的,那么系统整理下]

Updated on with 0 views and 0 comments

简单的梳理下何为MySql事务
MySQL 事务主要用于处理操作量大,复杂度高的数据。比如说,在人员管理系统中,你删除一个人员,你既需要删除人员的基本资料,也要删除和该人员相关的信息,如信箱,文章等等,这样,这些数据库操作语句就构成一个事务!

  • 在 MySQL 中只有使用了 Innodb 数据库引擎的数据库或表才支持事务。
  • 事务处理可以用来维护数据库的完整性,保证成批的 SQL 语句要么全部执行,要么全部不执行。
  • 事务用来管理 insert,update,delete 语句

一般来说,事务是必须满足4个条件(ACID)::原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。

  • 原子性: 一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

  • 一致性: 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。

  • 隔离性: 数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。

  • 持久性: 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

在 MySQL 命令行的默认设置下,事务都是自动提交的,即执行 SQL 语句后就会马上执行 COMMIT 操作。因此要显式地开启一个事务务须使用命令 BEGIN 或 START TRANSACTION,或者执行命令 SET AUTOCOMMIT=0,用来禁止使用当前会话的自动提交。

MYSQL 事务处理主要有两种方法:

1、用 BEGIN, ROLLBACK, COMMIT来实现

  • BEGIN 开始一个事务
  • ROLLBACK 事务回滚
  • COMMIT 事务确认

2、直接用 SET 来改变 MySQL 的自动提交模式:

  • SET AUTOCOMMIT=0 禁止自动提交
  • SET AUTOCOMMIT=1 开启自动提交

@Transactional

先看下该注解各属性大意

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
//可继承
@Inherited
@Documented
public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";
    @AliasFor("value")
    String transactionManager() default "";
        /**
         * 该属性用于设置事务的传播行为。
         * 例如:@Transactional(propagation=Propagation.NOT_SUPPORTED,readOnly=true)
         */
    Propagation propagation() default Propagation.REQUIRED;
        /**
         * 该属性用于设置底层数据库的事务隔离级别,
         * 事务隔离级别用于处理多事务并发的情况,
         * 通常使用数据库的默认隔离级别即可,基本不需要进行设置
         */
    Isolation isolation() default Isolation.DEFAULT;
  //该属性用于设置事务的超时秒数,默认值为-1表示永不超时
    int timeout() default -1;
   //该属性用于设置当前事务是否为只读事务,设置为true表示只读,false则表示可读写,默认值为false
    boolean readOnly() default false;

    Class<? extends Throwable>[] rollbackFor() default {};
        /**
         * 该属性用于设置需要进行回滚的异常类名称数组,
         * 当方法中抛出指定异常名称数组中的异常时,
         * 则进行事务回滚
         * 例如:@Transactional(rollbackForClassName={"RuntimeException","Exception"})。
         */
    String[] rollbackForClassName() default {};
        /**
         * 该属性用于设置不需要进行回滚的异常类数组,
         * 当方法中抛出指定异常数组中的异常时,不进行事务回滚
         */
    Class<? extends Throwable>[] noRollbackFor() default {};
        /**
         * 该属性用于设置不需要进行回滚的异常类名称数组,
         * 当方法中抛出指定异常名称数组中的异常时,
         * 不进行事务回滚。
         * 例如:@Transactional(noRollbackForClassName={"RuntimeException","Exception"})
         */
    String[] noRollbackForClassName() default {};
}

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

主要看下Propagation(Spring事务传播行为)与 Isolation(对应数据库事务隔离机制),它们都是springframework.transaction.annotation下的枚举类。

Propagation代码如下:

package org.springframework.transaction.annotation;

public enum Propagation {
    /**
     * 当前的方法必须运行在事务中,
     * 如果没有事务就新建一个事务。
     * 新事务和方法一起开始,
     * 随着方法返回或者抛出异常时终止。(默认)
     */
    REQUIRED(0),
    /**
     * 规定当前方法支持当前事务,
     * 但是如果没有事务在运行就使用非事务方法执行。 
     */
    SUPPORTS(1),
//方法必须在事务中执行,否则抛出异常
    MANDATORY(2),
    /**
     * 不管是否存在事务,都创建一个新的事务,
     * 原来的挂起,新的执行完毕,继续执行老的事务
     */
    REQUIRES_NEW(3),
//定义为当前事务不支持的方法,在该方法执行期间正在运行的事务会被暂停
    NOT_SUPPORTED(4),
//当前方法永远不在事务中运行,否则抛出异常
    NEVER(5),
//使方法运行在嵌套事务中,如果不是在嵌套事务中就和PROPAGATION_REQUIRED效果一样。
    NESTED(6);

    private final int value;

    private Propagation(int value) {
        this.value = value;
    }

    public int value() {
        return this.value;
    }
}

Isolation代码如下:

package org.springframework.transaction.annotation;

public enum Isolation {
//使用数据库默认隔离级别(默认,注Mysql innodb默认隔离级别RR)
    DEFAULT(-1),
    READ_UNCOMMITTED(1),
    READ_COMMITTED(2),
    REPEATABLE_READ(4),
    SERIALIZABLE(8);

    private final int value;

    private Isolation(int value) {
        this.value = value;
    }

    public int value() {
        return this.value;
    }
}

点击查看关于数据库隔离级别相关部分介绍

Transactional失效场景

默认情况对非运行时异常不进行回滚操作

  1. Transactional注解标注方法修饰符为非public时,@Transactional注解将会不起作用。
  2. 在类内部调用调用类内部@Transactional标注的方法(this.transactionalMethod())。这种情况下也会导致事务不开启。
  3. 事务方法内部捕捉了异常,没有抛出新的异常,导致事务操作不会进行回滚。
  4. 数据库引擎是否支持事务,MySql的引擎MyIsam不支持事务,如需事务控制生效,则库和表的引擎必须是InnoDB
  5. @Transaction注解所在的类是否被加载成Bean,需要在运行时为类生成代理对象。那么前提是这个类一定被Spring管理并加载成了一个Bean对象
  6. 应确保被调用方法中使用的数据源都加载了事务管理器,在SpringBoot项目中,如果是单数据源,那么系统会默认为单数据源配置事务管理器DataSourceTransactionManager。如果是多数据源,则一定确保所有的数据源都配置了事务管理器。

失效1 应是底层实现必须pulbic修饰,这点是规则
失效2 注解@Transaction的底层实现是Spring AOP技术,而Spring AOP技术使用的是动态代理而自己调用自己的过程,并不存在代理对象的调用,这样就不会产生AOP去为我们设置@Transactional配置的参数,这样就出现了自调用注解失效的问题
失效3 这里并不是事务失效,只是异常被catch到了未抛出,方法正常执行完毕,事务正常提交

附上一张图
image.png


标题:@Transactional及Spring事务传播[零零散散的,那么系统整理下]
作者:liqitian3344
地址:https://liqitian.com/articles/2020/01/21/1579585904105.html