Spring事务

1,367 阅读10分钟

Spring事务概述

Spring中事务可以有编程式、声明式两种方式来实现,所谓编程式事务是指我们需要在业务中加入实现事务的相关代码,包括定义事务的开始,事务提交、事务回滚。而对于声明式事务,是通过AOP来实现的,对于声明式事务管理我们只要在需要使用到事务的方法上加上@Transational注解或者通过XML配置文件进行事务的配置(不常用),不需要在业务逻辑代码中掺杂事务管理的代码,提高了我们的开发效率。

Spring事务管理

Spring事务管理的核心接口

Spring事务管理的实现涉及三个核心接口,分别为:PlatformTransactionManager、TransactionDefinition、TransactionStatus,他们三个接口都位于org.springframework.transaction包中。

  • PlatformTransactionManager:PlatformTransactionManager接口是Spring提供平台事务管理器,什么叫做平台事务管理器呢?意思就是Spring并不直接管理事务,而是通过这个平台接口,将事务管理的职责委托给JDBC或者Hibernate等持久化机制所提供的相关平台框架的事务来实现的。当底层采用不同的持久层技术时,系统只需使用不同的PlatformTransactionManager实现类就可以了。该接口中提供了3个事务操作方法,如下:
public interface PlatformTransactionManager {
    //根据TransactionDefinition获取事务状态信息TransactionStatus
    //返回的TransactionStatus对象就表示一个事务,它被关联到当前执行的线程上。
    TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
    //提交事务
    void commit(TransactionStatus var1) throws TransactionException;
    //回滚事务
    void rollback(TransactionStatus var1) throws TransactionException;
}
  • TransactionDefinition:TransactionDefinition接口是事务定义的对象,该对象中定义了事务规则,并提供了获取事务相关信息的方法,如下:
public interface TransactionDefinition {
    //获取事务的传播行为
    int getPropagationBehavior();
    //获取事务的隔离级别
    int getIsolationLevel();
    //获取事务的超时时间
    int getTimeout();
    //判断事务是否只读
    boolean isReadOnly();
    //获取事务对象名称
    @Nullable ---->表示该方法可以返回NULL值
    String getName();
  • TransactionStatus:TransactionStatus接口是事务的状态,它表示某一时间点上事务的状态信息,它会被关联到当前执行的线程上。该接口具有以下几个方法:
public interface TransactionStatus extends SavepointManager, Flushable {
    //判断是否是新事务
    boolean isNewTransaction();
    //判断是否存在保存点
    boolean hasSavepoint();
    //设置事务回滚
    void setRollbackOnly();
    //判断是否回滚
    boolean isRollbackOnly();
    //刷新事务
    void flush();
    //判断事务是否完成
    boolean isCompleted();
}

基于XML的声明式事务管理

基于XML的声明式事务管理是通过在Spring配置文件中配置事务规则的相关声明来实现的,其需要配置的内容如下:

