SpringBoot中事务@Transactional的使用

  • A+

Spring框架提供了便捷的事务管理。不会对业务调用造成任何干扰。不需要手动开启,提交事务,只需要在类或者方法上进行少量的注解就可以自动完成这些操作。本篇主要介绍了@Transactional注解的使用。

一.概念

1.事务介绍

  • 事务指逻辑上的一组操作,组成这组操作的各个单元,要不全部成功,要不全部失败。

2.事务的特点

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

二.Spring中的事务

1.事务管理器

  • 事务管理器接口统一定义了不同持久框架的事务行为,例如(Jpa,Hibernate等)如下
    public interface PlatformTransactionManager extends TransactionManager {
        //根据事务配置获取事务状态
        TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
        //提交事务
        void commit(TransactionStatus var1) throws TransactionException;
        //回滚事务
        void rollback(TransactionStatus var1) throws TransactionException;
    }
    

2.事务属性定义

  • TransactionDefinition类定义了事务的所有行为,我们在@Transactional注解中使用的属性实际就是配置这些值
    public interface TransactionDefinition {
        int PROPAGATION_REQUIRED = 0;
        int PROPAGATION_SUPPORTS = 1;
        int PROPAGATION_MANDATORY = 2;
        int PROPAGATION_REQUIRES_NEW = 3;
        int PROPAGATION_NOT_SUPPORTED = 4;
        int PROPAGATION_NEVER = 5;
        int PROPAGATION_NESTED = 6;
        int ISOLATION_DEFAULT = -1;
        int ISOLATION_READ_UNCOMMITTED = 1;
        int ISOLATION_READ_COMMITTED = 2;
        int ISOLATION_REPEATABLE_READ = 4;
        int ISOLATION_SERIALIZABLE = 8;
        int TIMEOUT_DEFAULT = -1;
        //获取事务传播行为
        default int getPropagationBehavior() {
            return 0;
        }
        //获取事务的隔离级别
        default int getIsolationLevel() {
            return -1;
        }
        //获取事务的过期时间
        default int getTimeout() {
            return -1;
        }
        //是否为只读事务
        default boolean isReadOnly() {
            return false;
        }
        //获取事务名称
        @Nullable
        default String getName() {
            return null;
        }
        //返回默认的事务实例
        static TransactionDefinition withDefaults() {
            return StaticTransactionDefinition.INSTANCE;
        }
    }
    

3.事务的传播行为

  • 当多个事务相互调用的时候,我们需要指出事务的传播行为。(例如:方法A调用了插入方法,方法B调用了修改方法,我们可以定义B方法继续在A方法的事务中运行,也可以定义B方法使用一个新的事务运行)Spring定义了七种传播行为:
    传播行为 含义
    TransactionDefinition.PROPAGATION_REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务,则加入到这个事务中。这是最常见的选择。
    TransactionDefinition.PROPAGATION_SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
    TransactionDefinition.PROPAGATION_MANDATORY 表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常。
    TransactionDefinition.PROPAGATION_REQUIRED_NEW 表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。
    TransactionDefinition.PROPAGATION_NOT_SUPPORTED 表示该方法不应该运行在事务中。如果当前存在事务,就把当前事务挂起。
    TransactionDefinition.PROPAGATION_NEVER 表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常。
    TransactionDefinition.PROPAGATION_NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。

4.事务的隔离级别

  • 在数据处理中,一次业务调用可能需要操作表中的相同数据,就会出现脏读不可重复读幻读问题,而事务隔离就是用来解决此类问题:
    1.脏读(Dirty reads)指一个事务读取了另外一个事务未提交的数据。 例如:事务A读取了事务B更新的数据,然后B回滚操作,那么A读取到的数据是脏数据。
    2.不可重复读(Nonrepeatable read) 事务 A 多次读取同一数据,事务 B 在事务A多次读取的过程中,对数据作了更新并提交,导致事务A多次读取同一数据时,结果不一致。
    3.幻读(Phantom read)一个事务的两次不同时间的相同查询返回了不同的的结果集。例如:一个 select 语句执行了两次,但是在第二次返回了第一次没有返回的行,那么这些行就是“phantom” row。
  • 不可重复读的和幻读很容易混淆,不可重复读侧重于修改,幻读侧重于新增或删除。解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表
  • Spring中定义了五种隔离规则,来解决上述问题:
    隔离级别 含义 脏读 不可重复读 幻读
    TransactionDefinition.ISOLATION_DEFAULT 使用后端数据库默认的隔离级别
    TransactionDefinition.ISOLATION_READ_UNCOMMITTED 允许读取尚未提交的数据变更(最低的隔离级别)
    TransactionDefinition.ISOLATION_READ_COMMITTED 允许读取并发事务已经提交的数据
    TransactionDefinition.ISOLATION_REPEATABLE_READ 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改
    TransactionDefinition.ISOLATION_SERIALIZABLE 最高的隔离级别,完全服从ACID的隔离级别,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的
  • 解释
    1. 事务隔离级别为ISOLATION_READ_UNCOMMITTED时,写数据只会锁住相应的行。
    2. 事务隔离级别为可ISOLATION_REPEATABLE_READ时,如果检索条件有索引(包括主键索引)的时候,默认加锁方式是next-key锁;如果检索条件没有索引,更新数据时会锁住整张表。一个间隙被事务加了锁,其他事务是不能在这个间隙插入记录的,这样可以防止幻读。
    3. 事务隔离级别为ISOLATION_SERIALIZABLE时,读写数据都会锁住整张表。此隔离规则类型在开发中很少用到。因为如果A,B两个事务操作同一个数据表。A先执行。A事务这个时候会把表给锁住,B事务执行的时候会直接报错。

