Spring事务管理:从原理到实战
🌟 一、为什么需要 Spring 的事务管理?
在没有 Spring 之前,Java EE 应用程序处理事务只有两种方式:
1. 全局事务(Global Transactions)—— EJB + JTA
- 适用于跨多个资源的事务(比如:数据库 + 消息队列)
- 使用 JTA(Java Transaction API),API 很复杂
- 必须依赖 应用服务器(如 WebLogic、WebSphere)
- 通常通过 EJB 的 CMT(容器管理事务)实现声明式事务
- 缺点:太重、难测试、代码复用性差
✅ 优点:能跨资源
❌ 缺点:必须用 EJB、必须部署在应用服务器、开发效率低
2. 本地事务(Local Transactions)—— JDBC 或 Hibernate 自带事务
- 比如用
connection.setAutoCommit(false)开启事务 - 简单,但只能用于单一资源(如一个数据库)
- 不能参与全局事务(JTA)
- 代码侵入性强(手动 commit/rollback)
✅ 优点:简单直接
❌ 缺点:无法跨资源、代码重复多、难以升级到分布式事务
✅ Spring 的解决方案:统一事务抽象模型
🎯 目标:让开发者写一次代码,可以在不同环境(单机/集群/应用服务器)下自由切换事务策略,而无需修改业务逻辑。
✔️ Spring 提供了什么?
| 特性 | 说明 |
|---|---|
| 一致的编程模型 | 不管底层是 JDBC、Hibernate、JPA 还是 JTA,API 都一样 |
| 声明式事务 | 用注解 @Transactional 自动管理事务,无需写 try-catch-commit/rollback |
| 编程式事务 | 手动控制事务边界,更灵活 |
| 与数据访问层无缝集成 | 和 JdbcTemplate、HibernateTemplate 等配合得天衣无缝 |
| 不依赖应用服务器 | 即使不用 EJB 和 JTA,也能享受声明式事务 |
🔧 二、核心概念:Spring 事务抽象架构
Spring 的事务机制基于几个关键接口和类:
1. PlatformTransactionManager 接口(核心)
这是 Spring 事务的“管理者”,所有事务操作都通过它来完成。
public interface PlatformTransactionManager {TransactionStatus getTransaction(TransactionDefinition definition);void commit(TransactionStatus status);void rollback(TransactionStatus status);
}
常见实现类:
| 实现类 | 用途 |
|---|---|
DataSourceTransactionManager | 用于 JDBC 或 MyBatis,单数据源 |
HibernateTransactionManager | 用于 Hibernate 框架 |
JpaTransactionManager | 用于 JPA(如 Hibernate 作为 JPA 实现) |
JtaTransactionManager | 用于 JTA,支持多资源分布式事务(需应用服务器或 Atomikos) |
💡 你可以像注入普通 Bean 一样配置它,完全解耦!
2. TransactionDefinition 接口(定义事务规则)
表示一个事务的属性配置,包括:
| 属性 | 含义 |
|---|---|
| Propagation(传播行为) | 方法被调用时如何处理事务(如:新建事务?加入已有?) |
| Isolation(隔离级别) | 脏读、不可重复读、幻读等问题的控制 |
| Timeout(超时时间) | 事务最长运行时间,超时自动回滚 |
| Read-only(只读) | 提示数据库优化查询性能 |
| Rollback for… | 哪些异常触发回滚(默认是 RuntimeException) |
3. TransactionStatus 接口(运行时状态)
代表当前事务的状态,可以用来:
- 判断是否是新事务:
isNewTransaction() - 设置回滚:
setRollbackOnly() - 判断是否已完成:
isCompleted()
📦 三、如何配置事务管理器?
Spring 支持多种数据访问技术,每种都有对应的事务管理器。
✅ 示例1:JDBC / MyBatis(单数据源)
<!-- 数据源 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"><property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/><property name="url" value="jdbc:mysql://localhost:3306/test"/><property name="username" value="root"/><property name="password" value="123456"/>
</bean><!-- 事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"><property name="dataSource" ref="dataSource"/>
</bean><!-- 开启注解事务支持 -->
<tx:annotation-driven transaction-manager="txManager"/>
✅ 示例2:Hibernate(本地事务)
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean"><property name="dataSource" ref="dataSource"/><property name="mappingResources"><list><value>user.hbm.xml</value></list></property><property name="hibernateProperties"><value>hibernate.dialect=org.hibernate.dialect.MySQL8Dialect</value></property>
</bean><bean id="txManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager"><property name="sessionFactory" ref="sessionFactory"/>
</bean><tx:annotation-driven transaction-manager="txManager"/>
✅ 示例3:JTA(分布式事务,跨数据库+MQ)
<!-- 从 JNDI 获取数据源(由应用服务器管理) -->
<jee:jndi-lookup id="dataSource" jndi-name="jdbc/MyDB"/><!-- 使用 JTA 事务管理器 -->
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/><tx:annotation-driven transaction-manager="txManager"/>
📝 注意:JTA 不关心具体资源,因为它依赖容器的全局事务协调器。
🧩 四、事务同步:资源如何绑定到事务?
当你在一个事务中多次访问数据库,Spring 要确保每次都使用同一个连接(Connection),否则事务就失效了。
Spring 是怎么做到的?
✅ 高层方案(推荐):
使用 Spring 封装的数据访问模板,如:
JdbcTemplateHibernateTemplateJpaRepository
这些模板内部会自动从当前事务上下文中获取已绑定的资源(如 Connection 或 Session),并保证线程安全。
✅ 低层方案(了解即可):
如果你直接使用原生 API(如 SessionFactory.openSession()),可以用工具类:
// 正确做法:获取与事务同步的 Connection
Connection conn = DataSourceUtils.getConnection(dataSource);// Hibernate 示例
Session session = SessionFactoryUtils.getSession(sessionFactory, true);
⚠️
true表示参与事务、自动同步
❗ 特殊类:TransactionAwareDataSourceProxy
这是一个代理包装类,让你的旧代码(期望传入 DataSource)也能参与 Spring 事务。
<bean id="dataSourceProxy" class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy"><property name="targetDataSource" ref="dataSource"/>
</bean>
📌 一般不需要用,除非集成老系统。
📌 五、声明式事务 vs 编程式事务
| 类型 | 方式 | 适用场景 |
|---|---|---|
| 声明式事务 | @Transactional 注解或 XML 配置 | 推荐!绝大多数业务方法 |
| 编程式事务 | 手动调用 TransactionManager 控制事务 | 复杂逻辑、条件判断是否开启事务 |
示例:声明式事务(最常用)
@Service
public class UserService {@Autowiredprivate JdbcTemplate jdbcTemplate;@Transactional // 开启事务public void transferMoney(String from, String to, double amount) {jdbcTemplate.update("UPDATE account SET balance = balance - ? WHERE name = ?", amount, from);// 如果这里抛出异常,上面的操作也会回滚jdbcTemplate.update("UPDATE account SET balance = balance + ? WHERE name = ?", amount, to);}
}
示例:编程式事务(灵活性高)
@Autowired
private PlatformTransactionManager txManager;public void transferWithProgrammaticTx() {DefaultTransactionDefinition def = new DefaultTransactionDefinition();def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);TransactionStatus status = txManager.getTransaction(def);try {// 业务操作jdbcTemplate.update("UPDATE account SET balance = balance - 100 WHERE name = 'A'");jdbcTemplate.update("UPDATE account SET balance = balance + 100 WHERE name = 'B'");txManager.commit(status); // 提交} catch (Exception e) {txManager.rollback(status); // 回滚}
}
🚀 六、Spring 如何改变传统规则?
“你不再需要应用服务器只是为了用声明式事务!”
| 以前 | 现在(Spring) |
|---|---|
| 想用声明式事务?必须用 EJB + 应用服务器 | 只要用 @Transactional + Spring 就行 |
| 想换事务方式?要重写代码 | 只改配置文件,代码不动 |
| 测试困难 | 可以在单元测试中轻松模拟事务 |
✅ 即使将来需要分布式事务,也只需更换
PlatformTransactionManager实现(如 JTA 或 Atomikos),业务代码不变!
📚 七、总结:Spring 事务的五大优势
| 优势 | 说明 |
|---|---|
| ✅ 一致性 | 统一 API,无论 JDBC/Hibernate/JPA/JTA |
| ✅ 声明式事务 | @Transactional 注解,零侵入 |
| ✅ 轻量级 | 不依赖应用服务器 |
| ✅ 易测试 | 可以在 Spring TestContext 中测试事务行为 |
| ✅ 灵活切换 | 本地事务 ↔ 分布式事务,只需改配置 |
💡 学习建议
- 先掌握声明式事务:
@Transactional是最常用的。 - 理解事务传播行为:特别是
REQUIRED,REQUIRES_NEW,NESTED。 - 学会配置事务管理器:根据你用的技术选对
PlatformTransactionManager。 - 结合日志调试事务:开启
DEBUG日志看事务开启/提交/回滚过程。 - 避免常见坑:
- 私有方法加
@Transactional无效(代理失效) - 同一类中方法调用绕过代理
- 异常被捕获但没抛出,导致不回滚
- 私有方法加
📚 下一步学习推荐
- Spring 官方文档 - Transaction Management
- 《Spring 实战》第4章:事务管理
- 视频课程:B站搜索 “Spring 事务源码解析”
如果你还想深入了解某个点,比如:
@Transactional的传播行为详解- 事务失效的 10 种场景
- Spring 事务底层原理(AOP + ThreadLocal)
- 与 MyBatis 整合事务
- 分布式事务解决方案(Seata、LCN、TCC)
欢迎继续提问!我可以为你逐个深入讲解。