  • 在Spring的XMl配置文件中添加如下:
<!--连接池-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/ssm?serverTimezone=GMT"/>
    <property name="user" value="root"/>
    <property name="password" value="root"/>
</bean>

<!--(1)配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>
<!--(2)配置事务通知(即增强方法)-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <!--指对哪些方法起作用,find*,表示对那些方法名称开头包含find的方法起作用-->
        <!--以及配置事务详情、如隔离级别,传播特性等-->
        <tx:method name="find*" read-only="true"></tx:method>
    </tx:attributes>
</tx:advice>
<!--(3)配置AOP增强,让spring自动对目标对象生成代理对象-->
<aop:config>
    <!--定义切点表达式-->
    <aop:pointcut id="pc" expression="execution(* com.demo.service.impl.*ServiceImpl.*(..))"/>
    <!--将切入点与通知整合-->
    <aop:advisor advice-ref="txAdvice" pointcut-ref="pc"></aop:advisor>
</aop:config>
  • 通过上面的配置,当我们执行那些以find开头的方法时,都会被增强,Spring会对方法中的数据库操作进行事务控制。

基于Annotation注解的声明式事务管理(最常使用)

基于Annotation注解的声明式事务管理方式使我们开发中最常使用的方式,这种方式非常简单,我们只需要在Spring的配置文件中配置如下,然后在需要使用事务的Bean类的方法放上加上@Transaction注解即可。

<!--连接池-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
    <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/ssm?serverTimezone=GMT"/>
    <property name="user" value="root"/>
    <property name="password" value="root"/>
</bean>

<!--(1)配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!--(2)注册事务注解驱动-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

Spring声明式事务的实现原理

Spring声明式事务操作是AOP的一个核心体现,当一个方法添加@Transactional注解之后( 基于Annotation的方式 ),Spring会基于这个类生成代理对象,会将代理对象作为bean。其本质是当使用这个代理对象的方法的时候会对该方法的前后进行拦截(具体由TransationalInterceptor来实现的),如果有事务要处理,那么会先把事务的自动提交给关闭,然后调用invoke方法实现具体的逻辑(SQL操作等),如果执行过程中没有异常发生,则通过commitTransationAfterReturning来完成事务的提交操作,具体的提交逻辑由doCommit方法来实现,如果执行过程中发生异常,则通过completeTransationThrowing来完成事务的回滚操作,回滚的具体逻辑由doRollBack方法来实现。

@Transational注解的常用参数

Transaction注解如果添加在Bean类上,则表示事务的设置会对整个Bean中的所有方法其作用,如果添加在方法上,则只对该方法起作用。

  • propagation:设置Spring事务的传播特性,如@Transactional(propagation = Propagation.REQUIRES_NEW)

  • isolation:设置Spring事务的隔离级别,如@Transactional(isolation = Isolation.READ_UNCOMMITTED)

  • rollbackFor:设置Spring事务当方法中抛出指定异常数组中的异常时进行事务回滚,如指定单个异常类@Transational(rollbackFor=RunTimeException.class),指定多个异常类用花括号{}包括多个异常类@Transational(rollbackFor={RunTimeException.class,SQLException.class})

  • rollbackForClassName:与rollbackFor不同的是,rollbackForClassName只需给出异常类的名称即可,如@Transational(rollbackFor="RunTimeException")

  • noRollbackFor:用于指定遇到特定异常时不回滚。如当遇到RunTimeException时不回滚,@Transational(noRollbackFor=RunTimeException.class)

  • noRollbackForClassName:用于指定遇到特定多个异常时不回滚事务。

  • timeout:设置事务的超时时间

Spring事务隔离级别

Spring事务的隔离级别和MySQL数据库的隔离级别一致。关于MySQL事务,可以查看我的另一篇文章MySQL事务。在Spring中默认是采用MySQL默认的隔离级别,即可重复读repeatable read,通过设置@Transational注解的isolation参数值就可以根据业务的需要指定Spring事务的隔离级别。

  • @Transactional(isolation = Isolation_DEFAULT):采用后端数据库默认的隔离级别。

  • @Transactional(isolation = Isolation.READ_UNCOMMITTED):读未提交(脏读, 不可重复读)

  • @Transactional(isolation = Isolation.READ_COMMITTED):读已提交(不可重复读、幻读)

  • @Transactional(isolation = Isolation.REPEATABLE_READ):可重复读(幻读)

  • @Transactional(isolation = Isolation.SERIALIZABLE):串行化。

Spring事务的传播机制

什么是Spring的传播行为 多个事务方法相互调用时(如当A类a方法调用B类中的b方法时,那么B类中的b方法是按b方法的事务还是A类中a方法的事务来执行呢?),事务如何在这些方法之间进行传播。在事务的管理过程中,传播行为可以控制是否需要创建事务以及事务如何创建。Spring中提供了7种不同的传播特性来保证事务的正常执行。

7种传播特性(重点)

  • Required(必须的):默认的传播特性,如果当前没有事务,则新建事务,否则加入这个事务。如a方法调用b方法,如果a方法存在事务,那么b方法就按a方法的事务来执行,如果a方法不存在事务,那么b方法就自己新建事务。

  • Supports(支持的):如果当前存在事务,则加入当前事务,否则以非事务的方式执行。如a方法调用b方法,如果a方法存在事务,那么b方法就按a方法的事务来执行,如果a方法不存在事务,那么b方法就以非事务的方式执行。

