Hexo

Spring事务管理

2019-04-13

之前面试时候(阿里三面)有被问到过 Spring 中的事务管理,当时基本不懂,回答的相当辣眼睛。
最近在造轮子时候用到了这方面的一些东西,干脆写篇博客来整理下这块的知识。很多内容源自于这篇博客

核心接口

这里写图片描述

事务管理器

Spring 不直接管理事务,而是提供了多个事务管理器,将事务管理的职责委托给 Hibernate 或者 JPA 等持久化机制所提供的相关平台框架的事务来实现。

Spring 事务管理器的接口是 PlatformTransactionManager,不同的持久化框架都实现了该接口,提供了对应的事务管理器。

1
2
3
4
5
6
7
8
public interface PlatformTransactionManager {
// 取得 TransactionStatus 对象,开启事务
TransactionStatus getTransaction(@Nullable TransactionDefinition var1) throws TransactionException;
// 提交
void commit(TransactionStatus var1) throws TransactionException;
// 回滚
void rollback(TransactionStatus var1) throws TransactionException;
}

具体的事务管理的实现,对于 Spring 来说是透明的,Spring 不关心底层采用了什么样的事务管理方法和实现机制,为不同的事务 API 提供一致的编程模型。

基本事务属性

这里写图片描述

事务属性是对事务的一些基本配置,描述了事务应当如何应用到方法上,事务包含了5个方面,如上图所示。

1
2
3
4
5
6
7
8
9
10
public interface TransactionDefinition {
// 返回事务传播行为
int getPropagationBehavior();
// 获取事务隔离级别:SQL 中的脏读,读提交,可重复读,串行化。
int getIsolationLevel();
// 设置事务超时时间
int getTimeout();
// 事务是否只读,事务管理器能够根据这个来进行优化,确保事务是只读的
boolean isReadOnly();
}

传播行为

事务的第一个属性是传播行为,当事务方法被另一个事务方法调用时候,必须制定事务如何传播。比如,方法可能继续在当前事务中运行,也可能另外开启一个新事务,在自己的事务中运行。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
2
3
4
5
6
7
8
9
void methodA() { // PROPAGATION_REQUIRED
...
methodB();
...
}

void methodB() { // PROPAGATION_REQUIRED
...
}

使用 Spring 声明式事务的话,Spring 会使用 AOP 技术对类进行增强,会根据事务的属性,自动在方法调用前决定是否开始一个事务,并且在方法执行后决定事务提交或是回滚。

调用方法 B 相当于:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
void main(String[] args) {
Connection conn = null;
try {
con = getConnection();
con.setAutoCommit(false);

methodB();

con.commit();
} catch (Exception e) {
con.rollback();
} finally {
closeCon(); // 释放资源
}
}

运行方法 A 的时候,和调用方法 B 类似,因为已经存在了一个事务。

PROPAGATION_SUPPORTS
1
2
3
4
5
6
7
8
9
void methodA() { // PROPAGATION_REQUIRED
// some code
methodB();
// some code
}

void methodB() { // PROPAGATION_SUPPORTS
// some code
}

单纯的调用执行方法 B,是不会开启事务的,执行 A 的时候,B 自动加入到 A 开启的事务中执行。

PROPAGATION_MANDATORY
1
2
3
4
5
6
7
8
9
void methodA() { // PROPAGATION_REQUIRED
// some code
methodB();
// some code
}

void methodB() { // PROPAGATION_MANDATORY
// some code
}

单独调用 B 的时候,由于 B 要求在活动的事务中被调用,因此会抛出异常,停止执行。

执行 A 的话,会自动开启一个事务,在执行到 B 的时候,就会正常执行。

PROPAGATION_REQUIRES_NEW
1
2
3
4
5
6
7
8
9
void methodA() { // PROPAGATION_REQUIRED
// some code
methodB();
// some code
}

void methodB() { // PROPAGATION_REQUIRES_NEW
// some code
}

调用方法 A 相当于

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
void main(String[] args) {
TransactionManager tm = null;
try {
tm = getTransactionManager();
tm.begin();
Transaction t1 = tm.getTransaction();
// some code
tm.suspend();
try {
tm.begin(); // 开启第二个事务
Transaction t2 = tm.getTransaction();
methodB();
t2.commit();
} catch (Exception e) {
t2.rollback();
} finally {
// 释放资源
}
tm.resume(t1); // method B 执行完了之后恢复第一个事务
// some code
t1.commit();
} catch (Exception e) {
t1.rollback();
} finally {
// 释放资源
}
}
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
2
3
4
5
6
7
8
9
10
11
12
13
14
public interface TransactionStatus extends SavepointManager, Flushable {

boolean isNewTransaction();

boolean hasSavepoint();

void setRollbackOnly(); // 设置为只回滚

boolean isRollbackOnly();

void flush();

boolean isCompleted();
}

描述一些处理事务提供简单的控制事务执行和查询事务状态的方法,在回滚或者提交时候需要引用对应的事务状态。

编程式事务 & 声明式事务

Spring 提供了对编程式事务和声明式事务的支持。编程式事务允许用户在代码中精确定义事务的边界,而声明式事务(基于 AOP)有助于用户将操作和事务规则解耦。具体的实现可以参照文章开头提到的博客。

参考

  1. Spring事务管理(详解+实例)
  2. 解惑 spring 嵌套事务
  3. Spring事务机制详解
Tags: Spring

扫描二维码,分享此文章