标签搜索

目 录CONTENT

文章目录

Spring @Transactional 注解的使用

周怡的技术日记
2020-11-29 / 0 评论 / 0 点赞 / 95 阅读 / 2,332 字
温馨提示:
本文最后更新于 2022-06-30,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

引言

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操作数据源D ataSource 提交或回滚事务。

事务管理的框架是由抽象事务管理器 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 AO P代理下,只有目标方法由外部调用,目标方法才由 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-856540388ee74eec8bb13fa9cc214af4

0

评论区