Spring 事务管理

Spring 事务管理

事务的回顾

数据库原理 中已经了解到,事务指的是满足 ACID 特性的一组操作,可以通过 Commit 提交一个事务,也可以使用 Rollback 进行回滚。事务要么都执行,要么都不执行。
事务有四种特性(ACID):

  1. 原子性(Atomicity)
    事务被视为不可分割的最小单元,事务的所有操作要么全部提交成功,要么全部失败回滚。
    回滚可以用回滚日志来实现,回滚日志记录着事务所执行的修改操作,在回滚时反向执行这些修改操作即可。

  2. 一致性(Consistency)
    数据库总是从一个一致性状态转换到另外一个一执行的状态
    数据库在事务执行前后都保持一致性状态。在一致性状态下,所有事务对一个数据的读取结果都是相同的。

  3. 隔离性(Isolation)
    一个事务所做的修改在最终提交以前,对其它事务是不可见的。

  4. 持久性(Durability)
    一旦事务提交,则其所做的修改将会永远保存到数据库中。即使系统发生崩溃,事务执行的结果也不能丢失。

Spring事务管理接口

  • PlatformTransactionManager: (平台)事务管理器
  • TransactionDefinition: 事务定义信息(事务隔离级别、传播行为、超时、只读、回滚规则)
  • TransactionStatus: 事务运行状态

spring.png

PlatformTransactionManager: (平台)事务管理器

Spring并不直接管理事务,而是提供了多种事务管理器,他们将事务管理的职责委托给Hibernate或者JTA等持久化机制所提供的相关平台框架的事务来实现。 Spring事务管理器的接口是: org.springframework.transaction.PlatformTransactionManager ,通过这个接口,Spring为各个平台如JDBC、Hibernate等都提供了对应的事务管理器,但是具体的实现就是各个平台自己的事情了。

PlatformTransactionManager接口中定义了三个方法:

1
2
3
4
5
6
7
public interface PlatformTransactionManager {
TransactionStatus getTransaction(TransactionDefinition var1) throws TransactionException;

void commit(TransactionStatus var1) throws TransactionException;

void rollback(TransactionStatus var1) throws TransactionException;
}

Spring 事务管理通过PlatformTransactionManager接口为不同的事务API提供一致的编程模型,具体的事务管理机制由对应各个平台去实现,几个比较常见的如下图所示:

spring2.png

在 MyBatis 配置文件中,可以看到 transactionManager 的 Bean 定义

1
2
3
4
5
6
<!-- 配置事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据库连接池 -->
<property name="dataSource" ref="dataSource" />
</bean>

TransactionDefinition: 事务定义信息

在 TransactionManager 定义了 TransactionStatus getTransaction(TransactionDefinition var1) throws TransactionException; 方法,这里的参数便是TransactionDefinition类,这个类就定义了一些基本的事务属性。

事务属性包含了五个方面:隔离级别、传播行为、回滚规则、是否可读、事务超时

在 TransactionDefinition 接口中定义了隔离级别、传播行为的常量以及关于事务的方法

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
public interface TransactionDefinition {
int PROPAGATION_REQUIRED = 0;
int PROPAGATION_SUPPORTS = 1;
int PROPAGATION_MANDATORY = 2;
int PROPAGATION_REQUIRES_NEW = 3;
int PROPAGATION_NOT_SUPPORTED = 4;
int PROPAGATION_NEVER = 5;
int PROPAGATION_NESTED = 6;
int ISOLATION_DEFAULT = -1;
int ISOLATION_READ_UNCOMMITTED = 1;
int ISOLATION_READ_COMMITTED = 2;
int ISOLATION_REPEATABLE_READ = 4;
int ISOLATION_SERIALIZABLE = 8;
int TIMEOUT_DEFAULT = -1;

// 返回事务的传播行为
int getPropagationBehavior();
// 返回事务的隔离级别
int getIsolationLevel();
// 返回事务必须在多少秒内完成
int getTimeout();
// 返回事务是否只读
boolean isReadOnly();
// 返回事务名字
String getName();
}
事务的隔离级别

TransactionDefinition 接口中定义了五个表示隔离级别的常量:

  • DEFAULT (默认级别)

  • READ UNCOMMITED (未提交读)

  • READ COMMITED (提交读)

  • REPEATABLE READ (可重复读)

  • SERIALIZABLE (可串行化)

事务传播行为

事务传播行为是为了解决业务层方法之间互相调用的事务问题

当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。

