spring--事务详解

spring事务

什么是事务
  • 我们常说的事务,一般指数据库事务。

  • 数据库事务是指 一个逻辑工作单元中执行的一系列(数据库操作),要么一起成功,要么一起失败

  • 当工作单元中的所有操作全部正确完成时,工作单元的操作才会生效,如果检测到一个错误,程序执行回滚操作,恢复原状,要么都执行,要么都不执行

  • 逻辑工作单元就是一个不可分割的操作序列,操作序列就是一系列的数据库操作

  • Spring的事务就是对数据库事务的封装,最后本质的实现还是在数据库,假如数据库不支持事务的话,spring的事务是没有作用的

  • 数据库的事务有开启、执行、提交、回滚,还有关闭,是数据库连接操作

事务ACID
  • 原子性(Atomicity)

    • 整个事务中的所有操作,要么全部完成,要么全部不完成,不可能停滞在中间某个环节。事务在执行过程中发生错误,会被回滚(ROLLBACK)到事务开始前的状态,就像这个事务从来没有执行过一样。原子性由undo log 日志来保证

    • undo log 日志:在MySQL数据库运行的过程中,会在后台生成日志,例如:在完成订单事务时,你写入insert 语句时,undo log日志会在后台为你生成一个对应的delete语句,当事务执行失败后,在回滚过程中只需要在后台执行delete语句即可

  • 一致性(Consistency)

    • 事务前后的数据完整性要保持一致

    • 一个事务可以封装状态改变(除非它是一个只读的)。事务必须始终保持系统处于一致的状态,不管在任何给定的时间并发事务有多少。也就是说:如果事务是并发多个,系统也必须如同串行事务一样操作。其主要特征是最终一致性

    • 使用业务的最终目的,由业务代码正确逻辑保证

    • 一致性是事务的最终目标,原子性,隔离性,持久性是实现一致性的手段

  • 隔离性(Isolation)

    • 针对多个用户进行操作,主要排除其他事务对本次事务的影响

    • 多个用户并发访问数据库时,数据库为每个用户开启的事务,不能被其他事务的操作数据所干扰

  • 持久性(Durability)

    • 事务一旦提交就不可逆

    • 事务没有提交,恢复到原状态,在事务完成以后,该事务对数据库所作的更改便持久的保存在数据库或文件之中,并不会被回滚。就算是宕机或者断电,也不会出现丢失的情况。

    • 持久性由redo log日志来保证

三大基础设施

spring中对事务的支持提供三大基础设施

  1. PlatformTransactionManager

  2. TransactionDefinition

  3. TransactionStatus

