事务管理是企业级应用开发中不可或缺的一环,Spring 框架通过 @Transactional 注解为开发者提供了声明式事务的便捷能力。然而,便利的背后隐藏着诸多细节与陷阱,本文将从原理出发,深入剖析 Spring 事务的传播行为、隔离级别以及常见的失效场景。
Spring 的声明式事务基于 AOP(面向切面编程) 实现。当我们在方法或类上添加 @Transactional 注解时,Spring 会在运行时为目标对象创建代理:
代理对象会在方法调用前后添加事务管理逻辑:开启事务、提交事务或回滚事务。
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
// 事务管理器名称
String value() default "";
// 传播行为
Propagation propagation() default Propagation.REQUIRED;
// 隔离级别
Isolation isolation() default Isolation.DEFAULT;
// 超时时间(秒)
int timeout() default -1;
// 只读事务
boolean readOnly() default false;
// 回滚异常类型
Class<? extends Throwable>[] rollbackFor() default {};
// 不回滚异常类型
Class<? extends Throwable>[] noRollbackFor() default {};
}
PlatformTransactionManager 是 Spring 事务管理的核心接口:
public interface PlatformTransactionManager {
// 获取事务
TransactionStatus getTransaction(TransactionDefinition definition);
// 提交事务
void commit(TransactionStatus status);
// 回滚事务
void rollback(TransactionStatus status);
}
常用的实现类包括:
DataSourceTransactionManager:JDBC 单数据源事务JtaTransactionManager:分布式事务(JTA)HibernateTransactionManager:Hibernate 框架事务事务传播行为定义了方法调用时事务的边界和交互方式。Spring 提供了 7 种传播行为:
| 传播行为 | 说明 |
|---|---|
REQUIRED | 默认行为,当前有事务则加入,无则新建 |
SUPPORTS | 有事务则加入,无则以非事务方式执行 |
MANDATORY | 必须有事务,否则抛出异常 |
REQUIRES_NEW | 挂起当前事务,创建新事务 |
NOT_SUPPORTED | 挂起当前事务,以非事务方式执行 |
NEVER | 必须无事务,否则抛出异常 |
NESTED | 嵌套事务,底层使用 savepoint |
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
orderDao.save(order);
// 调用库存服务
inventoryService.deductStock(order.getItemId(), order.getQuantity());
}
}
@Service
public class InventoryService {
// 与外层方法共用同一个事务
@Transactional(propagation = Propagation.REQUIRED)
public void deductStock(Long itemId, Integer quantity) {
inventoryDao.deduct(itemId, quantity);
}
// 独立事务,外层事务失败不影响此操作
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void deductStockIndependent(Long itemId, Integer quantity) {
inventoryDao.deduct(itemId, quantity);
}
}
REQUIRES_NEW 的典型应用场景:
@Transactional(propagation = Propagation.NESTED)
public void nestedOperation() {
try {
// 部分操作失败可独立回滚
riskyOperation();
} catch (Exception e) {
// 仅回滚到 savepoint,外层事务继续
log.error("子操作失败", e);
}
}
注意: NESTED 传播行为仅在支持 savepoint 的数据库上有效(如 MySQL、PostgreSQL)。
Spring 支持 5 种隔离级别:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 |
|---|---|---|---|
DEFAULT(默认) | 取决于数据库 | 取决于数据库 | 取决于数据库 |
READ_UNCOMMITTED | 允许 | 允许 | 允许 |
READ_COMMITTED | 禁止 | 允许 | 允许 |
REPEATABLE_READ | 禁止 | 禁止 | 允许 |
SERIALIZABLE | 禁止 | 禁止 | 禁止 |
脏读(Dirty Read):
// 事务 A
@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public void readUncommitted() {
// 可能读到事务 B 未提交的数据
Order order = orderDao.findById(1L);
}
不可重复读(Non-repeatable Read):
@Transactional(isolation = Isolation.READ_COMMITTED)
public void nonRepeatableRead() {
Order order1 = orderDao.findById(1L); // 余额: 1000
// 其他事务修改并提交
Order order2 = orderDao.findById(1L); // 余额: 900(不一致!)
}
幻读(Phantom Read):
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void phantomRead() {
List<Order> orders1 = orderDao.findByStatus("PENDING"); // 10 条
// 其他事务插入新订单并提交
List<Order> orders2 = orderDao.findByStatus("PENDING"); // 11 条(幻读)
}
@Service
public class AccountService {
// 转账操作需要强一致性
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void transfer(Long fromId, Long toId, BigDecimal amount) {
Account from = accountDao.findById(fromId);
Account to = accountDao.findById(toId);
from.deduct(amount);
to.add(amount);
accountDao.update(from);
accountDao.update(to);
}
// 统计查询可接受不可重复读
@Transactional(isolation = Isolation.READ_COMMITTED, readOnly = true)
public BigDecimal getTotalBalance() {
return accountDao.sumBalance();
}
}
@Service
public class UserService {
@Transactional
public void methodA() {
// 调用同类方法 B,事务失效!
// 因为这里调用的是目标对象本身,而非代理对象
methodB();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
// 期望的新事务不会生效
userDao.update();
}
}
解决方案:
@Service
public class UserService {
@Autowired
private ApplicationContext context;
@Transactional
public void methodA() {
// 通过代理对象调用
UserService proxy = context.getBean(UserService.class);
proxy.methodB();
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void methodB() {
userDao.update();
}
}
@Transactional
public void createUser(User user) {
try {
userDao.save(user);
sendEmail(user.getEmail()); // 可能抛出异常
} catch (Exception e) {
// 异常被捕获,事务不会回滚!
log.error("发送邮件失败", e);
}
}
解决方案:
@Transactional
public void createUser(User user) {
userDao.save(user);
try {
sendEmail(user.getEmail());
} catch (Exception e) {
log.error("发送邮件失败", e);
// 可选:标记用户需要手动发送邮件
}
}
默认情况下,Spring 只在遇到 RuntimeException 或 Error 时才回滚事务。
@Transactional
public void updateUser(User user) throws SQLException {
// 抛出 checked exception,事务不会回滚!
throw new SQLException("数据库错误");
}
解决方案:
@Transactional(rollbackFor = Exception.class)
public void updateUser(User user) throws Exception {
userDao.update(user);
}
@Service
public class ProductService {
// 事务不会生效,因为方法不是 public
@Transactional
void updateStock(Long productId, Integer quantity) {
productDao.updateStock(productId, quantity);
}
}
-- MyISAM 引擎不支持事务
CREATE TABLE user (
id BIGINT PRIMARY KEY,
name VARCHAR(50)
) ENGINE=MyISAM;
-- 应使用 InnoDB
CREATE TABLE user (
id BIGINT PRIMARY KEY,
name VARCHAR(50)
) ENGINE=InnoDB;
@Service
public class OrderApplicationService {
@Autowired
private OrderService orderService;
@Autowired
private PaymentService paymentService;
@Autowired
private InventoryService inventoryService;
// 事务边界放在应用服务层
@Transactional(rollbackFor = Exception.class)
public OrderResult placeOrder(OrderCommand command) {
// 1. 创建订单
Order order = orderService.createOrder(command);
// 2. 扣减库存
inventoryService.deductStock(command.getItemId(), command.getQuantity());
// 3. 发起支付
PaymentResult payment = paymentService.pay(order);
return OrderResult.of(order, payment);
}
}
@Service
public class ReportService {
// 标记为只读,Spring 会进行一些优化
@Transactional(readOnly = true)
public List<OrderReport> generateDailyReport(LocalDate date) {
return orderDao.findByDate(date);
}
}
@Service
public class BatchService {
// 防止长时间运行的事务占用连接
@Transactional(timeout = 30)
public void batchProcess(List<Long> ids) {
for (Long id : ids) {
process(id);
}
}
}
Spring 事务管理是一个看似简单但细节丰富的主题。理解以下关键点可以帮助我们避免大多数陷阱:
REQUIRED 适用于大多数场景,REQUIRES_NEW 用于需要独立事务的操作掌握这些知识后,我们就能在生产环境中正确、高效地使用 Spring 事务管理,构建可靠的企业级应用。
参考文档