周怡的博客

  • 首页
  • 文章归档
  • 默认分类
  • 关于页面

  • 搜索
Java Spring Boot Spring JVM 正则表达式 NodeJS CSS 微服务 MySQL Nginx 设计模式

Spring @Transactional 注解的使用

发表于 2020-06-21 | 0 | 阅读次数 855

引言

Spring的事务管理分为编程式和声明式两种方式。编程式事务指的是通过编码方式实现事务;声明式事务基于 AOP,将具体业务逻辑与事务处理解耦。使用Spring的声明式风格事务有两种使用方式,一是通过XML配置文件声明事务规则,二是使用@Transactional注解的方式。目前流行的方式是减少XML配置文件,使用Java风格的注解配置。在此,我主要介绍使用@Transactional注解的方式实现事务管理。

Spring @Transactional注解配置管理事务的步骤

使用基于@Transactional注解管理事务的配置分为三个步骤。

步骤一:在Spring XML配置文件中引入命名空间

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:aop="http://www.springframework.org/schema/aop"
    xmlns:tx="http://www.springframework.org/schema/tx"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd">

步骤二:在 xml 配置文件中,添加事务管理器bean

<!-- 使用annotation驱动定义事务 -->
<tx:annotation-driven />
<!-- 配置简单事务管理器,单个数据源 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

步骤三:将@Transactional注解添加到需要使用事务的方法或者类上,并设置合适的属性配置。

@Transactional 注解的属性信息如下表所示:

@Service
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public class PayServiceImpl implements IPayService {
    // 省略方法...
}

PayServiceImpl所有方法都支持事务并且是只读事务。需要注意的是,当类级别配置了@Transactional,方法级别也配置了@Transactional,会以方法级别的事务属性信息来管理事务。

@Transactional的传播行为和隔离级别

从上面的@Transactional注解的属性信息可知,事务的传播行为使用propagation属性配置,下面是事务的传播行为说明:

事务传播行为说明
@Transactional(propagation=Propagation.REQUIRED)默认配置,如果有事务,那么加入事务,没有的话新建一个。
@Transactional(propagation=Propagation.SUPPORTS)如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
@Transactional(propagation=Propagation.MANDATORY)必须在一个已有的事务中执行,否则抛出异常。
@Transactional(propagation=Propagation.REQUIRES_NEW)无论当前是否存在事务,都创建一个新的事务,原来的事务挂起,新的执行完毕后,继续执行老的事务。
@Transactional(propagation=Propagation.NOT_SUPPORTED)以非事务方式运行,如果当前存在事务,则把当前事务挂起。
@Transactional(propagation=Propagation.NEVER)以非事务方式运行,如果当前存在事务,则抛出异常。
@Transactional(propagation=Propagation.NESTED)如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则进行与Propagation.REQUIRED类似的操作。

值得注意一下的是Propagation.NESTED与Propagation.REQUIRES_NEW的区别:

Propagation.REQUIRES_NEW另起一个事务,将会与它的父事务相互独立,而Nested的事务和它的父事务是相依的,它的提交是要等和它的父事务一块提交的。也就是说,如果父事务最后回滚,它也会回滚的。Nested事务的好处是它有一个savepoint。也就是说ServiceB.methodB失败回滚,那么ServiceA.methodA也会回滚到savepoint点上,ServiceA.methodA可以选择另外一个分支,比如ServiceC.methodC,继续执行,来尝试完成自己的事务。但这个事务并没有在JavaEE标准中定义。

接下来介绍事务隔离级别,事务隔离级别的控制由isolation属性设置:

事务隔离级别说明
@Transactional(isolation = Isolation.DEFAULT)默认,取决于数据库
@Transactional(isolation = Isolation.READ_UNCOMMITTED)读取未提交数据(会出现脏读, 不可重复读),基本不用
@Transactional(isolation = Isolation.READ_COMMITTED)读取已提交数据(会出现不可重复读和幻读)
@Transactional(isolation = Isolation.REPEATABLE_READ)可重复读(会出现幻读)
@Transactional(isolation = Isolation.SERIALIZABLE)串行化

脏读: 一个事务读取到另一事务未提交的更新数据。

不可重复读 : 在同一事务中,多次读取同一数据返回的结果有所不同,换句话说,后续读取可以读到另一事务已提交的更新数据。相反,“可重复读”在同一事务中多次读取数据时,能够保证所读数据一样,也就是后续读取不能读到另一事务已提交的更新数据。

幻读 : 一个事务读到另一个事务已提交的insert数据。

Spring @Transactional的实现机制