  • Mandatory(强制的):如果当前存在事务,则加入事务,否则抛出异常。如a方法调用b方法,如果a方法存在事务,那么b方法就按a方法的事务来执行,如果a方法不存在事务,那么b方法就抛出异常。

  • Required_new:创建一个新事务,如果当前存在事务,则挂起事务。如a方法调用b方法,如果a方法存在事务,那么a方法的事务就会被挂起,然后b方法自己创建新的事务来执行。如果a方法不存在事务,b方法也是新建事务来执行。

  • Not_supported:以非事务的方式执行,如果当前存在事务,则挂起事务。如a方法调用b方法,如果a方法存在事务,那么a方法的事务就会被挂起,然后b方法以非事务的方式执行。如果a方法不存在事务,那么b方法也是按非事务的方式执行。

  • Never:不使用事务,如果当前存在事务,则抛出异常。如a方法调用b方法,如果a方法存在事务,那么b方法会抛出异常。如果a方法不存在事务,那么b方法按非事务的方式执行。

  • Nested:如果当前事务存在,则嵌套在事务中执行,否则和Required的操作一样。如a方法调用b方法,如果a方法存在事务,则会在a方法的事务执行过程中设置一个保存点,然后b方法会按a方法的事务执行,如果b方法执行失败,则会回滚到之前设置的保存点。否则b方法自己新建事务来执行。

🚨 挂起事务:所谓挂起事务是指,比如有两个方法AB,A方法支持事务,B方法不支持事务,那么A方法中对于数据库的操作是在事务的控制下完成的,而在A方法中调用B方法,那么A方法中开启的事务会挂起,B方法中的数据库操作不在事务的控制下完成,当B方法执行完成返回后,A方法继续在事务的控制下进行相关的数据库操作。

Required和Required_new的区别: Required_new是新建一个事务并且新开启的事务和原有的事务无关,而Nested则是如果当前存在事务时会开启一个嵌套事务,在Nested下,父事务回滚时,子事务也会回滚,而Required_new下,原有事务回滚,不会影响新开启的事务

Nested和Required的区别 Required情况下,调用方a存在事务时,则被调用方b和调用方a使用同一个事务,那么被调用方b出现异常时,由于共同使用同一个事务,所以无论是否捕获异常,事务都会回滚,而在Nested下,被调用方b发生异常时,调用方a可以捕获异常,这样只有子事务回滚,父事务不会回滚。

Spring是如何实现传播机制的

Spring的传播机制其实是通过ThreadLocal来实现的,ThreadLocal实现的就是为每一个线程提供一份共享变量的副本,在这个线程中这个变量副本是线程独享的。关于ThreadLocal,可以查看我的另一篇文章,⏩ThreadLocal,你掌握了吗?。那么ThreadLocal是如何实现事务的传播机制呢?我们都知道,当在一个线程中调用一个方法时,在这个方法中又调用了另一个方法,那么其实这两个方法都是由同一个线程来执行的,而在Spring的事务传播机制中,ThreadLocal中存储的是当前事务的相关信息,每次新建一个事务时,都会创建一个新的ThreaLocal来存储事务相关信息,那么Spring就可以根据我们定义的事务的传播特性,选择性地从ThreadLocal中获取指定的事务信息,从而实现事务的传播机制。(以上为个人理解,如果有错误,还望大佬们呢留言提出指正!👼)

Spring事务回滚规则

在Spring的默认配置下,Spring事务只有在抛出未受检异常即RuntimeException及其子类或者抛出Error错误才能回滚事务,而抛出受检异常则不会回滚事务。关于异常,可以查看我的关于异常的一篇文章 Java异常机制。当然,Spring也可以在@Tranctional注解中通过设置rollbackForrollbackForClassName参数,当抛出事先设置的异常时才回滚事务,可以根据我们的业务灵活运用。

以上就是关于Spring事务的相关知识点,如果有错误的地方,还请留言指正,如果觉得本文对你有帮助那就点个赞吧︿( ̄︶ ̄)︿

默认标题_动态分割线_2021-07-15-0.gif