引言
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。
@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 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代理后的方法调用执行流程: