Bootstrap

Spring复习(八)之声明式事务

1 环境搭建

1 导入sql文件、导入jar

 <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>4.0.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-orm</artifactId>
            <version>4.0.0.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>4.0.0.RELEASE</version>
        </dependency>

2 配置JDBC配置文件

 <context:component-scan base-package="spring.tx"></context:component-scan>


    <context:property-placeholder location="classpath:jdbc.properties"/>
    <!--  配置数据源-->
    <bean id="pooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="user" value="${jdbc.userName}"></property>
        <property name="password" value="${jdbc.password}"></property>
        <property name="jdbcUrl" value="${jdbc.url}"></property>
        <property name="driverClass" value="${jdbc.driverClass}"></property>
    </bean>


    <!-- 配置JdbcTemplate -->
    <bean class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="pooledDataSource"></property>
    </bean>

3 编写Servie层,编写Dao层,并加入容器和依赖注入

Dao层

@Repository
public class BookDao {

    @Autowired
    JdbcTemplate jdbcTemplate;


    /**
     * 1、减余额
     *
     * 减去某个用户的余额
     */
    public void updateBalance(String userName,int price){
        String sql = "UPDATE account SET balance=balance-? WHERE username=?";
        jdbcTemplate.update(sql, price,userName);
    }


    /**
     * 2、按照图书的ISBN获取某本图书的价格
     * @return
     */
    public int getPrice(String isbn){
        String sql = "SELECT price FROM book WHERE isbn=?";
        return jdbcTemplate.queryForObject(sql, Integer.class, isbn);
    }

    /**
     * 3、减库存;减去某本书的库存;为了简单期间每次减一
     */
    public void updateStock(String isbn){
        String sql = "UPDATE book_stock SET stock=stock-1 WHERE isbn=?";
        jdbcTemplate.update(sql, isbn);
    }


}

Service层

@Service
public class BookService {

    @Autowired
    BookDao bookDao;

    /**
     * 结账;传入哪个用户买了哪本书
     * @param username
     * @param isbn
     */
    public void checkout(String username,String isbn){
        //1、减库存
        bookDao.updateStock(isbn);

        //2 查价格
        int price = bookDao.getPrice(isbn);

        //3、减余额
        bookDao.updateBalance(username, price);
    }

}

4 测试

public class test {
        ApplicationContext ioc = new ClassPathXmlApplicationContext("tx.xml");

        @Test
        public void test() {
            BookService bookService = ioc.getBean(BookService.class);

            bookService.checkout("Tom", "ISBN-001");

            System.out.println("结账完成");
        }

}

2 声明式事务相关概念

1 事务概述

●在JavaEE企业级开发的应用领域,为了保证数据的完整性和一致性,必须引入数据库事务的概念,所以事务管理是企业级应用程序开发中必不可少的技术。
●事务就是一组由于逻辑上紧密关联而合并成一个整体(工作单元)的多个数据库操作,这些操作要么都执行,要么都不执行。
●事务的四个关键属性(ACID)

  • 原子性(atomicity):“原子”的本意是“不可再分”,事务的原子性表现为一个事务中涉及到的多个操作在逻辑上缺一不可。事务的原子性要求事务中的所有操作要么都执行,要么都不执行。
  • 一致性(consistency):“一致”指的是数据的一致,具体是指:所有数据都处于满足业务规则的一致性状态。一致性原则要求:一个事务中不管涉及到多少个操作,都必须保证事务执行之前数据是正确的,事务执行之后数据仍然是正确的。如果一个事务在执行的过程中,其中某一个或某几个操作失败了,则必须将其他所有操作撤销,将数据恢复到事务执行之前的状态,这就是回滚。
  • 隔离性(isolation):在应用程序实际运行过程中,事务往往是并发执行的,所以很有可能有许多事务同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏。隔离性原则要求多个事务在并发执行过程中不会互相干扰。
  • 持久性(durability):持久性原则要求事务执行完成后,对数据的修改永久的保存下来,不会因各种系统错误或其他意外情况而受到影响。通常情况下,事务对数据的修改应该被写入到持久化存储器中。

2 Spring事务管理

2.1 编程式事务管理

