【组件可编排】Spring Transaction解密

简介: 从原理到实战,从 JDBC 到 Spring,全面的解密 Java 事务管理

Transaction

八股文

数据库事务通常包含了一个序列的对数据库的读/写操作。数据库为一个事务中的操作提供了以下四个方面的保证:

• 原子性(Atomicity):事务作为一个整体被执行,包含在其中的对数据库的操作要么全部被执行,要么都不执行。

• 一致性(Consistency):事务应确保数据库的状态从一个一致状态转变为另一个一致状态。一致状态的含义是数据库中的数据应满足完整性约束。

• 隔离性(Isolation):多个事务并发执行时,一个事务的执行不应影响其他事务的执行。

• 持久性(Durability):已被提交的事务对数据库的修改应该永久保存在数据库中。

背诵了无数次的 ACID,它们真的就能用上面 4 句话讲完吗?


超出八股文

ACID 这个缩写,有很大一部分原因是程序员的恶趣味,为了凑齐 acid/['æsid]. n. 酸/(同理可见 BASE,为了凑齐 base/[beɪs]. n. 碱/)。这四个特性本就不是一个维度的,AID 是数据库事务锁提供的特性,C 是研发人员需要达成的一个业务目标。即使很多数据库内置了很多约束能力( NOT NULL、CHECK约束、唯一键、外键等),但是也远远达不到复杂业务的一致性诉求。从头开发一个存储系统到最终满足业务的一致性,工作量是巨大的。所以数据库屏蔽掉了底层存储的各种细节,为我们提供了通用的 AID 保证,以便让业务开发可以更简单快速的实现业务上的一致性。


再看原子性

原子性是站在单个事务的角度来看的。它本质是在描述一种“可中止性(abortability)”——能够在出现错误时中止事务,并且将已执行的事务操作丢弃。

设想一下,如果没有原子性的保证(不要以为只是想象,很多人写业务代码都不会开启事务),那对业务开发会是一个多大的灾难。设想一下A 要给 B 转账 100 块,先扣减了 A 100 块,然后程序重启了,接下来怎么办?这还只是一个简单的场景,可以记录一个日志来进行恢复。假如事务中的涉及到的状态越多,想要在出错时进行恢复,需要考虑的问题也就越多。


再看隔离性

隔离性是从两个或多个事务并发执行的角度出发的。每个事务都认为当前只有自己在执行,看不到其他并发事务带来的影响。

要做到事务间的完全隔离,最简单的方式就是不并发,redis就是这么做的。但是问题就来了,性能怎么办?多核硬被干成了单核;如果两个毫不相干的事务并发执行,也被限制到串行执行就得不偿失了。所以为了均衡性能,ANSI SQL 标准对隔离性进行了分级。在讲具体的隔离级别之前,得先看下如果完全不隔离,可能会遇到哪些问题?


并发事务可能遇到的问题

以下提到的问题都是在未进行事务隔离时,可能会遇到的并发性问题:

• 脏写(Dirty Write):事务 T1 修改了一条数据。另一个事务 T2 在 T1 完成之前,再次修改了这条数据,可能就会造成脏写。例如存在两条数据 x 和 y,业务上要求 x == y 必须成立。这和 T1 和 T2 并发地执行,它们为了满足业务规则,会分别将 x 、y 更新到某一个值。存在以下一种执行序列,会导致脏写出现:

• 脏读(Dirty Read):事务 T1 修改了一条数据。另一个事务 T2 在 T1 完成之前,读到了这条数据被修改后的内容。如果 T1 最终回滚了,那 T2 就会读到从未真实存在(从未提交)的数据。

• 不可重复读(Non-repeatable or Fuzzy Read):事务 T1 读取了一条数据。另一个事务T2 随后修改或删除了这条数据,然后提交。如果 T1 再去读取这条数据,它会读到被修改之后的数据,或者读不到这条数据(已被删除)。

• 幻读(Phantom):事务 T1 读取了一组满足 C 条件的数据。另一个事务 T2 新插/删除/更新一些满足 C 条件的数据,然后提交。如果 T1 再次使用 C 条件查询数据,它会得到和第一次读取不一样的数据集。

• 更新丢失(Lost Update):事务 T1 读取了一条数据。然后 T2 更新了同一条数据。随后 T1 根据读取的结果由去更新了这条数据,提交事务。T2 的更新丢失了(被 T1 覆盖了)。

• 写偏斜(Write Skew):假设数据 x 和 y 需要满足业务上的约束。事务 T1 读取了 x 和 y,此时 x 和 y 是满足业务约束的。此时事务 T2 也读取了 x 和 y,检查满足业务约束后更新了 x,然后提交。接着 T1 去更新了 y,然后提交。因为并发原因,导致 T1 和 T2 提交之后,x 和 y 的业务约束被打破了。


事务的隔离级别

以上四种隔离级别都是定义在 ANSI 标准中的,但是现在业内越来越多的声音在说标准的隔离级别定义得不够精确(或者说太少),导致有很多其他的问题没有涵盖在标准中。所以不同的数据库厂商,对决解决脏写、更新丢失、写偏斜有不同的实现,也有可能不实现。


再看持久性

持久性也是站在单个事务的视角来看的。对于已被成功提交的事务,它对数据的修改应该“永久”保存下来。

但是真的做得到永久吗?如果磁盘坏掉了?(据统计,在 10000 个硬盘的集群中,你应该接受每天坏一个硬盘的现实)那我做 RAID。如果地震把机房毁了了?那我做异地容灾。如果这个时候受到一个二向箔攻击呢?所以完美的持久性是不存在的,我们需要确定一个可接受的范围,然后在这个范围内提供持久性保障。


再看一致性

一致性一个很大的话题,值得专门写一个专题系列来讲,这里就不深入细节了。我们只需要知道一致性是通过数据库提供的 AID 来达成我们业务上的目标。并且这个目标不同,AID 的决策上会略有区别(例如选择合适的隔离级别,对持久性进行合适的容灾)。

业务的一致性目标是因具体情况而异的,脱离了业务谈一致性都十分抽象,导致不是很好理解。下面只列举两种比较常见的解释:

1. 对数据的一组特定约束必须始终成立。即 不变式(invariants)。(这里的约束可以理解为业务规则,即一致的数据一定满足所有的业务规则);

2. 保证事务仅仅能将数据从一种有效的状态变成另一种有效的状态。(在业务规则的约束下,数据只能在确定的、有效的状态之间转移,如果数据的转义超出了有效状态的范围,这是数据就不一致了)


Java JDBC Transaction

上一节讲述了数据库事务的各种特性,那么我们应该怎样在实际工作中使用它呢?Java 中只有一种和数据库的交互标准——JDBC。各种 ORM 框架和 spring 均是在 JDBC 标准上进行封装,所以有必要先对 JDBC 的事务处理进行了解,而 JDBC 中对事务最基本的操作集中在以下几个 Connection 的方法上:

publicinterfaceConnectionextendsWrapper, AutoCloseable {
/*** 设置当前连接的自动提交(auto-commit)模式:* * 如果设置为 true,则这个后续所有的 sql 都会以每条一个事务的形式执行;* * 如果设置为 false,则后续的 sql 会包装到单个事务中执行,直到遇到 commit 或 rollback 调用为止;* * 新的连接默认 auto-commit = true* 注:如果在事务中(auto-commit = false)修改 auto-commit 的值(改为 true),则会自动提交这个事务;*/voidsetAutoCommit(booleanautoCommit) throwsSQLException;
/*** 将从前一次 commit/rollback 调用到现在的所有变更持久化,并且释放掉当前连接持有的所有锁。* 是有当 auto-commit = false 时能使用*/voidcommit() throwsSQLException;
/*** 将当前事务的所有变更撤销,并且释放掉当前连接持有的所有锁。* 是有当 auto-commit = false 时能使用*/voidrollback() throwsSQLException;
intTRANSACTION_READ_UNCOMMITTED=1;
intTRANSACTION_READ_COMMITTED=2;
intTRANSACTION_REPEATABLE_READ=4;
intTRANSACTION_SERIALIZABLE=8;
/*** 尝试将当前事务的隔离级别更新的指定值* 注:如果在事务执行过程中改变隔离级别,具体行为视 Driver 实现而论*/voidsetTransactionIsolation(intlevel) throwsSQLException;
}



(以上代码的注释根据 JDBC Connection 注释翻译简化而来)

除了事务的基础操作外,JDBC 还提供了 Savepoint 功能(不是所有的数据库实现和 JDBC Driver 都支持此功能)。 Savepoint 可以理解为游戏里面的存档,我们可以在一次事务的过程中设置 N 个存档。存档的作用域是单个事务内,一旦这个事务提交或回滚后,它创建的存档点都会被删除。在事务的处理过程中,对于存档点可以:

1. 回滚事务到之前的某个存档,即存档之前的执行操作保留,存档之后执行的操作撤销;

2. 删除之前的某个存档。

publicinterfaceConnectionextendsWrapper, AutoCloseable {
/*** 在当前事务中创建一个不具名的 Savepoint,并返回。*/SavepointsetSavepoint() throwsSQLException;
/*** 在当前事务中创建一个具名的 Savepoint,并返回。*/SavepointsetSavepoint(Stringname) throwsSQLException;
/*** 回滚到给定的 SavePoint*/voidrollback(Savepointsavepoint) throwsSQLException;
/*** 释放给定的 SavePoint*/voidreleaseSavepoint(Savepointsavepoint) throwsSQLException;
}
publicinterfaceSavepoint {
/*** 自动为 Savepoint 生成的数字 ID*/intgetSavepointId() throwsSQLException;
/*** Savepoint的名称。如果是一个不具名的Savepoint,调用此方法会报错*/StringgetSavepointName() throwsSQLException;
}


虽然 JDBC 的 API 已经能完成事务的各种操作了,但是我们直接使用 JDBC 进行事务管理,有点冷兵器时代的感觉。具体可能会遇到哪些问题呢?

• 跨方法调用时,如何在多中调用环境中进行事务保证?特别是当一个方法有多个调用方,有的调用方本身不开启事务,有的调用方本身是开启了事务的;

• 在单线程环境下,如何在不同的阶段/方法去管理多个事务;

• 怎样避免大量的开事务、关事务模板代码?一旦代码中有一处开了事务忘关了,或者把外层开的事务错误关掉了,那调查过程是十分酸爽。


Spring Transaction

上一节描述了 JDBC 提供的事务管理基础能力,也提出了一些使用原始 JDBC 管理事务可能会遇到的一些问题。下面通过描述 Spring 对原始 JDBC 事务管理的抽象,来看一下 Spring 是怎么解决上述问题的。


事务管理器 PlatformTransactionManager

事务管理器是 Spring 提供的对底层事务管理能力抽象,也是后续编程式和声明式事务的基础设施。它定义了事务管理所需的基本能力。一般的开发者不需要直接面对它,而是通过编程式或声明式事务来进行事务管理;不同的厂商根据不同的场景,来实现具体的事务管理器,Spring 提供了 AbstractPlatformTransactionManager ,通过模板方法模式快速的实现具体的事务管理器。

publicinterfacePlatformTransactionManager {
/*** 根据具体需求,获取当前活动的事务,或者新建一个事务。* 部分需求只能在新建事务时生效,若是使用当前获得的事务会被忽略。* 并不是所有需求都能被具体的实现所支持,这个时候会抛出一个异常。* * @params definition 描述所需的事务需求,包含传播行为,隔离级别,超时时间等等* @returns 用来表示返回的当前事务或新建事务的对象 */TransactionStatusgetTransaction(@NullableTransactionDefinitiondefinition) throwsTransactionException;
/*** 完成给定的事务。如果事务对象被标记为 rollback-only,则回滚事务;否则提交事务。* 如果这个事务不是 getTransaction 调用中新建的,则忽略本次调用,将提交或回滚留给事务更外层的参与者决定。*/voidcommit(TransactionStatusstatus) throwsTransactionException;
/*** 回滚给定的事务。* 如果这个事务不是 getTransaction 调用中新建的,则忽略掉这个操作,并将当前的事务对象设置为 rollback-only*/voidrollback(TransactionStatusstatus) throwsTransactionException;
}
/*** 用来定义一个 Spring 事务的需求。* 注:定义的隔离级别和超时时间,仅会在新建一个事务时生效(复用当前已有事务时会被忽略)。当且仅当传播行为为  PROPAGATION_REQUIRED, PROPAGATION_REQUIRES_NEW 和 PROPAGATION_NESTED 可能会新建一个事务,所以使用其他的传播行为通常不会设置隔离级别和超时时间。* 此外,不是所有的事务管理器实现都支持这些属性的(隔离级别设置、超时时间设置),如果使用这些管理器实现,且指定了某些不支持的属性值,就会抛出异常。* */publicinterfaceTransactionDefinition {
/** 传播行为配置可选值 */// 默认的传播行为。当前上下文有事务,则使用当前事务;否则新开一个事务。intPROPAGATION_REQUIRED=0;
// 如果当前上下文有事务,则使用当前事务;否则在无事务的上下文中执行。intPROPAGATION_SUPPORTS=1;
// 强制使用当前上下文中的事务;如果当前上下文没有事务,则抛出异常。intPROPAGATION_MANDATORY=2;
// 始终新开一个事务,如果当前上下文中有事务,则将其挂起。intPROPAGATION_REQUIRES_NEW=3;
// 始终在无事务的上下文中执行,如果当前上下文中有事务,则将其挂起。intPROPAGATION_NOT_SUPPORTED=4;
// 始终在无事务的上下文中执行,如果当前上下文中有事务,则抛出异常。intPROPAGATION_NEVER=5;
// 如果当前上下文有事务,则新开一个嵌套事务;如果当前上下文没有事务,则新开一个事务。注意,不是所有的事务管理器实现都支持嵌套事务的intPROPAGATION_NESTED=6;
/** 隔离级别配置可选值 */// 不显示设置隔离级别。默认根据事务管理的实现,或使用JDBC、数据库设定的隔离级别intISOLATION_DEFAULT=-1;
intISOLATION_READ_UNCOMMITTED=Connection.TRANSACTION_READ_UNCOMMITTED;
intISOLATION_READ_COMMITTED=Connection.TRANSACTION_READ_COMMITTED;
intISOLATION_REPEATABLE_READ=Connection.TRANSACTION_REPEATABLE_READ;
intISOLATION_SERIALIZABLE=Connection.TRANSACTION_SERIALIZABLE;
// 不显示设置超时时间。默认根据事务管理的实现,或使用JDBC、数据库设定的超时时间intTIMEOUT_DEFAULT=-1;
intgetPropagationBehavior();
intgetIsolationLevel();
intgetTimeout();
// 是否是只读事务booleanisReadOnly();
// 可选,事务的名称@NullableStringgetName();
}
/*** 用来代表一个事务和它关联的状态信息*/publicinterfaceTransactionStatusextendsSavepointManager, Flushable {
/*** 是否是在 getTransaction 调用中新建的一个事务*/booleanisNewTransaction();
/*** 事务中是否创建了Savepoint*/booleanhasSavepoint();
/** 将当前事务标记为 rollback-only,即告诉事务管理器这个事务只能够回滚。*/voidsetRollbackOnly();
/*** 当前事务是否被标记为 rollback-only*/booleanisRollbackOnly();
/*** Flush the underlying session to the datastore, if applicable:* for example, all affected Hibernate/JPA sessions.* <p>This is effectively just a hint and may be a no-op if the underlying* transaction manager does not have a flush concept. A flush signal may* get applied to the primary resource or to transaction synchronizations,* depending on the underlying resource.*//*** 当前事务是否已完成(已提交或已混滚)*/booleanisCompleted();
// 创建并返回一个 Savepoint@OverrideObjectcreateSavepoint() throwsTransactionException;
// 回滚到指定的 Savepoint@OverridevoidrollbackToSavepoint(Objectsavepoint) throwsTransactionException;
// 显示地删除指定的 Savepoint@OverridevoidreleaseSavepoint(Objectsavepoint) throwsTransactionException;
}


传播行为

看完上面的事务定义中,发现有很多传播行为相关的定义。那么什么是传播行为?数据库的事务定义中可没有这个概念。为什么 Spring 要定义这么多种传播行为?这些传播行为分别有什么用呢?在 Spring 事务的上下文中,传播的东西是事务,而传播的媒介是方法调用。换句话说,Spring 的传播行为是为了定义事务在方法的调用链上,如何进行传播的。

image.png

下面的表格梳理了在不同的传播行为和调用链中,事务的实际行为。其中 A、B、X 是三个不同的方法:A->X 表示从 A 调用到 X 的调用链;A、B 为外层的方法,A 本身开启了在调用 X 之前开启了事务,而 B 本身是没有任务事务的;X 为内层被调用的方法,同时也是通过 Spring 传播行为控制的方法(也就是说 Spring 的传播行为生效与 A 或者 B 发起对 X 的调用时)。


传播行为的实现

上面的表格中枚举的不同传播行为,在不同的调用链下,有不同的操作。这些操作总结下来有以下几种原子操作:

• 复用当前事务:从当前线程的局部存储中取出当前使用的事务;

• 新开一个事务:开启一个新事务,将当前事务保存到新事务对象中(构成一个链式堆栈),并放入当前线程的局部存储中;

• 无事务执行:挂起当前事务,然后执行后续逻辑;

• 抛异常:抛出一个 IllegalTransactionStateException 异常;

• 挂起当前事务:将当前事务从线程局部存储中取出,清理掉上下文中的相关标识信息。在新事务完成之后,将之前挂起的事务恢复到当前上下文。

• 开启一个嵌套事务:详见下节。


嵌套事务的实现

前面介绍过数据库和 JDBC 对于Savepoint 的支持,也提到了 NESTED 这种传播行为。接下来介绍 Savepoint 在 Spring 事务管理中的一种实际应用——支持嵌套事务。

根据这中实现方式,我们还可以看出 REQURES_NEW 和 NESTED 两种传播行为的区别:

• 内部方法提交,外层方法也提交时:两者的结果相同;

• 内部方法回滚,外层方法也回滚时:两者的结果相同;

• 内部方法回滚,外层方法提交时:两者的结果相同;

• 内部方法提交,外层方法被回滚时:REQURES_NEW的内部方法的变更被提交,外层方法的变更被回滚(两个独立的事务);NESTED的内外方法变更都被回滚了(本质上就是一个事务被回滚了)。


编程式事务 TransactionTemplate

前面简单的描述了事务管理器的基本原理,也提到一点,就是事务管理器不是面向一般开发者使用的。本节就 Spring 基于事务管理器之上,提供给一般开发者的编程编程式事务 TransactionTemplate的使用方式。它本质上包含两大部分:创建和执行。


TransactionTemplate 的创建

一个 TransactionTemplate 一共包含两部分内容,它使用的事务管理器(必须的),以及它执行是所需的事务需求描述(TransactionDefinition,可选的)。


TransactionTemplate 的执行

一旦 TransactionTemplate 创建好了之后,它提供了一个基于命令模式的 execute API 给开发者使用。事务的作用域就是 action 的 doInTransaction前后,事务的传播行为也发生在外层的doInTransaction中调用到内层的doInTransaction时(可能调用栈不只一层,而是多层)。整个 execute 方法很简单,就不展开说明了,直接上代码。

public<T>Texecute(TransactionCallback<T>action) throwsTransactionException {
Assert.state(this.transactionManager!=null, "No PlatformTransactionManager set");
if (this.transactionManagerinstanceofCallbackPreferringPlatformTransactionManager) {
return ((CallbackPreferringPlatformTransactionManager) this.transactionManager).execute(this, action);
}
else {
TransactionStatusstatus=this.transactionManager.getTransaction(this);
Tresult;
try {
result=action.doInTransaction(status);
}
catch (RuntimeException|Errorex) {
// Transactional code threw application exception -> rollbackrollbackOnException(status, ex);
throwex;
}
catch (Throwableex) {
// Transactional code threw unexpected exception -> rollbackrollbackOnException(status, ex);
thrownewUndeclaredThrowableException(ex, "TransactionCallback threw undeclared checked exception");
}
this.transactionManager.commit(status);
returnresult;
}
}
privatevoidrollbackOnException(TransactionStatusstatus, Throwableex) throwsTransactionException {
Assert.state(this.transactionManager!=null, "No PlatformTransactionManager set");
logger.debug("Initiating transaction rollback on application exception", ex);
try {
this.transactionManager.rollback(status);
}
catch (TransactionSystemExceptionex2) {
logger.error("Application exception overridden by rollback exception", ex);
ex2.initApplicationException(ex);
throwex2;
}
catch (RuntimeException|Errorex2) {
logger.error("Application exception overridden by rollback exception", ex);
throwex2;
}
}


上面的代码有额外两点补充一下:

1. execute 接受的命令接口 TransactionCallback#doInTransaction 必须要有一个返回值,Spring 提供了TransactionCallbackWithoutResult#doInTransactionWithoutResult 包装类,可以实现无返回值的命令;

2. CallbackPreferringPlatformTransactionManager 声明了事务管理器的实现有自己的 execute 方法,TransactionTemplate只需要将自身的 execute 转发给这个事务管理器即可。

回到 Java JDBC Transaciton 小节最后提到的三个问题。使用编程式事务,前两个都被很好的解决了,第三个问题解决了一半,就是通过命令模式,保证了开启的事务一定会被完成(提交或回滚),但是使用事务时需要重复的模板代码,这一点并没有解决。


声明式事务 @Transactional

声明式事务就是为了解决编程式事务留下的最后一个问题——消除模板代码。它利用了 Spring 的 AOP 机制,在需要使用事务的方法上打上一个注解,Spring 启动时扫描对应注解,为原始对象生成对应的代理对象,在代理中使用事务管理器进行事务管理。

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Inherited@Documentedpublic@interfaceTransactional {
@AliasFor("transactionManager")
Stringvalue() default"";
@AliasFor("value")
StringtransactionManager() default"";
Propagationpropagation() defaultPropagation.REQUIRED;
Isolationisolation() defaultIsolation.DEFAULT;
inttimeout() defaultTransactionDefinition.TIMEOUT_DEFAULT;
booleanreadOnly() defaultfalse;
Class<?extendsThrowable>[] rollbackFor() default {};
String[] rollbackForClassName() default {};
Class<?extendsThrowable>[] noRollbackFor() default {};
String[] noRollbackForClassName() default {};
}


以上就是 @Transactional 的定义,其中:

1. 它可以用到公有方法、接口或类声明上。如果将 @Transactional 打在类上,则会对这个类的所有方法都 apply 这个注解。如果方法和其类上同时有注解,那么方法注解优先;

2. value 或 transactionManager 可以指定需要使用的事务管理器,否则根据 Spring 的规则匹配默认的事务管理器;

3. propagation,isolation,timeout 和 readOnly 的含义,同 TransactionDefinition。且@Transactional 为他们设置了合适的默认值;

4. 通过 rollbackFor 和 noRollbackFor,构成了一个事务回滚的异常类型黑白名单。方法调用抛出 rollbackFor 中的异常类型,会触发事务回滚;方法调用抛出 noRollbackFor 中的异常类型,不触发事务回滚。默认情况下,声明式事务会将 RuntimeException 和 Error 类型及其子类型的异常当做未知异常,从而触发事务回滚,而 checked exception 被当成业务异常,不触发回滚。

protectedObjectinvokeWithinTransaction(Methodmethod, @NullableClass<?>targetClass,
finalInvocationCallbackinvocation) throwsThrowable {
// If the transaction attribute is null, the method is non-transactional.TransactionAttributeSourcetas=getTransactionAttributeSource();
finalTransactionAttributetxAttr= (tas!=null?tas.getTransactionAttribute(method, targetClass) : null);
finalPlatformTransactionManagertm=determineTransactionManager(txAttr);
finalStringjoinpointIdentification=methodIdentification(method, targetClass, txAttr);
if (txAttr==null||!(tminstanceofCallbackPreferringPlatformTransactionManager)) {
// Standard transaction demarcation with getTransaction and commit/rollback calls.TransactionInfotxInfo=createTransactionIfNecessary(tm, txAttr, joinpointIdentification);
ObjectretVal=null;
try {
// This is an around advice: Invoke the next interceptor in the chain.// This will normally result in a target object being invoked.retVal=invocation.proceedWithInvocation();
}
catch (Throwableex) {
// target invocation exceptioncompleteTransactionAfterThrowing(txInfo, ex);
throwex;
}
finally {
cleanupTransactionInfo(txInfo);
}
commitTransactionAfterReturning(txInfo);
returnretVal;
}
else {
finalThrowableHolderthrowableHolder=newThrowableHolder();
// It's a CallbackPreferringPlatformTransactionManager: pass a TransactionCallback in.try {
Objectresult= ((CallbackPreferringPlatformTransactionManager) tm).execute(txAttr, status-> {
TransactionInfotxInfo=prepareTransactionInfo(tm, txAttr, joinpointIdentification, status);
try {
returninvocation.proceedWithInvocation();
}
catch (Throwableex) {
if (txAttr.rollbackOn(ex)) {
// A RuntimeException: will lead to a rollback.if (exinstanceofRuntimeException) {
throw (RuntimeException) ex;
}
else {
thrownewThrowableHolderException(ex);
}
}
else {
// A normal return value: will lead to a commit.throwableHolder.throwable=ex;
returnnull;
}
}
finally {
cleanupTransactionInfo(txInfo);
}
});
// Check result state: It might indicate a Throwable to rethrow.if (throwableHolder.throwable!=null) {
throwthrowableHolder.throwable;
}
returnresult;
}
catch (ThrowableHolderExceptionex) {
throwex.getCause();
}
catch (TransactionSystemExceptionex2) {
if (throwableHolder.throwable!=null) {
logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
ex2.initApplicationException(throwableHolder.throwable);
}
throwex2;
}
catch (Throwableex2) {
if (throwableHolder.throwable!=null) {
logger.error("Application exception overridden by commit exception", throwableHolder.throwable);
}
throwex2;
}
}
}



