跳到主要内容

事务管理

问题

Spring 事务的实现原理是什么?@Transactional 的传播行为有哪些?事务在哪些场景下会失效?

答案

Spring 事务的实现原理

Spring 事务基于 AOP 实现:@Transactional 注解的方法会被 TransactionInterceptor 增强,通过代理在方法前后管理事务的开启、提交和回滚。

核心组件:

  • PlatformTransactionManager:事务管理器接口(DataSourceTransactionManager、JpaTransactionManager)
  • TransactionDefinition:事务定义(传播行为、隔离级别、超时等)
  • TransactionStatus:事务状态(是否新事务、是否已完成)

@Transactional 使用

TransactionalUsage.java
@Service
public class OrderService {

@Transactional // 默认配置
public void createOrder(Order order) {
orderDao.insert(order);
inventoryService.deduct(order.getProductId(), order.getQuantity());
// 如果这里抛出 RuntimeException,两个操作都会回滚
}

@Transactional(
propagation = Propagation.REQUIRED, // 传播行为
isolation = Isolation.READ_COMMITTED, // 隔离级别
timeout = 30, // 超时(秒)
readOnly = false, // 是否只读
rollbackFor = Exception.class, // 回滚异常类型
noRollbackFor = BusinessException.class // 不回滚的异常
)
public void complexOperation() { /* ... */ }
}

七大传播行为

传播行为说明使用场景
REQUIRED(默认)有事务加入,无事务新建大多数场景
REQUIRES_NEW无论如何新建事务,原事务挂起独立事务(如日志记录)
NESTED存在事务则嵌套(保存点),无事务则新建子操作失败不影响父事务
SUPPORTS有事务加入,无事务非事务执行查询方法
NOT_SUPPORTED非事务执行,存在事务则挂起某些不需要事务的操作
MANDATORY必须在事务中调用,否则抛异常强制要求事务的方法
NEVER非事务执行,存在事务则抛异常禁止事务的方法

重点掌握前三种:

PropagationExample.java
@Service
public class OrderService {

@Transactional(propagation = Propagation.REQUIRED)
public void createOrder() {
orderDao.insert(order);
// REQUIRED:logService 加入当前事务
// REQUIRES_NEW:logService 开启新事务
// NESTED:logService 在保存点内执行
logService.log("创建订单");
}
}

@Service
public class LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void log(String msg) {
// 即使 createOrder 回滚,日志记录不受影响
logDao.insert(msg);
}
}
NESTED vs REQUIRES_NEW
  • REQUIRES_NEW:创建完全独立的新事务,外部事务回滚不影响内部事务
  • NESTED:嵌套事务(Savepoint),外部事务回滚会连带回滚嵌套事务,但内部事务回滚不影响外部事务

事务失效的常见场景

@Transactional 失效场景

1. 自调用(最常见)

同一类中方法 A 调用方法 B,B 的 @Transactional 不生效(因为绕过了代理):

@Service
public class UserService {
public void methodA() {
this.methodB(); // ❌ 自调用,不走代理
}
@Transactional
public void methodB() { /* ... */ }
}

2. 方法不是 public

Spring AOP 默认只代理 public 方法:

@Transactional
private void method() { } // ❌ private 方法不会被代理

3. 异常被吞掉

@Transactional
public void method() {
try {
// 业务操作
} catch (Exception e) {
log.error("出错了", e);
// ❌ 异常被 catch 了,Spring 感知不到,不会回滚
}
}

4. 抛出的异常类型不对

@Transactional // 默认只回滚 RuntimeException 和 Error
public void method() throws Exception {
throw new IOException("IO 异常"); // ❌ 受检异常不会触发回滚
}

// 解决:指定 rollbackFor
@Transactional(rollbackFor = Exception.class)

5. 数据库引擎不支持事务

MySQL 的 MyISAM 引擎不支持事务,需要使用 InnoDB。

6. 没有被 Spring 管理

Bean 没有被 Spring 管理(没有 @Service/@Component 注解),事务不生效。

7. 多线程调用

事务绑定在 ThreadLocal 中,新线程不在同一个事务中。

编程式事务

除了声明式(@Transactional),还可以使用 TransactionTemplate 编程式管理事务:

ProgrammaticTransaction.java
@Service
public class UserService {
@Autowired
private TransactionTemplate transactionTemplate;

public void createUser(User user) {
transactionTemplate.execute(status -> {
try {
userDao.insert(user);
roleDao.bindRole(user.getId(), "USER");
return true;
} catch (Exception e) {
status.setRollbackOnly(); // 手动标记回滚
throw e;
}
});
}
}

编程式事务更灵活,适合需要精细控制事务边界的场景。


常见面试问题

Q1: Spring 事务的实现原理?

答案

基于 AOP 动态代理。@Transactional 注解的类/方法会被 TransactionInterceptor 拦截,在方法执行前通过 PlatformTransactionManager 开启事务,正常返回时提交,异常时回滚。

底层连接管理通过 ThreadLocal 绑定到当前线程,保证同一事务中的多个 DAO 操作使用同一个数据库连接。

Q2: @Transactional 的传播行为有哪些?常用的是哪几个?

答案

7 种传播行为,常用 3 种:

  • REQUIRED(默认):有则加入,无则新建
  • REQUIRES_NEW:无论如何新建事务,独立于外部事务
  • NESTED:嵌套事务,通过 Savepoint 实现

Q3: @Transactional 失效的场景?

答案

  1. 自调用:同类中方法调用(不走代理)
  2. 非 public 方法:AOP 不代理非 public 方法
  3. 异常被 catch:Spring 感知不到异常
  4. 异常类型不匹配:受检异常默认不回滚(需 rollbackFor = Exception.class
  5. 非 Spring Bean
  6. 多线程:新线程不共享事务

Q4: 声明式事务和编程式事务的区别?

答案

对比声明式(@Transactional)编程式(TransactionTemplate)
实现AOP 代理代码中手动控制
侵入性低(注解即可)高(需要写代码)
粒度方法级别代码块级别(更细)
灵活性

大多数场景用声明式,需要精细控制时用编程式。

Q5: @Transactional(readOnly = true) 有什么用?

答案

标记事务为只读,数据库和 ORM 框架可以做优化:

  1. 数据库可能使用只读事务优化(不加写锁)
  2. Hibernate/JPA 不做脏检查(flush 时不检查变更)
  3. 主从架构中可能路由到从库

通常用于纯查询方法,提升性能。

相关链接