5.是否只读

  • 设置事务为只读事务,标识此事务对数据库的操作为查询,不会修改和删除数据。此时数据库引擎就会优化事务,提升查询效率。

6.事务超时

  • 一个事务执行时间太长会影响数据库资源师傅,所以我们可以设置一个超时时间,当超过执行时间,方法执行失败事务回滚。

7.事务回滚

  • 事务回滚定义了事务在遇到异常时是否回滚,当然我们可以显示的定义事务在出现指定异常时回滚,也可以显示定义出现指定异常不回滚,

三.Spring中@Transactional的介绍

1.引入事务管理器

  • 在spring-boot中我们引入对应的持久化框架,其中的starter包含了对应的事务管理器,例如:引入jpa依赖,就引入了核心包中的spring-tx,来管理事务:
        <dependencies>
            <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
         </dependency>
        </dependencies>
    

2.使用@EnableTransactionManagement启用事务

  • 在spring-boot中启用事务
    @SpringBootApplication
    @EnableTransactionManagement
    public class SpringBootTransactionalApplication {
        public static void main(String[] args) {
            SpringApplication.run(SpringBootTransactionalApplication.class, args);
        }
    
    }
    

3.@Transactional注解介绍

  • @Transactional注解介绍
    public @interface Transactional {
        //多个事务管理器的情况下指定使用的事务管理器,value/transactionManager属性都可以
        @AliasFor("transactionManager")
        String value() default "";
        //多个事务管理器的情况下指定使用的事务管理器,value/transactionManager属性都可以
        @AliasFor("value")
        String transactionManager() default "";
        //定义事务传播行为
        Propagation propagation() default Propagation.REQUIRED;
        //事务隔离规则
        Isolation isolation() default Isolation.DEFAULT;
        //事务超时时间
        int timeout() default -1;
        //是否只读事务
        boolean readOnly() default false;
        //以class的方式指定会触发回滚的异常类
        Class<? extends Throwable>[] rollbackFor() default {};
        //以类名的方式指定会触发回滚的异常类
        String[] rollbackForClassName() default {};
        //以class的方式指定不会触发回滚的异常类
        Class<? extends Throwable>[] noRollbackFor() default {};
        //以类名的方式指定不会触发回滚的异常类
        String[] noRollbackForClassName() default {};
    }
    

4.value/transactionManager属性

  • value/transactionManager属性,在使用多数据源或者其他持久性框架时,配置了多个事务管理器,在添加@Transactional注解时指定具体的事务管理器。在配置多个事务管理器是使用qualifier属性指定不同的值,value/transactionManager属性中使用qualifier指定的值。
    <tx:annotation-driven/>
    
        <bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            ...
            <qualifier value="order"/>
        </bean>
    
        <bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            ...
            <qualifier value="account"/>
        </bean>
    
    public class TransactionalService {
    
        @Transactional("order")
        public void setSomething(String name) { ... }
    
        @Transactional("account")
        public void doSomething() { ... }
    }
    
  • 也可以使用如下方式灵活处理多事务源,采用自定义注解的方式
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Transactional("order")
    public @interface OrderTx {
    }
    
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Transactional("account")
    public @interface AccountTx {
    }
    
    public class TransactionalService {
    
        @OrderTx
        public void setSomething(String name) {
            // ...
        }
    
        @AccountTx
        public void doSomething() {
            // ...
        }
    }
    

5.propagation属性

  • propagation属性引用了Propagation枚举类,其中定义了所支持的传播行为,默认为REQUIRED
  • 传播行为的定义
    传播行为 含义
    REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务,则加入到这个事务中。这是最常见的选择。
    SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行。
    MANDATORY 表示该方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常。
    REQUIRED_NEW 表示当前方法必须运行在它自己的事务中。一个新的事务将被启动。如果存在当前事务,在该方法执行期间,当前事务会被挂起。
    NOT_SUPPORTED 表示该方法不应该运行在事务中。如果当前存在事务,就把当前事务挂起。
    NEVER 表示当前方法不应该运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常。
    NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与REQUIRED类似的操作。

6. isolation属性

  • isolation用于指定事务的隔离规则,默认值为DEFAULT
  • 事务隔离规则
    隔离级别 含义 脏读 不可重复读 幻读
    DEFAULT 使用后端数据库默认的隔离级别
    READ_UNCOMMITTED 允许读取尚未提交的数据变更(最低的隔离级别)
    READ_COMMITTED 允许读取并发事务已经提交的数据
    REPEATABLE_READ 对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改
    SERIALIZABLE 最高的隔离级别,完全服从ACID的隔离级别,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的

7. 其余重要属性

  • timeout用于设置事务的超时属性。
  • readOnly用于设置事务是否只读属性。
  • rollbackFor、rollbackForClassName用于设置那些异常需要回滚;noRollbackFor、noRollbackForClassName用于设置那些异常不需要回滚。

8.@Transactional使用的注意事项

  • @Transactional实现依赖于spring的AOP,所有类内部的方法调用不会触发事务
  • @Transactional注解可以添加到类,接口,注解,方法上。添加到类上,则此类下所有的public声明的方法都会拥有相同的事务行为。在使用时,我们一般添加到方法上。
  • 若方法为只读,也需要事务,保证了隔离级别的正常支持,readOnly属性也可以对查询进行优化。
  • @Transactional 注解必须添加在public方法上,private、protected方法上是无效的

五.总结

  • SpringBoot中使用事务非常的简便,首先在启动类配置@EnableTransactionManagement,其次在需要事务管理的方法上添加@Transactional注解。
  • @Transactional注解只对public方法生效。
  • 因为AOP的特性,只有外部方法调用,@Transactional才会生效。
  • 了解@Transactional注解的传播范围,避免出现嵌套异常
  • github范例,点击获取
  • 参考:
zhangfeng

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: