系列文章目录
文章目录
一、何为事务?
事务(Transaction) 是一组不可分割的操作序列,这些操作要么全部成功执行,要么全部不执行。
Spring 提供了多种事务实现方式,主要分为编程式事务管理和声明式事务管理两大类。
1.编程式事务
需要在代码里显式地管理事务的边界,比如开启事务、提交事务或者回滚事务。它的灵活性较高,但会让业务代码和事务管理代码耦合在一起。
使用transactionTemplate实现的编程式事务代码如下。
@Service
public class UserService {
@Autowired
private TransactionTemplate transactionTemplate;
@Autowired
private UserRepository userRepository;
public User createUser(User user) {
return transactionTemplate.execute(status -> {
// 执行数据库操作
User savedUser = userRepository.save(user);
// 其他业务逻辑
return savedUser;
});
}
}
2.声明式事务管理
这种方式利用 AOP 技术,将事务管理的逻辑和业务代码分离开来,实现了事务管理的解耦。开发者只需通过注解或者 XML 配置来声明事务的属性,无需在代码中显式地管理事务。
使用@Transactional声明式事务注解,代码如下:
@Service
@Transactional // 类级别注解,所有公共方法都将参与事务
public class UserService {
@Autowired
private UserRepository userRepository;
public void createUser(User user) {
userRepository.save(user);
}
@Transactional(readOnly = true) // 方法级别注解,覆盖类级别的配置
public User getUserById(Long id) {
return userRepository.findById(id).orElse(null);
}
}
二、Spring 中事务传播行为及场景示例
在 Spring 中,事务传播行为(Propagation Behavior)定义了一个事务方法被另一个事务方法调用时,事务如何传播。理解这些行为对于设计正确的事务边界和处理复杂业务逻辑至关重要。以下是 Spring 提供的 7 种事务传播行为及其典型应用场景的详细解释:
1. REQUIRED(默认)
特性:如果当前存在事务,则加入该事务;如果没有事务,则创建一个新事务。场景:大多数业务操作,如增删改查,需要保证操作在同一个事务中。
示例:用户注册时同时创建用户信息和初始账户。
@Service
public class UserService {
@Autowired private AccountService accountService;
@Transactional
public void registerUser(User user) {
// 保存用户信息
userRepository.save(user);
// 调用另一个事务方法创建账户(加入当前事务)
accountService.createInitialAccount(user.getId());
}
}
@Service
public class AccountService {
@Transactional
public void createInitialAccount(Long userId) {
// 创建用户账户
accountRepository.save(new Account(userId, 0.0));
}
}
说明:如果 registerUser() 抛出异常,整个事务回滚,账户创建也会回滚。
2. REQUIRES_NEW
特性:无论当前是否存在事务,都会创建一个新事务,并挂起当前事务(如果存在)。场景:需要独立提交 / 回滚的操作,如日志记录、审计追踪。
示例:支付失败时记录日志,无论支付结果如何都要确保日志记录成功。
@Service
public class PaymentService {
@Autowired private LogService logService;
@Transactional
public void processPayment(Order order) {
try {
// 处理支付(可能失败)
paymentGateway.charge(order);
} catch (Exception e) {
// 记录失败日志(使用独立事务)
logService.logFailure("Payment failed: " + e.getMessage());
throw e;
}
}
}
@Service
public class LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logFailure(String message) {
// 记录日志(独立事务,不受主事务影响)
logRepository.save(new Log(message));
}
}
说明:即使 processPayment() 回滚,日志记录仍会提交。
3. SUPPORTS
特性:支持当前事务,如果没有事务,则以非事务方式执行。场景:查询操作,不需要事务保证,但可以参与已存在的事务。
示例:查询用户信息,通常不需要事务,但在批量操作中可能需要。
@Service
public class UserQueryService {
@Transactional(propagation = Propagation.SUPPORTS)
public User getUserById(Long id) {
return userRepository.findById(id).orElse(null);
}
}
说明:如果调用方有事务,查询会在该事务中执行;否则以非事务方式执行。
4. NOT_SUPPORTED
特性:以非事务方式执行操作,如果当前存在事务,则挂起该事务。场景:无需事务的耗时操作,如发送邮件、调用外部服务。
示例:用户注册后发送欢迎邮件,邮件发送失败不影响注册事务。
@Service
public class UserService {
@Autowired private EmailService emailService;
@Transactional
public void registerUser(User user) {
userRepository.save(user);
// 非事务方式发送邮件
emailService.sendWelcomeEmail(user);
}
}
@Service
public class EmailService {
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void sendWelcomeEmail(User user) {
// 发送邮件(不参与事务)
mailClient.send(user.getEmail(), "Welcome!", "...");
}
}
说明:如果 registerUser() 回滚,邮件仍会发送;反之,邮件发送失败不影响注册。
5. NEVER
特性:以非事务方式执行,如果当前存在事务,则抛出异常。场景:明确禁止事务的操作,如读取不需要一致性的数据。
示例:读取统计数据,不需要事务一致性。
@Service
public class StatsService {
@Transactional(propagation = Propagation.NEVER)
public long getTotalUserCount() {
return userRepository.count();
}
}
说明:如果在事务中调用 getTotalUserCount(),会抛出异常。
6. MANDATORY
特性:支持当前事务,如果没有事务,则抛出异常。场景:必须在已存在事务中执行的操作,如嵌套操作依赖外部事务。
示例:扣减库存,必须在订单创建的事务中执行。
@Service
public class OrderService {
@Autowired private InventoryService inventoryService;
@Transactional
public void createOrder(Order order) {
// 扣减库存(必须在当前事务中执行)
inventoryService.decreaseStock(order.getProductId(), order.getQuantity());
orderRepository.save(order);
}
}
@Service
public class InventoryService {
@Transactional(propagation = Propagation.MANDATORY)
public void decreaseStock(Long productId, int quantity) {
// 扣减库存逻辑
Product product = productRepository.findById(productId).orElseThrow();
product.setStock(product.getStock() - quantity);
productRepository.save(product);
}
}
说明:如果直接调用 decreaseStock() 而没有事务,会抛出异常。
7. NESTED
特性:如果当前存在事务,则在嵌套事务内执行;如果没有事务,则等同于 REQUIRED。嵌套事务可以独立回滚。场景:子操作需要独立回滚,但整体事务仍需保证一致性。
示例:批量导入数据,部分失败不影响其他记录。
@Service
public class BatchImportService {
@Autowired private RecordService recordService;
@Transactional
public void importRecords(List<Record> records) {
for (Record record : records) {
try {
// 每个记录在嵌套事务中处理
recordService.saveRecord(record);
} catch (Exception e) {
// 记录失败,但不影响其他记录
log.error("Failed to import record: {}", record, e);
}
}
}
}
@Service
public class RecordService {
@Transactional(propagation = Propagation.NESTED)
public void saveRecord(Record record) {
// 验证并保存记录
validateRecord(record);
recordRepository.save(record);
}
}
说明:如果某条记录保存失败,仅回滚该嵌套事务,其他记录不受影响。
总结对比
| 传播行为 | 事务不存在时 | 事务存在时 | 典型场景 |
|---|---|---|---|
REQUIRED | 创建新事务 | 加入当前事务 | 常规增删改查 |
REQUIRES_NEW | 创建新事务 | 挂起当前,创建新事务 | 独立提交的操作(如日志、审计) |
SUPPORTS | 非事务执行 | 加入当前事务 | 查询操作 |
NOT_SUPPORTED | 非事务执行 | 挂起当前,非事务执行 | 耗时操作(如邮件、外部服务) |
NEVER | 非事务执行 | 抛出异常 | 禁止事务的操作 |
MANDATORY | 抛出异常 | 加入当前事务 | 必须在事务中执行的操作 |
NESTED | 创建新事务 | 创建嵌套事务(可独立回滚) | 批量操作,部分失败不影响整体 |
使用建议
-
**优先使用 **
REQUIRED:大多数业务操作都应该在同一个事务中完成。 -
**谨慎使用 **
NESTED:嵌套事务依赖数据库支持(如 JDBC Savepoint),且行为与REQUIRES_NEW不同,需根据场景选择。 -
避免深层嵌套事务:过多嵌套会增加事务管理复杂度,优先考虑重构业务逻辑。
理解事务传播行为是实现复杂业务逻辑的关键,合理选择传播行为可以避免数据不一致和性能问题。
1711

被折叠的 条评论
为什么被折叠?



