Spring Boot 事务管理完全指南
一、事务基础概念
1.1 什么是事务?
事务(Transaction) 是数据库操作的基本单位,它是一组要么全部执行成功、要么全部不执行的SQL语句序列。事务确保数据的完整性和一致性。
1.2 ACID 特性 ⭐⭐⭐⭐⭐
事务具有四个核心特性,简称 ACID:
A - Atomicity(原子性)
- 事务中的所有操作要么全部完成,要么全部不完成
- 不会出现部分执行的情况
- 如果事务中任何操作失败,整个事务都会回滚
typescript
@Transactional
public void transferMoney(String from, String to, BigDecimal amount) {
// 这两个操作必须同时成功或同时失败
accountDao.debit(from, amount); // 扣款
accountDao.credit(to, amount); // 入账
}
C - Consistency(一致性)
- 事务执行前后,数据库必须保持一致性状态
- 数据必须符合所有预定义的规则(约束、触发器、级联等)
sql
-- 假设账户余额不能为负数
ALTER TABLE account ADD CONSTRAINT check_balance CHECK (balance >= 0);
I - Isolation(隔离性)
- 多个并发事务之间互不干扰
- 每个事务都感觉不到其他事务在同时执行
- 通过隔离级别来控制并发访问
D - Durability(持久性)
- 事务一旦提交,对数据的改变就是永久的
- 即使系统故障,数据也不会丢失
二、Spring 事务管理架构
2.1 Spring 事务抽象层
Spring 提供了一个统一的事务管理抽象层,屏蔽了不同事务API的差异:
css
┌─────────────────────────────────────┐
│ Spring 应用代码 │
│ @Transactional │
├─────────────────────────────────────┤
│ PlatformTransactionManager │ ← 核心接口
│ (平台事务管理器) │
├──────────┬──────────┬───────────────┤
│ │ │ │
│ DataSource JPA Hibernate │
│ Transaction Transaction Transaction│
│ Manager Manager Manager │
└──────────┴──────────┴───────────────┘
2.2 核心接口
PlatformTransactionManager
Spring 事务管理的核心接口:
csharp
public interface PlatformTransactionManager {
// 获取事务状态
TransactionStatus getTransaction(TransactionDefinition definition);
// 提交事务
void commit(TransactionStatus status);
// 回滚事务
void rollback(TransactionStatus status);
}
常用实现类:
DataSourceTransactionManager:JDBC 事务管理器JpaTransactionManager:JPA 事务管理器HibernateTransactionManager:Hibernate 事务管理器JtaTransactionManager:分布式事务管理器
三、声明式事务(@Transactional)
3.1 基本使用
启用事务支持
less
@SpringBootApplication
@EnableTransactionManagement // 启用事务管理
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
注意 :Spring Boot 自动配置已默认启用事务管理,通常不需要显式添加
@EnableTransactionManagement
方法级别事务
typescript
@Service
public class AccountService {
@Autowired
private AccountDao accountDao;
/**
* 转账操作 - 需要事务保证
*/
@Transactional
public void transferMoney(String fromAccount, String toAccount, BigDecimal amount) {
// 1. 从源账户扣款
accountDao.debit(fromAccount, amount);
// 2. 向目标账户入账
accountDao.credit(toAccount, amount);
// 如果任何一步失败,整个事务回滚
}
}
类级别事务
less
@Service
@Transactional // 类上所有 public 方法都启用事务
public class OrderService {
public void createOrder(Order order) {
// 自动在事务中执行
}
public void cancelOrder(Long orderId) {
// 自动在事务中执行
}
}
3.2 @Transactional 属性详解 ⭐⭐⭐⭐⭐
ini
@Transactional(
// 1. 传播行为
propagation = Propagation.REQUIRED,
// 2. 隔离级别
isolation = Isolation.READ_COMMITTED,
// 3. 超时时间(秒)
timeout = 30,
// 4. 是否只读
readOnly = false,
// 5. 回滚规则
rollbackFor = {Exception.class},
rollbackForClassName = {"RuntimeException"},
// 6. 不回滚规则
noRollbackFor = {BusinessException.class},
noRollbackForClassName = {"CustomException"}
)
public void businessMethod() {
// 业务逻辑
}
3.3 事务传播行为(Propagation)⭐⭐⭐⭐⭐
传播行为定义了当事务方法被另一个事务方法调用时,事务应该如何传播。
7 种传播行为:
| 传播行为 | 说明 | 使用场景 |
|---|---|---|
| REQUIRED | 如果存在事务则加入,否则新建事务(默认) | 大多数场景 |
| REQUIRES_NEW | 总是新建事务,挂起当前事务 | 独立记录日志、审计 |
| SUPPORTS | 如果存在事务则加入,否则非事务执行 | 查询操作 |
| NOT_SUPPORTED | 总是非事务执行,挂起当前事务 | 批量处理、性能敏感操作 |
| MANDATORY | 必须在事务中执行,否则抛异常 | 强制要求事务的场景 |
| NEVER | 必须非事务执行,否则抛异常 | 不允许事务的场景 |
| NESTED | 嵌套事务,保存点机制 | 部分回滚场景 |
示例 1:REQUIRED(默认)
java
@Service
public class OrderService {
@Autowired
private PaymentService paymentService;
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(Order order) {
// 创建订单
orderDao.insert(order);
// 调用支付服务 - 加入当前事务
paymentService.processPayment(order);
// 如果这里抛出异常,订单和支付都会回滚
}
}
@Service
public class PaymentService {
@Transactional(propagation = Propagation.REQUIRED)
public void processPayment(Order order) {
// 处理支付 - 加入 createOrder 的事务
paymentDao.insert(order.getPayment());
}
}
执行流程:
scss
createOrder 开始事务
├─ orderDao.insert()
├─ processPayment() [加入同一事务]
│ └─ paymentDao.insert()
└─ 如果任何地方失败 → 全部回滚
示例 2:REQUIRES_NEW
java
@Service
public class OrderService {
@Autowired
private AuditLogService auditLogService;
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(Order order) {
// 创建订单
orderDao.insert(order);
try {
// 记录审计日志 - 独立事务
auditLogService.logOrderCreation(order);
} catch (Exception e) {
// 日志记录失败不影响订单创建
log.error("Audit log failed", e);
}
// 继续其他业务逻辑
}
}
@Service
public class AuditLogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logOrderCreation(Order order) {
// 新建独立事务,不受外层事务影响
auditLogDao.insert(order);
// 即使这个方法失败并回滚,也不影响外层事务
}
}
执行流程:
lua
createOrder 开始事务 T1
├─ orderDao.insert()
├─ suspend T1
├─ logOrderCreation 开始新事务 T2
│ ├─ auditLogDao.insert()
│ └─ T2 提交或回滚(独立)
├─ resume T1
└─ T1 提交或回滚
示例 3:NESTED(嵌套事务)
java
@Service
public class BatchService {
@Transactional(propagation = Propagation.REQUIRED)
public void batchProcess(List<Order> orders) {
for (Order order : orders) {
try {
// 每个订单作为嵌套事务
processSingleOrder(order);
} catch (Exception e) {
// 单个订单失败,回滚到保存点,不影响其他订单
log.error("Order {} failed", order.getId(), e);
}
}
}
@Transactional(propagation = Propagation.NESTED)
public void processSingleOrder(Order order) {
// 嵌套事务 - 设置保存点
orderDao.insert(order);
paymentDao.insert(order.getPayment());
// 如果失败,只回滚到这个保存点
}
}
执行流程:
scss
batchProcess 开始事务 T1
├─ processSingleOrder(order1) [保存点 SP1]
│ ├─ 成功 → 释放 SP1
│ └─ 失败 → 回滚到 SP1
├─ processSingleOrder(order2) [保存点 SP2]
│ ├─ 成功 → 释放 SP2
│ └─ 失败 → 回滚到 SP2
└─ T1 提交(成功的订单保留)
注意 :NESTED 仅支持
DataSourceTransactionManager,且需要 JDBC 3.0+ 支持保存点
3.4 事务隔离级别(Isolation)⭐⭐⭐⭐
隔离级别定义了事务之间的可见性程度,用于解决并发问题。
并发问题:
| 问题 | 描述 | 示例 |
|---|---|---|
| 脏读(Dirty Read) | 读取到其他事务未提交的数据 | T1 修改数据但未提交,T2 读取到修改后的数据 |
| 不可重复读(Non-repeatable Read) | 同一事务中多次读取结果不一致 | T1 两次读取之间,T2 修改并提交了数据 |
| 幻读(Phantom Read) | 同一事务中查询返回的行数不一致 | T1 两次查询之间,T2 插入或删除了数据 |
5 种隔离级别:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 性能 |
|---|---|---|---|---|
| READ_UNCOMMITTED | ❌ 可能 | ❌ 可能 | ❌ 可能 | 最快 |
| READ_COMMITTED | ✅ 避免 | ❌ 可能 | ❌ 可能 | 快 |
| REPEATABLE_READ | ✅ 避免 | ✅ 避免 | ❌ 可能(MySQL避免) | 中等 |
| SERIALIZABLE | ✅ 避免 | ✅ 避免 | ✅ 避免 | 最慢 |
| DEFAULT | 使用数据库默认 | - | - | - |
示例:
java
@Service
public class ProductService {
/**
* 高并发场景 - 使用 READ_COMMITTED
* 允许不可重复读,提高并发性能
*/
@Transactional(isolation = Isolation.READ_COMMITTED)
public Product getProduct(Long id) {
return productDao.findById(id);
}
/**
* 财务计算 - 使用 REPEATABLE_READ
* 确保多次读取结果一致
*/
@Transactional(isolation = Isolation.REPEATABLE_READ)
public BigDecimal calculateTotal(List<Long> orderIds) {
BigDecimal total = BigDecimal.ZERO;
for (Long orderId : orderIds) {
// 多次读取,确保数据一致性
Order order = orderDao.findById(orderId);
total = total.add(order.getAmount());
}
return total;
}
/**
* 库存扣减 - 使用 SERIALIZABLE
* 最高隔离级别,避免超卖
*/
@Transactional(isolation = Isolation.SERIALIZABLE)
public void deductStock(Long productId, int quantity) {
Product product = productDao.findById(productId);
if (product.getStock() < quantity) {
throw new BusinessException("库存不足");
}
product.setStock(product.getStock() - quantity);
productDao.update(product);
}
}
3.5 事务超时和只读优化
超时设置
typescript
@Service
public class ReportService {
/**
* 设置超时时间为 60 秒
* 如果事务执行超过 60 秒,自动回滚
*/
@Transactional(timeout = 60)
public List<Order> generateReport(Date startDate, Date endDate) {
// 复杂查询和数据处理
return orderDao.findOrdersBetween(startDate, endDate);
}
}
只读事务优化
kotlin
@Service
public class QueryService {
/**
* 只读事务 - 性能优化
* 1. 数据库可以优化查询执行计划
* 2. 某些数据库会跳过锁检查
* 3. 明确语义,防止意外修改
*/
@Transactional(readOnly = true)
public List<User> findAllUsers() {
return userDao.findAll();
}
@Transactional(readOnly = true)
public User findUserById(Long id) {
return userDao.findById(id);
}
}
3.6 回滚规则 ⭐⭐⭐⭐
默认回滚规则
- 运行时异常(RuntimeException) :自动回滚
- 受检异常(Checked Exception) :不回滚
typescript
@Service
public class AccountService {
@Transactional
public void transfer(String from, String to, BigDecimal amount) {
accountDao.debit(from, amount);
// 运行时异常 - 自动回滚 ✅
if (amount.compareTo(BigDecimal.ZERO) <= 0) {
throw new IllegalArgumentException("金额必须大于0");
}
accountDao.credit(to, amount);
}
}
自定义回滚规则
csharp
@Service
public class OrderService {
/**
* 指定回滚异常
*/
@Transactional(rollbackFor = Exception.class)
public void createOrder(Order order) throws Exception {
// 任何 Exception 及其子类都会回滚
orderDao.insert(order);
// 即使是受检异常也会回滚
if (order.getAmount().compareTo(BigDecimal.ZERO) <= 0) {
throw new Exception("订单金额无效");
}
}
/**
* 指定不回滚异常
*/
@Transactional(noRollbackFor = BusinessException.class)
public void updateOrder(Order order) {
orderDao.update(order);
// 业务异常不会导致回滚
if (someCondition) {
throw new BusinessException("业务规则校验失败");
}
}
/**
* 组合使用
*/
@Transactional(
rollbackFor = {SQLException.class, IOException.class},
noRollbackFor = {ValidationException.class}
)
public void complexOperation() throws SQLException, IOException {
// SQLException 和 IOException 会回滚
// ValidationException 不会回滚
}
}
四、事务失效场景与解决方案 ⭐⭐⭐⭐⭐
4.1 常见失效场景
❌ 场景 1:方法不是 public
less
@Service
public class AccountService {
@Transactional
private void transfer() { // ❌ 无效 - 必须是 public
// ...
}
@Transactional
protected void update() { // ❌ 无效 - 必须是 public
// ...
}
}
✅ 解决方案:
typescript
@Transactional
public void transfer() { // ✅ 正确
// ...
}
❌ 场景 2:自调用(Self-invocation)
typescript
@Service
public class AccountService {
public void methodA() {
this.methodB(); // ❌ 自调用,事务无效
}
@Transactional
public void methodB() {
// 事务不会生效
}
}
原因:Spring AOP 基于代理,自调用绕过了代理对象
✅ 解决方案 1:注入自身
less
@Service
public class AccountService {
@Autowired
@Lazy // 避免循环依赖
private AccountService self;
public void methodA() {
self.methodB(); // ✅ 通过代理调用
}
@Transactional
public void methodB() {
// 事务生效
}
}
✅ 解决方案 2:提取到另一个 Service
typescript
@Service
public class AccountService {
@Autowired
private TransferService transferService;
public void methodA() {
transferService.methodB(); // ✅ 跨 Service 调用
}
}
@Service
public class TransferService {
@Transactional
public void methodB() {
// 事务生效
}
}
✅ 解决方案 3:使用 AopContext
typescript
@Service
public class AccountService {
public void methodA() {
((AccountService) AopContext.currentProxy()).methodB(); // ✅
}
@Transactional
public void methodB() {
// 事务生效
}
}
注意 :需要在启动类添加
@EnableAspectJAutoProxy(exposeProxy = true)
❌ 场景 3:异常被捕获
typescript
@Service
public class AccountService {
@Transactional
public void transfer(String from, String to, BigDecimal amount) {
try {
accountDao.debit(from, amount);
accountDao.credit(to, amount);
} catch (Exception e) {
// ❌ 异常被捕获,事务不会回滚
log.error("Transfer failed", e);
}
}
}
✅ 解决方案 1:手动回滚
typescript
@Transactional
public void transfer(String from, String to, BigDecimal amount) {
try {
accountDao.debit(from, amount);
accountDao.credit(to, amount);
} catch (Exception e) {
log.error("Transfer failed", e);
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly(); // ✅ 手动回滚
}
}
✅ 解决方案 2:重新抛出异常
typescript
@Transactional
public void transfer(String from, String to, BigDecimal amount) {
try {
accountDao.debit(from, amount);
accountDao.credit(to, amount);
} catch (Exception e) {
log.error("Transfer failed", e);
throw new RuntimeException(e); // ✅ 重新抛出运行时异常
}
}
❌ 场景 4:数据库引擎不支持事务
sql
-- ❌ MyISAM 不支持事务
CREATE TABLE account (
id INT PRIMARY KEY,
balance DECIMAL(10, 2)
) ENGINE=MyISAM;
-- ✅ InnoDB 支持事务
CREATE TABLE account (
id INT PRIMARY KEY,
balance DECIMAL(10, 2)
) ENGINE=InnoDB;
检查数据库引擎:
ini
SHOW TABLE STATUS WHERE Name = 'account';
❌ 场景 5:未被 Spring 管理
typescript
// ❌ 没有 @Service 或其他组件注解
public class AccountService {
@Transactional
public void transfer() {
// 事务无效 - 不是 Spring Bean
}
}
✅ 解决方案:
typescript
@Service // ✅ 添加组件注解
public class AccountService {
@Transactional
public void transfer() {
// 事务生效
}
}
❌ 场景 6:异步方法
less
@Service
public class NotificationService {
@Async // 异步执行
@Transactional
public void sendNotification(User user) {
// ❌ 事务可能无效 - 异步方法在新线程中执行
}
}
✅ 解决方案:分开事务和异步
typescript
@Service
public class NotificationService {
@Transactional
public void prepareNotification(User user) {
// 在事务中准备数据
Notification notification = buildNotification(user);
notificationDao.save(notification);
// 异步发送
sendNotificationAsync(notification);
}
@Async
public void sendNotificationAsync(Notification notification) {
// 异步发送,不在事务中
emailService.send(notification);
}
}
❌ 场景 7:多线程环境
typescript
@Service
public class BatchService {
@Transactional
public void batchProcess(List<Order> orders) {
// 主线程有事务
orders.parallelStream().forEach(order -> {
// ❌ 子线程中没有事务上下文
processOrder(order);
});
}
@Transactional
public void processOrder(Order order) {
// 在子线程中调用,事务无效
}
}
✅ 解决方案:每个线程独立事务
typescript
@Service
public class BatchService {
@Autowired
private TransactionTemplate transactionTemplate;
public void batchProcess(List<Order> orders) {
orders.parallelStream().forEach(order -> {
// 每个线程独立开启事务
transactionTemplate.execute(status -> {
processOrder(order);
return null;
});
});
}
public void processOrder(Order order) {
// 在 transactionTemplate 中执行
orderDao.insert(order);
}
}
五、编程式事务
5.1 TransactionTemplate
typescript
@Service
public class AccountService {
@Autowired
private TransactionTemplate transactionTemplate;
@Autowired
private AccountDao accountDao;
public void transfer(String from, String to, BigDecimal amount) {
// 编程式事务
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
try {
accountDao.debit(from, amount);
accountDao.credit(to, amount);
} catch (Exception e) {
status.setRollbackOnly(); // 手动回滚
throw e;
}
}
});
}
}
5.2 PlatformTransactionManager
typescript
@Service
public class AccountService {
@Autowired
private PlatformTransactionManager transactionManager;
@Autowired
private AccountDao accountDao;
public void transfer(String from, String to, BigDecimal amount) {
// 定义事务属性
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
def.setTimeout(30);
// 开启事务
TransactionStatus status = transactionManager.getTransaction(def);
try {
accountDao.debit(from, amount);
accountDao.credit(to, amount);
// 提交事务
transactionManager.commit(status);
} catch (Exception e) {
// 回滚事务
transactionManager.rollback(status);
throw e;
}
}
}
5.3 何时使用编程式事务?
| 场景 | 推荐方式 |
|---|---|
| 大多数业务场景 | 声明式事务(@Transactional) |
| 细粒度控制事务边界 | 编程式事务 |
| 动态决定事务属性 | 编程式事务 |
| 批量处理中的部分提交 | 编程式事务 |
| 条件性回滚 | 编程式事务 |
六、分布式事务 ⭐⭐⭐⭐⭐
6.1 什么是分布式事务?
分布式事务是指跨越多个数据库、微服务或资源管理器的事务,需要保证这些分散的操作要么全部成功,要么全部失败。
6.2 分布式事务场景
css
场景 1:微服务架构
┌──────────┐ ┌──────────┐ ┌──────────┐
│ 订单服务 │ ───→ │ 支付服务 │ ───→ │ 库存服务 │
│ MySQL │ │ MySQL │ │ MySQL │
└──────────┘ └──────────┘ └──────────┘
↓ ↓ ↓
创建订单 扣款 扣减库存
需要保证:三个操作要么全部成功,要么全部失败
场景 2:多数据源
┌──────────┐ ┌──────────┐
│ 用户库 │ │ 订单库 │
│ MySQL A │ │ MySQL B │
└──────────┘ └──────────┘
↓ ↓
创建用户 创建首单
需要保证:两个数据库操作的一致性
6.3 分布式事务解决方案
方案 1:两阶段提交(2PC - Two Phase Commit)
原理:
sql
协调者(Transaction Coordinator)
↓
┌──────────────┐ ┌──────────────┐
│ 参与者 A │ │ 参与者 B │
│ (资源管理器) │ │ (资源管理器) │
└──────────────┘ └──────────────┘
第一阶段:准备(Prepare)
协调者询问所有参与者:是否可以提交?
参与者执行事务但不提交,返回 Yes/No
第二阶段:提交/回滚(Commit/Rollback)
如果所有参与者都返回 Yes → 协调者通知所有人提交
如果有任一参与者返回 No → 协调者通知所有人回滚
优点:
- ✅ 强一致性
- ✅ 实现相对简单
缺点:
- ❌ 性能差(两次网络往返)
- ❌ 阻塞协议(协调者故障会导致参与者阻塞)
- ❌ 单点故障(协调者)
Spring 实现:
typescript
@Configuration
public class JtaConfig {
@Bean
public PlatformTransactionManager transactionManager() {
return new JtaTransactionManager();
}
}
@Service
public class DistributedService {
@Transactional // 使用 JTA 事务管理器
public void distributedOperation() {
// 操作数据源 A
dataSourceA.executeUpdate(...);
// 操作数据源 B
dataSourceB.executeUpdate(...);
// 自动进行 2PC
}
}
方案 2:TCC(Try-Confirm-Cancel)
原理:
markdown
Try 阶段:检查并预留资源
- 验证业务可行性
- 预留必要资源(如冻结库存)
Confirm 阶段:确认执行
- 真正执行业务操作
- 使用 Try 阶段预留的资源
Cancel 阶段:取消操作
- 释放 Try 阶段预留的资源
- 回滚操作
示例:
scss
@Service
public class OrderTccService {
@Autowired
private InventoryService inventoryService;
@Autowired
private PaymentService paymentService;
/**
* Try 阶段:预留资源
*/
@Transactional
public boolean tryCreateOrder(Order order) {
// 1. 预留库存
boolean stockReserved = inventoryService.reserveStock(
order.getProductId(),
order.getQuantity()
);
if (!stockReserved) {
return false;
}
// 2. 冻结支付额度
boolean paymentFrozen = paymentService.freezeAmount(
order.getUserId(),
order.getAmount()
);
return paymentFrozen;
}
/**
* Confirm 阶段:确认提交
*/
@Transactional
public void confirmCreateOrder(Order order) {
// 1. 扣减库存(使用预留的)
inventoryService.deductReservedStock(
order.getProductId(),
order.getQuantity()
);
// 2. 扣款(使用冻结的)
paymentService.deductFrozenAmount(
order.getUserId(),
order.getAmount()
);
// 3. 创建订单
orderDao.insert(order);
}
/**
* Cancel 阶段:取消回滚
*/
@Transactional
public void cancelCreateOrder(Order order) {
// 1. 释放预留库存
inventoryService.releaseReservedStock(
order.getProductId(),
order.getQuantity()
);
// 2. 解冻支付额度
paymentService.unfreezeAmount(
order.getUserId(),
order.getAmount()
);
}
}
优点:
- ✅ 性能较好(无锁)
- ✅ 最终一致性
缺点:
- ❌ 业务侵入性强(需要实现三个方法)
- ❌ 空回滚、悬挂等问题需要处理
框架支持:
- Seata TCC
- Hmily
- ByteTCC
方案 3:本地消息表
原理:
步骤 1:业务操作 + 写入消息表(同一事务)
步骤 2:定时任务扫描消息表,发送消息
步骤 3:下游服务消费消息,执行业务
步骤 4:下游服务返回成功,标记消息为已消费
步骤 5:定时任务清理已消费消息
示例:
scss
@Service
public class OrderMessageService {
@Autowired
private OrderDao orderDao;
@Autowired
private MessageDao messageDao;
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
/**
* 创建订单并记录消息
*/
@Transactional
public void createOrderWithMessage(Order order) {
// 1. 创建订单
orderDao.insert(order);
// 2. 记录消息(同一事务)
Message message = new Message();
message.setTopic("order-created");
message.setKey(order.getId().toString());
message.setContent(JSON.toJSONString(order));
message.setStatus("PENDING");
messageDao.insert(message);
}
/**
* 定时任务:发送待处理消息
*/
@Scheduled(fixedDelay = 5000)
public void sendPendingMessages() {
List<Message> pendingMessages = messageDao.findPendingMessages();
for (Message message : pendingMessages) {
try {
// 发送消息
kafkaTemplate.send(message.getTopic(), message.getKey(), message.getContent());
// 标记为已发送
message.setStatus("SENT");
messageDao.update(message);
} catch (Exception e) {
log.error("Send message failed", e);
// 下次重试
}
}
}
}
/**
* 下游服务:消费消息
*/
@Component
public class InventoryConsumer {
@KafkaListener(topics = "order-created")
@Transactional
public void consumeOrderCreated(String message) {
Order order = JSON.parseObject(message, Order.class);
// 扣减库存
inventoryService.deductStock(order.getProductId(), order.getQuantity());
// 幂等性检查:避免重复消费
// 如果已经处理过,直接返回
}
}
优点:
- ✅ 实现简单
- ✅ 最终一致性
- ✅ 解耦
缺点:
- ❌ 延迟较高
- ❌ 需要处理幂等性
- ❌ 消息表维护成本
方案 4:Saga 模式
原理:
css
正向操作:Service A → Service B → Service C
补偿操作:Service C 补偿 → Service B 补偿 → Service A 补偿
如果某步失败,依次执行前面所有步骤的补偿操作
示例:
scss
@Service
public class OrderSagaService {
@Autowired
private OrderService orderService;
@Autowired
private PaymentService paymentService;
@Autowired
private InventoryService inventoryService;
/**
* Saga 编排器
*/
public void createOrderSaga(Order order) {
try {
// 1. 创建订单
orderService.createOrder(order);
// 2. 支付
paymentService.pay(order);
// 3. 扣减库存
inventoryService.deductStock(order);
} catch (Exception e) {
// 失败时执行补偿
compensate(order);
throw e;
}
}
/**
* 补偿操作
*/
private void compensate(Order order) {
// 3. 补偿:恢复库存
try {
inventoryService.restoreStock(order);
} catch (Exception e) {
log.error("Restore stock failed", e);
}
// 2. 补偿:退款
try {
paymentService.refund(order);
} catch (Exception e) {
log.error("Refund failed", e);
}
// 1. 补偿:取消订单
try {
orderService.cancelOrder(order.getId());
} catch (Exception e) {
log.error("Cancel order failed", e);
}
}
}
优点:
- ✅ 长事务友好
- ✅ 松耦合
缺点:
- ❌ 补偿逻辑复杂
- ❌ 可能出现不一致窗口期
框架支持:
- Seata Saga
- Axon Framework
- Camunda
方案 5:最大努力通知
原理:
步骤 1:业务操作完成后,发送通知
步骤 2:如果通知失败,定时重试
步骤 3:重试 N 次后仍失败,人工介入
步骤 4:下游服务保证幂等性
适用场景:
- 对一致性要求不高
- 允许短暂不一致
- 如:订单创建后发送积分、优惠券
示例:
csharp
@Service
public class PointsService {
@Autowired
private RestTemplate restTemplate;
/**
* 最大努力通知:赠送积分
*/
public void grantPointsAfterOrder(Order order) {
int maxRetries = 5;
int retryCount = 0;
while (retryCount < maxRetries) {
try {
// 调用积分服务
restTemplate.postForObject(
"http://points-service/grant",
new PointsRequest(order.getUserId(), order.getPoints()),
Void.class
);
// 成功,退出
return;
} catch (Exception e) {
retryCount++;
log.warn("Grant points failed, retry {}/{}", retryCount, maxRetries);
// 指数退避
try {
Thread.sleep((long) Math.pow(2, retryCount) * 1000);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
}
// 重试耗尽,记录告警,人工介入
log.error("Failed to grant points after {} retries", maxRetries);
alertService.sendAlert("Points grant failed for order: " + order.getId());
}
}
6.4 Seata 分布式事务框架 ⭐⭐⭐⭐⭐
Seata 是阿里巴巴开源的分布式事务解决方案,支持 AT、TCC、Saga、XA 四种模式。
Seata AT 模式(自动补偿事务)
原理:
markdown
1. 一阶段:执行业务 SQL,生成 undo log
2. 二阶段:
- 提交:删除 undo log
- 回滚:根据 undo log 反向补偿
集成步骤:
1. 添加依赖
xml
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
<version>1.6.1</version>
</dependency>
2. 配置文件
yaml
seata:
enabled: true
application-id: order-service
tx-service-group: my_test_tx_group
service:
vgroup-mapping:
my_test_tx_group: default
grouplist:
default: 127.0.0.1:8091
config:
type: file
registry:
type: file
3. 使用 @GlobalTransactional
scss
@Service
public class OrderService {
@Autowired
private PaymentFeignClient paymentClient;
@Autowired
private InventoryFeignClient inventoryClient;
/**
* 全局事务
*/
@GlobalTransactional
public void createOrder(Order order) {
// 1. 创建订单(本地事务)
orderDao.insert(order);
// 2. 调用支付服务(远程事务)
paymentClient.pay(order.getUserId(), order.getAmount());
// 3. 调用库存服务(远程事务)
inventoryClient.deductStock(order.getProductId(), order.getQuantity());
// 如果任何一步失败,所有服务都会回滚
}
}
4. 下游服务
typescript
@Service
public class PaymentService {
/**
* 不需要 @GlobalTransactional
* Seata 会自动加入全局事务
*/
@Transactional
public void pay(Long userId, BigDecimal amount) {
// 扣款逻辑
accountDao.debit(userId, amount);
}
}
6.5 分布式事务选型建议
| 场景 | 推荐方案 | 理由 |
|---|---|---|
| 单体应用多数据源 | 2PC / JTA | 简单可靠 |
| 微服务强一致性 | Seata AT | 自动补偿,侵入性小 |
| 微服务高性能 | TCC | 无锁,性能好 |
| 长事务流程 | Saga | 适合业务流程长的场景 |
| 最终一致性可接受 | 本地消息表 / MQ | 解耦,可靠性高 |
| 通知类场景 | 最大努力通知 | 简单,成本低 |
七、事务最佳实践 ⭐⭐⭐⭐⭐
7.1 事务粒度控制
✅ 推荐:小事务
scss
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
// 只包含必要的数据库操作
orderDao.insert(order);
orderItemDao.batchInsert(order.getItems());
}
// 耗时操作放在事务外
public void createOrderWithNotification(Order order) {
// 1. 事务内:创建订单
createOrder(order);
// 2. 事务外:发送通知
sendEmail(order);
sendSms(order);
}
}
❌ 避免:大事务
scss
@Service
public class BadPracticeService {
@Transactional
public void badExample(Order order) {
// 数据库操作
orderDao.insert(order);
// ❌ HTTP 调用(耗时长,占用连接)
restTemplate.postForObject("http://external-api/...", ...);
// ❌ 文件 IO
Files.write(...);
// ❌ 复杂计算
complexCalculation();
// ❌ 等待用户输入
waitForUserInput();
}
}
7.2 避免事务嵌套过深
typescript
// ❌ 不推荐:多层嵌套
@Transactional
public void methodA() {
methodB(); // 嵌套
}
@Transactional
public void methodB() {
methodC(); // 嵌套
}
@Transactional
public void methodC() {
// ...
}
// ✅ 推荐:扁平化设计
@Transactional
public void processOrder(Order order) {
validateOrder(order); // 非事务方法
saveOrder(order); // 事务方法
notifyStakeholders(order); // 非事务方法
}
7.3 合理使用只读事务
kotlin
@Service
public class QueryService {
// ✅ 查询方法使用 readOnly
@Transactional(readOnly = true)
public List<Order> findOrdersByUserId(Long userId) {
return orderDao.findByUserId(userId);
}
// ✅ 批量查询使用 readOnly
@Transactional(readOnly = true)
public Map<Long, Product> findProductsByIds(List<Long> ids) {
return productDao.findByIdIn(ids).stream()
.collect(Collectors.toMap(Product::getId, p -> p));
}
}
7.4 正确处理异常
typescript
@Service
public class AccountService {
@Transactional(rollbackFor = Exception.class)
public void transfer(String from, String to, BigDecimal amount) {
try {
accountDao.debit(from, amount);
accountDao.credit(to, amount);
} catch (InsufficientBalanceException e) {
// 业务异常,记录日志但不回滚
log.warn("Insufficient balance: {}", from);
throw e; // 重新抛出,让调用方处理
} catch (Exception e) {
// 系统异常,记录日志并回滚
log.error("Transfer failed", e);
throw new SystemException("Transfer failed", e);
}
}
}
7.5 事务与缓存一致性
typescript
@Service
public class ProductService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
@Autowired
private ProductDao productDao;
/**
* 更新商品 - 先更新数据库,再删除缓存
*/
@Transactional
public void updateProduct(Product product) {
// 1. 更新数据库
productDao.update(product);
// 2. 删除缓存(Cache Aside Pattern)
redisTemplate.delete("product:" + product.getId());
}
/**
* 查询商品 - 先查缓存,再查数据库
*/
public Product getProduct(Long id) {
// 1. 查缓存
Product product = (Product) redisTemplate.opsForValue().get("product:" + id);
if (product != null) {
return product;
}
// 2. 查数据库
product = productDao.findById(id);
// 3. 写入缓存
if (product != null) {
redisTemplate.opsForValue().set("product:" + id, product, 1, TimeUnit.HOURS);
}
return product;
}
}
7.6 监控和日志
less
@Aspect
@Component
@Slf4j
public class TransactionMonitorAspect {
@Around("@annotation(org.springframework.transaction.annotation.Transactional)")
public Object monitorTransaction(ProceedingJoinPoint joinPoint) throws Throwable {
long start = System.currentTimeMillis();
String methodName = joinPoint.getSignature().toShortString();
try {
Object result = joinPoint.proceed();
long elapsed = System.currentTimeMillis() - start;
if (elapsed > 1000) {
log.warn("Slow transaction: {} took {} ms", methodName, elapsed);
}
return result;
} catch (Throwable e) {
long elapsed = System.currentTimeMillis() - start;
log.error("Transaction failed: {} after {} ms", methodName, elapsed, e);
throw e;
}
}
}
八、常见问题 FAQ
Q1:@Transactional 能用在静态方法上吗?
A: ❌ 不能。Spring AOP 基于代理,静态方法无法被代理。
Q2:同一个类中方法调用,事务会生效吗?
A: ❌ 不会。这是自调用问题,需要通过代理调用才能生效。
Q3:事务方法中捕获异常后,事务还会回滚吗?
A: ❌ 不会。除非手动调用 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()。
Q4:@Transactional 可以指定多个回滚异常吗?
A: ✅ 可以。
python
@Transactional(rollbackFor = {SQLException.class, IOException.class})
Q5:分布式事务一定会影响性能吗?
A: 是的。分布式事务涉及多次网络调用和协调,性能必然低于本地事务。应根据业务需求选择合适的方案。
Q6:如何测试事务是否生效?
A:
- 故意抛出异常,观察数据是否回滚
- 使用 AOP 切面监控事务执行情况
- 查看数据库日志
Q7:事务超时时间设置多少合适?
A: 根据业务场景:
- 简单 CRUD:5-10 秒
- 复杂业务:30-60 秒
- 批量处理:根据需要设置,但建议拆分批次
Q8:READ_COMMITTED 和 REPEATABLE_READ 如何选择?
A:
- READ_COMMITTED:高并发场景,允许不可重复读(Oracle、SQL Server 默认)
- REPEATABLE_READ:需要数据一致性,如财务计算(MySQL 默认)
九、总结
核心要点回顾
- ACID 特性:原子性、一致性、隔离性、持久性
- 传播行为:REQUIRED 最常用,REQUIRES_NEW 用于独立事务
- 隔离级别:根据并发需求选择,权衡一致性和性能
- 失效场景:注意自调用、异常捕获、非 public 方法等问题
- 分布式事务:根据场景选择 2PC、TCC、Saga、本地消息表等方案
- 最佳实践:小事务、只读优化、合理异常处理、监控日志
学习路线建议
css
入门 → 掌握 @Transactional 基本使用
↓
进阶 → 理解传播行为、隔离级别、失效场景
↓
高级 → 学习分布式事务解决方案
↓
专家 → 深入源码,理解 Spring 事务实现原理
参考资料:
- Spring 官方文档 - Transaction Management
- Seata 官方文档
- 《Spring 技术内幕》
- 《深入理解 Java 虚拟机》