• Spring


    一、初步理解

    理解事务之前,先讲一个你日常生活中最常干的事:取钱。

    比如你去ATM机取1000块钱,大体有两个步骤:首先输入密码金额,银行卡扣掉1000元钱;然后ATM出1000元钱。这两个步骤必须是要么都执行要么都不执行。如果银行卡扣除了1000块但是ATM出钱失败的话,你将会损失1000元;如果银行卡扣钱失败但是ATM却出了1000块,那么银行将损失1000元。所以,如果一个步骤成功另一个步骤失败对双方都不是好事,如果不管哪一个步骤失败了以后,整个取钱过程都能回滚,也就是完全取消所有操作的话,这对双方都是极好的。

    事务就是用来解决类似问题的。事务是一系列的动作,它们综合在一起才是一个完整的工作单元,这些动作必须全部完成,如果有一个失败的话,那么事务就会回滚到最开始的状态,仿佛什么都没发生过一样。

    在企业级应用程序开发中,事务管理必不可少的技术,用来确保数据的完整性和一致性。

    事务的四个特性 - ACID

    ● 原子性(Atomicity):事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成,要么完全不起作用。
    ● 一致性(Consistency):一旦事务完成(不管成功还是失败),系统必须确保它所建模的业务处于一致的状态,而不会是部分完成部分失败。在现实中的数据不应该被破坏。
    ● 隔离性(Isolation):可能有许多事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。
    ● 持久性(Durability):一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响,这样就能从任何系统崩溃中恢复过来。通常情况下,事务的结果被写到持久化存储器中。

    二、核心接口

    Spring事务管理的实现有许多细节,如果对整个接口框架有个大体了解会非常有利于我们理解事务,下面通过讲解Spring的事务接口来了解Spring实现事务的具体策略。 

    Spring事务管理涉及的接口的联系如下:

    1. 事务管理器

    Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。

    Spring事务管理器的接口是org.springframework.transaction.PlatformTransactionManager,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。此接口的内容如下:

    Public interface PlatformTransactionManager()...{  
        // 由TransactionDefinition得到TransactionStatus对象
        TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException; 
        // 提交
        Void commit(TransactionStatus status) throws TransactionException;  
        // 回滚
        Void rollback(TransactionStatus status) throws TransactionException;  
    }

    从这里可知具体的具体的事务管理机制对Spring来说是透明的,它并不关心那些,那些是对应各个平台需要关心的,所以Spring事务管理的一个优点就是为不同的事务API提供一致的编程模型,如JTA、JDBC、Hibernate、JPA。下面分别介绍各个平台框架实现事务管理的机制。

    Jdbc事务

    如果应用程序中直接使用JDBC来进行持久化,DataSourceTransactionManager会为你处理事务边界。为了使用DataSourceTransactionManager,你需要使用如下的XML将其装配到应用程序的上下文定义中:

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource" />
    </bean>

    实际上,DataSourceTransactionManager是通过调用java.sql.Connection来管理事务,而后者是通过DataSource获取到的。通过调用连接的commit()方法来提交事务,同样,事务失败则通过调用rollback()方法进行回滚。

    Hibernate事务

    如果应用程序的持久化是通过Hibernate实习的,那么你需要使用HibernateTransactionManager。对于Hibernate3,需要在Spring上下文定义中添加如下的<bean>声明:

    <bean id="transactionManager" class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    sessionFactory属性需要装配一个Hibernate的session工厂,HibernateTransactionManager的实现细节是它将事务管理的职责委托给org.hibernate.Transaction对象,而后者是从Hibernate Session中获取到的。当事务成功完成时,HibernateTransactionManager将会调用Transaction对象的commit()方法,反之,将会调用rollback()方法。

    Java持久化API事务(JPA)

    Hibernate多年来一直是事实上的Java持久化标准,但是现在Java持久化API作为真正的Java持久化标准进入大家的视野。如果你计划使用JPA的话,那你需要使用Spring的JpaTransactionManager来处理事务。你需要在Spring中这样配置JpaTransactionManager:

    <bean id="transactionManager" class="org.springframework.orm.jpa.JpaTransactionManager">
      <property name="sessionFactory" ref="sessionFactory" />
    </bean>

    JpaTransactionManager只需要装配一个JPA实体管理工厂(javax.persistence.EntityManagerFactory接口的任意实现)。JpaTransactionManager将与由工厂所产生的JPA EntityManager合作来构建事务。

    Java原生API事务

    如果你没有使用以上所述的事务管理,或者是跨越了多个事务管理源(比如两个或者是多个不同的数据源),你就需要使用JtaTransactionManager:

    <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
      <property name="transactionManagerName" value="java:/TransactionManager" />
    </bean>

    JtaTransactionManager将事务管理的责任委托给javax.transaction.UserTransaction和javax.transaction.TransactionManager对象,其中事务成功完成通过UserTransaction.commit()方法提交,事务失败通过UserTransaction.rollback()方法回滚。

    2. 基本事务属性定义

    上面讲到的事务管理器接口PlatformTransactionManager通过getTransaction(TransactionDefinition definition)方法来得到事务,这个方法里面的参数是TransactionDefinition类,这个类就定义了一些基本的事务属性。

    那么什么是事务属性呢?事务属性可以理解成事务的一些基本配置,描述了事务策略如何应用到方法上。事务属性包含了5个方面,如图所示:

    而TransactionDefinition接口内容如下:

    public interface TransactionDefinition {
      int getPropagationBehavior(); // 返回事务的传播行为
      int getIsolationLevel(); // 返回事务的隔离级别,事务管理器根据它来控制另外一个事务可以看到本事务内的哪些数据
      int getTimeout();  // 返回事务必须在多少秒内完成
      boolean isReadOnly(); // 事务是否只读,事务管理器能够根据这个返回值进行优化,确保事务是只读的
    }

    我们可以发现TransactionDefinition正好用来定义事务属性,下面详细介绍一下各个事务属性。 

    1 )传播行为

    事务的第一个方面是传播行为(propagation behavior)。当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。

    例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

    传播行为 含义
    PROPAGATION_REQUIRED* 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。这是默认值
    PROPAGATION_SUPPORTS* 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行
    PROPAGATION_MANDATORY 表示该方法必须在事务中运行,如果当前没有事务,则抛出一个异常
    PROPAGATION_REQUIRED_NEW

    创建一个新的事务,如果当前存在事务,则把当前事务挂起;如果使用JTATransactionManager的话,则需要访问TransactionManager

    PROPAGATION_NOT_SUPPORTED 以非事务方式运行,如果当前存在事务,则把当前事务挂起;如果使用JTATransactionManager的话,则需要访问TransactionManager
    PROPAGATION_NEVER 以非事务方式运行,如果当前存在事务,则抛出异常
    PROPAGATION_NESTED 如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行。嵌套的事务可以独立于当前事务进行单独地提交或回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样

    2 )隔离级别

    事务的第二个维度就是隔离级别(isolation level)。隔离级别定义了一个事务可能受其他并发事务影响的程度。 

    - 并发事务引起的问题 

    在典型的应用程序中,多个事务并发运行,经常会操作相同的数据来完成各自的任务。并发虽然是必须的,但可能会导致一下的问题。

    脏读

    Dirty reads

    脏读发生在一个事务读取了另一个事务改写但尚未提交的数据时。如果改写在稍后被回滚了,那么第一个事务获取的数据就是无效的。

    不可重复度

    Nonrepeatable read

    当同一个事务执行两次及以上相同的查询时,每次都得到不同的数据。一般因为另一并发事务在两次查询期间进行了更新

    幻读

    Phantom read

    幻读与不可重复读类似。它发生在一个事务(T1)读取了几行数据,接着另一个并发事务(T2)插入了一些数据时。在随后的查询中,第一个事务(T1)就会发现多了一些原本不存在的记录。

    - 不可重复读与幻读的区别:不可重复读侧重点在相同数据被修改,而幻读是删除或新增

    从理论上讲,事务应该完全隔离,避免并发事务导致的问题,但是这样可能对性能产生极大影响,因为事务必须按顺序进行了。所以在实际的开发中,为了提升性能,事务会以比较低的隔离级别运行。

    - 隔离级别

    隔离级别 含义

    数据库默认

    ISOLATION_DEFAULT

    使用底层数据库的默认隔离级别,大部分数据库,默认隔离级别都是READ_COMMITED

    读以提交

    ISOLATION_READ_UNCOMMITTED

    最低的隔离级别,允许读取尚未提交的数据变更,可能会导致脏读、幻读或不可重复读

    读未提交

    ISOLATION_READ_COMMITTED

    允许读取并发事务已经提交的数据,可以阻止脏读,但是幻读或不可重复读仍有可能发生

    可重复读

    ISOLATION_REPEATABLE_READ

    对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改,可以阻止脏读和不可重复读,但幻读仍有可能发生

    串行化

    ISOLATION_SERIALIZABLE

    最高的隔离级别,完全服从ACID的隔离级别,确保阻止脏读、不可重复读以及幻读,也是最慢的事务隔离级别,因为它通常是通过完全锁定事务相关的数据库表来实现的

    注意:事务的隔离级别需要底层数据库引擎的支持,而不是应用程序或者框架的支持

    ● Oracle支持2种事务隔离级别:READ_COMMITED,SERIALIZABLE
    ● MySQL支持4种事务隔离级别

    3 )只读

    如果事务只读数据但不修改可以通过配置只读事务属性,帮助数据库引擎优化事务。只读事务属性:表示这个事务只读读取数据,但是不更新数据

    4 )事务超时

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

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

    5 )回滚规则

    默认情况下只有未检查异常(RuntimeException和Error类型的异常)会导致事务回滚。事务的回滚规则可以通过属性管理

    rollbackFor:遇到时必须进行回滚

    noRollbackFor:一组异常类,遇到时必须不能回滚

    rollbackFor={IOException.class,SQLException.class},
    noRollbackRoe=ArithmeticException.class

    3. 事务状态

    上面讲到的调用PlatformTransactionManager接口的getTransaction()的方法得到的是TransactionStatus接口的一个实现,这个接口的内容如下:

    public interface TransactionStatus{
        boolean isNewTransaction(); // 是否是新的事务
        boolean hasSavepoint(); // 是否有恢复点
        void setRollbackOnly();  // 设置为只回滚
        boolean isRollbackOnly(); // 是否为只回滚
        boolean isCompleted; // 是否已完成
    }

    可以发现这个接口描述的是一些处理事务提供简单的控制事务执行和查询事务状态的方法,在回滚或提交的时候需要应用对应的事务状态。

    三、编程式事务

    1. 编程式和声明式事务的区别

    Spring提供了对编程式事务和声明式事务的支持,编程式事务允许用户在代码中精确定义事务的边界,而声明式事务(基于AOP)有助于用户将操作与事务规则进行解耦。

    简单地说,编程式事务侵入到了业务代码里面,但是提供了更加详细的事务管理;而声明式事务由于基于AOP,所以既能起到事务管理的作用,又可以不影响业务代码的具体实现。

    2. 如何实现编程式事务?

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

    1 ) 使用TransactionTemplate

    采用TransactionTemplate和采用其他Spring模板,如JdbcTempalte和HibernateTemplate是一样的方法。它使用回调方法,把应用程序从处理取得和释放资源中解脱出来。如同其他模板,TransactionTemplate是线程安全的。代码片段:

    TransactionTemplate tt = new TransactionTemplate(); // 新建一个TransactionTemplate
    Object result = tt.execute(
        new TransactionCallback(){  
            public Object doTransaction(TransactionStatus status){  
                updateOperation();  
                return resultOfUpdateOperation();  
        }  
    }); // 执行execute方法进行事务管理

    使用TransactionCallback()可以返回一个值。如果使用TransactionCallbackWithoutResult则没有返回值。

    2 )使用PlatformTransactionManager

    示例代码如下:

    DataSourceTransactionManager datm = new DataSourceTransactionManager(); // 定义一个某个框架平台的TransactionManager,如JDBC、Hibernate
    datm.setDataSource(this.getJdbcTemplate().getDataSource()); // 设置数据源
    DefaultTransactionDefinition transDef = new DefaultTransactionDefinition(); // 定义事务属性
    transDef.setPropagationBehavior(DefaultTransactionDefinition.PROPAGATION_REQUIRED); // 设置传播行为属性
    TransactionStatus status = datm.getTransaction(transDef); // 获得事务状态
    try {
        // 数据库操作
        datm.commit(status);// 提交
    } catch (Exception e) {
        datm.rollback(status);// 回滚
    }

    四、声明式事务

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

    XML - 通知声明式的管理事务

    <!-- 1. 配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!-- 2. 配置事务属性 -->
    <!--<tx:advice>元素声明事务通知 -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <!-- 根据方法名指定事务的属性 -->
            <tx:method name="*" />
            <!--propagation配置事务传播行为 -->
            <tx:method name="purchase" propagation="REQUIRES_NEW" />
            <!--isolation配置事务的隔离级别 -->
            <tx:method name="update*" isolation="SERIALIZABLE" />
            <!--rollback-for配置事务遇到异常必须回滚,no-rollback-for配置事务遇到异常必须不能回滚 -->
            <tx:method name="add*" rollback-for="java.io.IOException" no-rollback-for="com.dmsd.spring.tx.BookStockException" />
            <!--read-only配置事务只读属性 -->
            <tx:method name="find*" read-only="true" />
            <!--timeout配置事务的超时属性 -->
            <tx:method name="get*" timeout="3" />
        </tx:attributes>
    </tx:advice>
    
    <!-- 3. 配置事务切入点, 以及把事务切入点和事务属性关联起来 -->
    <aop:config>
        <aop:pointcut expression="execution(* com.atguigu.spring.tx.xml.service.*.*(..))" id="txPointCut" />
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut" />
    </aop:config>

    注解 - 用 @Transactional 注解声明式的管理事务

    Spring中注解的方式@Transactional标注事务方法。为了将方法定义为支持事务处理,可以在方法上添加@Transactional注解。根据Spring AOP基于代理机制,只能标注公有方法。如果在类上标注@Transactional注解,那么这个类中所有公有方法都会被定义为支持事务。

    <!-- 配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!-- 启用事务注解 -->
    <tx:annotation-driven transaction-manager="transactionManager" />

    在方法上添加

    // 添加事务注解
    // 1.使用 propagation 指定事务的传播行为, 即当前的事务方法被另外一个事务方法调用时
    // 如何使用事务, 默认取值为 REQUIRED, 即使用调用方法的事务
    // REQUIRES_NEW: 事务自己的事务, 调用的事务方法的事务被挂起.
    // 2.使用 isolation 指定事务的隔离级别, 最常用的取值为 READ_COMMITTED
    // 3.默认情况下 Spring 的声明式事务对所有的运行时异常进行回滚. 也可以通过对应的
    // 属性进行设置. 通常情况下去默认值即可.
    // 4.使用 readOnly 指定事务是否为只读. 表示这个事务只读取数据但不更新数据,
    // 这样可以帮助数据库引擎优化事务. 若真的事一个只读取数据库值的方法, 应设置 readOnly=true
    // 5.使用 timeout 指定强制回滚之前事务可以占用的时间.
    @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.READ_COMMITTED, noRollbackFor = {ArithmeticException.class }, rollbackFor = IOException.class, readOnly = false, timeout = 3)
    @Override
    public void updateBalance() {
    
        double money = 1000;
        // 转出钱的方法
        userDao.subBalance(2, money);
        // int i = 1 / 0;
        // 转入钱的方法
        userDao.addBalance(1, money);
    }

    事务失效

    因为spring事务是基于aop的代理机制,当方法中调用this本身的方法时候即使在this的方法标明事务注解,但是事务注解会失效。

    如下:

    @Transactional
    @Override
    public void purchase(String username, String isbn) {
        this.update(username, isbn);
    }
     
    @Transactional
    public void update(String username, String isbn) {
        //1. 获取书的单价
        int price = bookShopDao.findBookPriceByIsbn(isbn);
        //2. 更新数的库存
        bookShopDao.updateBookStock(isbn);
        //3. 更新用户余额
        bookShopDao.updateUserAccount(username, price);
    }

    原因:因为调用ths本身方法不走代理机制,这个时候可以通过配置解决这个问题。

    解决事务失效

    在配置中添加如下内容

    <!--开启aspectj代理,并暴露aop代理到ThreadLocal-->
    <aop:aspectj-autoproxy expose-proxy="true"/>

    将上述调用的地方改成如下

    @Transactional
    @Override
    public void purchase(String username, String isbn) {
        ((BookShopServiceImpl)AopContext.currentProxy()).update(username, isbn);
    }

    版权声明:本文为CSDN博主「Trigl」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。

    原文链接:https://blog.csdn.net/trigl/article/details/50968079

  • 相关阅读:
    网络编程-进程-1、什么叫进程?
    Python做性能测试-1、Locust基础篇
    网络编程-线程-6、互斥锁解决线程中数据安全问题
    网络编程-线程-5、多个线程共享全局变量造成资源争抢,数据混乱
    网络编程-线程-4、多个线程之间共享全局变量
    网络编程-线程-3、通过继承Thread类创建线程
    网络编程-线程-2、如何查看有多少个线程在运行
    网络编程-线程-1、一张图让你看懂多线程工作原理
    网络编程-多任务,并发,并行--从底层工作原理解释,让你不再傻傻分不清这些到底什么意思
    二、网络编程-socket之TCP协议开发客户端和服务端通信
  • 原文地址:https://www.cnblogs.com/Dm920/p/12096642.html
Copyright © 2020-2023  润新知