Administrator
发布于 2022-10-08 / 50 阅读
0
0

Spring声明式事务

事务概述

原子性:事务的原子性要求事务中的所有操作要么都执行,要么都不执行。

一致性:所有数据都处于满足业务规则的一致性状态。

隔离性:隔离性原则要求多个事务在并发执行过程中不会互相干扰

持久性:对数据的修改永久的保存下来

Spring事务管理

1、编程式事务管理
  使用原生的JDBC API进行事务管理

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

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

 

2、声明式事务
 以前通过复杂的编程来编写一个事务,现在替换为只需要告诉Spring哪个方法是事务方法即可;Spring自动进行事务控制;

事务管理代码的固定模式作为一种横切关注点,可以通过AOP方法模块化,
进而借助Spring AOP框架实现声明式事务管理。

AOP环绕通知
//获取连接
//设置非自动提交
   目标代码执行
//正常提交
//异常回滚
//最终关闭

但是,自己写一个切面进行事务控制还是很复杂的,考虑的因素有很多,比如多线程获取到的connection不同的问题等
所以Spring已经考虑到这个问题,这样的事务切面已经有了。(事务切面===事务管理器)

image
 

事务管理器可以在目标方法运行前后进行事务控制(事务切面);

快速的为某个方法添加事务:
1)、配置出这个事务管理器让他工作;
2)、开启基于注解的事务
3)、给事务方法加@Transactional注解

 <context:component-scan base-package="com.lly"/>
    <!--开启基于注解的AOP功能-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>


    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/tx?serverTimezone=UTC"></property>
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"></property>
    </bean>



    <!--事务控制-->
    <!--1、配置事务管理器(切面):让其进行事务控制;一定要导入面向切面编程的jar包-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--控制数据源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!--2、开启给予注解的事务控制模式:依赖tx名称空间-->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
    <!--3、给事务方法加注解即可@Transactional-->

 
在测试的时候进行事务控制可能会失败,原因如下:
image-1665217092205
 
将自动提交的属性设置为false

事务的细节一:超时属性和只读设置

由于事务可以在行和表上获得锁,因此长事务会占用资源,并对整体性能产生影响。

如果一个事物只读取数据但不做修改,数据库引擎可以对这个事务进行优化。

超时事务属性:事务在强制回滚之前可以保持多久。这样可以防止长期运行的事务占用资源。秒为单位。

只读事务属性: 表示这个事务只读取数据但不更新数据, 这样可以帮助数据库引擎优化事务。
 @Transactional(timeout = 1,readOnly = true)

事务的细节二:异常回滚

运行时异常(非检查异常):可以不用处理,默认都会回滚。
编译时异常(检查异常):要么try-catch;要么throws出去。默认不回滚。
事务的回滚:默认发生运行时异常时都回滚,发生编译时异常时不回滚。
  1. rollbackFor属性:指定遇到时必须进行回滚的异常类型,可以为多个
  2. noRollbackFor属性:指定遇到时不回滚的异常类型,可以为多个
@Transactional(rollbackFor = Exception.class,noRollbackFor = {Exception.class})

事务的细节三:调整隔离级别

  数据库必须要有隔离并发运行各个事务的能力

数据库事务并发问题
假设现在有两个事务: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表时,多出了一些行。

隔离级别

读未提交:READ UNCOMMITTED

允许Transaction01读取Transaction02未提交的修改。

读已提交:READ COMMITTED

要求Transaction01只能读取Transaction02已提交的修改。

可重复读:REPEATABLE READ

确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间
禁止其它事务对这个字段进行更新。

串行化:SERIALIZABLE

确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,
禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。

image-1665220478237

Spring中指定隔离级别

@Transactional(isolation = Isolation.READ_COMMITTED)

演示
读未提交下的脏读

读已提交下的不可重复读问题

可重复读的隔离级别下避免了所有问题

事务的细节四:传播行为

简介:
当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。
例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。
事务的传播行为可以由传播属性指定。Spring定义了7种类传播行为。

传播行为

  传播行为设置
  @Transactional(propagation = Propagation.REQUIRED)

重点为以下两个:
本类的事务方法调用就相当于合成为一个事务(即将代码copy到该方法中,所以会失效)
REQUIRED传播行为
默认会在现有的事务内运行。这个默认的传播行为就是REQUIRED。

REQUIRED:同一辆车(要翻车一起翻)

事务的属性都是继承于大事务;自己的都失效,
坐一辆车车速多少软座硬座不是子事务能自己选的,只能跟着大事务。
只有这一条线上任何一处出现异常,整条先全崩,回滚
将之前事务用的connection传递给这个方法使用

image-1665226124530

REQUIRES_NEW传播行为

表示该方法必须启动一个新事务,并在自己的事务内运行。如果有事务在运行,就应该先挂起它。

REQUIRED_NEW:开一辆新车(你翻车不影响我)

不继承大事务,自己用自己的
一处事务崩,只是他崩了,跟其他没有关系,他自己回滚。
这个方法直接使用新的connection

基于XML文档的声明式事务配置

<!-- 配置事务切面 -->
	<aop:config>
		<aop:pointcut 
			expression="execution(* com.atguigu.tx.component.service.BookShopServiceImpl.purchase(..))" 
			id="txPointCut"/>
		<!-- 将切入点表达式和事务属性配置关联到一起 -->
		<aop:advisor advice-ref="myTx" pointcut-ref="txPointCut"/>
	</aop:config>
	
	<!-- 配置基于XML的声明式事务  -->
	<tx:advice id="myTx" transaction-manager="transactionManager">
		<tx:attributes>
			<!-- 设置具体方法的事务属性 -->
			<tx:method name="find*" read-only="true"/>
			<tx:method name="get*" read-only="true"/>
			<tx:method name="purchase" 
				isolation="READ_COMMITTED" 
	no-rollback-for="java.lang.ArithmeticException,java.lang.NullPointerException"
				propagation="REQUIRES_NEW"
				read-only="false"
				timeout="10"/>
		</tx:attributes>
	</tx:advice>




评论