spring知识八------事务管理

事务简介

事务主要是用来操作数据库的,他的使用是用来保证数据的完整性和一致性。
事务就是一系列的动作,它们被当做一个单独的工作单元。这些动作要么全部完成,要么全部不起作用。

事务的四个关键属性(ACID)

原子性(atomicity): 事务是一个原子操作,由一系列动作组成。事务的原子性确保动作要么全部完成要么完全不起作用。
一致性(consistency): 一旦所有事务动作完成,事务就被提交。数据和资源就处于一种满足业务规则的一致性状态中。
隔离性(isolation): 可能有许多事务会同时处理相同的数据,因此每个事物都应该与其他事务隔离开来,防止数据损坏。
持久性(durability): 一旦事务完成,无论发生什么系统错误,它的结果都不应该受到影响。通常情况下,事务的结果被写到持久化存储器中。

Spring对事务的管理

Spring 的核心事务管理抽象是 【org.springframework.transaction.PlatformTransactionManager 】接口,他有一系列的实现类,用于不同的事务管理,其中最常用的类是【 HibernateTransactionManager】和【 DataSourceTransactionManager】。HibernateTransactionManager用 Hibernate 框架存取数据库。DataSourceTransactionManager在应用程序中只需要处理一个数据源,而且通过 JDBC 存取。
它为事务管理封装了一组独立于技术的方法。无论使用 Spring 的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。

Spring 既支持编程式事务管理,也支持声明式的事务管理

编程式事务管理: 将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。在编程式管理事务时,必须在每个事务操作中包含额外的事务管理代码,这样的话,会引起业务代码混乱。

声明式事务管理: 大多数情况下比编程式事务管理更好用。它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。事务管理作为一种横切关注点, 可以通过 AOP 方法模块化。Spring 通过 Spring AOP 框架支持声明式事务管理。建议使用这种方式。

声明式事务管理应用
声明式事务三步走:
第一步:配置文件中添加对应的命名空间 tx,引入对应的约束。
第二步:配置文件中声明式事务配置。
第三步:进行事务处理。
注意:在声明事务配置时,不同的配置事务管理器,有不同的类来完成,所配置的属性也有所不同
注意:在进行事务处理时,不要在daoImpl层处理,应该将事务的处理提取到Service层,因为事务的回滚一般会牵扯到多个dao的使用,所以在Service层进行事务的处理,这样能最大化的保证数据的完整性。

实例: 用户去书店买书,减少库存与用户余额扣除可以是认为是一个事务,需要同时进行处理。

第一步:添加命名空间tx

xmlns:tx="http://www.springframework.org/schema/tx"
http://www.springframework.org/schema/tx 
http://www.springframework.org/schema/tx/spring-tx-4.0.xsd"

第二步:声明式事务的配置

<!-- 配置事务管理器 属性的配置可以在类中进行发现  -->
===============================================================
<!-- DataSourceTransactionManager -->
<bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="datasource"></property>
</bean>

<!-- 启用事务注解 -->
<tx:annotation-driven transaction-manager="dataSourceTransactionManager"/>
==================================================================
<!--HibernateTransactionManager-->

<!-- 配置事务管理器 属性的配置可以在类中进行发现 -->
<bean id="txManager"
        class="org.springframework.orm.hibernate3.HibernateTransactionManager">
        <property name="sessionFactory" ref="sessionFactory" />
    </bean>
<!-- 启用事务注解 -->
<tx:annotation-driven transaction-manager="txManager"/>

第三步:进行事务处理,在对应的方法上面添加注解@Transactional

@Transactional  //添加事务注解 一般都是在service层添加
    public void buyBook(String username,String isbn) {
        // 1 获取书的单价
        int price=bookStoreDAO.findbookPriceByIsbn(isbn);       
        // 2 更新书的库存 
        bookStoreDAO.updateBookStock(isbn);
        // 3 更新用户余额
        bookStoreDAO.updateUserAccount(username,price);
    }

@Transactional 注解

@Transactional 注解可以在方法上定义为支持事务处理的方法。
@Transactional注解可以在类上进行定义,表示这个类中的所有公共方法都会被定义成支持事务处理的
@Transactional注解属性propagation表示事务的传播行为
@Transactional注解属性isolation表示事务的隔离级别
@Transactional注解属性rollbackFor表示事务的回滚
@Transactional注解属性readOnly表示只读
@Transactional注解属性timeout表示事务过期

事务的传播行为propagation

事务的交互使用,当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。 方法可能继续在现有事务中运行, 也可能开启一个新事务, 并在自己的事务中运行。
事务的传播行为可以由传播属性指定。Propagation(传播,扩展)默认值是required,即是如果当前存在事务,则不进行创建,只是利用以前的,如果没有,则是进行创建。
这里写图片描述

官网对于REQUIRED和REQUIRED_NEW的说明,也是最常用的两个,其余的自行查询API。
对于REQUIRED,则是用同一事务进行处理,一旦出错则是回滚到最初的状态,
对于REQUIRED_NEW,则是如果在一个事务中,又出现了一个新事务,对不起,我嫌弃你,我要自己再重新开启一个事务,而对于第一个事务,你先挂起,等我执行完之后你再来执行,有一种喧宾夺主的感觉。

这里写图片描述
这里写图片描述

例子:买书 资金可以买第一本书,无法购买第二本书


    // 一个人买多本书
    @Transactional  // 此事务中利用for循环多次购买,多次打开买书事务
    public void checkout(String username,List<String> isbns) {
        for(String isbn:isbns){
            bookStoreService.buyBook(username,isbn);  //buyBook 中是存在简单的事务模拟
        }
    }