①使用原生的JDBC API进行事务管理
[1]获取数据库连接Connection对象
[2]取消事务的自动提交
[3]执行操作
[4]正常完成操作时手动提交事务
[5]执行失败时回滚事务
[6]关闭相关资源

 TransactionFilter{
               try{
                    //获取连接
                    //设置非自动 提交
                    chain.doFilter();
                    //提交
               }catch(Exception e){
                    //回滚 
               }finllay{
                    //关闭连接释放资源
               }      
     }

②评价
使用原生的JDBC API实现事务管理是所有事务管理方式的基石,同时也是最典型的编程式事务管理。编程式事务管理需要将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。在使用编程的方式管理事务时,必须在每个事务操作中包含额外的事务管理代码。相对于核心业务而言,事务管理的代码显然属于非核心业务,如果多个模块都使用同样模式的代码进行事务管理,显然会造成较大程度的代码冗余。

2.2 声明式事务管理

以前通过复杂的编程来编写一个事务,替换为只需要告诉Spring哪个方法是事务方法即可

  • 大多数情况下声明式事务比编程式事务管理更好:它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
  • 事务管理代码的固定模式作为一种横切关注点,可以通过AOP方法模块化,进而借助Spring AOP框架实现声明式事务管理。
  • Spring在不同的事务管理API之上定义了一个抽象层,通过配置的方式使其生效,从而让应用程序开发人员不必了解事务管理API的底层实现细节,就可以使用Spring的事务管理机制。
  • Spring既支持编程式事务管理,也支持声明式的事务管理。
  • 原理
AOP:环绕通知可以去做;
                     //获取连接
                    //设置非自动 提交
                    目标代码执行(横切关注点)
                    //正常提交
                    //异常回滚
                    //最终关闭

最终效果:
          BookService{

               @this is a tx-method(Transactional)
               public void checkout(){
                         //xxxxx
               }
          }

2.3 Spring提供的事务管理器(事务管理的切面类)

  • Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从特定的事务技术中独立出来。开发人员通过配置的方式进行事务管理,而不必了解其底层是如何实现的。
  • Spring的核心事务管理抽象是PlatformTransactionManager。它为事务管理封装了一组独立于技术的方法。
  • 无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的。
  • 事务管理器可以以普通的bean的形式声明在Spring IOC容器中。
    2.4 事务管理器的主要实现
    ①DataSourceTransactionManager:在应用程序中只需要处理一个数据源,而且通过JDBC存取。
    ②JtaTransactionManager:在JavaEE应用服务器上用JTA(Java Transaction API)进行事务管理
    ③HibernateTransactionManager:用Hibernate框架存取数据库

在这里插入图片描述
这个事务管理器就可以在目标方法运行前后进行事务控制(事务切面);
我们目前都使用DataSourceTransactionManager;即可;

3 快速为某个方法添加声明式事务

1)配置出这个事务管理器让他工作;

2)开启基于注解的事务

3)给事务方法加@Transactional注解

<!-- 事务控制 -->
	<!--1:配置事务管理器(切面)让其进行事务控制;一定导入面向切面编程的几个包  
			spring-aspects-4.0.0.RELEASE.jar
			com.springsource.net.sf.cglib-2.2.0.jar
			com.springsource.org.aopalliance-1.0.0.jar
			com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar
	-->
	<bean id="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<!-- 控制住数据源,因为是操作连接 -->
		<property name="dataSource" ref="pooledDataSource"></property>
	</bean>
	<!--2:开启基于注解的事务控制模式;依赖tx名称空间  -->
	<tx:annotation-driven transaction-manager="tm"/>
	<!--3:给事务方法加注解@Transactional  -->
