美文网首页Java架构技术进阶
干货!Spring事物传播特性隔离级别ACID及StringBo

干货!Spring事物传播特性隔离级别ACID及StringBo

作者: 今天你敲代码了吗 | 来源:发表于2020-08-08 14:53 被阅读0次

    前言

    今天来详细说一下Spring事物传播特性隔离级别ACID及StringBoot编程式事

    一致的事物模型

    • Spring的统一事务模型,解决的一个核心问题,就是不管你用的是什么数据访问方式,Hibernate、MyBatis、JDBC,你的Service层的代码都是一样的,不需要做任何变动。
    • 无论使用JTA或DataSource事务Spring都能很好的统一在一起。

    事物抽象核心接口

    PlatformTransactionManager 事务管理器

    • 包路径 org.springframework.transaction.PlatformTransactionManager
        /**
         * 根据指定的传播行为,返回当前活动的事务或创建新的事务。
         * @param definition 传播行为
         * @return TransactionStatus 事务状态
         * @throws TransactionException 所有事务异常的超类
         */
        TransactionStatus getTransaction(@Nullable TransactionDefinition definition) throws TransactionException;
        /**
         * 提交事物
         * @param status 事务状态
         * @throws TransactionException 所有事务异常的超类
         */
        void commit(TransactionStatus status) throws TransactionException;
        /**
         * 事物回滚
         * @param status 事务状态
         * @throws TransactionException 所有事务异常的超类
         */
        void rollback(TransactionStatus status) throws TransactionException;
    
    

    TransactionDefinition 事务的一些基础信息,如超时时间、隔离级别、传播属性等

    public interface TransactionDefinition {
    
        /**
         * (1)事物传播特性:
         * 支持当前事务;如果不存在,则创建新事务。
         * 一般情况下是事物默认的设置
         */
        int PROPAGATION_REQUIRED = 0;
    
        /**
         * (2)事物传播特性:
         * 支持当前事务;如果不存在事务,则以非事务方式执行。
         * 它定义了一个可以应用于同步的事务作用域。因此,相同的资源下只有一个
         * 使用 {PROPAGATION_SUPPORTS}时要小心尤其是不依赖{PROPAGATION_REQUIRED}或{PROPAGATION_REQUIRES_NEW}
         * 在{PROPAGATION_SUPPORTS}范围内(这可能导致运行时发生同步冲突)
         * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setTransactionSynchronization
         * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#SYNCHRONIZATION_ON_ACTUAL_TRANSACTION
         */
        int PROPAGATION_SUPPORTS = 1;
    
        /**
         * (3)事物传播特性:
         * 支持当前事务;如果不存在当前事务,则抛出异常
         * PROPAGATION_MANDATORY 范围内的事务同步始终由周围的事务驱动。
         */
        int PROPAGATION_MANDATORY = 2;
    
        /**
         * (4)事物传播特性:
         * 创建新事务,如果当前事务存在,则挂起当前事务。
         * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
         */
        int PROPAGATION_REQUIRES_NEW = 3;
    
        /**
         * (5)事物传播特性:
         * 创建新事务,如果当前事务存在,则挂起当前事务。
         * 实际的事务暂停不会在所有事务管理器上开箱即用。
         * 这尤其适用于{@link org.springframework.transaction.jta.JtaTransactionManager},
         * 它要求{@code javax.transaction.TransactionManager} 对其可用
         * 请注意,在{@code PROPAGATION_NOT_SUPPORTED}范围内不能进行事务同步。现有同步将被暂停并适当恢复。
         * @see org.springframework.transaction.jta.JtaTransactionManager#setTransactionManager
         */
        int PROPAGATION_NOT_SUPPORTED = 4;
    
        /**
         * (6)事物传播特性:
         * 不支持当前事务;如果当前事务存在,则引发异常。
         * 事务同步在{@code PROPAGATION_NEVER}范围内是不可用的。
         */
        int PROPAGATION_NEVER = 5;
    
        /**
         * (7)事物传播特性:
         * 如果当前事务存在,则在嵌套事务中执行,否则的行为类似于{@link #PROPAGATION_REQUIRED}。
         * 实际创建嵌套事务只适用于特定的事务管理器。
         * @see org.springframework.jdbc.datasource.DataSourceTransactionManager
         */
        int PROPAGATION_NESTED = 6;
    
        /**
         * 隔离级别越高,越能保证数据的完整性和一致性,但是对并发性能的影响也越大。
         * 对于多数应用程序,可以优先考虑把数据库系统的隔离级别设为Read Committed。
         * 它能够避免脏读取,而且具有较好的并发性能。尽管它会导致不可重复读、幻读和第二类丢失更新这些并发问题,
         * 在可能出现这类问题的个别场合,可以由应用程序采用悲观锁或乐观锁来控制。
         *
         * Spring中同时提供一个标识:ISOLATION_DEFAULT。表示使用后端数据库默认的隔离级别。
         * 大多数数据库默认的事务隔离级别是Read committed,比如Sql Server , Oracle。
         * MySQL的默认隔离级别是Repeatable read。
         */
        /**
         * 事务隔离级别
         * 使用底层数据存储的默认隔离级别。
         * 所有其他级别都对应于JDBC隔离级别。
         * @see java.sql.Connection
         */
        int ISOLATION_DEFAULT = -1;
    
        /**
         * 事务隔离级别
         * 未提交读取(Read Uncommitted)
         * 允许脏读取,但不允许更新丢失。如果一个事务已经开始写数据,则另外一个事务则不允许同时进行写操作,
         * 但允许其他事务读此行数据。该隔离级别可以通过“排他写锁”实现。
         * @see java.sql.Connection#TRANSACTION_READ_UNCOMMITTED
         */
        int ISOLATION_READ_UNCOMMITTED = Connection.TRANSACTION_READ_UNCOMMITTED;
    
        /**
         * 事务隔离级别
         * 已提交读取(Read Committed)
         * 允许不可重复读取,但不允许脏读取。这可以通过“瞬间共享读锁”和“排他写锁”实现。
         * 读取数据的事务允许其他事务继续访问该行数据,但是未提交的写事务将会禁止其他事务访问该行。
         * @see java.sql.Connection#TRANSACTION_READ_COMMITTED
         */
        int ISOLATION_READ_COMMITTED = Connection.TRANSACTION_READ_COMMITTED;
    
        /**
         * 事务隔离级别
         * 可重复读取(Repeatable Read)
         * 禁止不可重复读取和脏读取,但是有时可能出现幻读数据。这可以通过“共享读锁”和“排他写锁”实现。
         * 读取数据的事务将会禁止写事务(但允许读事务),写事务则禁止任何其他事务。
         * @see java.sql.Connection#TRANSACTION_REPEATABLE_READ
         */
        int ISOLATION_REPEATABLE_READ = Connection.TRANSACTION_REPEATABLE_READ;
    
        /**
         * 事务隔离级别
         * 序列化(Serializable) 提供严格的事务隔离。它要求事务序列化执行,事务只能一个接着一个地执行,不能并发执行。
         * 仅仅通过“行级锁”是无法实现事务序列化的,必须通过其他机制保证新插入的数据不会被刚执行查询操作的事务访问到。
         * @see java.sql.Connection#TRANSACTION_SERIALIZABLE
         */
        int ISOLATION_SERIALIZABLE = Connection.TRANSACTION_SERIALIZABLE;
        /**
         *使用基础事务系统的默认超时,如果不支持超时,则使用无。
         */
        int TIMEOUT_DEFAULT = -1;
    
        /**
         * 返回传播行为。 必须返回在{@link TransactionDefinition} 此接口
         * 上定义的{@code PROPAGATION_XXX}常量之一。
         * @return 传播行为
         * @see #PROPAGATION_REQUIRED
         * @see org.springframework.transaction.support.TransactionSynchronizationManager#isActualTransactionActive()
         */
            int getPropagationBehavior();
    
        /**
         * 返回隔离级别。
         * 必须返回在{@link TransactionDefinition }接口上定义的{@code ISOLATION_XXX}常量之一。
         * 这些常量被设计成与{@link java.sql.Connection}上相同常量的值相匹配.
         *
         * 专门设计用于{@link #PROPAGATION_REQUIRED}或{@link #PROPAGATION_REQUIRES_NEW},
         * 因为它只适用于新启动的事务。如果您希望在参与具有不同隔离级别的现有事务时拒绝隔离级别声明,
         * 请考虑将事务管理器上的“validateExistingTransactions”标志切换为“true”。
         *
         * 请注意,不支持自定义隔离级别的事务管理器在被赋予{@link #ISOLATION_DEFAULT}以外的任何其他级别时,都会引发异常。
         * @return 隔离级别
         * @see #ISOLATION_DEFAULT
         * @see org.springframework.transaction.support.AbstractPlatformTransactionManager#setValidateExistingTransaction
         */
        int getIsolationLevel();
    
        /**
         * 返回事务超时。
         * 必须返回秒数,或{@link #TIMEOUT_DEFAULT}。
         * 专为{@link #PROPAGATION_REQUIRED}或{@link #PROPAGATION_REQUIRES_NEW}使用而设计,因为它仅适用于新启动的事务
         * 请注意,不支持超时的事务管理器在给定{@link #TIMEOUT_DEFAULT}以外的任何其他超时时,将引发异常。
         * @return 事务超时
         */
        int getTimeout();
    
        /**
         * 返回是否作为只读事务进行优化。
         * 返回是否优化为只读事务。
         * 只读标志适用于任何事务上下文,无论是由实际资源事务支持
         * ({@link #PROPAGATION_REQUIRED} / {@link #PROPAGATION_REQUIRES_NEW})
         * 还是在资源级别以非事务方式操作({@link #PROPAGATION_SUPPORTS})。
         * 在后一种情况下,该标志仅适用于应用程序内的托管资源,例如Hibernate {@code Session}。
         * 这只是实际交易子系统的提示。 <i>不一定</ i>会导致写访问尝试失败。
         * 当请求只读事务时,无法解释只读提示的事务管理器将<i>不</ i>抛出异常。
         * @return  如果事务将被优化为只读,则{@code true}
         * @see org.springframework.transaction.support.TransactionSynchronization#beforeCommit(boolean)
         * @see org.springframework.transaction.support.TransactionSynchronizationManager#isCurrentTransactionReadOnly()
         */
        boolean isReadOnly();
    
        /**
         * 返回此事务的名称。可以是{@code null}。
         * 这将用作要在事务监视器中显示的事务名称(如WebLogic)。
         * 对于Spring的声明性事务,公开的名称将是{@code 完全限定类名+“.”+方法名}(默认情况下)。
         * @return 事务的名称
         * @see org.springframework.transaction.interceptor.TransactionAspectSupport
         * @see org.springframework.transaction.support.TransactionSynchronizationManager#getCurrentTransactionName()
         */
        @Nullable
        String getName();
    }
    
    

    TransactionStatus 事务的一些状态信息,如是否一个新的事务、是否已被标记为回滚

    public interface TransactionStatus extends SavepointManager, Flushable {
    
        /**
         * 返回当前事务是否为新事务;否则将参与现有事务,或者可能不会首先在实际事务中运行。
         */
        boolean isNewTransaction();
    
        /**
         * 返回此事务是否在内部携带保存点,即是否已基于保存点将其创建为嵌套事务。
         * 此方法主要是出于诊断目的,与{@link #isNewTransaction}一起使用。
         * 要以编程方式处理自定义保存点,请使用{@link SavepointManager}提供的操作
         * @see #isNewTransaction()
         * @see #createSavepoint()
         * @see #rollbackToSavepoint(Object)
         * @see #releaseSavepoint(Object)
         */
        boolean hasSavepoint();
    
        /**
         * 仅设置事务回滚。这将指示事务管理器事务的唯一可能结果可能是回滚,作为引发异常的替代方法,
         * 后者将反过来触发回滚。这主要用于{@link org.springframework.transaction.support.TransactionTemplate}
         * 或{@link org.springframework.transaction.interceptor.TransactionInterceptor},
         * 其中实际的提交/回滚决策由容器做出。
         * @see org.springframework.transaction.support.TransactionCallback#doInTransaction
         * @see org.springframework.transaction.interceptor.TransactionAttribute#rollbackOn
         */
        void setRollbackOnly();
    
        /**
         * 返回事务是否已被标记为仅回滚(通过应用程序或通过事务基础结构)
         */
        boolean isRollbackOnly();
    
        /**
         * 如果适用,将基础会话刷新到数据存储区:例如,所有受影响的Hibernate / JPA会话。
         * 实际上,这只是一个提示,如果基础事务管理器没有flush概念,则可能是不做任何事情。
         * 刷新信号可以应用于主资源或事务同步,具体取决于底层资源。
         */
        @Override
        void flush();
    
        /**
         * 返回此事务是否已完成,即是否已提交或回滚。
         * @see PlatformTransactionManager#commit
         * @see PlatformTransactionManager#rollback
         */
        boolean isCompleted();
    }
    
    

    事务的4个特点

    原子性( Atomicity)

    事务的所有操作要么全部成功,要么全部回滚。

    一致性( Consistency)

    总是从一个一致性的状态转换到另一个一致性的状态。

    隔离性( Isolation)

    多个事务并发执行时,一个事务的执行不应影响其他事务的执行

    持久性( Durability)

    已被提交的事务对数据库的修改应该永久保存在数据库中。

    如果在操作前(事务还没有提交)服务器宕机或者断电,那么重启数据库以后,数据状态应该为 张三:100,李四:100 如果在操作后(事务已经提交)服务器宕机或者断电,那么重启数据库以后,数据状态应该为 张三:0,李四:200

    什么是脏读、幻读、不可重复读

    脏读

    Read Uncommitted(读取未提交内容)在该隔离级别,所有事务都可以看到其他未提交事务的执行结果。本隔离级别很少用于实际应用,因为它的性能也不比其他级别好多少。读取未提交的数据,也被称之为脏读(Dirty Read)。

    在时间点5,事务A再次查询数据时,事务B并没有提交事务,但是,新的数据也被事务A查出来了。这就是脏读。

    不可重复读(虚读)

    Read Committed(读取提交内容)是大多数数据库系统的默认隔离级别(比如Sql Server , Oracle,但不是MySQL默认的,Mysql默认Repeatable read)。它满足了隔离的简单定义:一个事务只能看见已经提交事务所做的改变。这种隔离级别 也支持所谓的不可重复读(Nonrepeatable Read),因为同一事务的其他实例在该实例处理其间可能会有新的commit,所以同一select可能返回不同结果。

    时间点6之前,事务A中 3 5时间点两次查询结果是一致的。事务B提交事务以后,事务A中7时间点再次查询,查询到了新增的这条数据。在事务A中,多次查询的结果不一致,这就是“不可重复读”。

    幻读

    指一个线程中的事务读取到了另外一个线程中提交的insert的数据。

    隔离级别

    请查看源码中TransactionDefinition 注释

    编程式事务

    最简单的方式手动注入声明事物

        @Autowired
        private TransactionTemplate transactionTemplate;
    
    

    简单示例

        transactionTemplate.execute(new TransactionCallbackWithoutResult() {
            @Override
            protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                // 写sql
    
                // transactionStatus.setRollbackOnly();
            }
        });
    
    

    TransactionCallbackWithoutResult:无返回值使用 TransactionCallback:有返回值使用

    声明式事务

    概述

    Spring 的声明式事务管理在底层是建立在 AOP 的基础之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。

    声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过等价的基于标注的方式),便可以将事务规则应用到业务逻辑中。因为事务管理本身就是一个典型的横切逻辑,正是 AOP 的用武之地。Spring 开发团队也意识到了这一点,为声明式事务提供了简单而强大的支持。

    在开发中使用声明式事务,不仅因为其简单,更主要是因为这样使得纯业务代码不被污染,极大方便后期的代码维护。

    和编程式事务相比,声明式事务唯一不足地方是,后者的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。

    声明式事务管理也有两种常用的方式:

    • 基于和命名空间的xml配置文件;

    • 基于@Transactional注解。

    • Stringboot 需要在启动类中增加 @EnableTransactionManagement 可以配置如:@EnableTransactionManagement(mode = AdviceMode.PROXY,proxyTargetClass=true,order = Ordered.LOWEST_PRECEDENCE )

      - proxyTargetClass:是否是基于类的代理
      - mode:事务管理功能底层实现是用JDK动态代理还是AspectJ。一般默认使用java就可以
      - order:aop拦截顺序,默认最低优先级,这样的好处是自己做的aop拦截都可以在此之上
      
      

    @Transactional的属性

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

    示例

    1、在接口或类的声明处 ,写一个@Transactional. 要是只在接口上写, 接口的实现类就会继承下来、接口的实现类的具体方法,可以覆盖类声明处的设置 @Transactional //类级的注解、适用于类中所有的public的方法

    @Transactional
    public class TestServiceBean implements TestService {} 
    

    当类中某些方法不需要事物时方法上增加 @Transactional(propagation = Propagation.NOT_SUPPORTED)

    2、在方法上增加事物注解

    事物传播行为

    • @Transactional(propagation=Propagation.REQUIRED) 如果有事务, 那么加入事务, 没有的话新建一个(默认情况下)
    • @Transactional(propagation=Propagation.NOT_SUPPORTED) 容器不为这个方法开启事务
    • @Transactional(propagation=Propagation.REQUIRES_NEW) 不管是否存在事务,都创建一个新的事务,原来的挂起,新的执行完毕,继续执行老的事务
    • @Transactional(propagation=Propagation.MANDATORY) 必须在一个已有的事务中执行,否则抛出异常
    • @Transactional(propagation=Propagation.NEVER) 必须在一个没有的事务中执行,否则抛出异常(与Propagation.MANDATORY相反)
    • @Transactional(propagation=Propagation.SUPPORTS) 如果其他bean调用这个方法,在其他bean中声明事务,那就用事务.如果其他bean没有声明事务,那就不用事务.

    事物超时设置:

    • @Transactional(timeout=30) //默认是30秒

    事务隔离级别:

    • @Transactional(isolation = Isolation.READ_UNCOMMITTED) 读取未提交数据(会出现脏读, 不可重复读) 基本不使用
    • @Transactional(isolation = Isolation.READ_COMMITTED) 读取已提交数据(会出现不可重复读和幻读)
    • @Transactional(isolation = Isolation.REPEATABLE_READ) 可重复读(会出现幻读)
    • @Transactional(isolation = Isolation.SERIALIZABLE) 串行化
    大家看完有什么不懂的可以在下方留言讨论也可以关注.
    谢谢你的观看。
    觉得文章对你有帮助的话记得关注我点个赞支持一下!

    链接:https://juejin.im/post/6858101467665367053

    相关文章

      网友评论

        本文标题:干货!Spring事物传播特性隔离级别ACID及StringBo

        本文链接:https://www.haomeiwen.com/subject/fhdhdktx.html