在TransactionDefinition接口中定义中六个表示传播行为的常量:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//-----------支持当前事务的情况------------
// 如果当前存在事务,则加入该事务;如果当前没有事务,则创建一个新的事务。
int PROPAGATION_REQUIRED = 0;
// 如果当前存在事务,则加入该事务;如果当前没有事务,则以非事务的方式继续运行。
int PROPAGATION_SUPPORTS = 1;
// 如果当前存在事务,则加入该事务;如果当前没有事务,则抛出异常。(mandatory:强制性)
int PROPAGATION_MANDATORY = 2;
//----------不支持当前事务的情况:-------------
// 创建一个新的事务,如果当前存在事务,则把当前事务挂起。
int PROPAGATION_REQUIRES_NEW = 3;
// 以非事务方式运行,如果当前存在事务,则把当前事务挂起。
int PROPAGATION_NOT_SUPPORTED = 4;
// 以非事务方式运行,如果当前存在事务,则抛出异常。
int PROPAGATION_NEVER = 5;
//-------------其他情况-----------------
// 如果当前存在事务,则创建一个事务作为当前事务的嵌套事务来运行;如果当前没有事务,则该取值等价于TransactionDefinition.PROPAGATION_REQUIRED。
int PROPAGATION_NESTED = 6;

这里需要指出的是,前面的六种事务传播行为是 Spring 从 EJB 中引入的,他们共享相同的概念。而 PROPAGATION_NESTED 是 Spring 所特有的。以 PROPAGATION_NESTED 启动的事务内嵌于外部事务中(如果存在外部事务的话),此时,内嵌事务并不是一个独立的事务,它依赖于外部事务的存在,只有通过外部的事务提交,才能引起内部事务的提交,嵌套的子事务不能单独提交。如果熟悉 JDBC 中的保存点(SavePoint)的概念,那嵌套事务就很容易理解了,其实嵌套的子事务就是保存点的一个应用,一个事务中可以包括多个保存点,每一个嵌套子事务。另外,外部事务的回滚也会导致嵌套子事务的回滚。

事务超时属性

指一个事务允许执行的最长时间 int getTimeout();如果超过该时间限制但事务还没有完成,则自动回滚事务。 返回int类型,单位是秒。

事务只读属性

boolean isReadOnly();返回事务是否是只读,类型是boolean。只读属性是指对事务的资源进行只读,而事务的资源就是指那些被事务管理的资源,比如数据源、 JMS 资源,以及自定义的事务性资源等等。

回滚规则

这些规则定义了哪些异常会导致事务回滚而哪些不会。默认情况下,事务只有遇到运行期异常时才会回滚,而在遇到检查型异常时不会回滚(这一行为与EJB的回滚行为是一致的)。
但是你可以声明事务在遇到特定的检查型异常时像遇到运行期异常那样回滚。同样,你还可以声明事务遇到特定的异常不回滚,即使这些异常是运行期异常。

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();
}

在 TransactionManager 中,TransactionStatus getTransaction(TransactionDefinition var1) throws TransactionException; 方法返回的就是TransactionStatus对象,然后程序根据返回的对象来获取事务状态,然后进行相应的操作。

Spring 编程式事务和声明式事务

编程式事务处理:编程式事务使用TransactionTemplate或者直接使用底层的PlatformTransactionManager。对于编程式事务管理,spring推荐使用TransactionTemplate。

声明式事务处理:声明式事务是建立在AOP之上的。其本质是对方法前后进行拦截,然后在目标方法开始之前创建或者加入一个事务,在执行完目标方法之后根据执行情况提交或者回滚事务。声明式事务最大的优点就是不需要通过编程的方式管理事务,这样就不需要在业务逻辑代码中掺杂事务管理的代码,只需在配置文件中做相关的事务规则声明(或通过基于@Transactional注解的方式),便可以将事务规则应用到业务逻辑中。

显然声明式事务管理要优于编程式事务管理,这正是spring倡导的非侵入式的开发方式。声明式事务管理使业务代码不受污染,一个普通的POJO对象,只要加上注解就可以获得完全的事务支持。和编程式事务相比,声明式事务唯一不足地方是,它的最细粒度只能作用到方法级别,无法做到像编程式事务那样可以作用到代码块级别。但是即便有这样的需求,也存在很多变通的方法,比如,可以将需要进行事务管理的代码块独立为方法等等。

声明式事务管理也有两种常用的方式,一种是基于tx和aop名字空间的xml配置文件,另一种就是基于@Transactional注解。显然基于注解的方式更简单易用,更清爽。

参考资料