@Transactional
	public void checkout(String username,String isbn){

4 事务的隔离级别

4.1 数据库事务并发问题

假设现在有两个事务:Transaction01和Transaction02并发执行。
①脏读
[1]Transaction01将某条记录的AGE值从20修改为30。
[2]Transaction02读取了Transaction01更新后的值:30。
[3]Transaction01回滚,AGE值恢复到了20。
[4]Transaction02读取到的30就是一个无效的值。
②不可重复读
[1]Transaction01读取了AGE值为20。
[2]Transaction02将AGE值修改为30。
[3]Transaction01再次读取AGE值为30,和第一次读取不一致。
③幻读
[1]Transaction01读取了STUDENT表中的一部分数据。
[2]Transaction02向STUDENT表中插入了新的行。
[3]Transaction01读取了STUDENT表时,多出了一些行。

4.2 隔离级别

数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。
①读未提交READ UNCOMMITTED
允许Transaction01读取Transaction02未提交的修改。
读未提交的脏读问题
在这里插入图片描述
读已提交READ COMMITTED
要求Transaction01只能读取Transaction02已提交的修改。
读已提交的不可重复读问题
在这里插入图片描述
可重复读REPEATABLE READ
确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。
在这里插入图片描述
串行化SERIALIZABLE
确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。
⑤各个隔离级别解决并发问题的能力见下表

脏读不可重复读幻读
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE

⑥各种数据库产品对事务隔离级别的支持程度

OracleMySQL
READ UNCOMMITTED×
READ COMMITTED
REPEATABLE READ×√(默认)
SERIALIZABLE

4.3 在Spring中指定事务隔离级别

①注解
用@Transactional注解声明式地管理事务时可以在@Transactional的isolation属性中设置隔离级别

5 触发事务回滚的异常

5.1 默认情况

捕获到RuntimeException或Error时回滚,而捕获到编译时异常不回滚。

5.2 设置途经

①注解
@Transactional 注解
[1]rollbackFor属性:指定遇到时必须进行回滚的异常类型,可以为多个
[2]noRollbackFor属性:指定遇到时不回滚的异常类型,可以为多个
在这里插入图片描述

6 事务的超时和只读属性

6.1 简介

由于事务可以在行和表上获得锁,因此长事务会占用资源,并对整体性能产生影响。
如果一个事物只读取数据但不做修改,数据库引擎可以对这个事务进行优化。
超时事务属性:事务在强制回滚之前可以保持多久。这样可以防止长期运行的事务占用资源。
只读事务属性: 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务。
8.2 设置
①注解
@Transaction注解
在这里插入图片描述

②XML
在Spring 2.x事务通知中,超时和只读属性可以在tx:method元素中进行指定

7 事务的传播行为

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
传播行为(事务的传播+事务的行为);如果有多个事务进行嵌套运行,子事务是否要和大事务共用一个事务;
事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为。
在这里插入图片描述
注意:REQUIRED事务属性来源于大事务
注解用法

@Transactional(propagation=Propagation.REQUIRES_NEW)
public void checkout(String username,String isbn){

代码示例
大事务调用

@Service
public class MulService {
	
	@Autowired
	private BookService bookService;
	
	@Transactional
	public void mulTx(){
		//都是可以设置的;
		//传播行为来设置这个事务方法是不是和之前的大事务共享一个事务(使用同一条连接);
		//REQUIRED  
		bookService.checkout("Tom", "ISBN-001");
		
		//REQUIRED   REQUIRES_NEW
		bookService.updatePrice("ISBN-002", 998);
		
		//int i = 10/0;
	}

}

两个小事务

@Service
public class BookService {
	
	@Autowired
	BookDao bookDao;

	@Transactional(propagation=Propagation.REQUIRES_NEW)
	public void checkout(String username,String isbn){
		//1、减库存
		bookDao.updateStock(isbn);
		
		int price = bookDao.getPrice(isbn);

		//2、减余额
		bookDao.updateBalance(username, price);
		
	}
	
	
	@Transactional(propagation=Propagation.REQUIRES_NEW)
	public void updatePrice(String isbn,int price){
		bookDao.updatePrice(isbn, price);
	}
	
}
  • 当两个两个小事务都是REQUIRED的时候
  • 当两个事务小事务都是REQUIRES_NEW的时候
    在这里插入图片描述
    深刻理解
  • 主方法最后10/0异常
    B() D()及其整个里头的所有方法都成功
  • 当F崩了10/0 整个异常往上映射
    F以上已经执行的REQUIRES_NEW方法都会成功,其他会崩
multx(){
 		//REQUIRED
	  	A(){
	  		//REQUIRES_NEW
	  		B(){}
	  		//REQUIRED
	  		c(){}
	  		}
	  		
	  	//REQUIRES_NEW
	  	D(){
	  		DDDD()//  REQUIRES_NEW不崩,REQUIRED崩
	  		//REQUIRED
	  		E(){
	  			//REQUIRES_NEW
	  			F(){
	  			 int 10/0
	  			}
	  			}
	  			//REQUIRES_NEW
	  			G(){}
	  		}
		int 10/0
}

8 事务之XML配置

基于xml配置的事务;依赖tx名称空间和aop名称空间
1)Spring中提供事务管理器(事务切面)声明

bean id="tm" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!-- 控制住数据源 -->
        <property name="dataSource" ref="pooledDataSource"></property>
    </bean>

2)告诉Spring哪些方法是事务方法;事务切面按照我们的切入点表达式去切入事务方法
(相当于原来直接在事务方法上加注解@Transactional
advice-ref 指的是事务切面的配置信息<tx:advice>

 <aop:config>
        <aop:pointcut expression="execution(* spring.tx.ser*.*.*(..))" id="txPoint"/>
        <!-- 事务建议;事务增强     advice-ref:指向事务管理器的配置 -->
        <aop:advisor advice-ref="myAdvice" pointcut-ref="txPoint"/>
    </aop:config>
  1. 配置这个事务管理器配置出事务方法;
    (相当于原来配置注解@Transactional 的值)
    transaction-manager 指的是切面声明的id
<tx:advice id="myAdvice" transaction-manager="tm">
        <!--事务属性  -->
        <tx:attributes>
            <!-- 指明哪些方法是事务方法;
            切入点表达式只是说,事务管理器要切入这些方法,
            哪些方法加事务使用tx:method指定的 -->
            <tx:method name="*"/>
            <tx:method name="checkout" propagation="REQUIRED" timeout="-1"/>
            <tx:method name="get*" read-only="true"/>
        </tx:attributes>
    </tx:advice>

整个XML文件

<!-- 0、引入外部配置文件 -->
<context:property-placeholder location="classpath:dbconfig.properties" />
<!-- 1、配置数据源 -->
<bean id="pooledDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
	<property name="user" value="${jdbc.user}"></property>
	<property name="password" value="${jdbc.password}"></property>
	<property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property>
	<property name="driverClass" value="${jdbc.driverClass}"></property>
</bean>
<!-- 2、配置JdbcTemplate操作数据库   value="#{pooledDataSource}"  ref="pooledDataSource"-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
	<property name="dataSource" value="#{pooledDataSource}"></property>
</bean>

<------------------以上通用------------------>

 
 <!-- 
 基于xml配置的事务;依赖tx名称空间和aop名称空间
 	1)、Spring中提供事务管理器(事务切面),配置这个事务管理器
    2)、配置出事务方法;
	3)、告诉Spring哪些方法是事务方法;
		(事务切面按照我们的切入点表达式去切入事务方法)
  -->
   <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
 	<property name="dataSource" ref="pooledDataSource"></property>
 </bean>
 
 <aop:config>
 	<aop:pointcut expression="execution(* com.atguigu.ser*.*.*(..))" id="txPoint"/>
 	<!-- 事务建议;事务增强     advice-ref:指向事务管理器的配置 -->
 	<aop:advisor advice-ref="myAdvice" pointcut-ref="txPoint"/>
 </aop:config>
 
 <!--  配置事务管理器;    事务建议;事务增强;事务属性;
 transaction-manager="transactionManager":指定是配置哪个事务管理器;
 -->
 <tx:advice id="myAdvice" transaction-manager="transactionManager">
 		<!--事务属性  -->
 		<tx:attributes>
 			<!-- 指明哪些方法是事务方法;
 			切入点表达式只是说,事务管理器要切入这些方法,
 			哪些方法加事务使用tx:method指定的 -->
 			<tx:method name="*"/>
 			<tx:method name="checkout" propagation="REQUIRED" timeout="-1"/>
 			<tx:method name="get*" read-only="true"/>
 		</tx:attributes>
 </tx:advice>
;