事务管理器(PlatformTransactionManager

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框架实现事务管理的机制。

使用Spring管理事务,注意头文件的约束导入 : tx

 <xmlns:tx="http://www.springframework.org/schema/tx"
 ​
 http://www.springframework.org/schema/tx
 http://www.springframework.org/schema/tx/spring-tx.xsd">

事务管理器

  • 无论使用Spring的哪种事务管理策略(编程式或者声明式)事务管理器都是必须的。

  • 就是 Spring的核心事务管理抽象,管理封装了一组独立于技术的方法。

JDBC事务

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

配置好事务管理器后我们需要去配置事务的通知

 <!--配置事务通知-->
 <tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <!--配置哪些方法使用什么样的事务,配置事务的传播特性-->
        <tx:method name="add" propagation="REQUIRED"/>
        <tx:method name="delete" propagation="REQUIRED"/>
        <tx:method name="update" propagation="REQUIRED"/>
        <tx:method name="search*" propagation="REQUIRED"/>
        <tx:method name="get" read-only="true"/>
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
 </tx:advice>
基本事务属性的定义(TransactionDefinition)

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

事务属性是实现事务特性重要组成部分

事务属性一般指的就是就是spring的事务属性,常用的有五种spring事务属性

隔离性

就是事务的隔离级别,分为 read uncommitted(读未提交),read committed(读已提交),repeatable read(可重复读), Serialization(串行化),不过spring事务会在这个基础上多一种default(就是已连接的数据库级别为准)MySQL的默认隔离级别是repeatable read,所以default就是可重复读。

注意事项:数据库事务中的隔离性是特征与目标,spring事务中的隔离性是实现该目标的方式

传播性

事务传播行为是为了解决业务层方法之间相互调用的事务问题,当一个事务方法被另一个事务方法调用时(A方法调用B方法)当方法之间相互调用时,事务会以何种状态存在,这些规则就涉及到事物的传播性

三条规则来定义这七种传播性:

  1. 创建新事务还是加入当前事务

  2. 多个事务并存的时候是否会互相影响

  3. 有实物抛异常,还是没事务抛异常

 public enum Propagation {
 ​
 ​
     REQUIRED(TransactionDefinition.PROPAGATION_REQUIRED),
  
     SUPPORTS(TransactionDefinition.PROPAGATION_SUPPORTS),
 ​
     MANDATORY(TransactionDefinition.PROPAGATION_MANDATORY),
 ​
     REQUIRES_NEW(TransactionDefinition.PROPAGATION_REQUIRES_NEW),
 ​
     NOT_SUPPORTED(TransactionDefinition.PROPAGATION_NOT_SUPPORTED),
 ​
     NEVER(TransactionDefinition.PROPAGATION_NEVER),
  
     NESTED(TransactionDefinition.PROPAGATION_NESTED);
 ​
 ​
     private final int value;
 ​
 ​
     Propagation(int value) {
        this.value = value;
     }
 ​
     public int value() {
        return this.value;
     }

是spring特有的事务管理概念,一共有七种传播方式:

传播性描述
REQUIRED(默认)如果当前存在事务,则加入到这个事务,如果当前没有事务,就新建一个事务,
SUPPORTS如果当前存在事务,则加入到这个事务,如果没有当前事务,就以非事务方法执行
MANDATORY如果当前存在事务,则加入到这个事务,如果当前没有事务,就抛出异常。
REQUIRES_NEW创建一个新的事务,如果当前存在事务,把当前事务挂起。(脱离当前事务的影响)
NOT_SUPPORTED以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。(脱离当前事务的影响)
NEVER以非事务方式执行操作,如果当前事务存在,则抛出异常。
NESTED如果当前存在事务,则创建一个新的事务作为当前事务的嵌套事务来执行,如果没有,则新建一个事务

代码展示:

 package com.lyc.service;
 ​
 import com.lyc.mapper.UserMapperIpml;
 import com.lyc.pojo.User;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.support.TransactionSynchronizationManager;
 ​
 import javax.annotation.Resource;
 import java.util.List;
 ​
 @Service
 public class TxAService {
     @Resource
     private TxBService bService;
     @Resource
     private UserMapperIpml userMapperIpml;
     @Transactional    //添加事务注解,默认是只读事务,相当于开启了一个事务
     public void handle1(int id){
         User user = userMapperIpml.selectUserById(id);
         userMapperIpml.updateUser(user);
         String name = TransactionSynchronizationManager.getCurrentTransactionName();
         System.out.println("handle1加入的事务名称:"+name);
         // 调用bService
         bService.handle2(id);
         if (1==1){
             throw new RuntimeException("异常");
         }
     }
 ​
 }

 package com.lyc.service;
 ​
 import com.lyc.mapper.UserMapperIpml;
 import com.lyc.pojo.User;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.support.TransactionSynchronizationManager;
 ​
 import javax.annotation.Resource;
 ​
 @Service
 public class TxBService {
     @Resource
     private UserMapperIpml  userMapperIpml;
     //无论是否加上@Transactional注解,都会开启事务,但是只有加上@Transactional注解的方法才会回滚
     @Transactional(propagation = Propagation.SUPPORTS)
     public void handle2(int id)
     {
         User user = userMapperIpml.selectUserById(id + 1);
         userMapperIpml.updateUser(user);
         // 获取当前事务的名称
         String name = TransactionSynchronizationManager.getCurrentTransactionName();
         System.out.println("当前事务的名称:" + name);
         if(true){
             throw new RuntimeException("事务回滚");
         }
     }
 }

代码分析:

在测试中,handle1事务方法里面调用handle2事务方法

REQUIRED(默认)

如果当前存在事务,则加入到这个事务,如果当前没有事务,就新建一个事务,

在handle2方法中,无论加不加transactional注解都会加入到handle1事务中,而不会自己创建,而在handle1方法中,事务成功回滚,在此次操作中handle1方法和handle2方法的事务都是handle1

如果handle1方法没有事务(没有@Transactional),handle2方法有事务(由@Transactional),则会新创建一个事务handle2,handle1方法没有事务,handle2方法的事务为handle2

SUPPORTS

如果当前存在事务,则加入到这个事务,如果没有当前事务,就以非事务方法执行

handle1方法如果有事务,handle2方法的事务传播性(行为)为SUPPORTS,则handle1和handle2方法的事务都为handle1,如果handle1方法没有事务,handle2方法则以非事务的方式继续运行,即无法进行事务回滚,也不会建立事务

MANDATORY

如果当前存在事务,则加入到这个事务,如果当前没有事务,就抛出异常。

handle1方法如果有事务,handle2方法的事务传播性(行为)为MANDATORY,则handle1和handle2方法的事务都为handle1,如果handle1方法没有事务,handle2方法则直接抛出异常

REQUIRES_NEW

创建一个新的事务,如果当前存在事务,把当前事务挂起。(脱离当前事务的影响)

handle1方法如果有事务,handle2方法的事务传播性(行为)为REQUIRE_NEW,则handle2会新建一个事务并脱离前面一个事务的影响,独立出来,如果handle1事务回滚,handle2事务不会回滚

比较适合 有方法大概率出错但又不想影响到其他方法的正常执行

NOT_SUPPORTED

以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。(脱离当前事务的影响)

handle1方法无论是否有事务,handle2方法的事务传播性(行为)为NOT_SUPPORTED,handle2都不会加入这个事务,也不会自己创建事务,handle2永远处于无事务的状态,也不会回滚

NEVER

以非事务方式执行操作,如果当前事务存在,则抛出异常。

handle2方法的事务传播性(行为)为NEVER,如果handle2存在事务就会抛异常

NESTED

如果当前存在事务,则创建一个新的事务作为当前事务的嵌套事务来执行,如果没有,则新建一个事务

如果handle1报错,则handle2也报错(外面的大事务会影响里面的小事务),即handle1方法报错回滚,则handle2方法也会回滚,如果handle2出现了异常回滚,只需要将在handle1方法中的handle2异常捕获,则handle1方法不会出现异常,不会回滚。

如果handle1方法没有事务(没有@Transactional),handle2方法有事务(由@Transactional),则会新创建一个事务handle2,handle1方法没有事务,handle2方法的事务为handle2

REQUIRES_NEW与NESTED的区别:

回滚规则

默认情况下,事务只有遇到运行期异常(RunException的子类)以及Error才会回滚,在遇到检查型(checkedException)异常时不会回滚

例如发生IOException并不会导致事务回滚,如果想要在发生IOException时也想触发事务回滚

代码展示:

 @Transcational(rollbackFor = IOException.class)
 public void handle2(){
     jdbcTemplate.update("update user set money = ? where username = ?;",1,"zhangsan");
     accountService.handle1();
 }

我们也可以指定在发生某些异常时不回滚,例如当系统抛出AirthmeticException异常不触发事务回滚

代码展示:

 @Transcational(norollbackFor = AirthmeticException.class)
 public void handle2(){
     jdbcTemplate.update("update user set money = ? where username = ?;",1,"zhangsan");
     accountService.handle1();
 }

是否只读

一般用在业务方法中全部都是查询的代码,没有增删改,作用:

让这些相同的查询,可以查到相同的结果。

语法:

 @Transactional(readOnly = true) //

只要在Transactional注解中添加(readOnly = true),就能让一个事务中相同的查询查到相同的结果(可重复读),添加之后,事务就会知道这只是一个只读的事件,就可以获得数据库与驱动的优化,性能会更好

超时时间

通过设置一个事务允许执行的最长时间,超过这个时间的限制,但是事务还没有提交,就会自动回滚

语法:

 @Transactional(timeout = 10) // 10秒内事务没有提交,则自动回滚 
事务状态(TransactionStatus

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

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

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

spring中的事务管理

Spring在不同的事务管理API之上定义了一个抽象层,使得开发人员不必了解底层的事务管理API就可以使用Spring的事务管理机制。Spring支持编程式事务管理和声明式事务管理

  • 声明式事务:

    • 一般情况下比编程式事务好用。

    • 将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。

    • 将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理。

  • 编程式事务:

    • 将事务管理代码嵌到业务方法中来控制事务的提交和回滚

    • 缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码

编程式事务类似于JDBC事务的写法,需要将事务的代码嵌入到业务逻辑中,代码的耦合度较高

声明式事务通过AOP的思想,将事务与业务逻辑代码解耦(基于@Transactional注解或者基于XML配置)

编程式事务

通过PlatformTransactionManager或者TransactionTemplate可以实现编程式事务

在spring boot项目中,这两个对象spring boot会自动提供,直接使用即可

3.2.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则没有返回值。

3.2.2 使用PlatformTransactionManager

示例代码如下:

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

在需要更精细化更复杂的事务控制,对性能有极致要求的场景下使用更合适,还有一些声明式事务失效的场景下使用,比如类的方法自调用,或者使用私有方法

声明式事务
底层实现
  • 当目标类被spring管理时(即@Component,@Service,@controller,@Repository注解时),spring会为目标对象创建一个代理对象。代理对象负责拦截目标方法的调用,并在必要时应用事务管理(AOP思想)

  • 代理对象内部包含一个事务拦截器TransactionInterceptor,负责处理事务相关的逻辑

  • 事务拦截器会检查方法上是否添加了@Transactional注解,来决定是否应用事务

  • 事务拦截器在目标方法执行前后应用事务通知,(前置通知和后置通知)在方法执行前,事务拦截器启动事务;在方法执行后,根据方法的执行结果决定事务的提交或回滚

代码展示:

 package com.lyc.service;
 ​
 import com.lyc.mapper.UserMapperIpml;
 import com.lyc.pojo.User;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.support.TransactionSynchronizationManager;
 ​
 import javax.annotation.Resource;
 import java.util.List;
 ​
 @Service
 public class TxAService {
     @Resource
     private TxBService bService;
     @Resource
     private UserMapperIpml userMapperIpml;
     @Transactional    //添加事务注解,默认是只读事务,相当于开启了一个事务
     public void handle1(int id){
         User user = userMapperIpml.selectUserById(id);
         userMapperIpml.updateUser(user);
         String name = TransactionSynchronizationManager.getCurrentTransactionName();
         System.out.println("handle1加入的事务名称:"+name);
         // 调用bService
         bService.handle2(id);
         if (1==1){
             throw new RuntimeException("异常");
         }
     }
 ​
 }

 package com.lyc.service;
 ​
 import com.lyc.mapper.UserMapperIpml;
 import com.lyc.pojo.User;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Propagation;
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.transaction.support.TransactionSynchronizationManager;
 ​
 import javax.annotation.Resource;
 ​
 @Service
 public class TxBService {
     @Resource
     private UserMapperIpml  userMapperIpml;
     //无论是否加上@Transactional注解,都会开启事务,但是只有加上@Transactional注解的方法才会回滚
     @Transactional(propagation = Propagation.SUPPORTS)
     public void handle2(int id)
     {
         User user = userMapperIpml.selectUserById(id + 1);
         userMapperIpml.updateUser(user);
         // 获取当前事务的名称
         String name = TransactionSynchronizationManager.getCurrentTransactionName();
         System.out.println("当前事务的名称:" + name);
         if(true){
             throw new RuntimeException("事务回滚");
         }
     }
 }

handle1方法开启了事务,会有一个前置通知和一个后置通知,而handle2方法同样有一个前置通知和一个后置通知,handle1调用handle2,则handle1有两个前置和两个后置,也就是说有两个高级切面,会被分成四个低级切面,目标方法在启动前,事务拦截器就会启动事务,也就是before1和before2都会启动事务方法,方法执行后,根据方法的执行结果来决定事务的提交和回滚,也就是after1和after2会执行提交或回滚。

  • 事务拦截器还负责事务的传播行为

事务失效原因:
  • 类的自调用:直接调用本类的方法,没有通过代理对象来调用方法,代理对象内部的事务拦截器不会拦截到这次行为。则不可能开启事务

  • 使用私有方法:因为spring的事务管理是基于AOP实现的,AOP代理无法拦截目标对象内部的私有方法调用。直接没有代理对象

  • 使用多线程:在主线程中开启的事务不会自动传播到其创建并执行的子线程中

  • 如果事务回滚,将报错代码用try catch来进行捕获,事务就不会失效

  • 其他比如定义事务属性传播性时定义的是REQUIRES_NEW(脱离当前事务),事务也不会受到影响

希望对大家有所帮助!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值