声明式事务管理 (Declarative Transaction Management)
注意
大多数 Spring 框架用户选择声明式事务管理。此选项对应用程序代码的影响最小,因此最符合非侵入性轻量级容器的理想。
Spring 框架的声明式事务管理是通过 Spring 面向切面编程 (AOP) 实现的。然而,由于事务切面代码随 Spring 框架发行版一起提供,并可以以样板方式使用,因此通常无需理解 AOP 概念即可有效使用这些代码。
Spring 框架的声明式事务管理与 EJB CMT 类似,你可以指定细化到单个方法级别的事务行为(或不使用事务)。如果需要,你可以在事务上下文中调用 setRollbackOnly()。这两种类型的事务管理之间的区别在于:
- 环境无关性:与绑定到 JTA 的 EJB CMT 不同,Spring 框架的声明式事务管理可以在任何环境中工作。通过调整配置文件,它可以配合 JTA 事务工作,也可以配合使用 JDBC、JPA 或 Hibernate 的本地事务工作。
- 普适性:你可以将 Spring 框架的声明式事务管理应用于任何类,而不仅仅是像 EJB 这样的特殊类。
- 回滚规则 (Rollback Rules):Spring 框架提供了声明式的回滚规则,这是 EJB 所没有的功能。Spring 同时提供编程式和声明式对回滚规则的支持。
- 自定义行为:Spring 框架允许你使用 AOP 定制事务行为。例如,你可以在事务回滚时插入自定义行为。你还可以添加任意通知(Advice)以及事务通知。而使用 EJB CMT,除了
setRollbackOnly()之外,你无法影响容器的事务管理。 - 远程调用限制:Spring 框架不支持像高端应用服务器那样跨远程调用传播事务上下文。如果你需要此功能,我们建议使用 EJB。但在使用此类功能前请仔细考虑,因为通常情况下,开发者并不希望事务横跨远程调用。
回滚规则的概念非常重要。它们允许你指定哪些异常(和 Throwable)应导致自动回滚。你可以在配置中(而不是 Java 代码中)声明式地指定这一点。因此,虽然你仍然可以在 TransactionStatus 对象上调用 setRollbackOnly() 来回滚当前事务,但通常你可以指定一个规则,即 MyApplicationException 必须始终导致回滚。此选项的显著优势是业务对象不依赖于事务基础设施。例如,它们通常不需要导入 Spring 事务 API 或其他 Spring API。
虽然 EJB 容器的默认行为是在发生系统异常(通常是运行时异常)时自动回滚事务,但 EJB CMT 不会在发生应用异常(即除了 java.rmi.RemoteException 之外的检查型异常)时自动回滚事务。虽然 Spring 声明式事务管理的默认行为遵循 EJB 惯例(仅在非检查型异常上自动回滚),但定制此行为通常非常有用。
补充教学
1. 声明式事务的核心:AOP 代理机制
Spring 实现声明式事务的核心是 AOP 代理 (AOP Proxy)。
- 当你为一个 Bean 标记
@Transactional时,Spring 实际上创建了一个代理对象。 - 调用流程:当你调用该方法时,你实际上是在调用代理。代理首先开启事务,然后执行你的业务逻辑。如果逻辑成功,代理提交事务;如果抛出特定的异常,代理回滚事务。
- 避坑指南:由于它是基于代理的,如果你在同一个类中一个没有事务的方法内部调用带
@Transactional的方法,事务不会生效,因为这种调用绕过了代理对象。
2. 回滚规则的“现代陷阱”
Spring 默认只回滚 RuntimeException 和 Error。
- 现实问题:如果你在代码中自定义了一个继承自
Exception的检查型异常(Checked Exception),并且在方法中抛出了它,Spring 默认不会回滚事务。 - 解决方案:
- 现代推荐:尽可能让业务异常继承自
RuntimeException。 - 显式配置:使用
@Transactional(rollbackFor = Exception.class)来强制所有异常都触发回滚。
- 现代推荐:尽可能让业务异常继承自
3. @Transactional vs XML 配置
在 Spring 发展史中,有三种声明事务的方式:
- 早期 (ProxyFactoryBean):已过时,过于繁琐。
- 中期 (XML
<tx:advice>):曾非常流行,特别是当你想要通过切点表达式为整个 Service 层的特定命名规则(如insert*,update*)自动添加事务时非常高效。 - 现代 (@Transactional):最常用。它将事务配置与业务逻辑放在一起,虽然有少量“侵入性”,但可读性和维护性极佳。
4. 为什么不支持远程事务传播?
Spring 官方文档明确指出不建议事务跨远程调用,这是基于微服务架构设计原则的:
- 性能隐患:远程调用是不稳定的。如果事务横跨远程调用,数据库连接会被长时间挂起,极易导致系统崩溃。
- 自治性破坏:事务应该在每个服务内部闭环。
- 替代方案:在微服务环境下,我们使用 Saga 模式、消息队列实现最终一致性 或 分布式事务框架 (如 Seata),而不是传统的事务上下文传播。