引言
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。
@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've gone as far as we can go and haven'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 代理后的方法调用执行流程:
评论区