使用 Spring 和 Hibernate 跨多个数据库进行分布式事务的“最佳”方法是啥

Posted

技术标签:

【中文标题】使用 Spring 和 Hibernate 跨多个数据库进行分布式事务的“最佳”方法是啥【英文标题】:What is the 'best' way to do distributed transactions across multiple databases using Spring and Hibernate使用 Spring 和 Hibernate 跨多个数据库进行分布式事务的“最佳”方法是什么 【发布时间】:2010-09-12 19:18:51 【问题描述】:

我有一个应用程序 - 更像是一个实用程序 - 它位于角落并定期更新两个不同的数据库。

这是一个使用 Spring Application Context 构建的小型独立应用程序。上下文中配置了两个 Hibernate Session Factories,依次使用 Spring 中配置的 Commons DBCP 数据源。

目前没有事务管理,但我想补充一些。对一个数据库的更新取决于对另一个数据库的成功更新。

应用程序不位于 Java EE 容器中 - 它由从 shell 脚本调用的静态启动器类引导。启动器类实例化应用程序上下文,然后调用其中一个 bean 上的方法。

围绕数据库更新进行事务处理的“最佳”方式是什么?

我将“最佳”的定义留给您,但我认为它应该是“易于设置”、“易于配置”、“便宜”和“易于打包和重新分发”的一些功能。当然 FOSS 会很好。

【问题讨论】:

【参考方案1】:

在多个数据库上分配事务的最佳方式是:不要。

有些人会将您指向 XA,但 XA(或两阶段提交)是谎言(或市场化)。

想象一下:在第一阶段告诉 XA 管理器它可以发送最终提交后,到其中一个数据库的网络连接失败。怎么办?暂停?这会使其他数据库损坏。回滚?两个问题:你不能回滚提交,你怎么知道第二个数据库发生了什么?可能是成功提交数据后网络连接失败,只丢失了“成功”消息?

最好的方法是将数据复制到一个地方。使用允许您中止复制并随时继续复制的方案(例如,忽略您已有的数据或按 ID 排序选择并仅请求副本的记录 > MAX(ID))。通过交易保护这一点。这不是问题,因为您只是从源中读取数据,因此当事务因任何原因失败时,您可以忽略源数据库。因此,这是一个普通的旧单源交易。

复制数据后,在本地进行处理。

【讨论】:

分布式事务必须满足所有 4 个 ACID 属性。你怎么了?您描述的场景不可能发生,因为管理器正在相互通信,并且只有在所有参与节点都交换了“GO”时才会提交。 @Falcon:那么如果网络在 PREPARE 和 COMMIT 之间发生故障会发生什么?还是其中一台服务器死了? “不可能发生”在现实中不可能发生。 不,他们没有被指示回滚,因为在这种情况下,一些节点已经提交。发生的情况是当崩溃的节点变得可用时,事务协调器告诉它再次提交。因为节点在“准备”阶段做出了积极响应,所以它需要能够“提交”,即使它从崩溃中恢复。 我发现对标准的支持太广泛了,不会被一个人的轶事证据所左右。谢谢你们的cmets。 @Nicholas 我发现只有那些通过解决这个标准造成的问题来赚钱的公司才能得到广泛的支持。 “消费者”(=不得不忍受这种解决方案的人)通常会尝试一次,然后寻找更好的解决方案。也就是说,我的回答在逻辑上是合理的。我的方法比 XA 简单得多,而且我可以证明它总是有效的。 XA 更像是一个承诺,而不是事实。【参考方案2】:

在您的上下文中设置事务管理器。 Spring 文档有示例,非常简单。那么当你想执行一个事务时:

try  
    TransactionTemplate tt = new TransactionTemplate(txManager);

    tt.execute(new TransactionCallbackWithoutResult()
    protected void doInTransactionWithoutResult(
            TransactionStatus status) 
        updateDb1();
        updateDb2();
    
 catch (TransactionException ex) 
    // handle 

有关更多示例和信息,请查看以下内容: XA transactions using Spring

【讨论】:

这个例子并没有真正回答问题,甚至回答错误:OP 提到他配置了两个 Hibernate 会话工厂,这需要两个单独的事务管理器。答案中的示例仅使用了一个未指定更近的事务管理器。因此,使用单个 Hibernate 事务管理器永远不会在错误时回滚两个 DB 之一。使用例如ChainedTransactionManager(如@Pani Dhakshnamurthy 所述)可能会有所帮助,但此答案中未提及。【参考方案3】:

当您说“两个不同的数据库”时,您是指不同的数据库服务器,还是同一个数据库服务器中的两个不同的架构?

如果是前者,那么如果您想要完整的事务性,那么您需要 XA 事务 API,它提供完整的两阶段提交。但更重要的是,您还需要一个事务协调器/监视器来管理不同数据库系统之间的事务传播。这是 JavaEE 规范的一部分,并且是其中相当稀少的一部分。 TX 协调器本身是一个复杂的软件。您的应用程序软件(如果您愿意,可以通过 Spring)与协调器对话。

但是,如果您只是指同一数据库服务器中的两个数据库,那么普通 JDBC 事务应该可以正常工作,只需在单个事务中对两个数据库执行操作即可。

【讨论】:

【参考方案4】:

在这种情况下,您需要一个事务监视器(支持 XA 协议的服务器)并确保您的数据库也支持 XA。大多数(全部?)J2EE 服务器都内置了事务监视器。如果您的代码不在 J2EE 服务器中运行,那么有很多独立的替代方案 - Atomicos、Bitronix 等。

【讨论】:

【参考方案5】:

您可以尝试支持分布式数据库事务的 Spring ChainedTransactionManager - http://docs.spring.io/spring-data/commons/docs/1.6.2.RELEASE/api/org/springframework/data/transaction/ChainedTransactionManager.html。这可能是 XA 的更好替代方案

【讨论】:

以上是关于使用 Spring 和 Hibernate 跨多个数据库进行分布式事务的“最佳”方法是啥的主要内容,如果未能解决你的问题,请参考以下文章

Hibernate + Spring 使用多个数据源?

使用带有 Spring 和 Hibernate 的会话工厂处理多个数据库连接

Spring-Hibernate 使用多个数据源/数据库

使用 QueryDSL 和 Spring 的存储库编写跨表查询

Spring MVC (Hibernate+Maven) - 多个控制器和会话(登录/注销)

如何通过使用 JPA + Hibernate 和 Spring-boot 在一个数据库中使用多个模式?