Spring 事务

MySQL 事务 中介绍过「什么是事务」「事务的特性 (ACID)」「事务的隔离级别」,Spring 事务在这些方面其实是差不多滴,所以就不过多介绍这些内容

Spring 对事务的支持方式

Spring 支持两种方式的事务管理,分别为:编程式事务管理、声明式事务管理

编程式事务管理

顾名思义,编程式事务管理需要通过具体的类编写部分代码来手动管理事务,比如使用TransactionTemplate或者TransactionManager

如果使用 Spring 的 XML 配置来管理 Bean,那么首先需要在文件中添加对应的 Bean:

使用TransactionTemplate类进行编程式事务管理的示例如下:

使用TransactionManager类进行编程式事务管理的示例如下:

声明式事务管理

除了上面不怎么方便的编程式事务管理,还有声明式事务管理,说白了就是基于注解,不需要写大量代码,一个注解就可以搞定,这个注解就是@Transactional

声明式事务管理是使用注解@Transactional,它底层基于 AOP 实现,会为声明有@Transactional的类生成代理对象,在执行目标方法前后统一进行事务管理

在使用注解开启事务之前,需要在 Spring XML 配置文件中添加下面内容:

使用TransactionManager类进行编程式事务管理的示例如下:

Spring 事务管理接口

在 Spring 框架中,和事务管理相关的接口有 3 个:

我们可以把PlatformTransactionManager看作是事务的管理者,而TransactionDefinitionTransactionStatus可看作是对事务的描述

PlatformTransactionManager 事务管理接口

Spring 并没有直接管理事务,而是提供了多种事务管理器,如:为 JDBC 提供了DataSourceTransactionManager;为 Hibernate 提供了HibernateTransactionManager;为 JPA 提供了JpaTransactionManager

Spring 提供的多种事务管理器都实现了PlatformTransactionManager接口,其中定义了三个方法:

TransactionDefinition 事务定义信息

PlatformTransactionManager 通过getTransaction()获取一个事务,该方法的参数就是一个TransactionDefinition,定义了事务的一些基本属性,包括五个方面:

TransactionStatus 事务运行状态

TransactionStatus 接口用来记录事务的状态,它定义了一组方法,用来获取或判断事物的状态信息

事务传播行为

事务传播行为是指不同事务之间相互调用后事务的状态 (多个事务合并成一个事务?每个事务独立存在?)

举个简单的例子:如果分别为方法 A、B、C 开启了事务 TA、TB、TC,当方法 A 调用方法 B 和 C 后,那么三个事务将会何去何从??

上面例子中,方法 A 可以称为外部方法,方法 A 调用了方法 B 和 C,那么方法 B 和 C 可以称为内部方法。当调用方法 B 和 C 时,当前已经有事务 TA

在 TransactionDefinition 定义了 7 种传播行为:

上面 7 种传播行为,下面只详细分析常用的 3 种:PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_NESTED

PROPAGATION_REQUIRED

PROPAGATION_REQUIRED 是平常使用最多的一个事务传播行为,如果使用@Transactional没有指定传播行为,那么默认的传播行为就是 PROPAGATION_REQUIRED

如果当前没有事务,就新建一个事务;如果当前已经存在事务,就加入到该事务中。更具体地:

1

PROPAGATION_REQUIRES_NEW

新建事务,如果当前存在事务,把当前事务挂起。更具体地:

所以,无论外部方法有无开启事务,被PROPAGATION_REQUIRED修饰的内部方法都与外部方法相互独立,互不干扰

但是,如果内部方法抛出异常,且在外部方法中没有被 catch 捕获,那么外部方法就能感知到该异常,所以会回滚外部方法所在的事务

强调:如果方法 A 回滚,方法 B 和 C 不会受影响;如果方法 B 和 C 回滚,方法 A 也不会受影响 (主要想和 PROPAGATION_NESTED 区分)

2

PROPAGATION_NESTED

如果当前存在事务,就在嵌套事务中执行;如果当前不存在事务,就执行与 PROPAGATION_REQUIRED 类似的操作

继续延续上面的例子,如果方法 A 回滚,方法 B 和 C 也会跟着回滚;如果方法 B 和 C 回滚,方法 A 不会回滚

举个生动的小例子:父亲做啥孩子必须跟着做啥,但孩子做啥父亲不一定跟着做啥

@Transactional 底层原理

@Transactional基于 AOP 实现,AOP 又基于 动态代理 实现,会为目标对象动态生成一个代理对象,实际上执行的是代理对象中的方法,由代理对象调用目标方法,这样代理对象就可以在调用目标方法前后执行一些特殊处理,称之为功能增强。如果目标对象实现了接口,那么 Spring 会使用 JDK 动态代理,否则会使用 CGLIB 动态代理

更具体的,如果一个类或类中的public方法上使用了@TransactionalSpring IoC 容器会在启动时为其创建一个代理对象,在调用被@Transactional修饰的方法时,实际调用的是代理对象的invoke()方法,该方法的作用:执行目标方法前开启事务,目标方法执行过程中抛出异常时回滚事务,目标方法顺利执行完后提交事务

最后再啰嗦一句:所以动态代理主要是增强目标方法执行前、后、抛出异常三个时刻!!

Spring AOP 自调用问题

上面介绍过,AOP 工作原理其实是执行代理对象中的方法,这样可以增强功能,实现事务管理

但如果同一个类中的方法自调用的话,那么执行的就是目标对象中的方法,也就无法走动态代理,进而无法让事务生效,如下面代码所示:

如果直接调用A()方法,那么就会使B()方法开启的事务失效,因为没有走代理

解决方法:使用 AspectJ 代理 Spring AOP 代理

@Transactional 注意事项

参考文章