在调用声明@Transactional的目标方法时,Spring默认使用AOP代理,在代码运行时生成一个代理对象,根据@Transactional 的属性配置信息,这个代理对象决定该声明@Transactional的目标方法是否被拦截器TransactionInterceptor拦截,在TransactionInterceptor拦截时,会在目标方法开始执行之前创建并加入事务,并执行目标方法的逻辑, 最后根据执行情况是否出现异常,利用抽象事务管理器AbstractPlatformTransactionManager操作数据源DataSource提交或回滚事务。

事务管理的框架是由抽象事务管理器AbstractPlatformTransactionManager来提供的,而具体的底层事务处理实现,由PlatformTransactionManager的具体实现类来实现,如事务管理器DataSourceTransactionManager。不同的事务管理器管理不同的DataSource。

AbstractPlatformTransactionManager

@Transactional使用注意事项

Spring的事务管理是基于接口代理(jdk)或动态字节码(cglib)技术,通过AOP实施事务增强的。

@Transaction应用public方法才有效

(1)对于基于接口动态代理的AOP事务增强来说,由于接口的方法是public的,这就要求实现类的实现方法必须是public的(不能是protected,private等),同时不能使用static的修饰符。所以,可以实施接口动态代理的方法只能是使用“public”或“public final”修饰符的方法,其它方法不可能被动态代理,相应的也就不能实施AOP增强,也即不能进行Spring事务增强。

(2)基于CGLib字节码动态代理的方案是通过扩展被增强类,动态创建子类的方式进行AOP增强植入的。由于使用final,static,private修饰符的方法都不能被子类覆盖,所以这些方法将不能被实施的AOP增强。

正确使用@Transactional的rollbackFor属性

默认情况下,如果在事务中抛出了未检查异常(继承自RuntimeException的异常)或者Error,Spring会回滚事务;否则,Spring将不会回滚事务。
如果在事务中抛出了其他类型的异常,并期望Spring能够回滚事务,可以指定rollbackFor属性。例如:
@Transactional(propagation= Propagation.REQUIRED, rollbackFor= PayException.class)
通过分析Spring源码可以知道,使用递归调用寻找异常类,若在目标方法中抛出的异常是rollbackFor指定的异常的子类,事务同样会回滚。

/**
 * Return the depth of the superclass matching.
 * <p>{@code 0} means {@code ex} matches exactly. Returns
 * {@code -1} if there is no match. Otherwise, returns depth with the
 * lowest depth winning.
 */
public int getDepth(Throwable ex) {
    return getDepth(ex.getClass(), 0);
}

private int getDepth(Class<?> exceptionClass, int depth) {
    if (exceptionClass.getName().contains(this.exceptionName)) {
        // Found it!
        return depth;
    }
    // If we&#039;ve gone as far as we can go and haven&#039;t found it...
    if (exceptionClass == Throwable.class) {
        return -1;
    }
    return getDepth(exceptionClass.getSuperclass(), depth + 1);
}

避免Spring AOP的自我调用问题

在Spring AOP代理下,只有目标方法由外部调用,目标方法才由Spring生成的代理对象来管理,这会造成自调用问题。若在同一个类中的其他没有添加@Transactional 注解的方法内部调用了有@Transactional注解的方法,有@Transactional 注解的方法的事务将被忽略,出现异常事务时,不会回滚。

@Service
public class OrderService {
    public void insert() {
        this.insertOrder();
    }
    @Transactional(propagation = Propagation.REQUIRES_NEW)
    public void insertOrder() {
        // 省略操作代码
    }
}

在insert方法中直接使用this指向目标对象调用insertOrder方法,因此调用this.insertOrder()将不会执行insertOrder事务切面,即不会执行事务增强,因此insertOrder方法的事务定义“@Transactional(propagation = Propagation.REQUIRES_NEW)”将不会执行增强,事务被忽略,出现异常事务不会发生回滚。

下图为Spring事务使用AOP代理后的方法调用执行流程:

spring aop

参考资料

网易乐得技术团队:Spring @Transactional原理及使用

  • 本文作者: 周怡
  • 本文链接: https://www.zhouyi.tech/archives/springtransactional注解的使用
  • 版权声明: 本博客所有文章除特别声明外,均采用CC BY-NC-SA 3.0 许可协议。转载请注明出处!
# Java # Spring Boot # Spring # JVM # 正则表达式 # NodeJS # CSS # 微服务 # MySQL # Nginx # 设计模式
类的加载过程
Spring Boot 集成 Liquibase
  • 文章目录
  • 站点概览
周怡

周怡

30 日志
5 分类
11 标签
RSS
Creative Commons
© 2022 周怡
由 Halo 强力驱动
|
主题 - NexT.Mist v5.1.4
粤ICP备17144706号