声明式事务的一个坑

因为声明式事务是基于代理机制实现的,所以一定要通过代理对象调用目标方法才能够让事务管理生效。举个例子

@ComponentpublicclassFoo {
publicvoida() {
this.b();
    }
@Transactionalpublicvoidb() {
// do something    }
}


在上面这个场景中,a 方法内调用 this.b()时,Transactional 是不生效的。因为 this 是原始 bean 对象,直接在 this上进行 b 的调用,是触发不了外层代理的事务处理逻辑的。正确的使用方式可参考以下代码:

@ComponentpublicclassFoo {
@AutowireprivateApplicationContextapplicationContext;
publicvoida() {
// 从 spring 中拿到代理之后的 bean,再发起调用applicationContext.getBean(Foo.class).b();
    }
@Transactionalpublicvoidb() {
// do something    }
}


参考

https://arxiv.org/pdf/cs/0701157.pdf

https://blog.acolyer.org/2016/02/24/a-critique-of-ansi-sql-isolation-levels/

相关文章
|
4月前
|
Java 应用服务中间件 Nacos
Spring Cloud 常用各个组件详解及实现原理(附加源码+实现逻辑图)
Spring Cloud 常用各个组件详解及实现原理(附加源码+实现逻辑图)
91 0
|
1月前
|
人工智能 自然语言处理 Java
Spring AI,Spring团队开发的新组件,Java工程师快来一起体验吧
文章介绍了Spring AI,这是Spring团队开发的新组件,旨在为Java开发者提供易于集成的人工智能API,包括机器学习、自然语言处理和图像识别等功能,并通过实际代码示例展示了如何快速集成和使用这些AI技术。
Spring AI,Spring团队开发的新组件,Java工程师快来一起体验吧
|
1月前
|
Java Spring
Spring的AOP组件详解
该文章主要介绍了Spring AOP(面向切面编程)组件的实现原理,包括Spring AOP的基础概念、动态代理模式、AOP组件的实现以及Spring选择JDK动态代理或CGLIB动态代理的依据。
Spring的AOP组件详解
|
27天前
|
Java 开发工具 Spring
【Azure 事件中心】azure-spring-cloud-stream-binder-eventhubs客户端组件问题, 实践消息非顺序可达
【Azure 事件中心】azure-spring-cloud-stream-binder-eventhubs客户端组件问题, 实践消息非顺序可达
|
2月前
|
安全 前端开发 Java
Java技术栈中的核心组件:Spring框架
Java作为一门成熟的编程语言,其生态系统拥有众多强大的组件和框架,其中Spring框架无疑是Java技术栈中最闪耀的明星之一。Spring框架为Java开发者提供了一套全面的编程和配置模型,极大地简化了企业级应用的开发流程。
35 1
|
3月前
|
NoSQL 前端开发 Java
技术笔记:springboot分布式锁组件spring
技术笔记:springboot分布式锁组件spring
42 1
|
3月前
|
Java API 数据安全/隐私保护
在Spring Boot中,过滤器(Filter)是一种非常有用的组件
在Spring Boot中,过滤器(Filter)是一种非常有用的组件
67 6
|
3月前
|
负载均衡 前端开发 Java
OpenFeign:Spring Cloud声明式服务调用组件
该文本是关于OpenFeign在Spring Cloud中的使用的问答总结。涉及的问题包括:OpenFeign是什么,Feign与OpenFeign的区别,如何使用OpenFeign进行远程服务调用,OpenFeign的超时控制以及日志增强。OpenFeign被描述为Spring官方的声明式服务调用和负载均衡组件,它支持使用注解进行接口定义和服务调用,如@FeignClient和@EnableFeignClients。OpenFeign与Feign的主要区别在于OpenFeign支持Spring MVC注解。超时控制通过Ribbon进行设置,默认超时时间为1秒。
|
4月前
|
SpringCloudAlibaba Java 持续交付
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(一)基础知识+各个组件介绍+聚合父工程创建
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(一)基础知识+各个组件介绍+聚合父工程创建
691 1
|
4月前
|
Java 开发者 容器
【Java】深入了解Spring容器的两个关键组件
【Java】深入了解Spring容器的两个关键组件
26 0