💡 摘要:你是否遇到过
@Transactional注解失效的“灵异事件”? 是否在多数据源环境下为事务管理头疼不已? Spring Boot 虽然通过自动配置简化了 Spring 的事务管理, 但其背后的 AOP 代理机制、传播行为、隔离级别、回滚规则 仍充满陷阱。 本文将深度剖析 Spring Boot 事务管理的底层原理, 从@Transactional注解的属性详解、失效场景、编程式事务, 到分布式事务解决方案(Seata, Saga), 并结合DataSourceTransactionManager、JpaTransactionManager等实战配置, 最后揭秘为什么this.method()调用会导致事务失效。 文末附事务传播行为决策图与面试高频问题清单,助你彻底掌握 Spring 事务。
Spring Boot 的便捷性体现在自动配置上。当你引入 spring-boot-starter-jdbc 或 spring-boot-starter-data-jpa 时,Spring Boot 会自动配置一个 PlatformTransactionManager:
# application.yml
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: root
password: password
driver-class-name: com.mysql.cj.jdbc.Driver// Spring Boot 自动配置的等效代码
@Configuration
@EnableTransactionManagement // 启用事务管理
public class TransactionConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource); // 最常用的实现
}
}✅
@EnableTransactionManagement: 启用基于注解的事务管理,它会创建一个BeanPostProcessor,为带有@Transactional的 Bean 创建代理。
@Transactional 注解深度解析这是最常用的方式,基于 AOP 动态代理实现。
propagation(传播行为)——事务的“调用规则”传播行为 | 说明 | 使用场景 |
|---|---|---|
REQUIRED (默认) | 有则加入,无则新建 | 最常用,业务方法 |
REQUIRES_NEW | 总是新建事务,挂起当前 | 日志记录、独立操作 |
SUPPORTS | 有则加入,无则非事务 | 查询方法 |
MANDATORY | 必须有事务,否则异常 | 内部方法要求事务上下文 |
NEVER | 必须无事务,否则异常 | 避免在事务中执行特定操作 |
NOT_SUPPORTED | 挂起当前事务,非事务执行 | 耗时操作(如文件上传) |
NESTED | 嵌套事务(Savepoint) | 部分回滚 |
@Service
public class OrderService {
@Autowired
private PaymentService paymentService;
@Autowired
private LogService logService;
@Transactional
public void createOrder(Order order) {
// 1. 创建订单 (REQUIRED)
orderRepository.save(order);
try {
// 2. 支付 (REQUIRES_NEW - 独立事务)
paymentService.processPayment(order);
} catch (PaymentException e) {
// 支付失败,订单仍可回滚
throw new OrderCreationException("Payment failed", e);
}
// 3. 记录日志 (REQUIRES_NEW - 独立事务)
logService.logOrderCreation(order); // 即使订单回滚,日志也应保留
}
}
@Service
public class LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logOrderCreation(Order order) {
logRepository.save(new LogEntry(order, "CREATED"));
}
}✅
REQUIRES_NEW是关键:确保日志和支付独立于主事务。
isolation(隔离级别)——解决并发问题隔离级别 | 脏读 | 不可重复读 | 幻读 | 说明 |
|---|---|---|---|---|
DEFAULT | - | - | - | 数据库默认(MySQL: REPEATABLE_READ) |
READ_UNCOMMITTED | ✅ | ✅ | ✅ | 最低,性能最高 |
READ_COMMITTED | ❌ | ✅ | ✅ | Oracle 默认 |
REPEATABLE_READ | ❌ | ❌ | ✅ | MySQL 默认 |
SERIALIZABLE | ❌ | ❌ | ❌ | 最高,性能最低 |
@Transactional(isolation = Isolation.READ_COMMITTED)
public List<Order> getRecentOrders() {
// 查询最近订单,避免脏读即可
return orderRepository.findRecent();
}🔥 MySQL 的
REPEATABLE_READ能防止幻读吗? InnoDB 通过 Next-Key Locks 在大多数情况下防止了幻读,但并非完全等同于SERIALIZABLE。
rollbackFor / noRollbackFor ——回滚的“开关”@Transactional(
// 指定哪些异常触发回滚
rollbackFor = {BusinessException.class, SQLException.class},
// 指定哪些异常不触发回滚(即使它是 RuntimeException)
noRollbackFor = {NotificationException.class}
)
public void processOrder(Order order) throws BusinessException {
orderRepository.save(order);
try {
notificationService.sendNotification(order);
} catch (NotificationException e) {
// 通知失败,但订单仍应提交
log.warn("Notification failed, but order is saved", e);
// 不 throw,或 throw 被 noRollbackFor 捕获的异常
}
}✅ 默认规则:
RuntimeException 及其子类:回滚Error:回滚Checked Exception:不回滚timeout 和 readOnly// 设置超时,避免长时间锁
@Transactional(timeout = 30) // 30秒
public void updateInventory(Inventory inventory) {
// ...
}
// 只读事务,优化性能(如查询)
@Transactional(readOnly = true)
public List<User> findAllUsers() {
return userRepository.findAll();
}✅
readOnly的优化: 告诉数据库这是只读操作,数据库可进行优化(如使用快照)。
@Transactional 的常见失效场景(必看!)@Service
public class UserService {
@Transactional
public void updateWithLog(User user) {
updateUser(user); // ❌ 直接调用,无事务!
logUserActivity(user); // ❌ 直接调用,无事务!
}
// 这两个方法的 @Transactional 无效!
@Transactional
private void updateUser(User user) { /* ... */ }
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logUserActivity(User user) { /* ... */ }
}🔑 原因:
updateWithLog调用的是this.updateUser(),是目标对象的直接调用,绕过了代理。 解决方法:
@Service Bean 中。AopContext.currentProxy()(不推荐,侵入性强)。@Service
public class UserService {
@Autowired
private UserInternalService userInternalService; // 提取到内部服务
@Transactional
public void updateWithLog(User user) {
userInternalService.updateUser(user); // ✅ 通过代理调用
userInternalService.logUserActivity(user); // ✅ 通过代理调用
}
}public 方法@Transactional // ❌ 无效!
private void privateMethod() { ... }
@Transactional // ❌ 无效!
protected void protectedMethod() { ... }✅
@Transactional只对public方法有效。
@Transactional
public void riskyOperation() {
try {
dangerousMethod(); // 可能抛出 RuntimeException
} catch (Exception e) {
// 捕获了异常但没有抛出,事务不会回滚!
log.error("Operation failed", e);
}
// 方法正常结束,事务提交!
}✅ 解决:要么不捕获,要么捕获后
throw。
@Transactional
public void checkedExceptionMethod() throws IOException {
// 抛出 IOException (checked exception)
// 默认不会触发回滚!
throw new IOException("File not found");
}✅ 解决:显式指定
rollbackFor = IOException.class。
当声明式事务不够用时,使用编程式事务。
TransactionTemplate@Service
public class OrderService {
@Autowired
private TransactionTemplate transactionTemplate;
@Autowired
private OrderRepository orderRepository;
public void createOrderWithTemplate(Order order) {
transactionTemplate.execute(status -> {
try {
orderRepository.save(order);
// ... 其他操作 ...
return null;
} catch (BusinessException e) {
status.setRollbackOnly(); // 手动标记回滚
throw e; // 可选择抛出
}
});
}
}✅ 简洁,推荐用于简单逻辑。
PlatformTransactionManager@Service
public class ProductService {
@Autowired
private PlatformTransactionManager transactionManager;
@Autowired
private ProductRepository productRepository;
public void updateStock(Product product, int quantity) {
// 1. 定义事务
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
// 2. 开启事务
TransactionStatus status = transactionManager.getTransaction(def);
try {
product.setStock(product.getStock() - quantity);
productRepository.save(product);
// 3. 提交
transactionManager.commit(status);
} catch (Exception e) {
// 4. 回滚
transactionManager.rollback(status);
throw new RuntimeException("Update stock failed", e);
}
}
}✅ 最灵活,但代码冗长。
当应用连接多个数据库时,需要配置多个 TransactionManager。
@Configuration
public class DataSourceConfig {
@Bean
@Primary
@ConfigurationProperties("spring.datasource.orders")
public DataSource ordersDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@ConfigurationProperties("spring.datasource.users")
public DataSource usersDataSource() {
return DataSourceBuilder.create().build();
}
@Bean
@Primary
public PlatformTransactionManager ordersTransactionManager(
@Qualifier("ordersDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
public PlatformTransactionManager usersTransactionManager(
@Qualifier("usersDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}@Service
public class UserService {
@Transactional("usersTransactionManager") // 指定事务管理器
public void updateUser(User user) {
userRepository.save(user);
}
}✅
@Transactional("transactionManagerBeanName"):指定使用哪个TransactionManager。
当事务跨越多个微服务或数据库时,需使用分布式事务方案:
✅ 分布式事务是另一个复杂话题,本文不展开。
实践 | 说明 |
|---|---|
✅ 优先使用 @Transactional | 简洁,声明式 |
✅ 避免内部调用 | 防止代理失效 |
✅ 显式指定 rollbackFor | 避免 Checked Exception 不回滚 |
✅ 合理设置 timeout | 防止长时间锁 |
✅ 查询使用 readOnly = true | 性能优化 |
✅ 保持事务短小 | 减少锁竞争 |
✅ 多数据源明确指定 TransactionManager | 避免混淆 |
⚠️ 避免在事务中进行远程调用 | 防止长时间阻塞 |
@Transactional 的原理是什么?答:基于 AOP 动态代理。 Spring 为标记
@Transactional的 Bean 创建代理(JDK 动态代理或 CGLIB), 在方法调用前后通过TransactionInterceptor拦截, 调用PlatformTransactionManager的getTransaction、commit、rollback方法。
答:
publicrollbackFor)REQUIRED 和 REQUIRES_NEW 的区别?答:
REQUIRED:加入现有事务或新建。REQUIRES_NEW:总是新建事务,挂起当前事务。REQUIRES_NEW 的事务独立提交或回滚,不影响外层事务。答:
@Transactional 可以用在 private 方法上吗?答:不可以。
@Transactional只对public方法有效, 因为 Spring AOP 代理通常只能拦截public方法调用。
Spring Boot 的事务管理强大而便捷,但需深入理解其机制:
PlatformTransactionManager 和 AOP 代理。@Transactional 是首选,但要警惕失效场景。TransactionManager。掌握这些知识,你才能写出高可靠、高性能的 Spring Boot 应用。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。