如何在数据库 A 上 Spring 管理的事务范围内使用数据库 B,所有这些都没有 XA?
How to work with database B within the scope of Spring-managed transaction on database A, all without XA?
首先,这个问题不是关于应用外部XA事务管理器,因为这肯定能解决问题。问题是如果不假设以下情况如何解决:
- 两个数据库中的事务都在单个 Java 线程中执行
- 读取和写入数据from/to 两个数据库,以及其他非事务性源和目标
- 不需要两个数据库之间的最大数据一致性
- 乐观的 1PC 提交是可以接受的(第二次提交失败不应触发第一个数据库的回滚)
- 程序化或声明式事务范围控制都可以
现在让我们假设以下场景:
- 开始数据库 A 的事务 X
- 在数据库A中读写一些数据
- 根据这些数据,有时我需要使用 db B
- (继续在同一个 Java 线程中工作)
- 开始 db B 的事务 Y
- 在db B中读写一些数据
- 如果这里出现问题,回滚两个事务
- 提交事务 Y
- 有效恢复交易X
- 在数据库 A 中读写更多数据
- 如果这里出现问题,只回滚事务 X
- 提交事务 X
我的感觉是,一切都需要 "nested transaction" 解决方案,幸运的是 Spring DataSourceTransactionManager
支持。问题是,Propagation.NESTED
假定事务 X 和 Y 都在同一数据库 (DataSource
) 中执行,并且可能在相同的底层 JDBC Connection
上执行。但这显然不是我的情况,因为数据库具有单独的连接并且能够支持独立的事务。
我尝试的另一种可能的解决方案是创建两个 DataSourceTransactionManager
实例,每个数据库一个。乍一看,它看起来是一个更简洁的解决方案——但后来我意识到标准 Spring 类 严重依赖静态、线程本地字段,因此在尝试同时使用两个管理器时保证相互踩踏通过同一个线程(见上面的假设)。不行。
现在我正在考虑将所有相关的 Spring 事务管理 类 子类化为 "separate" 包之间的那些共享静态字段。不过感觉像是在发明自行车,所以我宁愿不做。
由于外部 XA 事务管理器被视为矫枉过正(由于非常松散的一致性要求,见上文),是唯一可以下降到 JDBC 级别并以编程方式管理事务 Y 的解决方案(开始,阅读,写入数据,提交)?还是我在 spring-tx
?
中遗漏了一些高级概念?
我不是 Spring 专家(因此我不能对子类化的想法说什么)但我知道 Spring 事务使用 JTA 的能力。
正如您所说,DataSourceTransactionManager
仅适用于每个资源,并且 NESTED
功能是可能的,因为 JDBC api 及其使用安全点的功能(https://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html#setSavepoint--).此能力仅限于一个 Connection
.
我认为您可以按照您的建议进行手动 JDBC 管理。或者我仍然会考虑事务管理器。事务管理器不仅管理 XA 事务,而且还提供 JTA api 的实现,您可以使用声明式或编程式方法。事务管理的最大开销是这种 XA 处理——数据需要在应用程序和数据库端准备期间保存到驱动器。如果您只使用 non-XA 资源的事务管理功能,那么事务管理器会为您提供 JTA api 到 运行 事务,不提供一致性(这不是您需要的)并且不使用XA 开销。
如果您使用事务管理器和两个 non-XA 资源 (DriverManagerDataSource
),那么您可以驱动事务,例如 - 开始 - 更新数据 - 暂停 #1 - 开始 - 更新数据 - 提交 #2 -恢复 #1 - 提交。
不幸的是,您的特定情况最适合 JTA 不支持的嵌套事务模型。
但即使使用 NESTED
Spring 范围,这种情况也正是您所需要的。嵌套的工作方式是,如果嵌套事务是 rolled-back,则外部事务不会自动 rolled-back。换句话说,嵌套事务(事务Y
)的回滚并不意味着外部事务也是rolled-back(事务X
)。
首先,这个问题不是关于应用外部XA事务管理器,因为这肯定能解决问题。问题是如果不假设以下情况如何解决:
- 两个数据库中的事务都在单个 Java 线程中执行
- 读取和写入数据from/to 两个数据库,以及其他非事务性源和目标
- 不需要两个数据库之间的最大数据一致性
- 乐观的 1PC 提交是可以接受的(第二次提交失败不应触发第一个数据库的回滚)
- 程序化或声明式事务范围控制都可以
现在让我们假设以下场景:
- 开始数据库 A 的事务 X
- 在数据库A中读写一些数据
- 根据这些数据,有时我需要使用 db B
- (继续在同一个 Java 线程中工作)
- 开始 db B 的事务 Y
- 在db B中读写一些数据
- 如果这里出现问题,回滚两个事务
- 提交事务 Y
- 有效恢复交易X
- 在数据库 A 中读写更多数据
- 如果这里出现问题,只回滚事务 X
- 提交事务 X
我的感觉是,一切都需要 "nested transaction" 解决方案,幸运的是 Spring DataSourceTransactionManager
支持。问题是,Propagation.NESTED
假定事务 X 和 Y 都在同一数据库 (DataSource
) 中执行,并且可能在相同的底层 JDBC Connection
上执行。但这显然不是我的情况,因为数据库具有单独的连接并且能够支持独立的事务。
我尝试的另一种可能的解决方案是创建两个 DataSourceTransactionManager
实例,每个数据库一个。乍一看,它看起来是一个更简洁的解决方案——但后来我意识到标准 Spring 类 严重依赖静态、线程本地字段,因此在尝试同时使用两个管理器时保证相互踩踏通过同一个线程(见上面的假设)。不行。
现在我正在考虑将所有相关的 Spring 事务管理 类 子类化为 "separate" 包之间的那些共享静态字段。不过感觉像是在发明自行车,所以我宁愿不做。
由于外部 XA 事务管理器被视为矫枉过正(由于非常松散的一致性要求,见上文),是唯一可以下降到 JDBC 级别并以编程方式管理事务 Y 的解决方案(开始,阅读,写入数据,提交)?还是我在 spring-tx
?
我不是 Spring 专家(因此我不能对子类化的想法说什么)但我知道 Spring 事务使用 JTA 的能力。
正如您所说,DataSourceTransactionManager
仅适用于每个资源,并且 NESTED
功能是可能的,因为 JDBC api 及其使用安全点的功能(https://docs.oracle.com/javase/8/docs/api/java/sql/Connection.html#setSavepoint--).此能力仅限于一个 Connection
.
我认为您可以按照您的建议进行手动 JDBC 管理。或者我仍然会考虑事务管理器。事务管理器不仅管理 XA 事务,而且还提供 JTA api 的实现,您可以使用声明式或编程式方法。事务管理的最大开销是这种 XA 处理——数据需要在应用程序和数据库端准备期间保存到驱动器。如果您只使用 non-XA 资源的事务管理功能,那么事务管理器会为您提供 JTA api 到 运行 事务,不提供一致性(这不是您需要的)并且不使用XA 开销。
如果您使用事务管理器和两个 non-XA 资源 (DriverManagerDataSource
),那么您可以驱动事务,例如 - 开始 - 更新数据 - 暂停 #1 - 开始 - 更新数据 - 提交 #2 -恢复 #1 - 提交。
不幸的是,您的特定情况最适合 JTA 不支持的嵌套事务模型。
但即使使用 NESTED
Spring 范围,这种情况也正是您所需要的。嵌套的工作方式是,如果嵌套事务是 rolled-back,则外部事务不会自动 rolled-back。换句话说,嵌套事务(事务Y
)的回滚并不意味着外部事务也是rolled-back(事务X
)。