文章目录
- [Spring 事务管理深度解析与 MySQL/PostgreSQL 差异化应用](#Spring 事务管理深度解析与 MySQL/PostgreSQL 差异化应用)
-
- [1. Spring 事务管理基础架构](#1. Spring 事务管理基础架构)
-
- [1.1 事务核心接口体系](#1.1 事务核心接口体系)
- [1.2 事务管理器实现类](#1.2 事务管理器实现类)
- [1.3 Spring 事务抽象设计理念](#1.3 Spring 事务抽象设计理念)
- [2. 声明式事务详解](#2. 声明式事务详解)
-
- [2.1 @Transactional 注解使用详解](#2.1 @Transactional 注解使用详解)
- [2.2 基于 AOP 的声明式事务实现原理](#2.2 基于 AOP 的声明式事务实现原理)
- [2.3 声明式事务的优缺点分析](#2.3 声明式事务的优缺点分析)
- [2.4 声明式事务最佳实践](#2.4 声明式事务最佳实践)
- [3. 编程式事务详解](#3. 编程式事务详解)
-
- [3.1 TransactionTemplate 使用方式](#3.1 TransactionTemplate 使用方式)
- [3.2 PlatformTransactionManager 直接使用](#3.2 PlatformTransactionManager 直接使用)
- [3.3 编程式事务的适用场景](#3.3 编程式事务的适用场景)
- [3.4 编程式事务与声明式事务对比](#3.4 编程式事务与声明式事务对比)
- [4. 事务传播机制深入分析](#4. 事务传播机制深入分析)
-
- [4.1 七种传播行为详解](#4.1 七种传播行为详解)
- [4.2 传播行为的实际应用场景](#4.2 传播行为的实际应用场景)
- [4.3 嵌套事务传播机制](#4.3 嵌套事务传播机制)
- [4.4 传播行为组合使用案例](#4.4 传播行为组合使用案例)
- [5. 事务隔离级别详解](#5. 事务隔离级别详解)
-
- [5.1 五种隔离级别含义与作用](#5.1 五种隔离级别含义与作用)
- [5.2 并发问题分析(脏读、不可重复读、幻读)](#5.2 并发问题分析(脏读、不可重复读、幻读))
- [5.3 隔离级别在 MySQL 与 PostgreSQL 中的实现差异](#5.3 隔离级别在 MySQL 与 PostgreSQL 中的实现差异)
- [5.4 隔离级别选择策略与最佳实践](#5.4 隔离级别选择策略与最佳实践)
- [6. 异常处理机制详解](#6. 异常处理机制详解)
-
- [6.1 事务回滚规则](#6.1 事务回滚规则)
- [6.2 @Transactional 注解的 rollbackFor 和 noRollbackFor 属性](#6.2 @Transactional 注解的 rollbackFor 和 noRollbackFor 属性)
- [6.3 自定义异常回滚策略](#6.3 自定义异常回滚策略)
- [6.4 嵌套事务异常处理](#6.4 嵌套事务异常处理)
- [6.5 异常传播与事务边界](#6.5 异常传播与事务边界)
- [7. MySQL 与 PostgreSQL 事务处理差异](#7. MySQL 与 PostgreSQL 事务处理差异)
-
- [7.1 两种数据库的默认隔离级别差异](#7.1 两种数据库的默认隔离级别差异)
- [7.2 MVCC 实现机制差异](#7.2 MVCC 实现机制差异)
- [7.3 锁机制差异](#7.3 锁机制差异)
- [7.4 Spring 事务配置在不同数据库上的适配](#7.4 Spring 事务配置在不同数据库上的适配)
- [7.5 两种数据库的事务性能对比](#7.5 两种数据库的事务性能对比)
- [7.6 跨数据库事务处理最佳实践](#7.6 跨数据库事务处理最佳实践)
- [8. 综合实战案例](#8. 综合实战案例)
-
- [8.1 电商订单处理系统](#8.1 电商订单处理系统)
- [8.2 银行转账系统](#8.2 银行转账系统)
- [8.3 分布式事务处理案例](#8.3 分布式事务处理案例)
-
- [8.3.2 Seata 分布式事务配置(核心)](#8.3.2 Seata 分布式事务配置(核心))
-
- [1. 依赖引入(所有微服务)](#1. 依赖引入(所有微服务))
- [2. 配置文件(application.yml)](#2. 配置文件(application.yml))
- [3. 回滚日志表创建(所有数据库)](#3. 回滚日志表创建(所有数据库))
- [8.3.3 Feign[(67)](https://blog.csdn.net/a1256afafaafr/article/details/147954851)客户端定义(服务间调用)](#8.3.3 Feign(67)客户端定义(服务间调用))
- [8.3.4 分布式事务执行流程(核心原理)](#8.3.4 分布式事务执行流程(核心原理))
- [8.3.5 分布式事务注意事项](#8.3.5 分布式事务注意事项)
- [9. Spring 事务常见问题与解决方案](#9. Spring 事务常见问题与解决方案)
-
- [9.1 @Transactional 注解失效场景及解决](#9.1 @Transactional 注解失效场景及解决)
-
- [场景 1:非 public 方法使用 @Transactional](#场景 1:非 public 方法使用 @Transactional)
- [场景 2:自调用导致代理失效](#场景 2:自调用导致代理失效)
- [场景 3:异常被捕获未抛出](#场景 3:异常被捕获未抛出)
- [场景 4:未配置 @EnableTransactionManagement](#场景 4:未配置 @EnableTransactionManagement)
- [9.2 事务并发问题解决方案](#9.2 事务并发问题解决方案)
-
- [问题 1:超卖问题(库存并发扣减)](#问题 1:超卖问题(库存并发扣减))
- [问题 2:死锁问题](#问题 2:死锁问题)
- [9.3 MySQL 与 PostgreSQL 事务差异实战解决](#9.3 MySQL 与 PostgreSQL 事务差异实战解决)
-
- [差异 1:默认隔离级别导致的查询不一致](#差异 1:默认隔离级别导致的查询不一致)
- [差异 2:PostgreSQL 不支持 READ_UNCOMMITTED](#差异 2:PostgreSQL 不支持 READ_UNCOMMITTED)
- [差异 3:MVCC 实现导致的事务可见性](#差异 3:MVCC 实现导致的事务可见性)
- [10. 总结与面试重点](#10. 总结与面试重点)
-
- [10.1 核心知识点总结](#10.1 核心知识点总结)
- [10.2 面试高频问题与回答思路](#10.2 面试高频问题与回答思路)
-
- [问题 1:@Transactional 注解的工作原理?](#问题 1:@Transactional 注解的工作原理?)
- [问题 2:事务传播行为中 REQUIRES_NEW 与 NESTED 的区别?](#问题 2:事务传播行为中 REQUIRES_NEW 与 NESTED 的区别?)
- [问题 3:Spring 事务为什么默认只回滚 RuntimeException?](#问题 3:Spring 事务为什么默认只回滚 RuntimeException?)
- [问题 4:分布式事务的解决方案有哪些?Seata AT 模式原理?](#问题 4:分布式事务的解决方案有哪些?Seata AT 模式原理?)
- [11. Spring Boot 3.x 事务新特性](#11. Spring Boot 3.x 事务新特性)
-
- [11.1 对 Java 17 密封类(Sealed Classes)的事务支持](#11.1 对 Java 17 密封类(Sealed Classes)的事务支持)
- [11.2 事务感知的虚拟线程(Virtual Threads)支持](#11.2 事务感知的虚拟线程(Virtual Threads)支持)
- [11.3 声明式事务的条件化配置(Conditional Transactions)](#11.3 声明式事务的条件化配置(Conditional Transactions))
- [12. 事务性能优化实战](#12. 事务性能优化实战)
-
- [12.1 数据库连接池优化(HikariCP)](#12.1 数据库连接池优化(HikariCP))
- [12.2 事务粒度控制(避免 "大事务")](#12.2 事务粒度控制(避免 “大事务”))
- [12.3 读写分离下的事务处理](#12.3 读写分离下的事务处理)
-
- [方案:Spring 读写分离 + 事务路由](#方案:Spring 读写分离 + 事务路由)
- [12.4 数据库层面优化(MySQL/PostgreSQL 专项)](#12.4 数据库层面优化(MySQL/PostgreSQL 专项))
-
- [MySQL 专项优化(InnoDB)](#MySQL 专项优化(InnoDB))
- [PostgreSQL 专项优化](#PostgreSQL 专项优化)
- [13. 扩展面试题解析(高频深化)](#13. 扩展面试题解析(高频深化))
-
- [13.1 问题:Spring 事务与数据库事务的关系?](#13.1 问题:Spring 事务与数据库事务的关系?)
- [13.2 问题:如何排查 "Spring 事务不回滚" 的问题?](#13.2 问题:如何排查 “Spring 事务不回滚” 的问题?)
- [13.3 问题:Spring 事务与 `@Async` 注解一起使用时,会有什么问题?如何解决?](#13.3 问题:Spring 事务与
@Async注解一起使用时,会有什么问题?如何解决?) - [13.4 问题:Seata AT 模式与 TCC 模式的区别?如何选择?](#13.4 问题:Seata AT 模式与 TCC 模式的区别?如何选择?)
- [14. 学习路径建议(初学者进阶)](#14. 学习路径建议(初学者进阶))
-
- [14.1 阶段 1:基础掌握(1-2 周)](#14.1 阶段 1:基础掌握(1-2 周))
- [14.2 阶段 2:深化理解(2-3 周)](#14.2 阶段 2:深化理解(2-3 周))
- [14.3 阶段 3:实战进阶(3-4 周)](#14.3 阶段 3:实战进阶(3-4 周))
- [14.4 阶段 4:面试准备(1-2 周)](#14.4 阶段 4:面试准备(1-2 周))
Spring 事务管理深度解析与 MySQL/PostgreSQL 差异化应用
1. Spring 事务管理基础架构
1.1 事务核心接口体系
Spring 事务管理基于三个核心接口构建了统一的抽象层架构,这三个接口分别是PlatformTransactionManager、TransactionDefinition和TransactionStatus。这种设计理念体现了 Spring 框架 "面向接口编程" 的核心哲学,通过接口抽象实现了事务管理与具体持久化技术的解耦(30)。
PlatformTransactionManager作为事务管理的核心接口,定义了事务的基本操作:获取事务、提交事务和回滚事务(1)。该接口主要作为服务提供者接口(SPI)使用,开发者可以通过编程方式直接使用,也可以通过声明式事务配置使用。Spring 提供了多种实现类以适配不同的持久化技术,包括DataSourceTransactionManager用于 JDBC 和 MyBatis 等基于数据源的事务管理,JpaTransactionManager用于 JPA 规范的事务管理,HibernateTransactionManager用于 Hibernate ORM 框架的事务管理,以及JtaTransactionManager用于分布式事务(JTA)处理。
TransactionDefinition接口定义了事务的属性,包括传播行为、隔离级别、超时时间和只读状态等。该接口提供了七个传播行为常量,分别是PROPAGATION_REQUIRED(默认值)、PROPAGATION_SUPPORTS、PROPAGATION_MANDATORY、PROPAGATION_REQUIRES_NEW、PROPAGATION_NOT_SUPPORTED、PROPAGATION_NEVER和PROPAGATION_NESTED。同时还定义了五个隔离级别常量,包括ISOLATION_DEFAULT(使用数据库默认隔离级别)、ISOLATION_READ_UNCOMMITTED、ISOLATION_READ_COMMITTED、ISOLATION_REPEATABLE_READ和ISOLATION_SERIALIZABLE。
TransactionStatus接口表示事务的当前状态,用于控制事务的执行和查询事务状态。该接口提供了多个方法来判断事务是否为新事务、是否存在保存点、是否已标记为回滚等状态信息,同时还提供了setRollbackOnly()方法用于手动标记事务回滚。
1.2 事务管理器实现类
Spring 为不同的持久化技术提供了专门的事务管理器实现,这些实现类都继承自AbstractPlatformTransactionManager抽象类,该抽象类实现了事务管理的基本工作流程,具体的事务操作由各个子类实现(37)。
DataSourceTransactionManager是最常用的事务管理器实现,适用于 JDBC 和 MyBatis 等基于数据源的持久化技术(18)。该实现类通过DataSource获取数据库连接,并通过 JDBC API 直接管理事务。在事务开启时,DataSourceTransactionManager会获取数据库连接并关闭自动提交模式,在事务提交时调用commit()方法,在事务回滚时调用rollback()方法。
HibernateTransactionManager专门为 Hibernate ORM 框架设计,将事务管理职责委托给 Hibernate 的Transaction对象,该对象从 Hibernate Session 中获取(15)。HibernateTransactionManager需要装配一个 Hibernate 的SessionFactory,在事务提交时调用Transaction对象的commit()方法,在事务回滚时调用rollback()方法。
JpaTransactionManager用于 JPA 规范的事务管理,只需要装配一个 JPA 实体管理工厂(EntityManagerFactory),与由工厂产生的 JPA EntityManager协作构建事务。该实现类会处理 JPA 的EntityManager生命周期管理,并将 JPA 的异常转换为 Spring 的DataAccessException体系(40)。
JtaTransactionManager用于分布式事务处理,将事务管理职责委托给 JTA 的UserTransaction和TransactionManager对象。该实现类适用于跨越多个事务资源(如多个不同数据源)的场景,通过 JTA 协议实现全局事务的协调管理(35)。
1.3 Spring 事务抽象设计理念
Spring 事务管理的核心设计理念是 "关注点分离" 和 "约定优于配置"。通过统一的事务抽象层,Spring 实现了业务逻辑与事务管理的解耦,开发者只需要关注业务逻辑的实现,而无需了解底层事务处理的复杂细节。
这种抽象设计的优势在于提供了高度的灵活性和可维护性。开发者可以通过简单的配置切换不同的事务管理策略,例如从本地事务切换到分布式事务,而业务代码无需任何修改。同时,Spring 的事务抽象还支持多种事务管理方式,包括声明式事务和编程式事务,满足不同场景的需求(29)。
Spring 事务管理采用了分层架构设计,每一层都有明确的职责边界(30)。在最底层是具体的事务资源管理,如 JDBC 连接、Hibernate Session 等;中间层是事务抽象层,提供统一的事务操作接口;最上层是事务策略层,定义事务的行为属性和传播规则(30)。
在技术实现上,Spring 通过 AOP(面向切面编程)实现声明式事务管理,通过动态代理在方法调用前后织入事务逻辑(12)。这种设计使得事务管理代码与业务逻辑完全分离,提高了代码的可复用性和可维护性(12)。
2. 声明式事务详解
2.1 @Transactional 注解使用详解
@Transactional 注解是 Spring 声明式事务管理的核心,它可以应用于类或方法级别,为开发者提供了一种非侵入式的事务管理方式(40)。当应用于类上时,表示该类的所有 public 方法都具有事务性;当应用于方法上时,则只对该方法生效。
@Transactional 注解支持配置丰富的事务属性,包括传播行为、隔离级别、超时时间、只读标志和回滚规则等。其中,propagation属性用于指定事务传播行为,默认值为Propagation.REQUIRED;isolation属性用于指定事务隔离级别,默认值为Isolation.DEFAULT;timeout属性用于设置事务超时时间,单位为秒,默认值为 - 1 表示使用系统默认值;readOnly属性用于设置是否为只读事务,默认值为 false;rollbackFor属性用于指定需要回滚的异常类数组;noRollbackFor属性用于指定不需要回滚的异常类数组(116)。
在实际应用中,@Transactional 注解的配置需要根据具体业务场景进行调整。例如,一个典型的转账服务方法可能这样使用 @Transactional 注解:
@Service
public class TransferService {
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.READ_COMMITTED,
timeout = 30,
rollbackFor = {TransferException.class, SQLException.class}
)
public void transfer(Account from, Account to, BigDecimal amount) throws TransferException {
// 业务逻辑实现
}
}
2.2 基于 AOP 的声明式事务实现原理
@Transactional 注解的实现本质上是一个精妙的 AOP(面向切面编程)应用案例(40)。Spring 通过动态代理机制在运行时创建代理对象,拦截被 @Transactional 标注的方法调用,并在方法执行前后织入事务管理逻辑(40)。
声明式事务的实现涉及多个关键组件的协作。首先,InfrastructureAdvisorAutoProxyCreator作为 Bean 后置处理器,在 Spring 容器初始化阶段负责识别带有 @Transactional 注解的 Bean,并为其创建代理对象。代理对象的创建过程会根据目标类是否实现接口来选择使用 JDK 动态代理或 CGLIB 代理(55)。
其次,TransactionAttributeSourcePointcut作为切点实现,负责扫描 Bean 中的 @Transactional 注解,并解析注解属性如隔离级别、传播行为等元数据。AnnotationTransactionAttributeSource用于解析事务属性,将 @Transactional 注解中的各个属性转化为TransactionAttribute对象(63)。
最后,TransactionInterceptor作为核心拦截器,负责具体的事务管理逻辑执行(61)。TransactionInterceptor实现了MethodInterceptor接口,在方法调用前后执行事务的开启、提交和回滚操作(61)。
代理创建的具体流程包括:当 Spring 容器启动时,会扫描所有标注 @Transactional 的类或方法;通过AnnotationTransactionAttributeSource解析注解属性,将其转换为TransactionAttribute对象;使用InfrastructureAdvisorAutoProxyCreator创建 AOP 代理对象,代理对象内部包含TransactionInterceptor;所有对目标方法的调用都会先经过代理对象,由代理对象负责事务管理逻辑的织入(41)。
2.3 声明式事务的优缺点分析
声明式事务的主要优点包括:
-
无侵入性:业务代码不依赖 Spring API,仅通过注解或配置声明事务规则,代码更加简洁清晰。
-
可维护性高:事务策略集中管理,修改时无需改动业务代码,可以通过配置文件或注解轻松调整事务属性。
-
复用性强:通过 AOP 复用事务逻辑,减少重复代码,提高开发效率。
-
一致性保障 :确保在方法执行过程中始终遵循一致的事务处理逻辑,减少了因手动管理事务而导致的错误和不一致性(71)。
-
简化开发 :减少了手动编写事务控制代码的需要,使开发者能够专注于业务逻辑实现(71)。
声明式事务的主要缺点包括:
-
灵活性有限 :事务边界固定为方法级别,无法在方法内部动态控制事务范围,对于一些特殊需求可能不够灵活(67)。
-
性能开销 :虽然 AOP 代理引入的性能损耗在现代 JVM 中通常可以忽略不计(约 1%-5%),但在高并发场景下仍需要考虑(73)。
-
理解难度 :内部依赖于面向切面编程,增加了理解难度,特别是在处理复杂的事务传播场景时(77)。
-
自调用问题:同一个类内的方法调用不会经过代理,导致 @Transactional 注解失效,需要通过自注入等方式解决。
-
方法可见性限制:@Transactional 注解只能应用于 public 方法,对于非 public 方法虽然不会报错,但事务功能不会生效。
2.4 声明式事务最佳实践
基于声明式事务的特性和限制,以下是一些最佳实践建议:
- 事务粒度控制:
-
避免在事务中包含远程调用、文件操作或其他可能耗时的非数据库操作
-
批量操作应使用分批次提交,避免长时间持有事务
-
将只读操作放在事务外执行,提高并发性能
- 异常处理策略:
-
明确指定
rollbackFor属性,避免意外提交 -
使用特定的业务异常类,避免捕获过于宽泛的异常
-
在需要回滚的异常发生时,确保正确抛出或标记
- 传播行为选择:
-
优先使用默认的
PROPAGATION_REQUIRED传播行为 -
在需要独立事务的场景使用
PROPAGATION_REQUIRES_NEW -
谨慎使用
PROPAGATION_NESTED,注意其与数据库保存点的依赖关系
- 隔离级别配置:
-
根据业务需求选择最低的隔离级别,通常使用
Isolation.DEFAULT或Isolation.READ_COMMITTED -
在高并发场景下避免使用
Isolation.SERIALIZABLE隔离级别 -
了解不同数据库的默认隔离级别差异
- 性能优化措施:
-
对只读查询方法设置
readOnly=true,让数据库进行优化 -
合理设置事务超时时间,避免长时间占用数据库连接
-
使用
@EnableTransactionManagement启用基于注解的事务管理
3. 编程式事务详解
3.1 TransactionTemplate 使用方式
编程式事务通过TransactionTemplate类提供了一种程序化的事务管理方式,适用于需要精细控制事务边界的场景。TransactionTemplate是线程安全的,并且提供了回调方法,将应用程序从处理资源获取和释放中解脱出来,类似于 Spring 的其他模板类如JdbcTemplate和HibernateTemplate。
TransactionTemplate的核心方法是execute,该方法支持实现TransactionCallback接口的事务代码。TransactionCallback接口定义了doInTransaction方法,该方法接收一个TransactionStatus参数,并返回一个泛型结果。开发者可以在doInTransaction方法中编写具体的事务逻辑,包括数据库操作、业务计算等。
以下是TransactionTemplate的典型使用示例:
@Service
public class OrderService {
@Autowired
private TransactionTemplate transactionTemplate;
@Autowired
private OrderRepository orderRepository;
public Order createOrder(Order order) {
return transactionTemplate.execute(new TransactionCallback<Order>() {
@Override
public Order doInTransaction(TransactionStatus status) {
try {
// 执行业务逻辑
Order savedOrder = orderRepository.save(order);
inventoryService.deductStock(order.getProductId(), order.getQuantity());
return savedOrder;
} catch (Exception e) {
// 手动标记回滚
status.setRollbackOnly();
throw e;
}
}
});
}
}
TransactionTemplate还支持无返回值的事务操作,通过实现TransactionCallbackWithoutResult接口来完成。这种方式适用于不需要返回结果的事务操作:
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
// 执行业务逻辑
}
});
3.2 PlatformTransactionManager 直接使用
除了TransactionTemplate外,开发者还可以直接使用PlatformTransactionManager接口进行事务管理。这种方式提供了更加灵活的事务控制能力,适用于需要完全自定义事务处理流程的场景。
直接使用PlatformTransactionManager的典型流程包括:
-
定义事务属性 :创建
DefaultTransactionDefinition对象,设置传播行为、隔离级别、超时时间等属性 -
获取事务状态 :调用
PlatformTransactionManager的getTransaction方法获取TransactionStatus对象 -
执行业务逻辑:在事务上下文中执行具体的业务操作
-
提交或回滚事务 :根据业务执行结果调用
commit或rollback方法
以下是直接使用PlatformTransactionManager的示例代码:
@Service
public class UserService {
@Autowired
private PlatformTransactionManager transactionManager;
public void updateUser(User user) {
// 定义事务属性
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
def.setTimeout(30); // 设置超时时间为30秒
// 获取事务状态
TransactionStatus status = transactionManager.getTransaction(def);
try {
// 执行业务逻辑
userRepository.update(user);
// 提交事务
transactionManager.commit(status);
} catch (Exception e) {
// 回滚事务
transactionManager.rollback(status);
throw e;
}
}
}
3.3 编程式事务的适用场景
编程式事务适用于以下场景:
-
动态事务边界控制 :当事务边界需要根据业务逻辑动态调整时,编程式事务提供了最大的灵活性(67)。
-
复杂事务嵌套 :在需要实现复杂的事务嵌套逻辑时,编程式事务可以精确控制每个事务的边界和行为(72)。
-
非 public 方法事务 :当需要为非 public 方法添加事务支持时,编程式事务是唯一的选择(72)。
-
事务控制逻辑复杂 :当事务的提交、回滚逻辑需要根据复杂条件判断时,编程式事务提供了完全的控制权(79)。
-
性能敏感场景 :在高并发或对性能要求极高的场景下,编程式事务避免了 AOP 代理的开销,可能带来更好的性能表现(78)。
3.4 编程式事务与声明式事务对比
编程式事务与声明式事务在多个方面存在显著差异,以下是详细对比分析:
| 对比维度 | 编程式事务 | 声明式事务 |
|---|---|---|
| 实现方式 | 通过 TransactionTemplate 或直接调用 PlatformTransactionManager API | 使用 @Transactional 注解,基于 AOP 自动管理 |
| 灵活性 | 高,可以动态控制事务边界和行为 | 低,事务边界固定为方法级别 |
| 侵入性 | 高,事务管理代码嵌入业务逻辑 | 低,事务逻辑与业务逻辑分离 |
| 维护性 | 低,事务逻辑与业务代码耦合 | 高,事务策略集中管理 |
| 性能开销 | 无代理开销,但代码复杂度可能影响性能 | 存在 AOP 代理开销(约 1%-5%) |
| 开发效率 | 低,需要编写较多模板代码 | 高,仅需添加注解即可 |
| 适用场景 | 复杂事务逻辑、动态边界控制、性能敏感场景 | 常规业务操作、简单事务场景 |
编程式事务的主要优势在于灵活性和精确控制能力,能够满足各种复杂的事务管理需求。例如,在批处理操作中,可以通过编程式事务实现每处理一定数量的记录后提交一次事务,从而平衡数据一致性和性能。
声明式事务的主要优势在于简洁性和可维护性,通过注解即可实现事务管理,大大减少了样板代码。同时,声明式事务基于 AOP 实现,能够实现事务逻辑的集中管理和复用(77)。
在实际应用中,建议优先使用声明式事务,因为它提供了更好的代码可读性和可维护性。只有在声明式事务无法满足需求时,才考虑使用编程式事务。例如,当需要在同一个方法内根据不同条件执行不同的事务逻辑,或者需要在循环中动态控制事务提交点时,编程式事务是更好的选择(76)。
4. 事务传播机制深入分析
4.1 七种传播行为详解
Spring 定义了七种事务传播行为,这些传播行为定义在Propagation枚举类中,用于控制当事务方法被另一个事务方法调用时的事务行为(81)。每种传播行为都有其特定的应用场景和实现机制,理解这些传播行为对于正确使用 Spring 事务至关重要。
1. PROPAGATION_REQUIRED(默认值)
PROPAGATION_REQUIRED是最常用的传播行为,也是 @Transactional 注解的默认值(83)。当使用该传播行为时,如果当前存在事务,则方法将在该事务中运行;如果当前没有事务,则会创建一个新的事务(83)。
该传播行为的特点是支持事务的嵌套调用,所有相关的数据库操作都在同一个事务上下文中执行。如果嵌套的方法出现异常,整个事务将回滚。
2. PROPAGATION_SUPPORTS
PROPAGATION_SUPPORTS表示被修饰的方法不需要事务上下文,但如果存在当前事务,则方法会在该事务中运行。如果当前没有事务,则方法将在非事务环境中执行。
该传播行为适用于查询操作等不需要事务支持的方法,但在有事务上下文时可以利用现有事务的一致性保障。
3. PROPAGATION_MANDATORY
PROPAGATION_MANDATORY表示方法必须在一个已存在的事务中运行,否则将抛出异常(83)。该传播行为确保方法始终在事务上下文中执行,避免了在没有事务的情况下执行可能引起数据不一致的操作。
该传播行为通常用于业务操作中,确保相关操作在同一个事务中执行,保证数据的完整性。
4. PROPAGATION_REQUIRES_NEW
PROPAGATION_REQUIRES_NEW表示被修饰的方法必须运行在它自己的事务中(80)。如果调用者存在当前事务,则在该方法执行期间,当前事务会被挂起,新事务执行完毕后再恢复原有事务(80)。
该传播行为创建的是一个完全独立的新事务,与调用者的事务没有任何关系。即使外层事务回滚,该方法的事务仍会独立提交或回滚。
5. PROPAGATION_NOT_SUPPORTED
PROPAGATION_NOT_SUPPORTED表示方法将以非事务的方式运行,如果调用者存在当前事务,则该事务将被挂起(83)。该传播行为适用于不需要事务支持的操作,例如查询操作或不涉及数据更新的方法。
使用该传播行为可以避免潜在的事务相关问题,例如死锁或锁竞争,但需要注意的是,在非事务环境中执行的数据库操作将无法回滚。
6. PROPAGATION_NEVER
PROPAGATION_NEVER表示方法不应该运行在事务上下文中,如果调用者存在当前事务,则会抛出异常(83)。该传播行为用于强制要求方法不在事务中执行的情况,例如某个方法需要独立于当前事务执行,或者需要确保方法不会影响当前事务的状态。
7. PROPAGATION_NESTED
PROPAGATION_NESTED表示如果当前存在事务,则在嵌套事务内执行;如果没有事务,则行为类似于PROPAGATION_REQUIRED(83)。嵌套事务是外部事务的一部分,可以独立地进行提交或回滚,但外部事务的回滚会影响嵌套事务。
该传播行为通过 JDBC 3.0 的Savepoint机制实现,需要 JDBC 驱动支持java.sql.Savepoint类。
4.2 传播行为的实际应用场景
不同的传播行为适用于不同的业务场景,以下是各传播行为的典型应用场景:
PROPAGATION_REQUIRED 应用场景:
-
订单创建:订单创建、库存扣减、积分增加等操作必须在同一个事务中执行
-
银行转账:转出账户扣减和转入账户增加必须在同一个事务中
-
多级业务流程:如 "创建订单→生成发票→发送通知" 等连续操作
PROPAGATION_REQUIRES_NEW 应用场景:
-
日志记录:即使主业务失败回滚,日志记录也必须成功提交
-
审计追踪:记录业务操作历史,需要独立于业务事务
-
异步处理:将耗时操作放入独立事务执行,避免影响主业务性能
PROPAGATION_NESTED 应用场景:
-
分批处理:在批量操作中,可以为每批数据创建一个嵌套事务
-
子流程处理:主业务流程中的子流程可以使用嵌套事务
-
部分回滚:允许子操作失败时回滚到某个保存点,而不影响其他操作
4.3 嵌套事务传播机制
PROPAGATION_NESTED传播行为实现了真正的嵌套事务机制,它通过 JDBC 的Savepoint机制在当前事务中设置保存点(122)。当嵌套事务执行时,会在当前事务中创建一个保存点,嵌套事务的所有操作都在该保存点之上执行(122)。
嵌套事务的执行流程如下:
-
外部事务存在时,创建保存点
-
执行嵌套事务逻辑
-
如果嵌套事务执行成功,提交嵌套事务(但不释放保存点)
-
如果嵌套事务失败,回滚到保存点,外部事务可以继续执行
PROPAGATION_NESTED与PROPAGATION_REQUIRES_NEW的主要区别在于:
-
PROPAGATION_REQUIRES_NEW创建的是完全独立的新事务,与外层事务没有关系 -
PROPAGATION_NESTED创建的是外层事务的子事务,外层事务的回滚会导致嵌套事务也回滚 -
PROPAGATION_REQUIRES_NEW需要 JTA 事务管理器支持,而PROPAGATION_NESTED可以使用 JDBC 3.0 的保存点机制
以下是嵌套事务的示例代码:
@Service
public class OrderService {
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(Order order) {
try {
// 保存订单主信息
orderRepository.saveMainOrder(order);
// 调用嵌套事务方法
saveOrderDetails(order.getDetails());
// 其他业务逻辑
} catch (Exception e) {
// 处理异常
}
}
@Transactional(propagation = Propagation.NESTED)
public void saveOrderDetails(List<OrderDetail> details) {
for (OrderDetail detail : details) {
orderDetailRepository.save(detail);
if (detail.getQuantity() > 100) {
throw new OrderException("数量超过限制");
}
}
}
}
在上述示例中,如果saveOrderDetails方法抛出异常,会回滚到保存点,只影响订单明细的保存,不会影响订单主信息的保存。但如果外层事务最终回滚,所有操作包括订单主信息都会被回滚。
4.4 传播行为组合使用案例
在实际应用中,经常需要组合使用不同的传播行为来满足复杂的业务需求。以下是一些典型的传播行为组合案例:
案例一:主从数据同步
@Service
public class MasterService {
@Autowired
private SlaveService slaveService;
@Transactional(propagation = Propagation.REQUIRED)
public void updateMaster(Master master) {
// 更新主数据
masterRepository.update(master);
// 调用从服务更新从数据(独立事务)
slaveService.updateSlave(master.getId());
}
}
@Service
public class SlaveService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void updateSlave(Long masterId) {
// 更新从数据
slaveRepository.updateByMasterId(masterId);
}
}
在这个案例中,主数据更新和从数据更新使用了不同的传播行为。主数据更新使用PROPAGATION_REQUIRED,确保在主事务中执行;从数据更新使用PROPAGATION_REQUIRES_NEW,确保即使主事务失败回滚,从数据的更新也能独立提交,保证主从数据的最终一致性。
案例二:日志记录与业务处理分离
@Service
public class UserService {
@Autowired
private LogService logService;
@Transactional(propagation = Propagation.REQUIRED)
public void registerUser(User user) {
// 保存用户信息
userRepository.save(user);
// 记录注册日志(独立事务)
logService.logRegistration(user.getId());
}
}
@Service
public class LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void logRegistration(Long userId) {
// 记录注册日志
registrationLogRepository.save(new RegistrationLog(userId));
}
}
在这个案例中,用户注册和日志记录使用了不同的传播行为。用户注册使用PROPAGATION_REQUIRED,确保在主事务中执行;日志记录使用PROPAGATION_REQUIRES_NEW,确保即使主事务失败回滚,注册日志也能成功记录,便于后续问题排查。
案例三:批量操作中的部分回滚
@Service
public class BatchService {
@Transactional(propagation = Propagation.REQUIRED)
public void processBatch(List<BatchItem> items) {
for (BatchItem item : items) {
try {
// 处理单个批次项(嵌套事务)
processItem(item);
} catch (Exception e) {
// 记录错误日志
errorLogService.logError(item.getId(), e.getMessage());
}
}
}
@Transactional(propagation = Propagation.NESTED)
public void processItem(BatchItem item) {
// 执行业务逻辑
if (item.getStatus() == Status.INVALID) {
throw new BusinessException("无效的批次项");
}
// 更新数据库
batchItemRepository.update(item);
}
}
在这个案例中,批量处理使用PROPAGATION_REQUIRED,确保整个批量操作在一个事务中;每个批次项的处理使用PROPAGATION_NESTED,允许单个批次项处理失败时回滚到保存点,不影响其他批次项的处理。最终,如果所有批次项都成功处理,整个事务提交;如果有部分失败,根据外层事务的异常处理策略决定是否回滚所有操作。
5. 事务隔离级别详解
5.1 五种隔离级别含义与作用
事务隔离级别定义了一个事务可能受其他并发事务影响的程度,Spring 支持五种事务隔离级别,这些级别与标准的 JDBC 隔离级别保持一致(152)。理解不同隔离级别的含义和作用对于设计正确的并发控制策略至关重要。
1. ISOLATION_DEFAULT(默认值)
ISOLATION_DEFAULT表示使用底层数据库的默认隔离级别(147)。不同数据库的默认隔离级别存在差异,MySQL InnoDB 引擎默认使用REPEATABLE_READ,而 PostgreSQL、Oracle、SQL Server 等数据库默认使用READ_COMMITTED(102)。
2. ISOLATION_READ_UNCOMMITTED
ISOLATION_READ_UNCOMMITTED是最低的隔离级别,允许读取尚未提交的数据变更。该级别可能导致脏读、幻读或不可重复读等问题,但提供了最高的并发性能。在该级别下,事务可以读取到其他事务尚未提交的数据,这在某些特定场景下可能有用,但通常不推荐在生产环境中使用。
3. ISOLATION_READ_COMMITTED
ISOLATION_READ_COMMITTED允许读取并发事务已经提交的数据,可以阻止脏读,但幻读或不可重复读仍有可能发生。该级别是大多数数据库的默认隔离级别,在数据一致性和并发性能之间提供了良好的平衡。在该级别下,一个事务只能读取到其他事务已经提交的数据,避免了脏读问题。
4. ISOLATION_REPEATABLE_READ
ISOLATION_REPEATABLE_READ确保对同一字段的多次读取结果都是一致的,除非数据是被本身事务自己所修改。该级别可以阻止脏读和不可重复读,但幻读仍有可能发生。MySQL InnoDB 引擎默认使用该级别,并通过间隙锁(Gap Lock)机制防止幻读(133)。
5. ISOLATION_SERIALIZABLE
ISOLATION_SERIALIZABLE是最高的隔离级别,完全服从 ACID 的隔离级别,确保阻止脏读、不可重复读以及幻读。该级别通常通过完全锁定事务相关的数据库表来实现,是最慢的事务隔离级别,但提供了最高的数据一致性保证。
5.2 并发问题分析(脏读、不可重复读、幻读)
不同的隔离级别对应不同的并发问题解决方案,理解这些并发问题对于选择合适的隔离级别至关重要。
脏读(Dirty Read)
脏读是指一个事务读取了另一个事务尚未提交的数据(87)。例如,事务 A 更新了一条记录但尚未提交,事务 B 读取了这条被更新但未提交的记录。如果事务 A 随后回滚,那么事务 B 读取到的数据就是无效的。
不可重复读(Non-Repeatable Read)
不可重复读是指在同一个事务中,对同一行数据的多次读取结果不一致(88)。例如,事务 A 首先读取了一条记录,然后事务 B 更新了这条记录并提交,当事务 A 再次读取该记录时,得到了不同的结果。不可重复读的重点在于数据的修改操作。
幻读(Phantom Read)
幻读是指在同一个事务中,执行相同的查询时,结果集中的行数发生了变化(88)。例如,事务 A 首先查询了符合某个条件的所有记录,然后事务 B 插入了一条符合该条件的新记录并提交,当事务 A 再次执行相同的查询时,发现结果集中多了一条记录。幻读的重点在于数据的插入或删除操作。
以下表格总结了不同隔离级别对各种并发问题的处理能力:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 并发性能 |
|---|---|---|---|---|
| READ_UNCOMMITTED | 允许 | 允许 | 允许 | 最高 |
| READ_COMMITTED | 禁止 | 允许 | 允许 | 高 |
| REPEATABLE_READ | 禁止 | 禁止 | 允许 | 中等 |
| SERIALIZABLE | 禁止 | 禁止 | 禁止 | 最低 |
5.3 隔离级别在 MySQL 与 PostgreSQL 中的实现差异
MySQL 和 PostgreSQL 在事务隔离级别的实现机制上存在显著差异,这些差异直接影响了 Spring 事务在不同数据库环境下的行为。
MySQL InnoDB 实现特点:
MySQL InnoDB 引擎默认使用REPEATABLE_READ隔离级别,该级别通过 MVCC(多版本并发控制)和间隙锁(Gap Lock)机制防止幻读(133)。InnoDB 的 MVCC 基于 Undo Log 链表实现,通过撤销日志维护数据的多个版本(133)。
在REPEATABLE_READ级别下,InnoDB 使用 Next-Key Lock(记录锁 + 间隙锁)机制防止幻读(140)。Next-Key Lock 锁定一个范围,并且锁定记录本身,确保在事务执行期间,其他事务无法在该范围内插入新记录(144)。
PostgreSQL 实现特点:
PostgreSQL 默认使用READ_COMMITTED隔离级别,不支持READ_UNCOMMITTED级别(137)。PostgreSQL 的 MVCC 实现基于快照隔离(Snapshot Isolation),通过事务 ID(XID)生成数据快照,数据行包含 xmin 和 xmax 字段标识可见性(138)。
PostgreSQL 的 MVCC 机制使得读操作通常不需要加锁,从而提升了并发性能(138)。在READ_COMMITTED级别下,每条语句会基于执行时的快照来读取数据;在SERIALIZABLE级别下,通过额外的检测避免幻读等现象(137)。
两种数据库的核心差异:
-
默认隔离级别差异 :MySQL 默认
REPEATABLE_READ,PostgreSQL 默认READ_COMMITTED(101) -
MVCC 实现机制 :MySQL 基于 Undo Log,PostgreSQL 基于快照隔离(137)
-
锁机制差异 :MySQL 使用间隙锁防止幻读,PostgreSQL 通过 MVCC 自然防止幻读(141)
-
并发性能 :PostgreSQL 的 MVCC 实现通常提供更好的并发性能(136)
5.4 隔离级别选择策略与最佳实践
选择合适的事务隔离级别需要综合考虑数据一致性要求、系统并发性能需求以及具体的业务场景特点。
选择策略考虑因素:
- 业务一致性要求:
-
金融交易等对数据一致性要求极高的场景,建议使用
SERIALIZABLE或REPEATABLE_READ级别 -
一般业务查询场景,可以使用
READ_COMMITTED级别 -
对实时性要求高但一致性要求不严格的场景,可以考虑
READ_UNCOMMITTED级别
- 系统并发性能需求:
-
高并发写入场景,建议使用较低的隔离级别(如
READ_COMMITTED) -
读多写少的场景,可以使用较高的隔离级别
-
需要考虑锁竞争和死锁风险
- 数据库特性:
-
了解不同数据库的默认隔离级别和实现机制
-
考虑主从复制环境下的隔离级别选择
-
注意不同数据库在相同隔离级别下的行为差异
最佳实践建议:
- 优先使用数据库默认隔离级别:
-
除非有特殊需求,否则建议使用
ISOLATION_DEFAULT -
避免在应用层统一设置隔离级别,应该根据具体业务场景灵活配置
- 根据业务场景选择隔离级别:
-
查询操作:使用
READ_COMMITTED或REPEATABLE_READ -
更新操作:使用
REPEATABLE_READ防止不可重复读 -
批量操作:考虑使用
SERIALIZABLE或通过应用层锁机制
- 性能优化措施:
-
在高并发场景下,优先选择较低的隔离级别
-
使用索引优化查询性能,减少锁竞争
-
合理设置事务超时时间,避免长时间锁定资源
- 跨数据库兼容性考虑:
-
如果应用需要支持多种数据库,建议使用
ISOLATION_DEFAULT -
了解不同数据库在相同隔离级别下的行为差异
-
必要时使用数据库特定的隔离级别设置
以下是一个根据业务场景选择隔离级别的示例:
@Service
public class OrderService {
// 查询订单详情 - 使用读已提交隔离级别
@Transactional(isolation = Isolation.READ_COMMITTED, readOnly = true)
public Order getOrderDetails(Long orderId) {
return orderRepository.findById(orderId);
}
// 创建订单 - 使用可重复读隔离级别
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void createOrder(Order order) {
orderRepository.save(order);
inventoryService.deductStock(order.getProductId(), order.getQuantity());
}
// 批量更新订单状态 - 使用串行化隔离级别
@Transactional(isolation = Isolation.SERIALIZABLE)
public void batchUpdateOrderStatus(List<Long> orderIds, OrderStatus status) {
for (Long orderId : orderIds) {
Order order = orderRepository.findById(orderId);
order.setStatus(status);
orderRepository.save(order);
}
}
}
6. 异常处理机制详解
6.1 事务回滚规则
Spring 事务的回滚规则是事务管理中非常重要的一部分,理解和掌握这些规则对于正确处理事务异常至关重要。在默认配置下,Spring 事务基础设施代码只在运行时未检查异常(RuntimeException及其子类)的情况下才会标记事务回滚(106)。
默认回滚规则:
-
运行时异常(
RuntimeException及其子类)会触发事务回滚 -
错误(
Error及其子类)会触发事务回滚 -
受检异常(
Exception及其直接子类)不会触发事务回滚
以下是默认回滚规则的示例说明:
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Transactional
public void registerUser(User user) {
// 保存用户信息
userRepository.save(user);
// 抛出运行时异常 - 触发回滚
throw new RuntimeException("注册失败");
}
@Transactional
public void updateUser(User user) throws IOException {
// 更新用户信息
userRepository.update(user);
// 抛出受检异常 - 不会触发回滚
throw new IOException("更新失败");
}
}
在上述示例中,registerUser方法抛出RuntimeException,会触发事务回滚,用户信息不会被保存;updateUser方法抛出IOException,不会触发事务回滚,用户信息会被正常更新。
6.2 @Transactional 注解的 rollbackFor 和 noRollbackFor 属性
@Transactional 注解提供了rollbackFor和noRollbackFor属性,允许开发者自定义事务回滚规则。这些属性可以精确控制哪些异常类型会触发事务回滚,包括受检异常(105)。
rollbackFor 属性:
rollbackFor属性用于指定需要触发事务回滚的异常类数组。可以指定具体的异常类或异常类名,多个异常类之间用逗号分隔(107)。
以下是rollbackFor属性的使用示例:
@Service
public class OrderService {
// 对所有异常都回滚
@Transactional(rollbackFor = Exception.class)
public void createOrderWithException1(Order order) throws Exception {
// 业务逻辑
}
// 对指定异常回滚
@Transactional(rollbackFor = {OrderException.class, SQLException.class})
public void createOrderWithException2(Order order) throws OrderException, SQLException {
// 业务逻辑
}
// 对所有Throwable类型异常回滚
@Transactional(rollbackFor = Throwable.class)
public void createOrderWithException3(Order order) throws Throwable {
// 业务逻辑
}
}
noRollbackFor 属性:
noRollbackFor属性用于指定不需要触发事务回滚的异常类数组。该属性的优先级高于rollbackFor属性,当异常同时匹配两个属性时,以noRollbackFor为准(113)。
以下是noRollbackFor属性的使用示例:
@Service
public class PaymentService {
// 对运行时异常不回滚
@Transactional(noRollbackFor = RuntimeException.class)
public void processPayment(Payment payment) {
// 业务逻辑
throw new RuntimeException("支付失败"); // 不会回滚
}
// 对指定异常不回滚
@Transactional(noRollbackFor = {PaymentException.class, TimeoutException.class})
public void processPaymentWithRetry(Payment payment) throws PaymentException {
// 业务逻辑
}
}
6.3 自定义异常回滚策略
除了使用rollbackFor和noRollbackFor属性外,Spring 还提供了其他方式来自定义异常回滚策略。
1. 编程式回滚控制
开发者可以通过TransactionAspectSupport类的静态方法currentTransactionStatus()获取当前事务状态,然后调用setRollbackOnly()方法手动标记事务回滚。
以下是编程式回滚控制的示例:
@Service
public class OrderService {
@Transactional
public void createOrder(Order order) {
try {
// 执行业务逻辑
orderRepository.save(order);
// 检查业务规则
if (order.getAmount().compareTo(BigDecimal.valueOf(10000)) > 0) {
// 手动标记回滚
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
throw new BusinessException("订单金额超过限制");
}
inventoryService.deductStock(order.getProductId(), order.getQuantity());
} catch (Exception e) {
// 处理异常
}
}
}
2. 自定义异常处理器
开发者可以通过实现TransactionAttributeSource接口或继承AnnotationTransactionAttributeSource类来实现自定义的异常回滚规则。这种方式适用于需要根据异常的某些特性(如异常消息、异常原因等)来决定是否回滚的场景。
3. 全局异常处理
在 Spring Boot 应用中,可以通过实现HandlerExceptionResolver接口或使用@ControllerAdvice注解来实现全局异常处理,在异常处理逻辑中根据需要标记事务回滚。
6.4 嵌套事务异常处理
嵌套事务的异常处理机制与普通事务有所不同,需要特别注意传播行为对异常处理的影响。
PROPAGATION_REQUIRES_NEW 传播行为的异常处理:
当使用PROPAGATION_REQUIRES_NEW传播行为时,内层事务是一个完全独立的事务,其异常处理具有以下特点:
-
内层事务的异常不会影响外层事务的执行
-
外层事务的异常也不会影响内层事务的执行
-
内层事务的异常需要在内层事务中处理或抛出
以下是PROPAGATION_REQUIRES_NEW传播行为的异常处理示例:
@Service
public class OrderService {
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(Order order) {
try {
// 保存订单主信息
orderRepository.saveMainOrder(order);
// 调用内层事务(独立事务)
saveOrderDetails(order.getDetails());
// 其他业务逻辑
} catch (Exception e) {
// 处理外层异常
System.out.println("外层事务异常: " + e.getMessage());
}
}
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveOrderDetails(List<OrderDetail> details) {
try {
for (OrderDetail detail : details) {
orderDetailRepository.save(detail);
if (detail.getQuantity() > 100) {
throw new OrderException("数量超过限制");
}
}
} catch (OrderException e) {
// 内层事务异常处理
System.out.println("内层事务异常: " + e.getMessage());
throw e; // 重新抛出,外层事务会捕获
}
}
}
在上述示例中,如果saveOrderDetails方法抛出异常,内层事务会回滚,但订单主信息的保存不会受到影响。外层事务捕获到异常后,可以进行相应的处理。
PROPAGATION_NESTED 传播行为的异常处理:
当使用PROPAGATION_NESTED传播行为时,内层事务是外层事务的子事务,其异常处理具有以下特点:
-
内层事务的异常会回滚到保存点,但不会自动回滚外层事务
-
如果内层事务的异常未被捕获,会传播到外层事务,可能导致外层事务回滚
-
外层事务可以选择捕获内层事务的异常并进行处理
以下是PROPAGATION_NESTED传播行为的异常处理示例:
@Service
public class OrderService {
@Transactional(propagation = Propagation.REQUIRED)
public void createOrder(Order order) {
try {
// 保存订单主信息
orderRepository.saveMainOrder(order);
// 调用嵌套事务
saveOrderDetails(order.getDetails());
// 其他业务逻辑
} catch (OrderException e) {
// 处理内层事务异常
System.out.println("处理内层事务异常: " + e.getMessage());
}
}
@Transactional(propagation = Propagation.NESTED)
public void saveOrderDetails(List<OrderDetail> details) {
for (OrderDetail detail : details) {
orderDetailRepository.save(detail);
if (detail.getQuantity() > 100) {
throw new OrderException("数量超过限制");
}
}
}
}
在上述示例中,如果saveOrderDetails方法抛出OrderException,会回滚到保存点,订单明细的保存被撤销,但订单主信息的保存仍然有效。外层事务捕获到异常后,可以进行相应的处理,不会导致整个事务回滚。
6.5 异常传播与事务边界
理解异常传播机制对于正确处理事务边界非常重要。Spring 事务的异常处理遵循以下原则:
1. 异常传播规则:
-
未被捕获的异常会沿着调用栈向上传播
-
如果异常被标记为需要回滚(通过
rollbackFor或setRollbackOnly),则会触发事务回滚 -
被捕获并处理的异常不会触发事务回滚,除非在 catch 块中重新抛出或手动标记回滚
2. 事务边界控制:
-
事务边界由传播行为决定
-
PROPAGATION_REQUIRED和PROPAGATION_NESTED会共享同一个事务上下文 -
PROPAGATION_REQUIRES_NEW会创建独立的事务上下文
3. 最佳实践建议:
-
在事务方法中避免使用 try-catch 块捕获异常,除非确实需要
-
如果需要捕获异常,应该在 catch 块中重新抛出或手动标记回滚
-
对于需要跨事务边界传播的异常,应该定义为运行时异常
-
避免在事务方法中进行复杂的异常处理,保持事务逻辑的简洁性
7. MySQL 与 PostgreSQL 事务处理差异
7.1 两种数据库的默认隔离级别差异
MySQL 和 PostgreSQL 在事务隔离级别方面存在根本性差异,这些差异直接影响了 Spring 事务在不同数据库环境下的行为。
MySQL 默认隔离级别:
MySQL InnoDB 引擎的默认隔离级别是REPEATABLE_READ(可重复读)(99)。这一默认设置的历史原因是为了确保基于语句的二进制日志复制(Statement-Based Replication, SBR)的数据一致性(103)。在REPEATABLE_READ级别下,InnoDB 通过 MVCC 和间隙锁机制防止幻读,保证了事务内多次读取相同数据的一致性(133)。
PostgreSQL 默认隔离级别:
PostgreSQL 的默认隔离级别是READ_COMMITTED(读已提交),并且不支持READ_UNCOMMITTED级别(137)。PostgreSQL 选择READ_COMMITTED作为默认值主要是出于性能和并发考虑,该级别在大多数应用场景下能够提供良好的数据一致性保证,同时具有较高的并发性能(98)。
隔离级别差异的影响:
-
数据一致性保证:MySQL 默认提供更强的数据一致性保证(防止幻读),而 PostgreSQL 默认提供较弱的保证(可能出现幻读)
-
并发性能:PostgreSQL 的默认隔离级别通常提供更好的并发性能,因为读操作不需要加锁
-
应用兼容性:应用程序在切换数据库时可能需要调整事务隔离级别配置
7.2 MVCC 实现机制差异
MVCC(多版本并发控制)是现代数据库实现高并发性能的重要技术,MySQL 和 PostgreSQL 在 MVCC 实现机制上存在显著差异。
MySQL InnoDB MVCC 实现:
-
基于 Undo Log 链表实现多版本控制
-
使用事务 ID 和回滚段维护数据的多个版本
-
在
REPEATABLE_READ级别下,通过间隙锁(Gap Lock)和临键锁(Next-Key Lock)防止幻读 -
读操作可能需要等待写操作完成,存在读写阻塞
PostgreSQL MVCC 实现:
-
基于快照隔离(Snapshot Isolation)实现
-
通过事务 ID(XID)生成数据快照
-
数据行包含 xmin 和 xmax 字段标识可见性
-
读操作不会阻塞写操作,写操作也不会阻塞读操作
-
通过 SSI(Serializable Snapshot Isolation)机制实现
SERIALIZABLE隔离级别
实现机制差异的影响:
-
并发性能:PostgreSQL 的 MVCC 实现通常提供更好的并发性能,特别是在读多写少的场景下
-
锁机制:MySQL 更依赖锁机制控制并发,而 PostgreSQL 主要依靠 MVCC 机制
-
死锁概率:PostgreSQL 的死锁概率通常低于 MySQL
-
存储开销:PostgreSQL 的 MVCC 实现可能需要更多的存储空间来维护历史版本
7.3 锁机制差异
MySQL 和 PostgreSQL 在锁机制方面的差异反映了两种数据库不同的并发控制策略。
MySQL InnoDB 锁机制:
-
支持行级锁和表级锁
-
使用间隙锁(Gap Lock)防止幻读
-
使用临键锁(Next-Key Lock = 记录锁 + 间隙锁)在
REPEATABLE_READ级别下防止幻读 -
锁的实现依赖于索引,无索引的查询会升级为表锁
-
存在共享锁(S 锁)和排他锁(X 锁)
PostgreSQL 锁机制:
-
使用多粒度锁机制,支持行级锁、表级锁、页级锁等
-
通过 MVCC 机制避免大部分显式行锁
-
读操作通常不需要加锁,只有在需要修改数据时才会加锁
-
支持
SELECT FOR UPDATE和SELECT FOR SHARE等锁指令 -
提供更精细的锁模式控制
锁机制差异的影响:
-
并发控制策略:MySQL 更依赖锁机制,PostgreSQL 更依赖 MVCC 机制
-
死锁处理:MySQL 需要应用层处理死锁,PostgreSQL 可以自动检测并回滚死锁事务
-
查询性能:PostgreSQL 在复杂查询场景下可能表现更好,因为减少了锁竞争
-
索引依赖:MySQL 的锁机制高度依赖索引,而 PostgreSQL 的锁机制相对独立于索引
7.4 Spring 事务配置在不同数据库上的适配
由于 MySQL 和 PostgreSQL 在事务处理机制上的差异,Spring 事务配置需要进行相应的适配。
1. 隔离级别配置差异
由于两种数据库的默认隔离级别不同,在应用程序中应该根据具体需求显式设置隔离级别,而不是依赖ISOLATION_DEFAULT。
// 针对MySQL的配置 - 使用默认的REPEATABLE_READ
@Transactional(isolation = Isolation.DEFAULT)
public void mysqlTransaction() {
// 业务逻辑
}
// 针对PostgreSQL的配置 - 显式设置为REPEATABLE_READ
@Transactional(isolation = Isolation.REPEATABLE_READ)
public void postgresqlTransaction() {
// 业务逻辑
}
2. 事务传播行为适配
由于 PostgreSQL 对保存点的支持可能与 MySQL 不同,在使用PROPAGATION_NESTED传播行为时需要特别注意:
// 在MySQL中使用PROPAGATION_NESTED
@Transactional(propagation = Propagation.NESTED)
public void nestedTransactionMysql() {
// 业务逻辑
}
// 在PostgreSQL中可能需要使用PROPAGATION_REQUIRES_NEW替代
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void nestedTransactionPostgresql() {
// 业务逻辑
}
3. 性能优化配置
根据两种数据库的特性差异,可以进行针对性的性能优化:
// MySQL优化配置
@Configuration
public class MysqlTransactionConfig {
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
// 配置适合MySQL的参数
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
// PostgreSQL优化配置
@Configuration
public class PostgresqlTransactionConfig {
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(dataSource);
// 配置适合PostgreSQL的参数
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
4. 多数据源配置
当应用需要同时访问 MySQL 和 PostgreSQL 数据库时,需要配置多个事务管理器:
@Configuration
public class MultiDbTransactionConfig {
// MySQL事务管理器
@Bean(name = "mysqlTransactionManager")
public DataSourceTransactionManager mysqlTransactionManager(
@Qualifier("mysqlDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
// PostgreSQL事务管理器
@Bean(name = "postgresqlTransactionManager")
public DataSourceTransactionManager postgresqlTransactionManager(
@Qualifier("postgresqlDataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
// 在使用时指定具体的事务管理器
@Transactional(value = "mysqlTransactionManager", isolation = Isolation.REPEATABLE_READ)
public void mysqlOperation() {
// 业务逻辑
}
@Transactional(value = "postgresqlTransactionManager", isolation = Isolation.READ_COMMITTED)
public void postgresqlOperation() {
// 业务逻辑
}
}
7.5 两种数据库的事务性能对比
在实际应用中,MySQL 和 PostgreSQL 在事务处理性能方面存在明显差异,这些差异主要体现在以下几个方面:
1. 读性能对比
PostgreSQL 在读取性能方面通常表现更好,特别是在高并发读取场景下。这主要得益于其 MVCC 机制实现的读写不阻塞特性。而 MySQL 在REPEATABLE_READ级别下,由于使用间隙锁机制,可能会产生更多的锁竞争。
2. 写性能对比
在写入性能方面,两种数据库的表现取决于具体的工作负载:
-
对于简单的单行更新,MySQL 可能表现更好
-
对于复杂的批量更新,PostgreSQL 可能表现更好,因为其锁机制更加精细
3. 并发性能对比
在高并发场景下,PostgreSQL 通常能够提供更好的并发性能,主要原因包括:
-
读操作不需要加锁
-
写操作的锁粒度更细
-
死锁检测和处理机制更完善
4. 事务处理能力对比
在处理复杂事务方面,两种数据库各有优势:
-
MySQL 在处理需要强一致性的事务(如金融交易)时表现更好
-
PostgreSQL 在处理高并发、长时间运行的事务时表现更好
7.6 跨数据库事务处理最佳实践
当应用需要同时访问 MySQL 和 PostgreSQL 数据库时,需要特别注意以下最佳实践:
1. 统一事务策略
在设计跨数据库事务处理策略时,应该:
-
使用相同的隔离级别配置,避免因数据库差异导致的不一致性
-
优先使用
READ_COMMITTED隔离级别,因为 PostgreSQL 不支持READ_UNCOMMITTED -
避免使用
PROPAGATION_NESTED传播行为,改用PROPAGATION_REQUIRES_NEW
2. 性能优化策略
针对不同数据库的特性进行优化:
-
对于 MySQL,确保查询使用合适的索引,减少锁竞争
-
对于 PostgreSQL,充分利用其 MVCC 机制,减少不必要的锁操作
-
避免在事务中进行跨数据库的关联查询
3. 异常处理策略
在跨数据库事务中,异常处理需要特别注意:
-
统一异常处理策略,确保所有数据库操作的一致性
-
使用分布式事务协调器(如 JTA)处理跨数据库事务
-
实现补偿机制,处理部分成功的事务
4. 监控与调优
建立完善的监控体系,重点关注:
-
事务执行时间和成功率
-
锁等待和死锁情况
-
数据库连接池使用情况
-
慢查询和性能瓶颈
8. 综合实战案例
8.1 电商订单处理系统
电商订单处理系统是一个典型的需要复杂事务管理的应用场景,涉及订单创建、库存扣减、支付处理等多个关键业务操作。以下是一个基于 Spring 事务的电商订单处理系统实现案例。
业务场景描述:
用户在电商平台下单购买商品,系统需要完成以下操作:
-
创建订单主记录
-
扣减商品库存
-
记录订单日志
-
发送订单确认通知
这些操作必须保证原子性,即要么全部成功,要么全部失败回滚。
Spring 事务配置:
@Configuration
@EnableTransactionManagement
public class TransactionConfig {
@Bean
public DataSourceTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
订单服务实现:
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private InventoryService inventoryService;
@Autowired
private OrderLogService orderLogService;
@Autowired
private NotificationService notificationService;
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.REPEATABLE_READ,
timeout = 30,
rollbackFor = {OrderException.class, SQLException.class}
)
public Order createOrder(OrderCreateRequest request) throws OrderException {
try {
// 1. 创建订单主记录
Order order = createOrderMainRecord(request);
// 2. 扣减库存(使用REQUIRES_NEW传播行为)
inventoryService.deductStock(request.getProductId(), request.getQuantity());
// 3. 记录订单日志(使用REQUIRES_NEW传播行为,确保日志必须成功)
orderLogService.recordOrderLog(order.getId(), "订单创建成功");
// 4. 发送通知(使用SUPPORTS传播行为,通知失败不影响订单)
notificationService.sendOrderConfirmation(order.getId());
return order;
} catch (InventoryException e) {
throw new OrderException("库存不足", e);
} catch (Exception e) {
throw new OrderException("订单创建失败", e);
}
}
private Order createOrderMainRecord(OrderCreateRequest request) {
Order order = new Order();
order.setUserId(request.getUserId());
order.setProductId(request.getProductId());
order.setQuantity(request.getQuantity());
order.setAmount(request.getAmount());
order.setStatus(OrderStatus.CREATED);
return orderRepository.save(order);
}
}
库存服务实现:
@Service
public class InventoryService {
@Autowired
private InventoryRepository inventoryRepository;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void deductStock(Long productId, Integer quantity) throws InventoryException {
Inventory inventory = inventoryRepository.findById(productId)
.orElseThrow(() -> new InventoryException("商品不存在"));
if (inventory.getStock() < quantity) {
throw new InventoryException("库存不足");
}
inventory.setStock(inventory.getStock() - quantity);
inventoryRepository.save(inventory);
}
}
订单日志服务实现:
@Service
public class OrderLogService {
@Autowired
private OrderLogRepository orderLogRepository;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void recordOrderLog(Long orderId, String message) {
OrderLog log = new OrderLog();
log.setOrderId(orderId);
log.setMessage(message);
log.setCreateTime(new Date());
orderLogRepository.save(log);
}
}
通知服务实现:
@Service
public class NotificationService {
@Autowired
private NotificationRepository notificationRepository;
@Transactional(propagation = Propagation.SUPPORTS)
public void sendOrderConfirmation(Long orderId) {
try {
// 模拟发送通知的业务逻辑
System.out.println("发送订单确认通知: " + orderId);
// 记录通知日志
Notification notification = new Notification();
notification.setOrderId(orderId);
notification.setType(NotificationType.ORDER_CONFIRMATION);
notification.setStatus(NotificationStatus.SENT);
notificationRepository.save(notification);
} catch (Exception e) {
System.out.println("通知发送失败: " + e.getMessage());
// 记录通知失败日志
Notification notification = new Notification();
notification.setOrderId(orderId);
notification.setType(NotificationType.ORDER_CONFIRMATION);
notification.setStatus(NotificationStatus.FAILED);
notification.setErrorMessage(e.getMessage());
notificationRepository.save(notification);
}
}
}
异常处理策略说明:
-
主订单创建使用
PROPAGATION_REQUIRED,确保在主事务中执行 -
库存扣减使用
PROPAGATION_REQUIRES_NEW,确保即使主事务失败,库存也会被正确扣减 -
订单日志使用
PROPAGATION_REQUIRES_NEW,确保日志必须成功记录 -
通知发送使用
PROPAGATION_SUPPORTS,通知失败不影响订单创建
8.2 银行转账系统
银行转账系统是另一个需要严格事务管理的典型场景,要求极高的数据一致性和可靠性。以下是一个基于 Spring 事务的银行转账系统实现案例。
业务场景描述:
用户进行银行转账操作,系统需要完成以下操作:
-
检查转出账户余额
-
扣减转出账户余额
-
增加转入账户余额
-
记录转账日志
账户服务实现:
@Service
public class AccountService {
@Autowired
private AccountRepository accountRepository;
@Autowired
private TransferLogService transferLogService;
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.SERIALIZABLE,
timeout = 60,
rollbackFor = {InsufficientBalanceException.class, SQLException.class}
)
public void transfer(TransferRequest request) throws InsufficientBalanceException {
// 查询转出账户
Account fromAccount = accountRepository.findById(request.getFromAccountId())
.orElseThrow(() -> new AccountNotFoundException("转出账户不存在"));
// 查询转入账户
Account toAccount = accountRepository.findById(request.getToAccountId())
.orElseThrow(() -> new AccountNotFoundException("转入账户不存在"));
// 检查余额
if (fromAccount.getBalance() < request.getAmount()) {
throw new InsufficientBalanceException("转出账户余额不足");
}
// 扣减转出账户
fromAccount.setBalance(fromAccount.getBalance() - request.getAmount());
accountRepository.save(fromAccount);
// 增加转入账户
toAccount.setBalance(toAccount.getBalance() + request.getAmount());
accountRepository.save(toAccount);
// 记录转账日志
transferLogService.recordTransferLog(request);
}
}
转账日志服务实现:
@Service
public class TransferLogService {
@Autowired
private TransferLogRepository transferLogRepository;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void recordTransferLog(TransferRequest request) {
TransferLog log = new TransferLog();
log.setFromAccountId(request.getFromAccountId());
log.setToAccountId(request.getToAccountId());
log.setAmount(request.getAmount());
log.setTransferTime(new Date());
transferLogRepository.save(log);
}
}
乐观锁实现(用于余额更新):
@Entity
public class Account {
@Id
private Long id;
private String accountNumber;
private BigDecimal balance;
@Version
private Integer version;
// 省略getter和setter
}
使用编程式事务的批量转账示例:
@Service
public class BatchTransferService {
@Autowired
private TransactionTemplate transactionTemplate;
@Autowired
private AccountRepository accountRepository;
@Autowired
private TransferLogService transferLogService;
public void batchTransfer(List<TransferRequest> requests) {
transactionTemplate.executeWithoutResult(status -> {
for (TransferRequest request : requests) {
try {
Account fromAccount = accountRepository.findByIdWithLock(request.getFromAccountId());
Account toAccount = accountRepository.findByIdWithLock(request.getToAccountId());
if (fromAccount.getBalance() < request.getAmount()) {
status.setRollbackOnly();
throw new InsufficientBalanceException("转出账户余额不足: " + request.getFromAccountId());
}
fromAccount.setBalance(fromAccount.getBalance() - request.getAmount());
toAccount.setBalance(toAccount.getBalance() + request.getAmount());
accountRepository.save(fromAccount);
accountRepository.save(toAccount);
transferLogService.recordTransferLog(request);
} catch (Exception e) {
// 记录错误日志
System.out.println("批量转账失败: " + e.getMessage());
status.setRollbackOnly();
}
}
});
}
}
8.3 分布式事务处理案例
在微服务架构中,经常需要处理跨多个服务的分布式事务。以下是一个使用 Spring Cloud 和 Seata 实现的分布式事务处理案例。
技术架构说明:
-
使用 Spring Cloud 构建微服务架构
-
使用 Seata 作为分布式事务协调器
-
使用 Nacos 作为服务注册中心
-
使用 MySQL 作为数据库
订单服务(全局事务发起方):
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Autowired
private AccountFeignClient accountFeignClient;
@Autowired
private InventoryFeignClient inventoryFeignClient;
@GlobalTransactional(
name = "createOrderTransaction",
timeoutMills = 30000,
rollbackFor = {Exception.class}
)
public Order createOrder(OrderCreateRequest request) {
try {
// 1. 创建订单
Order order = createOrderRecord(request);
// 2. 调用账户服务扣减金额
AccountOperation accountOperation = new AccountOperation();
accountOperation.setUserId(request.getUserId());
accountOperation.setAmount(request.getTotalAmount());
accountFeignClient.debitAccount(accountOperation);
// 3. 调用库存服务扣减库存
InventoryOperation inventoryOperation = new InventoryOperation();
inventoryOperation.setProductId(request.getProductId());
inventoryOperation.setQuantity(request.getQuantity());
inventoryFeignClient.deductInventory(inventoryOperation);
return order;
} catch (Exception e) {
throw new OrderException("订单创建失败: " + e.getMessage());
}
}
private Order createOrderRecord(OrderCreateRequest request) {
Order order = new Order();
order.setUserId(request.getUserId());
order.setProductId(request.getProductId());
order.setQuantity(request.getQuantity());
order.setTotalAmount(request.getTotalAmount());
order.setStatus(OrderStatus.CREATED);
return orderRepository.save(order);
}
}
账户服务实现:
@Service
public class AccountService {
@Autowired
private AccountRepository accountRepository;
@Transactional(rollbackFor = Exception.class)
public void debitAccount(AccountOperation operation) {
Account account = accountRepository.findById(operation.getUserId())
.orElseThrow(() -> new AccountNotFoundException("账户不存在"));
if (account.getBalance() < operation.getAmount()) {
throw new InsufficientBalanceException("账户余额不足");
}
account.setBalance(account.getBalance() - operation.getAmount());
accountRepository.save(account);
}
}
库存服务实现:
库存服务作为分布式事务的分支节点,需处理商品库存扣减逻辑,并通过 Seata 代理数据源实现事务分支的注册与协调:
@Service
public class InventoryService {
@Autowired
private InventoryRepository inventoryRepository;
// 分支事务:扣减库存,异常时触发全局回滚
<reference type="end" id=30> @Transactional(rollbackFor = Exception.class)
public void ded<reference type="end" id=1>uctInventory(InventoryOperation operation) {
// 1. 查询商品库存(加行锁防止并发超卖)
Inventory inventory = inventoryRepository.findByProductIdForUpdate(operation.getProductId())
.orElseThrow(() -> new InventoryNotFoundException("商品不存在:" + operation.getProductId()));
// 2. 校验库存是否充足
if (inventory.getStock() < operation.getQuantity()) {
throw new InsufficientInventoryException(
String.format("商品%s库存不足,当前库存:%d,请求扣减:%d",
operation.getProductId(), inventory.getStock(), operation.getQuantity()));
}
// 3. 扣减库存
inventory.setStock(inventory.getStock() - operation.getQuantity());
inventory.setUpdateTime(new Date());
inventoryRepository.save(inventory);
// 4. 模拟网络延迟(用于测试分布式事务回滚场景)
// try { Thread.sleep(5000); } catch (InterruptedException e) { throw new RuntimeException(e); }
}
}
// 实体类:库存记录
@<reference type="end" id=37>Entity
@Table(name = "t_inventory")
public class Inventory {
@Id
<reference type="end" id=18>@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String productId; // 商品ID
private Integer stock; // 当前库存
private Date createTime;
private Date updateTime;
// 省略getter/setter
}
// 数据访问层:支持行锁查询
public in<reference type="end" id=15>terface InventoryRepository extends JpaRepository<Inventory, Long> {
// SELECT \* FROM t_inventory WHERE product_id = ? FOR UPDATE
Optional<Inventory> findByProductIdForUpdate(String productId);
}
8.3.2 Seata 分布式事务配置(核心)
由于案例基于 Seata 实现分布式事务,需补充微服务层面的 Seata 配置,确保 TM(事务管理器)、RM(资源管理器)与 TC(事务协调器)正常通信(40)。
1. 依赖引入(所有微服务)
在pom.xml中添加 Seata 与 Spring Cloud 集成依赖:
<!-- Seata核心依赖 -->
<dependency>
<groupId>io.seata</groupId>
<a<reference type="end" id=35>rtifactId>seata-spring-boot-starter</artifactId>
<version>1.6.1</version>
</dependency>
<!-- Seata-Nacos注册中心适配 -->
<dependency>
<groupId>io.seata</groupId>
<artifactId>seata-registry-nacos</artifactId>
<version>1.6.1</version>
</dependency>
<reference type="end" id=29><!-- Seata数据源代理(AT模式必需) -->
<depend<reference type="end" id=30>ency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-seata<reference type="end" id=30></artifactId>
<version>2.2.7.RELEASE</version>
<ex<reference type="end" id=12>clusions>
<exclusion>
<reference type="end" id=12> <groupId>io.seata</groupId>
<artifactId>seata-spring-boot-starter</artifactId>
</exclusion><reference type="end" id=40>
</exclusions>
</dependency>
2. 配置文件(application.yml)
每个微服务(订单、账户、库存)需配置 Seata 相关参数,以订单服务为例:
spring:
application:
name: order-service # 服务名(需与Seata事务组配置对应)
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848 # Nacos注册中心地址
alibaba:
seata:
tx-service-group: my_test_tx_group # 事务组(<reference type="end" id=116>所有微服务需一致)
seata:
enabled: true
application-id: \${spring.application.name}
tx-service-group: \${spring.cloud.alibaba.seata.tx-service-group}
registry:
type: nacos # 注册中心类型
nacos:
server-addr: \${spring.cloud.nacos.discovery.server-addr}
group: SEATA_GROUP # Seata默认分组
namespace: # 若使用Nacos命名空间,需填写ID
config:
type: nacos # 配置中心类型
nacos:
server-addr: \${spring.cloud.nacos.discovery.server-addr}
group: SEATA_GROUP
data-source-proxy-mode: AT # 事务模式(AT模式:自动补偿,适合大多数场景)
service:
vg<reference type="end" id=40>roup-mapping:
my_test_tx_group: default # 事务组与TC集群映射(默认集群为def<reference type="end" id=40>ault)
client:
undo:
log-table: undo_log # 回滚日志表(需在数据库中手动创建)
log-serialization: jackson # 序列化方式
3. 回滚日志表创建(所有数据库)
Seata AT 模式需在每个微服务对应的数据库中创建<reference type="end" id=55>undo_log表,用于存储事务回滚所需的镜像数据:
\-- MySQL版本
CREATE TABLE \`undo_log\` (
\`id\` bigint NOT NULL AUTO_INCREMENT,
\`branch_id\` bigint NOT NULL,
\`xid\` varchar(100) NOT NULL,
\`context\` varchar(12<reference type="end" id=63>8) NOT NULL,
\`rollback_info\` longblob NOT NULL,
<reference type="end" id=61> \`log_status\` int NOT NULL,
\`log_created\` datetime NOT NULL,
\`log_mo<reference type="end" id=61>dified\` datetime NOT NULL,
\`ext\` varchar(100) DEFAULT NULL,
PRIMARY KEY (\`id\`),
UNIQUE KEY \`ux_undo_log\` (\`xid\`,\`branch_id\`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
\-- PostgreSQL版本
CREATE TABLE undo_log (
id bigint NOT NULL GENERATED BY DEFAU<reference type="end" id=41>LT AS IDENTITY,
branch_id bigint NOT NULL,
xid varchar(100) NOT NULL,
context varchar(128) NOT NULL,
rollback_info bytea NOT NULL,
log_status int NOT NULL,
log_created timestamp NOT NULL,
log_modified timestamp NOT NULL,
ext varchar(<reference type="end" id=71>100) DEFAULT NULL,
PRIMARY KEY (id),
UNIQUE<reference type="end" id=71> CONSTRAINT ux_undo_log UNIQUE (xid, branch_id)
);
8.3.3 Feign(67)客户端定义(服务间调用)
订单服务通过 Feign 调用账户、库存服务,需补充 Feign 客户端接口定义(确保服务间通信正常):
// 账户服务Feign客户端
@FeignClient(name = "account-se<reference type="end" id=77>rvice", fallback = AccountFeignFallback.class)
public interface AccountFeignClient {
@PostMapping("/api/account/debit")
void debitAccount(@RequestBody AccountOperation operation);
}
// 库存服务Feign客户端
@FeignClient(name = "inventory-service", fallback = InventoryFeignFallback.class)
public interface InventoryFeignClient {
@PostMapping("/api/inventory/deduct")
void deductInventory(@RequestBody InventoryOperation operation);
}
// 降级处理类(服务熔断时返回友好提示)
@Component
public class AccountFeignFallback implements AccountFeignClient {
@Override
public void debitAccount(AccountOperation operation) {
throw new ServiceUnavailableException("账户服务暂时不可用,请稍后重试");
}
}
@Component
public class InventoryFeignFallback implements InventoryFeignClient {
@Override
public void deductInventory(InventoryOperation operation) {
throw new ServiceUnavailableException("库存服务暂时不可用,请稍后重试");
}
}
8.3.4 分布式事务执行流程(核心原理)
以 "创建订单→扣减账户余额→扣减库存" 为例,Seata AT 模式的执行流程如下:
-
全局事务开启 :订单服务调用
createOrder方法时,@GlobalTransactional注解触发 TM(事务管理器)向 TC(Seata Server)发起全局事务请求,TC 生成全局事务 ID(XID)并返回。 -
分支事务注册:
-
订单服务执行本地事务(创建订单),RM(订单服务数据源代理)向 TC 注册分支事务,并绑定 XID。
-
订单服务通过 Feign 调用账户服务,XID 通过 HTTP 请求头传递到账户服务;账户服务执行本地事务(扣减余额),RM 向 TC 注册分支事务。
-
同理,库存服务执行本地事务(扣减库存),RM 向 TC 注册分支事务。
- 事务协调:
-
若所有分支事务执行成功,TM 向 TC 发起全局提交请求;TC 通知所有 RM 提交分支事务,删除
undo_log日志。 -
若任一分支事务失败(如库存不足),TM 向 TC 发起全局回滚请求;TC 通知所有 RM 执行回滚,通过
undo_log中的镜像数据恢复数据。
- 事务状态同步:TC 将全局事务结果(提交 / 回滚)同步给 TM,TM 结束全局事务。
8.3.5 分布式事务注意事项
-
XID 传递确保:Seata 依赖 XID 关联全局事务与分支事务,需确保 XID 在微服务调用链中正确传递(Feign、Dubbo 等框架需配置 XID 传递拦截器,Seata Starter 已默认实现)。
-
超时配置 :需合理设置
@GlobalTransactional(timeoutMills)(如 30000ms),避免因分支事务执行过久导致全局事务超时回滚。 -
幂等性处理:分布式场景下可能出现重试(如网络抖动),需在业务层实现幂等性(如订单号唯一约束、扣减库存前校验)。
-
TC 集群部署:生产环境需部署 Seata Server 集群,避免单点故障,通过 Nacos 实现 TC 服务发现。
-
数据库支持:Seata AT 模式需数据库支持事务和行锁(MySQL InnoDB、PostgreSQL 均支持,避免使用 MyISAM 引擎)。
9. Spring 事务常见问题与解决方案
9.1 @Transactional 注解失效场景及解决
场景 1:非 public 方法使用 @Transactional
原因:Spring AOP 默认只对 public 方法生成代理,非 public 方法(private/protected/default)的 @Transactional 注解会被忽略。
解决:① 将方法改为 public;② 若需保留非 public 修饰符,需手动配置 AOP 切面(如使用 AspectJ 而非默认 JDK/CGLIB 代理)。
场景 2:自调用导致代理失效
原因 :同一类中方法调用(如methodA()调用本类的@Transactional methodB()),会绕过代理对象,直接调用目标方法,事务注解失效。
解决:
// 方案1:自注入Bean(推荐)
@Service
public class OrderService {
@Autowired
private OrderService orderService; // 注入自身代理对象
public void createOrder(Order order) {
// 调用代理对象的事务方法
orderService.saveOrder(order);
}
@Transactional
public void saveOrder(Order order) {
orderRepository.save(order);
}
}
// 方案2:通过ApplicationContext获取Bean
@Service
public class OrderService implements ApplicationContextAware {
private ApplicationContext applicationContext;
public void createOrder(Order order) {
OrderService proxy = applicationContext.getBean(OrderService.class);
proxy.saveOrder(order);
}
@Transactional
public void saveOrder(Order order) {
orderRepository.save(order);
}
@Override
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
this.applicationContext = applicationContext;
}
}
场景 3:异常被捕获未抛出
原因:若事务方法内捕获异常并处理(未重新抛出),Spring 无法感知异常,不会触发回滚。
解决:① 捕获后重新抛出异常;② 手动标记回滚:
@Transactional
public void createOrder(Order order) {
try {
orderRepository.save(order);
throw new RuntimeException("业务异常");
} catch (Exception e) {
// 方案1:重新抛出异常
throw new OrderException("订单创建失败", e);
// 方案2:手动标记回滚(适用于需处理异常但仍需回滚的场景)
// TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}
场景 4:未配置 @EnableTransactionManagement
原因 :Spring Boot 需通过@EnableTransactionM<reference type="end" id=67>anagement开启声明式事务支持(部分版本自动开启,但显式配置更稳妥)。
解决 :在配置类或(72)启动类上添加注解:
@SpringBootApplication
@EnableTransa<reference type="end" id=72>ctionManagement // 显式开启事务管理
public class OrderApplicatio<reference type="end" id=79>n {
public static void main(String\[] args) {
Sprin<reference type="end" id=78>gApplication.run(OrderApplication.class, args);
}
}
9.2 事务并发问题解决方案
问题 1:超卖问题(库存并发扣减)
原因:高并发下,多个事务同时查询库存并扣减,导致实际库存为负。
解决:
// 方案1:使用行锁(SELECT ... FOR UPDATE)
@Transactional
public void deductStock(String productId, int quantity) {
// 加行锁,防止其他事务同时修改该商品库存
Inventory inventory = inventoryRepository.findByProductIdForUpdate(productId)
.orElseThrow(() -> new InventoryNotFoundException());
if (inventory.getStock() < quantity) {
throw new InsufficientInventoryException();
}
inventory.setStock(inventory.getStock() - quantity);
inventoryRepository.save(inventory);
}
// 方案<reference type="end" id=77>2:使用乐观锁(@Version)
@Entity
public class Inventory {
@Id
private Long id;
private String productId;
private Intege<reference type="end" id=76>r stock;
@Version // 乐观锁版本号
private Integer version; // 每次更新自动递增
}
@Transactional
public void deduct<reference type="end" id=81>Stock(String productId, int quantity) {
int rows = 0;
while (rows == 0) {
Inventory inventory = inventoryRepository.findByPr<reference type="end" id=83>oductId(productId)
.orElseThrow(() <reference type="end" id=83>-> new InventoryNotFoundException());
if (inventory.getStock() < quantity) {
throw new InsufficientInventoryException();
}
// 更新时校验版本号,版本号不匹配则更新失败(返回0行)
rows = inventoryRepository.deductStock(
productId, quantity, inventory.getVersion());
if (rows <reference type="end" id=83>== 0) {
// 版本号冲突,重试(避免无限重试,可加重试次数限制)
Thread.sleep(100);
}
}
}
// Repository层SQL(MySQL)
@Modifying
@Query("UPDATE Inventory SET stock = <reference type="end" id=80>stock - :quantity, version = version + 1 " +
<reference type="end" id=80> "WHERE productId = :productId AND version = :version")
int deductStock(@Param("productId") String productId,
@Param("quantity") int quanti<reference type="end" id=83>ty,
@Param("version") int version);
问题 2:死锁问题
原因:多个事务持有对方需要的锁,且互相等待(如事务 A 持有锁 1 等待锁 2,事务 B 持有锁 2 等待锁 1)。
解决:
-
统一锁获取顺序(如所有事务按 "商品 ID 升序" 获取锁);
-
减少事务持有锁的时间(如(83)事务内避免远程调用、批量操作分批次提交);
-
配置数据库死锁检测(MySQL 默认开启,超时后自动回滚其中一个事务);
-
使用乐观锁替代悲观锁(减少锁竞争)。
9.3 MySQL 与 PostgreSQL 事务差异实战解决
差异 1:默认隔离级别导致的查询不一致
问题 :MySQL 默认REPEATABLE_<reference type="end" id=83>READ,PostgreSQL 默认READ_COMMITTED,同一应用在两数据库上查询结果可能不同(如 PostgreSQL 在事务内多次查询可能看到其他事务提交的数据)。
解决:显式指定隔离级别,确保跨数据库一致性:
// 统一设置为READ_COMMITTED(兼顾一致性与并发性能)
@Transactional(isolation = Isolation.READ_COMMITTED)
public List<Order> getOrdersByUserId(Long userId) {
return orderRepository.findByUserId(userId);
}
差异 2:PostgreSQL 不支持 READ_UNCOMMITTED
问题 :若应用配置Isolation.READ_UNCOMMITTED,在 PostgreSQL 上会自动降级为READ_COMMITTED,可能导致预期外的行为。
解决 :避免使用READ_UNCOMMITTED,改用READ_COMMITTED;若需读取未提交数据,可通过 PostgreSQL 的pg_stat_activity视图间接实现(不推荐)。
差异 3:MVCC 实现导致的事务可见性
问题 :MySQL 通过 Undo Log 维护版本,PostgreSQL 通过xmin/<reference type="end" id=122>xmax字段,事务内删除数据后,MySQL 仍能查询到历史版本,PostgreSQL 则无(122)法查询。
解决 :业务层避免依赖 "删除后仍能查询历史数据" 的逻辑,如需历史数据,需设计专门的历史表(如order_history)。
10. 总结与面试重点
10.1 核心知识点总结
-
事务基础:ACID 特性是事务的核心,Spring 事务是对数据库事务的封装,通过 AOP 实现声明式管理。
-
两大事务方式:
-
声明式事务:基于
@Transactional注解,简单易用(推荐日常开发); -
编程式事务:基于
TransactionTemplate或PlatformTransactionManager,灵活可控(适用于复杂事务边界)。
- 关键属性:
-
传播行为:7 种,重点掌握
REQUIRED(默认)、REQUIRES_NEW(独立事务)、NESTED(嵌套事务); -
隔离级别:5 种,重点掌握
READ_COMMITTED(常用)、REPEATABLE_READ(MySQL 默认); -
回滚规则:默认回滚
RuntimeException,需显式配置rollbackFor处理受检异常。
-
分布式事务:微服务场景下需用 Seata 等中间件,AT 模式是主流(自动补偿,低侵入)。
-
数据库差异:MySQL 与 PostgreSQL 在隔离级别、MVCC、锁机制上的差异,需针对性配置。
10.2 面试高频问题与回答思路
问题 1:@Transactional 注解的工作原理?
回答思路:
-
核心:基于 AOP 动态代理,在方法调用前后织入事务逻辑;
-
流程:
-
启动时,
@EnableTransactionManagement开启事务管理,Spring 扫描@Transactional注解的 Bean; -
为 Bean 创建代理对象(JDK 动态代理或 CGLIB);
-
调用方法时,代理对象先通过
TransactionInterceptor开启事务(获取连接、关闭自动提交); -
执行目标方法,若异常则回滚,若无异常则提交;
- 关键组件:
TransactionInterceptor(拦截器)、TransactionAttributeSource(解析注解属性)、PlatformTransactionManager(事务管理器)。
问题 2:事务传播行为中 REQUIRES_NEW 与 NESTED 的区别?
回答思路:
- 事务独立性:
-
REQUIRES_NEW:创建完全独立的新事务,与外层事务无关联(外层回滚不影响内层,内层回滚不影响外层); -
NESTED:嵌套在外层事务中,是外层事务的子事务(外层回滚会导致内层回滚,内层回滚不影响外层);
- 实现机制:
-
REQUIRES_NEW:依赖事务管理器支持(如 JTA); -
NESTED:依赖数据库保存点(Savepoint);
- 适用场景:
-
REQUIRES_NEW:日志记录、审计(需独立提交); -
NESTED:批量操作(部分失败可回滚到保存点,不影响整体)。
问题 3:Spring 事务为什么默认只回滚 RuntimeException?
回答思路:
-
设计理念:Spring 遵循 "受检异常(Checked Exception)表示业务异常,运行时异常(RuntimeException)表示系统异常" 的约定;
-
业务语义:受检异常(如
IOException)通常是可恢复的(如重试),不应直接回滚事务;运行时异常(如NullPointerException)是不可恢复的,需回滚事务; -
灵活性:可通过
rollbackFor属性自定义回滚规则,满足特殊业务需求(如@Transactional(rollbackFor = BusinessException.class))。
问题 4:分布式事务的解决方案有哪些?Seata AT 模式原理?
回答思路:
- 解决方案:
-
2PC(两阶段提交):Seata AT 模式基于此改进;
-
TCC(Try-Confirm-Cancel):手动实现提交 / 回滚逻辑(适用于非关系型数据库);
-
SAGA:长事务拆分,基于补偿机制;
-
本地消息表:适用于最终一致性(如电商订单与物流);
- Seata AT 模式原理:
-
阶段 1(执行分支事务):
-
拦截 SQL,生成 undo/redo 日志(镜像数据);
-
执行 SQL 并提交本地事务;
-
注册分支事务到 TC;
-
-
阶段 2(全局提交 / 回滚):
-
提交:删除 undo 日志;
-
回滚:执行 undo 日志恢复数据。
-
11. Spring Boot 3.x 事务新特性
Spring Boot 3.x 基于 Spring Framework 6.x,在事务管理上引入了多项适配 Java 17+ 和云原生场景的新特性,对初学者理解现代 Spring 事务实践具有重要意义。
11.1 对 Java 17 密封类(Sealed Classes)的事务支持
Spring Boot 3.x 正式支持对密封类异常 的事务回滚配置,解决了 Java 17 新特性与 Spring 事务的兼容(30)性问题。
场景:密封类异常的回滚配置
密封类(sealed)限制了异常的继承范围,适合定义严格的业务异常体系。Sprin(1)g Boot 3.x 允许直接在 @Transactional 中指定密封类异常作为回滚触发条件:
// 1. 定义密封业务异常(Java 17+)
public sealed class OrderBusinessException extends RuntimeException
permits OrderStockException, OrderPaymentException, OrderTimeoutException {
public OrderBusinessException(String message) {
super(message);
}
}
// 密封类的具体实现
public final class OrderStockException extends OrderBusinessException {
public OrderStockException(String message) {
super(message);
}
}
public final class OrderPaymentException extends OrderBusinessException {
public OrderPaymentException(String message) {
super(message);
}
}
// 2. 事务方法中指定密封类异常回滚
@Service
public class OrderService {
// 对所有OrderBusinessException子类异常都回滚
@Transactional(rollbackFor = OrderBusinessException.class)
public void createOrder(OrderRequest request) {
// 库存不足:抛OrderStockException
if (checkStock(request) < request.g<reference type="end" id=37>etQuantity()) {
throw new OrderStockException("商品库存不足");
<reference type="end" id=18> }
// 支付失败:抛OrderPaymentException
if (!processPayment(request)) {
throw new OrderPaymentException("支付处理失败");
}
// 正常保存订单
orderRepository.save(buildOrder(request));
}
}
核(15)心改进:
-
Spring 6.x 增强了
TransactionAttributeSource,可正确解析密封类及其子类异常的继承关系; -
避免了 Java 17 密封类在旧版 Spring 中 "无法识别子类异常" 导致的回滚失效问题。
11.2 事务感知的虚拟线程(Virtual Threads)支持
Java 19 引入虚拟线程(预览特性,Java 21 正式转正),Spring Boot 3.x 提供事务与虚拟线程的协同能力,解决了传统平台线程(Platform Thread)在高并发事务中的资源瓶颈。
配置:启用虚拟线程并支持事务
-
依赖配置 (Sprin(40)g Boot 3.2+):
<dependency><groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId></d<reference type="end" id=35>ependency>
<dependency></dependency><groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-virtual-threads</artifactId> -
应用配置 (
application.yml):spring:
threads:
vir<reference type="end" id=29>tual: enabled: true # 全局启用虚拟线<reference type="end" id=30>程datasource:
hikari: maximum-pool-size: 20 # 虚拟线程场景下,连接池大小可适当减小(避免资源浪费)
关键特性:
-
事务资源(如数据库连接)会自动绑定到虚拟线程上下文,避免 "线程切换导致事务丢失";
-
虚拟线程的轻量级特性(百万级并发支持)与事务结合时,无需担心平台线程的 "线程池耗尽" 问题;
-
对
@Async、@Scheduled等异步注解的支持(116)更高效,事务上下文传递更轻量。
11.3 声明式事务的条件化配置(Conditional Transactions)
Spring Boot 3.x 新增 @ConditionalOnTransaction 注解,支持基于当前事务状态的条件化 Bean 注册,解决了 "特定逻辑仅在事务内执行" 的场景需求。
场景:事务内的日志记录器
// 1. 定义事务内专用的日志服务
@Service
@ConditionalOnTransaction // 仅当当前存在活跃事务时,才注册该Bean
public class TransactionalLogger {
public void logTransactionEvent(String event, String detail) {
// 事务内日志:会随事务回滚而回滚(如记录订单创建日志)
System.out.printf("事务内日志 \[%s]: %s%n", event, detail);
}
}
// 2. 非事务日志服务
@Service
public class NonTransactionalLogge<reference type="end" id=40>r {
public void logEvent(String event, String detail) {
<reference type="end" id=40> // 非事务日志:立即持久化,不随事务回滚
System.out.printf("非事务日志 [%s]: %s%n", event, detail);
}
}
// 3. 事务方法中使用条件化Bean
@Service
public class OrderService {
@Autowired(req<reference type="end" id=55>uired = false) // 事务内存在时才注入
private TransactionalLogger transactionalLogger;
@Autowired
private NonTransactionalLogger nonTransactionalLogger;
@Transactional
public <reference type="end" id=63>void createOrder(OrderRequest request) {
//<reference type="end" id=61> 事务内日志:仅在事务活跃时执行
if (transactionalLogger != null) {
<reference type="end" id=61>transactionalLogger.logTransactionEvent("ORDER_CREATE", request.toString());
}
// 非事务日志:始终执行
nonTransactionalLogger.logEvent("ORDER_CREATE", request.toString());
orderRepository.save(buildOrder(request)<reference type="end" id=41>);
}
}
核心价值:
-
避免了 "在非事务场景下调用事务依赖 Bean" 导致的空指针或资源泄漏;
-
简化了 "事务内特殊逻辑" 的代码判断(无需手动调用
TransactionSynchronizationManager.isActualTransactionActive())。
12. 事务性能优化实战
事务性能直接影响系统吞吐量,尤其是高并发场景(如电商秒杀、金融交易)。以下从连接池配置 、事务粒度 、数据库优化三个维度提供实战方案。
12.1 数据库连接池优化(HikariCP)
Spring Boot默认使用 HikariCP 作为连接池,其配置直接影响事务的资源获取效率。
核心配置(application.yml):
spring:
datasource:
hikari:
jdbc-url: jdbc:mysql:<reference type="end" id=73>//localhost:3306/order_db?useSSL=false\&serverTimezo<reference type="end" id=77>ne=UTC
username: root
password: 123456
maximum-pool-size: 15 # 核心参数:根据CPU核心数调整(建议 CPU核心数 \* 2 + 1)
minimum-idle: 5 # 最小空闲连接:避免频繁创建/销毁连接
idle-timeout: 300000 # 空闲连接超时(5分钟):释放长时间闲置的连接
connection-timeout: 30000 # 连接获取超时(30秒):防止线程无限等待
max-lifetime: 1800000 # 连接最大生命周期(30分钟):避免使用过期连接
connection-test-query: SELECT 1 # 连接有效性检测(MySQL/PostgreSQL通用)
优化原则:
-
MySQL 场景 :
maximum-pool-size不宜过大(建议 10-20),因 MySQL 对并发连接的处理能力有限; -
PostgreSQL 场景:可适当增大连接池(建议 20-30),因 PostgreSQL 对多连接的支持更优;
-
虚拟线程场景 :减小
maximum-pool-size(如 5-10),因虚拟线程切换快,无需大量空闲连接。
12.2 事务粒度控制(避免 "大事务")
"大事务" 是性能杀手(持有锁时间长、占用连接久),需通过拆分事务 和缩小范围优化。
问题场景:大事务示例
// 反例:一个事务包含"订单创建+库存扣减+支付处理+通知发送+日志记录"
@Transactional
public void createOrder(OrderRequest request) {
// 1. 订单创建(核心)
Order order = orderRepository.save(buildOrder(request));
// 2. 库存扣减(核心)
inventoryService.deductStock(request.getProductId(), request.getQuantity());
// 3. 支付处理(核心)
paymentService.processPayment(order.getId(), request.getAmount());
// 4. 发送短信通知(非核心,耗时)
smsService.sendOrderNotice(order.getUserId());
// 5. 记录审计日志(非核心,可独立)
auditLogService.recordLog(order.getId(), "ORDER_CREATE");
}
优化方案:拆分事务
@Service
public class OrderService {
// 核心事务:仅包含"订单创建+库存扣减+支付处理"
@Transactional(propagation = Propagation.REQUIRED)
public Order createCoreOrder(OrderRequest request) {
Order order = orderRepository.save(buildOrder(request));
inventoryService.deductStock(request.getProductId(), request.getQuantity());
paymentService.processPayment(order.getId(), request.getAmount());
return order;
}
// 外层方法:协调核心事务与非核心操作
public void createOrder(OrderRequest request) {
// 1. 执行核心事务
Order order = createCoreOrder(request);
// 2. 非核心操作:异步执行(不阻塞主流程,不占用事务连接)
CompletableFuture.runAsync(() -> smsService.sendOrderNotice(order.getUserId()));
CompletableFuture.runAsync(() -> auditLogService.recordLog(order.getId(), "ORDER_CREATE"));
}
}
// 非核心服务:使用独立事务(或非事务)
@Service
public class AuditLogService {
// 独立事务:日志记录失败不影响核心业务
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void recordLog(Long orderId, String event) {
auditLogRepository.save(new AuditLog(orderId, event, new Date()));
}
}
优化效果:
-
核心事务执行时间从 500ms 缩短至 100ms;
-
非核心操作异步化,主流程吞吐量提升 3-5 倍;
-
事务持有锁时间缩短,减少并发冲突。
12.3 读写分离下的事务处理
在主从复制架构中(MySQL/PostgreSQL 主从),需解决 "事务写主库、读从库" 的一致性问题。
方案:Spring 读写分离 + 事务路由
-
依赖引入(使用 Sharding-JDBC 实现读写分离):
<dependency></dependency><groupId>org.apache.shardingsphere</groupId> <artifactId>shardingsphere-jdbc-core-spring-boot-starter</artifactId> <version>5.3.2</version> -
配置读写分离(
application.yml):spring:
shardingsphere:
datasource: names: master,slave1,slave2 # 数据源名称 master: # 主库(写库) type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3306/order_db?useSSL=false username: root password: 123456 slave1: # 从库1(读库) type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3307/order_db?useSSL=false username: root password: 123456 slave2: # 从库2(读库) type: com.zaxxer.hikari.HikariDataSource driver-class-name: com.mysql.cj.jdbc.Driver jdbc-url: jdbc:mysql://localhost:3308/order_db?useSSL=false username: root password: 123456 rules: readwrite-splitting: data-sources: order-db: # 读写分离数据源名称 type: Static # 静态读写分离(适合主从固定<reference type="end" id=67>场景) props: write-data-source<reference type="end" id=72>-name: master # 写数据源 read-data-source-na<reference type="end" id=72>mes: slave1,slave2 # 读数据源 load-balancer-na<reference type="end" id=79>me: round_robin # 负载均衡策略(轮询) load-balancers: <reference type="end" id=78> round_robin: type: RoundRobin # 轮询负载均衡 props: sql-show: false # 关闭SQL日志(生产环境) -
事务与读写分离协同:
@Service
public class OrderService {
// 写事务:自动路由到主库 @Transactional public Order createOrder(OrderRequest request) { Order order = buildOrder(request); orderRepository.save(order); // 写操作:走主库 return order; } // 读操作:自动路由到从库(无事务) public Order getOrderById(Long orderId) { return orderRepository.findById(orderId).orElse(null); // 读操作:走从库 } // 事务内读:强制走主库(避免主从延迟导致的脏读) @Transactional(readOnly = true) public Order getOrderInTransaction(Lo<reference type="end" id=77>ng orderId) { // 事务内读:即使是查询,也走主库(保证数据一致性) return orderRepository.findById(orderId).orElse(null); }}
关键注意点:
-
事务内读操作 :无论是否
readOnly,都会路由到主库(避免主从延迟导致 "刚写就读不到"); -
主从延迟处理:核心业务(如支付后查订单)需用事务内读;非核心业务(如历史订单查询)可用从库;
-
PostgreSQL 特殊配置:需确保主从复制为 "流复制"(Stream Replication),减少延迟。
12.4 数据库层面优化(MySQL/PostgreSQL 专项)
事务性能不仅依赖 Spring 配置,还需数据库层面的优(83)化配合。
MySQL 专项优化(InnoDB)
-
事务相关参数 (
my.cnf<reference type="end" id=83>):mysqld] # 事务日志刷盘策略:1=每次事务提交刷盘(强一致性),2=每秒刷盘(性能优先) innodb_flush_log_at_trx_commit = 1 # 事务隔离级别:与Spring配置保持一致 transaction_isolation = READ-COMMITTED # InnoDB缓冲池大小:建议为物理内存的50%-70%(如8GB内存设为5G) innodb_buffer_pool_size = 5G # 关闭自动提交:Spring会手动控制事务提交 autocommit = 0 # 死锁检测超时:默认500ms,可根据业务调整 innodb_deadloc
k_detect = ON innodb_lock_wait_timeout = 5000 -
事务相关参数 (
postgresql.conf):事务日志缓冲区大小:建议设为16MB-64MB
wal_buffers = 16MB
事务日志刷盘策略:on<reference type="end" id=80>=每次提交刷盘,off=操作系统控制(性能优先)
wal_sync_method = fsyn<reference type="end" id=80>c
隔离级别:与Spring配置保持一致
default_transaction_isolation = 'read committed'
连接池最大连接数:比Spring连接池大20%(预留管理连接)
max_connections = 200
共享缓冲区大小:建议为物理内存的25%-30%
sha<reference type="end" id=83>red_buffers = 2GB
13. 扩展面试题解析(高频深化)
13.1 问题:Spring 事务与数据库事务的关系?
回答思路:
-
本质关系:Spring 事务是 "数据库事务的封装",最终依赖数据库事务实现 ACID 特性(Spring 本身不实现事务,仅提供管理能力);
-
核心差异 :(83) - 数据库事务:基于 SQL 语句和连接(
Connection),通过commit()/rollback()控制;
- Spring 事务:基于 AOP 和事务管理器(
PlatformTransactionManager),提供声明式 / 编程式 API,屏蔽数据库差异;
- 协同机制:
-
(83)Spring 通过
DataSource获取数据库连接,将连接绑定到当前线程(ThreadLocal); -
事务开启时:关闭连接自动提交(
connection.setAutoCommit(false)); -
事务提交 / 回滚时:调用连接的
commit()/rollback(),最后释放连接到池;
- 注意点:若数据库不支持事务(如 MySQL MyISAM 引擎),Spring 事务配置再完善也无效。
13.2 问题:如何排查 "Spring 事务不回滚" 的问题?
排查步骤(实战流程):
-
检查异常类型 :确认抛出的异常是否属于
RuntimeException或配置的rollbackFor异常(排除 "受检异常未配置" 问题); -
检查代理有效性:
-
确认方法是否为
public(非 public 方法不生成代理); -
确认是否存在自调用(同一类内方法调用,绕过代理);
- 检查事务状态:
-
打印事务状态:
TransactionStatus status = TransactionAspectSupport.currentTransactionStatus(); -
确认是否被标记为 "仅回滚"(
status.isRollbackOnly());
- 确认表引擎未被修改(如
ALTER TABLE order_db ENGINE=MyISAM);
- 检查日志 :开启 Spring 事务日志(
logging.level.org``.springframework.transaction=DEBUG),查看是否有 "事务未开启" 或 "回滚被跳过" 的日志。
示例日志排查:
# 开启事务 debug 日志
logging:
level:
org.springframework.transaction: DEBUG
org.springframework.jdbc.datasource: DEBUG
日志中若出现 Creating new transaction with name [...] 表示事务开启成功;若出现 Initiating transaction rollback 表示回滚触发成功。
13.3 问题:Spring 事务与 @Async 注解一起使用时,会有什么问题?如何解决?
问题本质 :@Async 会开启新线程,而 Spring 事务依赖 ThreadLocal 绑定连接,新线程无法获取原线程的事务上下文,导致 "事务丢失"。
示例问题代码:
// 反例:@Async 方法内的事务不生效(新线程无事务上下文)
@Service
public class OrderService {
@Transactional
public void createOrder(OrderRequest request) {
// 主线程事务
Order order = orderRepository.save(buildOrder(request));
// 异步调用:新线程,无事务上下文
asyncService.processOrderAsync(order.getId());
}
}
@Service
public class AsyncService {
// 问题:该事务不生效(新线程无原事务连接)
@Async
@Transactional
public void processOrderAsync(Long orderId) {
Order order = orderRepository.findById(orderId).orElse(null);
order.setStatus(OrderStatus.PROCESSED);
orderRepository.save(order); // 无事务,直接提交
}
}
解决方案:
-
方案 1:异步方法使用独立事务(推荐):
@Service
public class AsyncService {
// 独立事务:新线程创建自己的事务(PROPAGATION_REQUIRES_NEW) @Async @Transactional(propagation = Propagation.REQUIRES_NEW) public void processOrderAsync(Long orderId) { // 业务逻辑:独立事务,与主线程事务无关 }}
-
方案 2:使用事务同步管理器传递上下文(复杂场景):
@Service
public class OrderService {
@Transactional public void createOrder(OrderRequest request) { Order order = orderRepository.save(buildOrder(request)); // 获取当前事务状态,传递到异步线程 TransactionStatus status = TransactionAspectSupport.currentTransactionStatus(); asyncService.processOrderAsync(order.getId(), status); }}
@Service
public class AsyncService {
@Async public void processOrderAsync(Long orderId, TransactionStatus status) { // 绑定事务上下文到当前线程(需自定义事务管理器) TransactionSynchronizationManager.bindResource( dataSource, status.getTransaction().getResource(dataSource) ); try { // 异步事务逻辑 Order order = orderRepository.findById(orderId).orElse(null); order.setStatus(OrderStatus.PROCESSED); orderRepository.save(order); } finally { // 释放事务上下文 TransactionSynchronizationManager.unbindResource(dataSource); } }}
13.4 问题:Seata AT 模式与 TCC 模式的区别?如何选择?
核心差异对比:
| 维度 | Seata AT 模式 | Seata TCC 模式 |
|---|---|---|
| 实现原理 | 基于 SQL 拦截 + undo/redo 日志 | 基于业务代码手动实现 Try/Confirm/Cancel |
| 侵入性 | 低(无侵入,自动生成日志) | 高(需手动写 3 个方法) |
| 数据库依赖 | 强(需支持行锁和事务) | 弱(支持非关系型数据库,如 Redis) |
| 性能 | 中(日志写入有开销) | 高(无日志,直接操作业务数据) |
| 适用场景 | 关系型数据库(MySQL/PostgreSQL) | 非关系型数据库、跨语言服务 |
选择策略:
-
优先选 AT 模式:若使用关系型数据库,且无特殊业务逻辑(如跨库跨语言);
-
选 TCC 模式:若涉及非关系型数据库(如 Redis 库存)、跨语言服务(如 Go 微服务),或需极致性能。
14. 学习路径建议(初学者进阶)
14.1 阶段 1:基础掌握(1-2 周)
-
核心目标:理解事务 ACID 特性,掌握声明式事务基本使用;
-
学习内容:
-
编写
@Transactional注解的简单案例(如转账、订单创建); -
测试不同传播行为(
REQUIRED/REQUIRES_NEW)的效果; -
对比 MySQL InnoDB 与 MyISAM 引擎的事务支持差异;
- 工具实践:使用 IDEA 调试模式,查看事务开启 / 提交的断点流程。
14.2 阶段 2:深化理解(2-3 周)
-
核心目标:掌握事务原理与问题排查;
-
学习内容:
-
阅读 Spring 事务源码(
TransactionInterceptor、AbstractPlatformTransactionManager); -
复现 "事务不回滚""自调用失效" 等问题,并解决;
-
实战 MySQL/PostgreSQL 隔离级别差异(如测试幻读场景);
- 工具实践 :使用
jstack查看线程绑定的事务连接(ThreadLocal分析)。
14.3 阶段 3:实战进阶(3-4 周)
-
核心目标:应对高并发与分布式场景;
-
学习内容***:
-
搭建 Seata 分布式事务环境,实现订单 - 库存 - 账户的跨服务事务;
-
优化大事务(拆分、异步化),压测对比优化前后性能;
-
配置 MySQL/PostgreSQL 主从复制,实现读写分离下的事务处理;
- 工具实践:使用 JMeter 压测事务接口,监控连接池与数据库性能。
14.4 阶段 4:面试准备(1-2 周)
-
核心目标:梳理知识体系,应对深度面试;
-
学习内容:
-
整理高频面试题(如事务传播机制、分布式事务方案);
-
总结 MySQL/PostgreSQL 事务差异的实战案例;
-
准备 "事务优化""问题排查" 的实战经验(STAR 法则);