引言
在企业级 Java 应用中,数据一致性是核心要求之一。例如:用户转账操作必须保证"扣款"和"入账"同时成功或同时失败,否则将导致资金错乱。
为解决这一问题,数据库提供了 事务(Transaction) 机制。而 Spring 框架通过 声明式事务(Declarative Transaction) ,让我们无需编写繁琐的 try-catch-finally 和 commit/rollback 代码,仅通过 注解或 XML 配置 即可实现事务管理。
本文将系统讲解:
- 什么是声明式事务?与编程式事务有何区别?
- @Transactional 注解的 7 大核心属性详解
- 事务传播行为(Propagation)实战分析(REQUIRED、REQUIRES_NEW 等)
- 事务隔离级别(Isolation)与脏读、不可重复读、幻读
- Spring 事务底层原理:AOP + PlatformTransactionManager
- 常见陷阱:自调用失效、异常不回滚、只读事务等
- XML 配置方式(了解)与最佳实践
无论你是刚接触 Spring 的新手,还是希望深入理解事务机制的开发者,本文都将为你提供清晰、实用且深入的指导。
第一章:事务基础与 Spring 事务模型
1.1 什么是事务?
事务是一组数据库操作 ,具备 ACID 特性:
- Atomicity(原子性):全部成功,或全部失败;
- Consistency(一致性):事务前后数据状态合法;
- Isolation(隔离性):并发事务互不干扰;
- Durability(持久性):提交后结果永久保存。
1.2 编程式事务 vs 声明式事务
| 类型 | 说明 | 优缺点 |
|---|---|---|
| 编程式事务 | 手动调用 transactionManager.getTransaction()、commit()、rollback() |
❌ 代码侵入性强,重复样板多 |
| 声明式事务 | 通过 @Transactional 或 XML 声明,由 Spring 自动管理 |
✅ 无侵入、简洁、易维护 |
📌 Spring 推荐使用声明式事务。
第二章:@Transactional 注解详解
2.1 基本用法
@Service
public class AccountService {
@Transactional
public void transfer(Long fromId, Long toId, BigDecimal amount) {
accountDao.debit(fromId, amount); // 扣款
accountDao.credit(toId, amount); // 入账
// 若此处抛出异常,整个方法回滚
}
}
✅ 只需一个注解,Spring 自动处理事务开启、提交、回滚。
2.2 七大核心属性
@Transactional 提供多个属性,用于精细控制事务行为:
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| propagation | Propagation | REQUIRED |
事务传播行为 |
| isolation | Isolation | DEFAULT |
事务隔离级别 |
| timeout | int | -1(无超时) |
事务超时时间(秒) |
| readOnly | boolean | false |
是否只读事务 |
| rollbackFor | Class[] | 空 | 指定哪些异常触发回滚 |
| noRollbackFor | Class[] | 空 | 指定哪些异常不回滚 |
| value / transactionManager | String | "" |
指定事务管理器 Bean 名称 |
示例:高级配置
@Transactional(
propagation = Propagation.REQUIRES_NEW,
isolation = Isolation.READ_COMMITTED,
timeout = 30,
readOnly = false,
rollbackFor = {SQLException.class, BusinessException.class},
noRollbackFor = {ValidationException.class}
)
public void complexOperation() {
// ...
}
第三章:事务传播行为(Propagation)
这是 最常被问到、也最容易出错 的知识点!
3.1 七种传播行为
| 传播行为 | 含义 | 典型场景 |
|---|---|---|
| REQUIRED(默认) | 如果当前存在事务,则加入;否则新建 | 大多数业务方法 |
| SUPPORTS | 如果当前存在事务,则加入;否则以非事务方式执行 | 查询类方法 |
| MANDATORY | 必须在事务中执行,否则抛异常 | 内部核心逻辑 |
| REQUIRES_NEW | 挂起当前事务,新建独立事务 | 日志记录、审计(需独立提交) |
| NOT_SUPPORTED | 挂起当前事务,以非事务方式执行 | 调用外部非事务服务 |
| NEVER | 不能在事务中执行,否则抛异常 | 幂等性校验 |
| NESTED | 如果当前存在事务,则嵌套执行(支持部分回滚) | 需要保存点(Savepoint)的场景 |
💡 重点掌握:REQUIRED、REQUIRES_NEW、NESTED
3.2 实战对比:REQUIRED vs REQUIRES_NEW
场景:用户注册 + 发送欢迎邮件
@Service
public class UserService {
@Autowired
private EmailService emailService;
@Transactional
public void register(User user) {
userDao.save(user); // (1)
emailService.sendWelcomeEmail(user); // (2)
}
}
@Service
public class EmailService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void sendWelcomeEmail(User user) {
// 发送邮件(可能失败,但不应影响用户注册)
emailClient.send(...);
emailLogDao.save(...); // 记录发送日志
}
}
- 若
sendWelcomeEmail抛异常:- REQUIRED:用户注册和邮件都回滚 ❌
- REQUIRES_NEW:用户注册成功,邮件日志回滚 ✅
✅ REQUIRES_NEW 适用于"独立成功"的子操作。
3.3 NESTED 嵌套事务(需数据库支持 Savepoint)
@Transactional
public void batchProcess() {
for (Item item : items) {
try {
processItem(item); // 方法内使用 NESTED
} catch (Exception e) {
// 单个 item 失败,不影响其他 item
log.error("处理失败", e);
}
}
}
@Transactional(propagation = Propagation.NESTED)
public void processItem(Item item) {
// ...
}
⚠️ 注意:MySQL InnoDB 支持 Savepoint,但 Oracle、PostgreSQL 行为略有差异。
第四章:事务隔离级别(Isolation)
4.1 四大并发问题
| 问题 | 描述 | 隔离级别解决 |
|---|---|---|
| 脏读(Dirty Read) | 读到未提交的数据 | READ_COMMITTED+ |
| 不可重复读(Non-repeatable Read) | 同一事务内多次读取结果不同(数据被修改) | REPEATABLE_READ+ |
| 幻读(Phantom Read) | 同一事务内多次查询返回行数不同(新增/删除) | SERIALIZABLE |
4.2 Spring 隔离级别枚举
| 隔离级别 | 数据库对应 | 说明 |
|---|---|---|
| DEFAULT | 使用数据库默认(如 MySQL=REPEATABLE_READ) | 推荐 |
| READ_UNCOMMITTED | 最低 | 允许脏读 |
| READ_COMMITTED | Oracle 默认 | 避免脏读 |
| REPEATABLE_READ | MySQL 默认 | 避免脏读 + 不可重复读 |
| SERIALIZABLE | 最高 | 完全串行,性能差 |
✅ 一般使用 DEFAULT 即可,除非有特殊需求。
第五章:Spring 事务底层原理
5.1 核心组件
- PlatformTransactionManager :事务管理器接口(如
DataSourceTransactionManager) - TransactionInterceptor:事务拦截器(AOP 通知)
- @EnableTransactionManagement:启用事务注解支持
5.2 工作流程
- Spring 启动时,扫描
@Transactional方法; - 通过 AOP 创建代理对象(JDK/CGLIB);
- 调用方法时,
TransactionInterceptor拦截:- 开启事务(
getTransaction()) - 执行目标方法
- 成功 → 提交(
commit()) - 异常 → 回滚(
rollback())
- 开启事务(
🔍 本质:基于 AOP 的环绕通知(@Around)
5.3 启用事务支持
方式1:注解驱动(推荐)
@Configuration
@EnableTransactionManagement // 启用 @Transactional
public class TxConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
方式2:Spring Boot 自动配置
只要引入 spring-boot-starter-jdbc 或 spring-boot-starter-data-jpa,事务自动启用,无需额外配置!
第六章:常见陷阱与解决方案
6.1 陷阱1:自调用导致事务失效
@Service
public class OrderService {
public void createOrder(Order order) {
this.validate(order); // ❌ this 调用,绕过代理!
this.save(order);
}
@Transactional
public void validate(Order order) {
// 此处事务不生效!
}
}
✅ 解决方案:
- 重构:将
validate移到另一个 Service; - 或通过
ApplicationContext获取代理自身(不推荐); - 或使用
AopContext.currentProxy()(需exposeProxy=true)。
6.2 陷阱2:异常被捕获,事务不回滚
@Transactional
public void transfer(...) {
try {
accountDao.debit(...);
accountDao.credit(...);
} catch (Exception e) {
log.error("转账失败", e);
// ❌ 异常被吞掉,Spring 不知道要回滚!
}
}
✅ 解决方案:
- 不要捕获异常,或捕获后重新抛出;
- 或显式调用
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
6.3 陷阱3:Checked Exception 默认不回滚
@Transactional
public void readFile() throws IOException {
// 若抛出 IOException(checked exception),默认不回滚!
}
✅ 解决方案:
@Transactional(rollbackFor = Exception.class) // 回滚所有异常
// 或
@Transactional(rollbackFor = IOException.class)
📌 Spring 默认只对 RuntimeException 和 Error 回滚。
6.4 陷阱4:只读事务误用
@Transactional(readOnly = true)
public void updateUser(User user) {
userDao.update(user); // ❌ 在只读事务中写数据!
}
- 某些数据库(如 Oracle)会抛异常;
- MySQL 可能静默执行,但违背设计意图。
✅ 建议:只读事务仅用于查询方法。
6.5 陷阱5:非 public 方法事务无效
@Transactional
protected void internalMethod() { ... } // ❌ JDK 动态代理无法拦截
✅ 解决方案 :确保方法为 public。
第七章:XML 配置方式(了解即可)
虽然注解是主流,但 XML 仍存在于老项目中:
<!-- 启用事务注解 -->
<tx:annotation-driven transaction-manager="txManager"/>
<!-- 或使用 XML 声明式事务 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<tx:attributes>
<tx:method name="transfer*" propagation="REQUIRED" rollback-for="Exception"/>
<tx:method name="get*" read-only="true"/>
</tx:attributes>
</tx:advice>
<aop:config>
<aop:pointcut id="serviceMethods" expression="execution(* com.service.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="serviceMethods"/>
</aop:config>
⚠️ 新项目应优先使用
@Transactional。
第八章:最佳实践总结
| 建议 | 说明 |
|---|---|
| 事务方法保持细粒度 | 避免大事务(长时间持有锁) |
| 明确指定 rollbackFor | 避免 checked exception 不回滚 |
| 避免事务中远程调用 | 网络延迟可能导致事务超时 |
| 不要在事务中做耗时操作 | 如文件 IO、复杂计算 |
| 测试事务回滚场景 | 使用 @Rollback 注解进行单元测试 |
| 监控长事务 | 防止数据库连接耗尽 |
结语
Spring 的声明式事务是其最成功的抽象之一------它将复杂的事务管理简化为一个注解,同时保留了足够的灵活性以应对各种业务场景。
但"简单"不等于"无脑"。只有理解其背后的 传播行为、隔离级别、代理机制和常见陷阱,才能真正写出健壮、可靠的事务代码。
记住:
事务不是魔法,它是责任 。
每一个
@Transactional,都承载着数据一致性的承诺。
希望本文能助你在事务之路上行稳致远。
参考资料
- Spring Framework 官方文档:Transaction Management
- 《Spring 实战(第6版)》第11章
- Martin Fowler: Patterns of Enterprise Application Architecture
- MySQL 官方文档:InnoDB Transaction Model