=====================================================================
   // 基本事务,进行买书,每次只能购买一本
    @Transactional (propagation=Propagation.REQUIRES_NEW)
    public void buyBook(String username,String isbn) {
        // 1 获取书的单价
        int price=bookStoreDAO.findbookPriceByIsbn(isbn);       
        // 2 更新书的库存 
        bookStoreDAO.updateBookStock(isbn);
        // 3 更新用户余额
        bookStoreDAO.updateUserAccount(username,price);
    }
===============================================================
@Test //事务的传播行为
    public void testtransaction(){
        cashierService.checkout("AA",Arrays.asList("1001","1002"));
    }
    /*数据库数据符合用户金额可以买第一本书,但是不能买第二本书,记得是在内部的那个事务中添加事务的传播属性
     * 默认的情况下, 使用propagation=Propagation.REQUIRED,则是在外面的事务中,不进行重新开启事务,
     * 这样的情况下,买第一本书的时候成功了,不发生事务回滚,
     * 但是在买第二本书的时候,则发生事务回滚,一下回滚到外面事务的开始位置,而将第一次买成功的也进行 了回滚
     * 
     * 使用propagation=Propagation.REQUIRES_NEW 则是进行重新事务的开启。
     * 这种情况下,买第一本书的时候成功了,不发生事务回滚,
     * 但是再买第二次的时候,由于我们设置了新开启事务,所以在买第二次的时候,新事务开启,资金不足的情况下,发生事务回滚,但是由于新开了一个事务,
     * 所以只是回到此事务的开始位置,即第一次买书结束的位置,而只是第一本书不进行回滚,而第二次的进行回滚。
     */

事务的隔离级别isolation

事务的隔离级别主要是用于在并发情况下的事务处理
当同一个应用程序或者不同应用程序中的多个事务在同一个数据集上并发执行时,可能会出现许多意外的问题。并发事务所导致的问题主要有:脏读、不可重复读、幻读。

脏读:对于两个事务T1,T2, T1 读取了已经被 T2 更新但还没有被提交的字段。 之后, 若 T2 回滚,T1读取的内容就是临时且无效的。【读但未提交】
不可重复读:对于两个事务 T1, T2,T1 读取了一个字段,然后 T2 更新了该字段.。之后, T1再次读取同一个字段,值就不同了。【读更读】
幻读:对于两个事务 T1,T2, T1 从一个表中读取了一个字段,然后 T2 在该表中插入了一些新的行。之后,如果 T1 再次读取同一个表,就会多出几行。【读插读】

理论上来说,事务应该彼此完全隔离,以避免并发事务所导致的问题。然而,那样会对性能产生极大的影响,因为事务必须按顺序运行,所以为了提升性能,事务会以较低的隔离级别运行。

事务的隔离级别要得到底层数据库引擎的支持,而不是应用程序或者框架的支持.
Oracle 支持的 2 种事务隔离级别:READ_COMMITED ,SERIALIZABLE
Mysql 支持 4 中事务隔离级别

Spring 支持的事务隔离级别
这里写图片描述

这里写图片描述

@Transactional其他属性介绍

回滚事务属性:一般我们只是用默认的即可。
默认情况下只有未检查异常(RuntimeException和Error类型的异常)会导致事务回滚。而受检查异常不会。
事务的回滚规则可以通过 @Transactional 注解的 rollbackFor 和 noRollbackFor 属性来定义。这两个属性被声明为 Class[] 类型的,因此可以为这两个属性指定多个异常类,大括号表示。

noRollbackFor={UserAccountException.class,IOException.class}

超时事务属性: 事务在强制回滚之前可以保持多久。这样可以防止长期运行的事务占用资源。
只读事务属性: 表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务。

基于XML配置的事务管理

基于XML的事务配置主要是在文件中进行配置。
三步走:
1.配置事务管理器
2.配置事务属性
3.配置事务切入点,以及把事务切入点和事务属性关联起来
配置文件如下:

<!-- 事务管理的配置 -->

        <!-- 基本bean配置 -->
        <bean id="bookStoreDAO" class="com.wf.springtransactionXml.BookStoreDAOImpl">
            <property name="jdbcTemplate" ref="jdbcTemplate"></property>
        </bean>

        <bean id="bookStoreService" class="com.wf.springtransactionXml.serviceImpl.BookStoreServiceImpl">
            <property name="bookStoreDAO" ref="bookStoreDAO"></property>
        </bean> 

        <bean id="cashierService" class="com.wf.springtransactionXml.serviceImpl.CashierServiceImpl">
            <property name="bookStoreService" ref="bookStoreService"></property>
        </bean>
        <!--1.配置事务管理器 -->
        <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
                <property name="dataSource" ref="datasource"></property>
        </bean>

        <!--2.配置事务属性     传播行为,隔离级别,事务回滚,只读事务,超时结束,  -->
        <tx:advice id="txAdvice" transaction-manager="dataSourceTransactionManager">
            <tx:attributes>
                <!-- <tx:method name="*"/>  匹配所有属性为默认值 -->
                <tx:method name="buyBook" propagation="REQUIRES_NEW"/>  <!-- 指定事务方法的属性 -->
                <!-- <tx:method name="buyBook*" propagation="REQUIRES_NEW"/>    指定事务方法的属性,可以使用通配符 -->
            </tx:attributes>
        </tx:advice>

        <!-- 3.配置事务切入点,以及把事务切入点和事务属性关联起来,即哪些方法需要配置事务 -->
            <aop:config>
                <aop:pointcut expression="execution(* com.wf.springtransactionXml.service.*.*(..))" 
                id="txPointCut"/>
                <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
                <!-- 在这个txPointCut匹配的方法上面执行事务txAdvice -->
            </aop:config>   

源码不再单独上传到博客资源,不容易通过,后期会发出百度云链接或者源码Github地址:
https://github.com/wangfa1994/SpringLearning/tree/spring007

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章