Spring 框架事务支持模型的优势 (Advantages of the Spring Framework’s Transaction Support Model)
传统上,Java EE 开发人员在事务管理方面有两种选择:全局事务或本地事务,这两者都有深刻的局限性。接下来的两节将简要回顾全局和本地事务管理,随后探讨 Spring 框架的事务管理支持如何解决这些局限。
全局事务
全局事务允许你处理多个事务资源,通常是关系型数据库和消息队列。应用服务器通过 JTA(Java Transaction API)管理全局事务,但 JTA 是一个非常笨重的 API(部分原因是其异常模型)。此外,JTA 的 UserTransaction 通常需要从 JNDI 获取,这意味着为了使用 JTA,你还必须使用 JNDI。全局事务的使用限制了应用程序代码的任何潜在重用,因为 JTA 通常仅在应用服务器环境中可用。
以前,使用全局事务的首选方式是通过 EJB CMT(容器管理事务)。CMT 是声明式事务管理的一种形式(与编程式事务管理相对)。尽管使用 EJB 本身仍需要 JNDI,但 EJB CMT 消除了对事务相关 JNDI 查找的需求。它消除了编写大部分(但非全部)用于控制事务的 Java 代码的需求。其显著缺点是 CMT 被绑定到 JTA 和应用服务器环境。此外,它只有在开发人员选择在 EJB 中实现业务逻辑(或至少在事务性 EJB 外层之后)时才可用。由于 EJB 整体的负面影响太大,在面对极具吸引力的声明式事务管理替代方案时,这并不是一个有吸引力的主张。
本地事务
本地事务是特定于资源的,例如与 JDBC 连接关联的事务。本地事务可能更容易使用,但有一个显著缺点:它们无法跨多个事务资源工作。例如,使用 JDBC 连接管理事务的代码无法在全局 JTA 事务中运行。由于应用服务器不参与事务管理,它无法帮助确保跨多个资源的正确性(值得注意的是,大多数应用程序使用单一事务资源)。另一个缺点是本地事务对编程模型具有侵入性。
Spring 框架的一致编程模型
Spring 解决了全局事务和本地事务的缺点。它让应用程序开发人员在任何环境中都能使用一致的编程模型。你只需编写一次代码,它就可以从不同环境中不同的事务管理策略中受益。Spring 框架同时提供声明式和编程式事务管理。大多数用户更喜欢声明式事务管理,这也是我们在大多数情况下推荐的做法。
通过编程式事务管理,开发人员直接使用 Spring 框架的事务抽象,该抽象可以在任何底层事务基础设施之上运行。通过首选的声明式模型,开发人员通常只需编写极少甚至不编写与事务管理相关的代码,因此不依赖于 Spring 框架的事务 API 或任何其他事务 API。
是否需要应用服务器进行事务管理?
Spring 框架的事务管理支持改变了传统规则,即企业级 Java 应用程序何时需要应用服务器。
特别是,你不需要仅仅为了通过 EJB 实现声明式事务而使用应用服务器。事实上,即使你的应用服务器具有强大的 JTA 功能,你可能会发现 Spring 框架的声明式事务比 EJB CMT 提供了更强大的功能和更高效的编程模型。
通常,只有当你的应用程序需要处理跨多个资源的事务时,才需要应用服务器的 JTA 功能,而这对于许多应用程序来说并不是必需的。许多高端应用程序转而使用单一的高度可伸缩数据库(如 Oracle RAC)。独立事务管理器(如 Atomikos Transactions)是其他选择。当然,你可能需要其他应用服务器功能,如 Java 消息服务 (JMS) 和 Jakarta EE 资源适配器架构 (JCA)。
Spring 框架让你能够选择何时将应用程序扩展到功能完整的应用服务器。过去那种“要么使用 EJB CMT/JTA,要么编写 JDBC 本地事务并在迁移到全局事务时面临巨量重构”的日子已经一去不复返了。使用 Spring 框架,只需更改配置文件中的某些 Bean 定义(而不是你的代码)即可完成切换。
补充教学
1. 历史背景:巨头统治的时代
在 Spring 诞生之前,如果你想要实现“声明式事务”(即通过配置而不是在代码里写 commit/rollback),你必须使用昂贵的 Java EE 应用服务器(如 WebLogic, WebSphere)。这意味着即使你只是做个简单的管理系统,也要背负沉重的服务器成本和复杂的配置。
2. Spring 带来的“自由度”
Spring 提出了一个极其大胆且成功的想法:事务抽象与底层实现解耦。
- 解耦 JTA/JNDI:你可以直接在普通的 Tomcat(甚至一个简单的单元测试)中运行事务。
- 平滑迁移:
- 起步期:本地单一数据库,使用
DataSourceTransactionManager(轻量、极快)。 - 成长期:业务扩展需要同时操作数据库和 ActiveMQ,只需把
PlatformTransactionManager的实现类换成JtaTransactionManager。 - 业务代码:从头到尾不需要改动一行
@Transactional代码。
- 起步期:本地单一数据库,使用
3. 理解“侵入性” (Invasive)
- 本地事务的侵入性:你需要通过
Connection对象手动开启事务。这意味着你的业务逻辑必须持有数据库连接,导致代码难以测试,且无法复用。 - JTA 的侵入性:虽然逻辑上解耦了,但它强制你依赖 JNDI 查找和容器环境。
- Spring 的非侵入性:你的业务方法只是一个普通的 POJO 方法。事务是通过 Spring 的 AOP 动态织入的。如果脱离 Spring 环境,它依然是一个可以运行的正常 Java 方法。
4. 现代视角:XA 还是补偿?
在现在的微服务时代,虽然 Spring 依然提供了完美的全局事务(XA 事务)支持,但开发者往往会尽量避免它。
- 原因:XA 事务(强一致性)通常涉及“两阶段提交”,在分布式环境下性能损耗极大。
- 现代方案:开发者更多使用 Spring 支持的 TCC (Try-Confirm-Cancel) 或 Saga 状态机模式 来实现“最终一致性”。但无论方案如何变化,Spring 提供的事务生命周期监听和抽象依然是所有这些模式的基石。