之前面试时候(阿里三面)有被问到过 Spring 中的事务管理,当时基本不懂,回答的相当辣眼睛。
最近在造轮子时候用到了这方面的一些东西,干脆写篇博客来整理下这块的知识。很多内容源自于这篇博客。
核心接口
事务管理器
Spring 不直接管理事务,而是提供了多个事务管理器,将事务管理的职责委托给 Hibernate 或者 JPA 等持久化机制所提供的相关平台框架的事务来实现。
Spring 事务管理器的接口是 PlatformTransactionManager
,不同的持久化框架都实现了该接口,提供了对应的事务管理器。
1 | public interface PlatformTransactionManager { |
具体的事务管理的实现,对于 Spring 来说是透明的,Spring 不关心底层采用了什么样的事务管理方法和实现机制,为不同的事务 API 提供一致的编程模型。
基本事务属性
事务属性是对事务的一些基本配置,描述了事务应当如何应用到方法上,事务包含了5个方面,如上图所示。
1 | public interface TransactionDefinition { |
传播行为
事务的第一个属性是传播行为,当事务方法被另一个事务方法调用时候,必须制定事务如何传播。比如,方法可能继续在当前事务中运行,也可能另外开启一个新事务,在自己的事务中运行。Spring 定义了七种传播行为:
传播行为 | 含义 |
---|---|
PROPAGATION_REQUIRED | 表示当前方法必须运行在事务中,如果当前事务存在,方法会在该事务中运行,否则会启动一个新的事务 |
PROPAGATION_SUPPORTS | 表示当前方法不需要事务上下文,但是如果当前事务存在的话,那么该方法会在这个事务中运行 |
PROPAGATION_MANDATORY | 表示当前方法必须运行在事务中,如果事务不存在,就抛出一个异常 |
PROPAGATION_REQUIRED_NEW | 表示当前方法必须运行在自己的事务中。一个新的事务将被启动。如果存在当前事务,那么在方法执行期间,当前事务会被挂起。如果使用JTATransactionManager 的话,则需要访问TransactionManager |
PROPAGATION_NOT_SUPPORTED | 表示当前方法不应该运行在事务中。如果存在当前事务,在方法运行期间,当前事务被挂起。如果使用JTATransactionManager 的话,则需要访问TransactionManager |
PROPAGATION_NEVER | 表示当前方法不运行在事务上下文中。如果当前正有一个事务在运行,则会抛出异常。 |
PROPAGATION_NESTED | 表示如果当前已经存在一个事务,那么该方法会在嵌套事务中运行,嵌套的事务可以独立于当前事务进行单独提交或回滚。如果事务不存在,则行为和 PROPAGATION_REQUIRED 一致。 |
PROPAGATION_REQUIRED
1 | void methodA() { // PROPAGATION_REQUIRED |
使用 Spring 声明式事务的话,Spring 会使用 AOP 技术对类进行增强,会根据事务的属性,自动在方法调用前决定是否开始一个事务,并且在方法执行后决定事务提交或是回滚。
调用方法 B 相当于:
1 | void main(String[] args) { |
运行方法 A 的时候,和调用方法 B 类似,因为已经存在了一个事务。
PROPAGATION_SUPPORTS
1 | void methodA() { // PROPAGATION_REQUIRED |
单纯的调用执行方法 B,是不会开启事务的,执行 A 的时候,B 自动加入到 A 开启的事务中执行。
PROPAGATION_MANDATORY
1 | void methodA() { // PROPAGATION_REQUIRED |
单独调用 B 的时候,由于 B 要求在活动的事务中被调用,因此会抛出异常,停止执行。
执行 A 的话,会自动开启一个事务,在执行到 B 的时候,就会正常执行。
PROPAGATION_REQUIRES_NEW
1 | void methodA() { // PROPAGATION_REQUIRED |
调用方法 A 相当于
1 | void main(String[] args) { |
PROPAGATION_NOT_SUPPORTED
表示方法总是非事务的执行,如果当前已经有事务了,那么就挂起该事务。
PROPAGATION_NEVER
表示方法永远不再事务上下文中执行,如果当前有事务的,直接抛出异常。
PROPAGATION_NESTED
如果一个活动的事务存在,则运行在一个嵌套的事务中. 如果没有活动事务, 则按 PROPAGATION_REQUIRED 属性执行。
辨析一下 PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED(参照这篇文章):
PROPAGATION_REQUIRES_NEW 启动一个新的,不依赖于环境的事务,这个事务将被完全的提交或回滚,而不依赖于外部事务,拥有自己的隔离级别,自己的锁。新的事务开始执行时候,外部事务被挂起,新的事务结束后,外部事务继续执行。新的事务执行成功与否都不会引起外部事务回滚等。
PROPAGATION_NESTED 开始一个嵌套的事务。这个事务是已经存在的事务的子事务。嵌套事务开始执行时候,会获取一个 save point,当嵌套事务失败,会回滚到该点。嵌套事务是外部事务的一部分,只有当外部事务结束后它才会被提交。假如外部事务对嵌套的内部事务进行了错误处理,那么内部事务的失败也不会影响到外部事物的正确提交。但是如果没有处理有关的错误,内部事务的失败也会导致外部事务同时失败。
隔离级别
隔离级别定义了事务的并发级别。
不可重复读和幻读的区别:
不可重复读重点强调修改,同样的条件,你读取过的数据,再次读取,发现不一样了。
幻读强调新增和删除。同样的条件,两次读取出来记录数不同。
隔离级别 | 效果 |
---|---|
ISOLATION_DEFAULT | 采用后端数据库默认的事务隔离级别,不额外配置 |
ISOLATION_READ_UNCOMMITTED | 最低级别的隔离,允许读取未提交的数据变更 |
ISOLATION_READ_COMMITTED | 允许读取并发事务已经提交的记录,可以阻止脏读 |
ISOLATION_REPEATABLE_READ | 对同一字段的多次读取都是一致的,除非数据被当前事务修改 |
ISOLATION_SERIALIZABLE | 最高级别的隔离,完全满足 ACID |
只读
第三个特性是是否为只读事务,如果事务只对后端数据库进行读操作,数据库可以利用事务的只读特性进行额外的优化。
事务超时
事务超时是事务的一个定时器,在特定时间内事务如果没有执行结束,就会回滚,不必一直等到事务执行结束。防止因为一些错误长时间锁定后端数据库。
回滚规则
回滚规则指定了哪些情况下事务会发生回滚。默认情况下,事务遇到运行期异常会回滚,遇到编译期异常不会回滚,不过这一点是可配置的。
事务状态
上述的 PlatformTransactionManager 接口的 getTransaction 方法得到 TransactionStatus 接口的一个实现,这个接口的内容:
1 | public interface TransactionStatus extends SavepointManager, Flushable { |
描述一些处理事务提供简单的控制事务执行和查询事务状态的方法,在回滚或者提交时候需要引用对应的事务状态。
编程式事务 & 声明式事务
Spring 提供了对编程式事务和声明式事务的支持。编程式事务允许用户在代码中精确定义事务的边界,而声明式事务(基于 AOP)有助于用户将操作和事务规则解耦。具体的实现可以参照文章开头提到的博客。
参考
扫描二维码,分享此文章