spring事务框架
我们这次分享要带着2个问题:
- 上次讨论的,不同传播行为下,外层事务的修改内层事务是否可见
- 为什么经常遇到"Transaction rolled back because it has been marked as rollback-only"
spring事务框架
spring事务框架的设计理念:让事务管理的关注点与数据访问关注点相分离。
- 当业务层使用事务api界定事务的时候,不需要考虑事务加诸于上的事务资源是什么,对不同的事务资源的管理有对应的框架实现类实现。
- 数据访问层对可能参与事务的数据资源进行访问的时候,只需要使用对应的数据访问API进行数据访问,不需要关心事务资源如何参与事务或者是否需要参与事务。
下面这段代码大家肯定都见过
try {
// 开启事务部分
Class.forName("com.mysql.jdbc.Driver");
conn = DriverManager.getConnection(URL, USER, PASSWD);
conn.setAutoCommit(false); // 自动提交设置为false
// 执行更新操作
pstmtupdate = conn.prepareStatement(updatesql);
pstmtupdate.executeUpdate();
// 提交
conn.commit();
} catch (Exception e) {
try {
conn.rollback();
} catch (SQLException e1) {}
e.printStackTrace();
} finally {
try {
...
if (conn != null) {
conn.close();
}
} catch (SQLException e2) {}
}
}
}
spring 事务管理api的样例代码
public void serviceMethod() {
// 事务的定义
TransactionDefinition definition = ...;
// 开启事务,相当于begin
TransactionStatus txStatus = getTransactionManager().getTransaction(definition);
try {
// dao1.doDataAccess();
// dao2.doDataAccess();
} catch (Exception e) {
// 回滚事务
getTransactionManager().rollback(txStatus);
throw e;
}
// 提交事务
getTransactionManager().commit(txStatus);
}
spring 事务整体抽象
spring的事务抽象主要包括3个接口,
- PlatformTransactionManager: spring 事务抽象框架的核心组件,负责界定事务的边界,可以看到三个方法主要是事务管理相关。
- TransactionDefinition:负责定义事务相关属性,包括隔离级别、传播行为等, PlatformTransactionManager将参照TransactionDefinition来开启相关事务
- TransactionStatus: 负责管理事务开始之后到事务结束期间的状态.
TransactionDefinition
主要定义了有哪些事务属性可以指定,包括
- 事务的隔离级别
- 事务的传播行为
- 超时时间
- 是否是只读事务
隔离级别
定义了5个常量用于区分可选择的隔离级别
- ISOLATION_DEFAULT: 使用数据库默认的隔离级别
- ISOLATION_READ_UNCOMMITTED
- ISOLATION_READ_COMMITTED
- ISOLATION_REPEATABLE_READ
- ISOLATION_SERIALIZABLE
传播行为
事务的传播行为表示整个事务处理过程中所跨越的业务对象,将以什么样的行为参与事务。
- PROPAGATION_REQUIRED: 如果当前存在一个事务,则加入当前事务。如果不存在任何事务,则创建一个新的事务。总之,要至少保证在一个事务中进行,通常是默认的事务传播行为。
- PROPAGATION_SUPPORTS:如果当前存在一个事务,则加入当前事务,如果当前不存在事务,则直接执行。对于一些查询方法来说,PROPAGATION_SUPPORTS通常是比较合适的传播行为选择。如果当前方法直接执行,不需要事务的支持,如果当前方法被其他方法调用,而其他方法开启了一个事务,保证当前方法能够加入到当前事务,并洞察当前事务对数据资源所做的更新。
- PROPAGATION_NOT_SUPPORTED: 不支持当前事务,是在没有事务的情况下执行,如果当前存在事物的话,当前事务原则上将会被挂起(Suspend)。不能获取到外层事务方法对数据资源所做的更新。
- PROPAGATION_MANDATORY: 强制要求当前存在一个事务,如果不存在,抛出异常。如果某个方法需要事务的支持,但是自身又不管理事务的提交或者回滚,比较适合。
- PROPAGATION_REQUIRES_NEW。 不管当前是否存在事务,都会创建新的事务。如果当前存在事务,会将当前的事务挂起。如果某个业务方法所做的事情不想影响到外层事务,比如当前的业务方法记录日志、发送消息,即使这些操作失败了,我们也不想影响外层事务的提交,PROPAGATION_REQUIRES_NEW比较适合。
- PROPAGATION_NEVER:永远不需要当前存在事务,如果存在事务,则报错。
- PROPAGATION_NESTED: 如果存在当前事务,则在当前事务的一个嵌套事务中执行,否则新建一个事务,在新的事务中执行。并非所有的PlatformTransactionManager都支持。
NESTED和REQUIRES_NEW的区别?
在外层事务存在情况下,这两个传播行为都需要新建一个事务,那么他们有什么异同呢?
REQUIRES_NEW创建的新事务和外层事务是同一个“层次”的,二者的地位是相同的,当被创建的新事务运行的时候,外层事务暂时被挂起。也就是说REQUIRES_NEW新建的事务虽然在当前外层事务内运行,但是新建的事务是独立于当前的外层事务而存在的,二者拥有各自的状态互不干扰,(这就说如果是RC隔离级别下,是读不到外层事务对数据资源所做的更改的,稍后我们看代码)。
而NESTED创建的事务是外层事务的“子事务”,当内部嵌套事务运行的时候,外层事务仍然是active的。内部子事务不能脱离外层事务独立存在,并且与外层事务共有事务状态。
那么nested适用什么场景?比如你有一个特别大的事务,希望内部通过一些子事务来执行,并且希望根据内层事务的执行结果,选择不同的分支执行。比如我们下面的代码。
/**
* PROPAGATION_REQUIRED
*/
public void externalService() {
try {
// PROPAGATION_NESTED
interObjA.internalServiceA();
} catch(Exception ex) {
// PROPAGATION_NESTED
interObjB.internalServiceB();
}
}
值得说明的是,这种方式并不会发生"Transaction rolled back because it has been marked as rollback-only"的错误。
TIMEOUT
提供了默认的TIMEOUT_DEFAULT常量的定义,默认是-1,采用当前数据资源事务默认的超时时间。
是否只读
需要说明的是,这只是一种优化的提示,最终是否提供优化,由具体的事务资源决定,但是即使不支持这种提示,也不会报错的。对于一些查询来说,我们通常希望他们采用只读事务。
相关的实现
TransactionDefinition只是一个接口,要为PlatformTransactionManager创建事务提供信息,需要有相应的实现类提供支持。虽然实现类不多,为了便于理解,根据使用场景,我们划分为两派。
编程式事务:TransactionTemplate
TransactionTemplate是Spring提供的进行编程式事务管理的模板方法类,直接继承了DefaultTransactionDefinition,可以直接通过TransactionTemplate本身提供事务控制属性。
go to source code TransactionTemplate
声明式事务:TransationAttribute
TransactionAttribute接口主要面向使用spring aop进行声明式事务管理的场景,实现了TransactionDefinition接口,并且提供了rollbackOn方法,这样我们可以指定业务方法在抛出哪些异常的情况下可以回滚事务。
默认实现类是DefaultTransationAttributeDefinition,提供了rollbackOn的具体实现,异常类型为unchecked exception的时候将回滚事务。
而DefaultTransationAttributeDefinition的实现类RuleBasedTransactionAttribute则允许我们自定义多条回滚的规则,如果异常类型与其中任何一条匹配,就可以回滚事务。如果传入的规则都不匹配,则向上调用父类的rollbackOn方法,判断是不是unchecked exception。
DelegatingTransactionAttribute人如其名,只是一个代理,将所有方法调用都委派给另一个具体的TransationAttributeDefinition实现类。
下面的一个问题就是TransactionAttribute是如何在声明式事务中使用的。
go to source code TransactionInterceptor
TransactionStatus
表示整个事务处理过程中的事务状态,更多时候,将在编程式事务中使用该接口。
在事务处理过程中,我们可以是使用TransactionStatus进行如下工作:
- 提供相应的方法查询事务的状态
- 通过setRollbackOnly()方法标记当前事务,使其回滚
- 如果相应的PlatformTransactionManger支持Savepoint,可以通过TransactionStatus在当前事务中创建内部嵌套的事务。稍后介绍transactionmanager的时候,会有更直观的理解。
SavepointManager
SavepointManger 接口在jdbc3.0的基础之上,对savepoint的支持提供了抽象,提供管理Savepoint的能力,从而支持内部嵌套事务。
AbstractTransactionStatus提供了一些公共的基础方法,比如savepoint的管理和是否回滚。而DefaultTransationStatus则是一个容器,负责事务的状态信息,包含从数据资源获得transaction obj、是外层新事物还是内层嵌套事务、是否只读等。
go to source code DefaultTransactionStatus
Transaction obj
记录了当前事务的必要信息,是从数据资源处获取的事务资源,内部封装了类似connection 或者session,封装在DefaultTransationStatus中。保存点等操作最终都是委托给transaction obj 来完成的。
PlatformTransactionManager
我们先回顾下之前,TransactionDefinition(负责提供事务的定义)和TransactionStatus(通过封装具体的transaction obj记录事务开启之后到结束之间的状态),然后我们介绍几个概念
TransactionSynchronization 事务同步器
是可以注册到事务处理过程中的回调接口,就是事务处理的事件监听器,当事务处理的某些规定事件发生时,会调用TransactionSynchronization上的对应方法执行相应的回调,比如事务完成后清理相应的事务资源等操作。
其中,beforeComletion 和afterCompletion 不管commit还是rollback都会调用,但是beforeCommit和afterCommit只有commit才会调用。
TransactionSynchronizationManger
除了管理TransactionSynchronization之外,还起到了类似ThreadLocal的作用。
整个事务执行期间,会有很多具体的事务资源,比如jdbc的Connection或者Hibernate的Session,这些需要合当前线程进行绑定,TransactionSynchronizationManger就是这些资源的绑定目的地。将这些资源统一放到TransactionSynchronizationManger中,无论是谁,要使用资源的话,都从这一个地方取,这就解除了事务管理代码和数据访问代码之间的直接耦合。
有了这些铺垫,我们进入正题。
实现类概览
PlatformTransactionManager是spring 事务抽象框架的核心组件,整个抽象体系基于模板和策略模式,PlatformTransactionmanager对事务管理进行统一的抽象,具体的实现策略交由具体的实现类。
AbstractPlatformTransactionManager作为其他实现类的父类,以模板方法的形式封装了固定的事务处理逻辑,而只将与事务资源相关的操作以protected或者abstract方法的形式留给子类实现。这些固定的事务内部处理逻辑主要体现在以下几个主要的模板方法中:
// 开启事务
public final TransactionStatus getTransaction(@Nullable TransactionDefinition definition)
throws TransactionException;
// 提交事务
public final void commit(TransactionStatus status) throws TransactionException;
// 回滚事务
public final void rollback(TransactionStatus status) throws TransactionException;
// 挂起事务
protected final SuspendedResourcesHolder suspend(@Nullable Object transaction)
// 恢复事务
protected final void resume(@Nullable Object transaction, @Nullable SuspendedResourcesHolder resourcesHolder) throws TransactionException
开启事务
从第一个模板方法开始,先大概了解下它的流程图:
go to source code AbstractPlatformTransactionManager
问题1 外层事务的修改内层事务是否可见
我们首先明确两个前提,就是外层肯定有事务并且隔离级别是RC,否则就不存在这个问题了,下面的隔离级别说的是内层方法的隔离级别,
| 传播行为 | 外层事务的修改 | 说明 |
|---|---|---|
| NEVER | 报错 | |
| NOT_SUPPORTED | 不可见 | 挂起外层事务 |
| SUPPORTS | 可见 | 同一个事务 |
| REQUIRES_NEW | 不可见 | 挂起外层事务,新建一个事务 |
| NESTED | 可见 | 同一个事务,不同的保存点 |
| REQUIRED | 可见 | 同一个事务 |
| MANDATORY | 可见 | 同一个事务 |
在介绍回滚和提交之前,还有几个变量,会影响到后面的提交和回滚流程,我们再确认下
| 外层是否存在事务 | 传播行为 | newTransaction | hasTransaction |
|---|---|---|---|
| 有 | NEVER | 报错 | 报错 |
| NOT_SUPPORTED | false | false | |
| REQUIRES_NEW | true | true | |
| NESTED | false | ture | |
| REQUIRED | false | true | |
| SUPPORTS | |||
| MANDATORY | |||
| 无 | NEVER | true | false |
| NOT_SUPPORTED | true | false | |
| REQUIRES_NEW | true | true | |
| NESTED | true | true | |
| REQUIRED | true | true | |
| SUPPORTS | true | false | |
| MANDATORY | 报错 | 报错 |
回滚事务
-
如果是嵌套事务,通过TransactionStatus 释放savepoint
-
如果当前事务是一个新的事务,则调用子类的doRollback 方法真正的回`滚事务。本质上是connetion.rollback或者seesion.rollback
-
否则的话,如果当前存在事务,说明是参与到外层事务的内层事务,通常就是设置当前的transaction object状态为rollback
-
触发Syncronization事件,只有triggerBeforeCompletion和triggerAfterCompletion
-
清理事务资源,清理的过程如下
- 设置TransactionStatus completed 为完成状态
- 清理与当前事务相关的Synchronization
- 调用doCleanupAfterCompletion释放事务资源,解除到TransactionSynchronizationManager的资源绑定。对于DataSourceTransactionManager来说,就是释放链接,设置autocommit为true,解除到threadlocal的绑定。
- 如果之前有挂起的事务,恢复挂起的事务。
事务提交
-
检测全局的rollbackonly表示,如果最外层事务已经被标记为rollbackonly,则执行回滚
-
如果发现transaction obj 已经被标记为rollbackonly了,执行回滚,这个时候 unexpected 参数为true,说明外层事务本来应该提交,但是内层事务标记回滚了,就会抛出我们之前遇到的那个异常
-
如果TransactionStatus持有savepoint,释放它,实际上在处理嵌套事务的提交
-
如果要提交的事务,是一个新事务,调用doCommit方法由子类去提交事务,对于DatasourceTransactionManager来说,就是connection.commit();
-
触发相应的Synchronization事件,比rollback多了triggerBeforeCommit和triggerAfterCommit
-
如果在提交过程中出现异常,根据rollbackOnCommitFailure参数决定
-
清理事务资源,清理的过程如下
- 设置TransactionStatus completed 为完成状态
- 清理与当前事务相关的Synchronization
- 调用doCleanupAfterCompletion释放事务资源,解除到TransactionSynchronizationManager的资源绑定。对于DataSourceTransactionManager来说,就是释放链接,设置autocommit为true,解除到threadlocal的绑定。
- 如果之前有挂起的事务,恢复挂起的事务。
问题2 "Transaction rolled back because it has been marked as rollback-only" 是什么问题?
- 内层事务和外层事务,是同一个事务(required,supports,mandatory),内层事务发生了异常,执行了回滚,但只是标记了transaction obj的rollbackOnly
- 外层事务捕获了内层事务的异常,正常执行完了逻辑,执行commit,这时候发现transaction obj被标记rollbackOnly,执行回滚,并报错。