Spring 事务管理

共 33 题
📑 题目列表 33 题
#
★★★

1. @Transactional 的传播行为(Propagation)的 7 种语义

请说明 @Transactional 的 7 种传播行为(Propagation)各自的语义?

  • REQUIRED、REQUIRES_NEW、SUPPORTS、NOT_SUPPORTED、MANDATORY、NEVER、NESTED
  • 每条传播行为的语义与适用场景
  • 传播行为的嵌套调用表现

@Transactional 的 7 种传播行为(Propagation):REQUIRED(默认,存在事务则加入,否则新建);REQUIRES_NEW(总是新建独立事务,挂起当前事务);SUPPORTS(有事务则加入,无事务则非事务执行);NOT_SUPPORTED(以非事务方式执行,挂起当前事务);MANDATORY(必须有事务,否则抛异常);NEVER(必须无事务,有事务则抛异常);NESTED(嵌套事务,存在事务则新建带保存点的事务,否则按 REQUIRED)。各传播控制事务的加入/新建/挂起/嵌套,是声明式事务的核心语义。

传播行为决定"方法运行时如何参与事务"。REQUIRED 最常用(加入现有事务),REQUIRES_NEW 用于独立提交,NESTED 用于部分回滚(保存点)。理解传播语义是配置事务边界的基础。

#
★★★

2. @Transactional 的隔离级别(Isolation)与数据库默认差异

请说明 @Transactional 的隔离级别(Isolation)及与数据库默认隔离级别的差异?

  • 五种隔离级别(DEFAULT、READ_UNCOMMITTED、READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE)
  • 各种隔离级别防御的并发问题
  • 与数据库默认隔离级别的关系

@Transactional 的隔离级别(Isolation)有:DEFAULT(使用数据库默认隔离级别)、READ_UNCOMMITTED(可读未提交数据,产生脏读)、READ_COMMITTED(只能读已提交数据,防脏读,默认 Oracle/SQL Server)、REPEATABLE_READ(可重复读,防脏读与不可重复读,默认 MySQL)、SERIALIZABLE(串行化,防所有并发问题,性能最低)。与数据库默认的差异:MySQL 默认 REPEATABLE_READ,Oracle/SQL Server 默认 READ_COMMITTED。@Transactional 的 isolation 若为 DEFAULT 则采用数据库默认,否则按指定级别执行(需数据库支持)。

隔离级别是"并发一致性"与"性能"的权衡。理解各级别防御的并发问题(脏读/不可重复读/幻读)及其与数据库默认的差异,是正确配置隔离级别的前提。

#
★★★

3. @Transactional(readOnly = true) 的优化与数据库只读事务

请说明 @Transactional(readOnly = true) 的优化作用,以及它与数据库只读事务的关系?

  • readOnly=true 标记只读事务
  • 优化点(Hibernate 不脏检查、连接设置只读)
  • 数据库只读事务的配合

@Transactional(readOnly = true) 将事务标记为只读。优化作用:Hibernate 在只读事务中跳过脏检查(dirty checking)与快照维护,减少开销;某些数据库驱动会设置连接为只读模式(如 MySQL 的 setReadOnly),提示数据库优化。但它不是强制只读,若在只读事务中执行写操作,不同数据库行为不同(可能抛异常或忽略)。配合数据库只读事务(Spring 的 readOnly 在 JDBC 层通过 Connection.setReadOnly 传递),可让数据库层面也声明只读。适合查询类 Service 方法。

readOnly=true 是"声明式优化"——通过跳过 JPA 脏检查与数据库只读提示降低开销。它更多是性能与语义优化,而非强制约束。

#
★★★

4. Spring 6.x 的事务管理变更

请说明 Spring 6.x 事务管理的主要变更?

  • Spring 6 事务相关 API 与行为变化
  • Jakarta 命名空间迁移影响
  • 对 JPA/JDBC 事务的适配

Spring 6.x 事务管理的主要变更加强了对 Jakarta EE 9/10 的适配(javax.* 迁移到 jakarta.*),例如 JtaTransactionManager 基于 jakarta.transaction 包。事务抽象(PlatformTransactionManager、TransactionStatus)保持稳定,但引入了对 Java 17 的基线要求,并优化了事务资源管理。此外,Spring 6 对声明式事务与编程式事务的 API 更简洁,长事务的检测与统计增强。整体上 Spring 6 的事务管理是"稳定性 + 规范升级"的演进,核心抽象与传播/隔离语义不变。

Spring 6 事务管理核心变化是"Jakarta 命名空间迁移 + 基线提升",事务自身的传播、隔离、回滚语义保持稳定。升级重点是包名与容器适配。

#
★★★

5. Spring Framework 7 在 JDK 25 虚拟线程下事务同步的线程绑定边界

请说明 Spring Framework 7 在 JDK 25 虚拟线程下事务同步的线程绑定边界?

  • 事务资源通过 ThreadLocal 绑定到线程
  • 虚拟线程下事务跨线程丢失
  • 事务必须在线程内完成

Spring 事务通过 TransactionSynchronizationManager 将事务资源(连接、事务同步)绑定到 ThreadLocal,因此事务与"当前执行的线程"绑定。在 JDK 25 虚拟线程下,虚拟线程是轻量线程,事务同样绑定在虚拟线程上;关键边界是:事务必须在同一虚拟线程内完成,若在事务内切换线程(如 non-虚拟线程执行异步任务、跨线程提交),ThreadLocal 中的事务资源不会自动传播,导致事务丢失或连接错乱。Spring Framework 7 遵循这一边界,事务同步与线程绑定不变,开发者需避免在事务内跨线程操作。

事务的线程绑定边界不因虚拟线程而改变——ThreadLocal 不跨线程传播。理解"事务必须同一线程内完成"是虚拟线程下事务正确性的前提。

#
★★★

6. Spring 事务与 JPA 的协作(JpaTransactionManager)

请说明 Spring 事务与 JPA 的协作方式,特别是 JpaTransactionManager 的作用?

  • JpaTransactionManager 管理 JPA 事务
  • 与 EntityManager、EntityManagerFactory 的关系
  • 事务边界与持久化上下文

JpaTransactionManager 是 Spring 针对 JPA 的事务管理器,它管理 JPA 的 EntityManager 与事务。它获取 EntityManagerFactory 创建 EntityManager,将事务绑定到当前线程(ThreadLocal),并协调 EntityManager 的持久化上下文(Persistence Context)与事务。事务提交时 flush 持久化上下文并提交,回滚时废弃持久化上下文。JpaTransactionManager 同时支持 JPA 与 JDBC 的混合事务(通过 JpaDialect 桥接底层 JDBC 连接)。@Transactional 配合 JpaTransactionManager 实现 JPA 的声明式事务管理。

JpaTransactionManager 是"Spring 事务 + JPA"的桥梁,负责把 Spring 事务语义映射到 JPA 的 EntityManager 事务与持久化上下文生命周期。

#
★★★

7. Spring 事务与 JTA 的集成(Atomikos/Narayana)

请说明 Spring 事务与 JTA 的集成,以及 Atomikos/Narayana 等 JTA 实现的角色?

  • JTA 分布式事务(两阶段提交)
  • Atomikos/Narayana 作为 JTA 实现
  • JtaTransactionManager 与 XA 数据源

JTA(Java Transaction API)用于分布式事务(跨多个数据源/资源),通过 XA 协议与两阶段提交(2PC)保证原子性。Spring 通过 JtaTransactionManager 集成 JTA,配合 Atomikos、Narayana 等 JTA 实现(提供 XA 数据源与事务管理器)。配置时使用 XA 数据源(如 AtomikosDataSourceBean)包装多个数据库,Spring 通过 JtaTransactionManager 统一管理跨数据源事务。JTA 适合多资源强一致场景,但 2PC 有性能与可用性开销,复杂分布式场景常改用最终一致(如 Seata、消息补偿)。

JTA 是"跨资源强一致"的方案,基于 2PC。Spring 与 Atomikos/Narayana 集成实现分布式事务,代价是性能与协调复杂度,需权衡。

#
★★★

8. Spring 事务与 Seata 分布式事务的协作

请说明 Spring 事务与 Seata 分布式事务的协作方式?

  • Seata 的分布式事务模型(AT/TCC/SAGA)
  • Seata 与 Spring 事务的协作
  • 全局事务与本地事务的关系

Seata 是开源分布式事务框架,提供 AT、TCC、SAGA、XA 等模式。与 Spring 事务协作时,Seata 通过全局事务(GlobalTransaction)组织跨服务/跨库的本地事务:@GlobalTransactional 标注在全局事务入口,内部各服务的 @Transactional 本地事务通过 Seata 的 XID 关联到同一全局事务,实现全局一致(AT 模式通过反向 SQL 补偿)。Seata 依赖 Spring 的本地事务处理 DB 操作,通过代理数据源拦截 SQL 记录 undo log,在全局提交/回滚时协调各分支。协作关键是"全局事务管理 + 本地事务执行"的结合。

Seata 与 Spring 事务的协作是"全局协调 + 本地执行":Seata 管全局事务与 XID 传播,Spring 管本地事务的 DB 操作,二者结合实现分布式一致。

#
★★★

9. Spring 事务抽象(PlatformTransactionManager)与编程式/声明式

请说明 Spring 事务抽象 PlatformTransactionManager,以及编程式事务与声明式事务的区别?

  • PlatformTransactionManager 统一事务抽象
  • 编程式事务(TransactionTemplate/TransactionManager API)
  • 声明式事务(@Transactional)

Spring 通过 PlatformTransactionManager 接口统一事务抽象,其实现包括 DataSourceTransactionManager(JDBC)、JpaTransactionManager(JPA)、JtaTransactionManager(JTA)。编程式事务通过 TransactionTemplate 或直接调用 TransactionManager 的 getTransaction/commit/rollback 显式控制事务边界,灵活但侵入代码;声明式事务通过 @Transactional 注解(基于 AOP 代理)声明式标记事务边界,代码侵入小、声明性强,是推荐方式。二者都基于 PlatformTransactionManager,只是控制方式不同。

PlatformTransactionManager 是事务的 SPI 抽象,编程式与声明式是其两种使用方式。声明式更简洁、编程式更精细,按需求选择。

@Autowired
private TransactionTemplate txTemplate;
public void doWork() {
    txTemplate.execute(status -> {
        // 编程式事务边界
        return result;
    });
}
#
★★★

10. Spring 事务的 readOnly 标志在 JDBC 驱动层面的优化(如 MySQL 的事务访问模式)

请说明 Spring 事务的 readOnly 标志在 JDBC 驱动层面的优化,如 MySQL 的事务访问模式?

  • readOnly 传递到 Connection.setReadOnly
  • MySQL 的只读事务优化
  • 对查询性能的影响

当 @Transactional(readOnly=true) 时,Spring 通过数据库驱动把只读标志传递给底层连接。在 JDBC 层面,DataSourceTransactionManager 会调用 Connection.setReadOnly(true),MySQL 驱动会相应设置事务的只读访问模式(SESSION TRANSACTION READ ONLY)。MySQL 的只读事务可避免部分锁与 redo 开销,提升只读查询性能,并配合连接池复用。注意:readOnly 主要影响数据库层面的优化提示,真正生效与否取决于驱动与数据库支持;Hibernate 还会跳过脏检查。总体是查询场景的性能优化。

readOnly 的 JDBC 层优化是把只读标志传给 Connection/数据库,使数据库采用只读事务访问模式,减少锁与日志开销,提升查询性能。

#
★★★

11. Spring 事务的 rollback 规则(RuntimeException vs Checked Exception)

请说明 Spring 事务的回滚规则,运行时异常与受检异常(Checked Exception)的区别?

  • 默认回滚规则:RuntimeException 回滚、Checked Exception 不回滚
  • 回滚规则的哲学
  • rollbackFor/noRollbackFor 调整

Spring 事务默认仅在抛出 RuntimeException(或 Error)时回滚,Checked Exception(受检异常)导致事务提交而不是回滚。这一设计哲学:受检异常通常代表可预期的业务异常(如校验失败),业务可能希望提交已有数据;运行时异常代表未预期的系统错误,应回滚保证一致性。可通过 rollbackFor 指定哪些异常回滚、noRollbackFor 指定哪些异常不回滚,覆盖默认规则。注意:默认规则是"运行时异常回滚、受检异常不回滚",与直觉相反,需牢记。

默认回滚规则基于"受检=业务预期、运行时=系统错误"的划分。理解这一点,才能正确用 rollbackFor 覆盖默认行为。

#
★★★

12. Spring 事务的 rollbackFor/noRollbackFor 在 RuntimeException 默认回滚语义下的精细控制

请说明 rollbackFor 与 noRollbackFor 如何在默认回滚语义下实现精细控制?

  • rollbackFor 指定额外回滚的异常
  • noRollbackFor 指定不回滚的异常
  • 默认语义(RuntimeException 回滚)基础上的覆盖

在默认"RuntimeException 回滚、Checked Exception 不回滚"语义上,rollbackFor 用于把本应回滚的异常纳入回滚(如指定某受检异常也回滚),noRollbackFor 用于把本应回滚的异常排除回滚(如指定某运行时异常不回滚)。二者可组合,提供精细的事务回滚控制。例如 @Transactional(rollbackFor = BusinessException.class, noRollbackFor = IgnorableException.class) 让 BusinessException 回滚、IgnorableException 不回滚。配置时以最具体匹配为准。

rollbackFor/noRollbackFor 是"默认规则的覆盖旋钮",用于精确表达业务对回滚的诉求。理解默认语义才能正确使用这两个属性。

#
★★

13. Spring 事务的传播行为(PROPAGATION_REQUIRED/REQUIRES_NEW/NESTED)在嵌套调用中的差异

请说明 PROPAGATION_REQUIRED、PROPAGATION_REQUIRES_NEW、PROPAGATION_NESTED 在嵌套调用中的差异?

  • REQUIRED 加入外层事务
  • REQUIRES_NEW 新建独立事务
  • NESTED 保存点嵌套事务

在嵌套调用中:PROPAGATION_REQUIRED 加入外层事务,内层回滚会标记整个事务为 rollback-only(可能影响外层);PROPAGATION_REQUIRES_NEW 挂起外层事务并新建独立事务,内层独立提交/回滚,互不影响;PROPAGATION_NESTED 在外层事务内基于保存点(savepoint)嵌套,内层回滚只回滚到保存点,不影响外层事务外层仍可提交。三者的核心差异是"是否共享事务、内层回滚是否影响外层":REQUIRED 共享、REQUIRES_NEW 独立、NESTED 保存点隔离。

嵌套调用差异集中在"内层回滚对外层的影响"。REQUIRED 全链回滚、REQUIRES_NEW 完全隔离、NESTED 保存点局部回滚,是选择传播行为的关键。

#
★★

14. Spring 事务的隔离级别(READ_COMMITTED/REPEATABLE_READ/SERIALIZABLE)

请说明 READ_COMMITTED、REPEATABLE_READ、SERIALIZABLE 三种隔离级别?

  • 三种隔离级别的定义
  • 各自防御的并发问题
  • 性能与一致性权衡

READ_COMMITTED(读已提交)只允许读取已提交数据,防止脏读,但可能产生不可重复读与幻读;REPEATABLE_READ(可重复读)保证同一事务内多次读取结果一致,防止脏读与不可重复读,但可能产生幻读(MySQL 默认并借助 MVCC 定义);SERIALIZABLE(串行化)最严格,通过对所有读加锁等方式防止脏读、不可重复读、幻读,但并发与性能最低。三者在"一致性"与"性能"之间权衡:从 READ_COMMITTED 到 SERIALIZABLE 一致性递增、性能递减。数据库默认通常为 READ_COMMITTED(Oracle)或 REPEATABLE_READ(MySQL)。

隔离级别是并发一致性与性能的平衡点。理解各级别防御的并发问题(脏读/不可重复读/幻读)及其代价,是事务配置的要点。

#
★★

15. TransactionTemplate 编程式事务的工程应用

请说明 TransactionTemplate 编程式事务的工程应用场景与用法?

  • TransactionTemplate 封装编程式事务
  • execute 回调中定义事务边界
  • 适用场景(精细控制、非 Bean 方法)

TransactionTemplate 是 Spring 提供的编程式事务模板,封装了 PlatformTransactionManager 的开启、提交、回滚逻辑。通过 execute 方法传入 TransactionCallback,回调内执行业务逻辑,异常时自动回滚;也可通过 TransactionStatus 手动 setRollbackOnly。适用场景:需要代码级精细控制事务边界(如事务内部分支回滚)、方法为非 Spring Bean 或自调用导致 @Transactional 失效、需要动态决定事务行为等。相比 @Transactional 更灵活但侵入代码。

TransactionTemplate 适合"声明式无法覆盖"的场景,如自调用失效、动态批量事务。它把事务控制显式化,权衡灵活性与代码侵入。

transactionTemplate.execute(status -> {
    try {
        repo.save(a);
        repo.save(b);
        return null;
    } catch (Exception e) {
        status.setRollbackOnly();
        throw e;
    }
});
#
★★

16. 事务与虚拟线程(synchronized 锁)的协作

请说明事务与虚拟线程(synchronized 锁)协作时的注意事项?

  • 虚拟线程下 synchronized 可能钉住载体线程
  • 事务持有连接与锁的交互
  • 避免阻塞与死锁

在虚拟线程下,synchronized 块若包含阻塞操作(如数据库访问、IO),可能"钉住"载体线程(pinning),使虚拟线程无法让出,消耗平台线程资源。事务通常伴随着数据库连接与锁,若在事务内用 synchronized 包住数据库操作,会同时持锁与持连接,可能引发锁等待与死锁,且放大虚拟线程钉住问题。协作建议:虚拟线程下尽量用 ReentrantLock 等可释放锁替换 synchronized(避免钉住),事务内避免长时间持锁与跨线程锁交互,事务资源及时释放,避免锁顺序不当导致死锁。

事务 + synchronized 的碰撞点在于"虚拟线程钉住"与"持锁持连接导致死锁"。用可重入锁替代 synchronized、控制事务内持锁时长是规避要点。

#
★★

17. 事务与锁顺序(避免死锁)的工程实践

请说明事务与锁顺序(避免死锁)的工程实践?

  • 死锁的产生(锁顺序不一致)
  • 统一锁顺序规避死锁
  • 事务内锁与数据库锁的协调

死锁常因多个事务以不同顺序获取多个锁(如 A 先锁 x 再锁 y,B 先锁 y 再锁 x)产生。工程实践:统一全局锁获取顺序(按固定顺序如资源 ID 排序后加锁),避免交叉加锁;缩小事务内持锁范围,事务尽量短;避免在事务内调用外部服务或长时间等待;使用锁超时与数据库锁等待超时;对批量操作按相同顺序处理。配合数据库死锁检测与重试,降低死锁影响。

死锁的本质是"锁顺序不一致 + 互相等待"。统一锁顺序、缩短事务、避免长持锁是规避死锁的三大原则。

#
★★

18. 事务事件(@TransactionalEventListener)与业务解耦

请说明 @TransactionalEventListener 事务事件如何实现业务解耦?

  • @TransactionalEventListener 监听事务事件
  • 事务提交后执行(AFTER_COMMIT 等)
  • 业务解耦与补偿

@TransactionalEventListener 用于监听事务提交/回滚等事件,通过 phase 属性指定监听时机(AFTER_COMMIT、AFTER_ROLLBACK、AFTER_COMPLETION、BEFORE_COMMIT)。默认在事务提交后执行(AFTER_COMMIT),用于在事务成功提交后触发后续异步操作(如发通知、消息、索引更新),实现业务解耦。若事务回滚,监听默认不执行(fallbackExecution=true 可强制在无事务时执行)。典型场景:订单提交后发短信、更新冗余存储,事务成功后再执行,避免部分成功的数据不一致。

@TransactionalEventListener 把"事务提交"与"后续副作用"解耦:事务成功才执行副作用,避免"事务回滚但副作用已执行"的不一致,是事件驱动与事务结合的经典模式。

#
★★

19. 内层事务回滚标记(rollback-only)如何导致外层 UnexpectedRollbackException,如何避免与排查

请说明内层事务回滚标记(rollback-only)如何导致外层 UnexpectedRollbackException,以及如何避免与排查?

  • PROPAGATION_REQUIRED 内层回滚标记外层 rollback-only
  • 外层捕获内层异常后仍提交导致 UnexpectedRollbackException
  • 避免与排查

当内层方法使用 REQUIRED 传播(共享外层事务)且发生异常导致回滚,会标记整个事务为 rollback-only;若外层捕获了内层异常并当作正常流程继续,最后提交时发现事务已被标记 rollback-only,只能被迫回滚并抛出 UnexpectedRollbackException。这是"内层已回滚但外层仍试图提交"的矛盾。避免方式:内层需要独立回滚的用 REQUIRES_NEW 或 NESTED;外层不要捕获内层异常继续提交;或让内层异常向上传播。排查时看 UnexpectedRollbackException 与 rollback-only 标记来源。

UnexpectedRollbackException 是"共享事务下内层回滚标记 + 外层继续提交"的产物。避免的关键是明确传播边界,不要让外层吞掉已标记回滚的内层异常。

#
★★

20. 事务传播与 Spring 事件可靠投递

请说明事务传播与 Spring 事件可靠投递的关系?

  • 事务内发布事件与事务提交的关系
  • @TransactionalEventListener 的可靠投递
  • 事件投递与事务一致性的权衡

Spring 事件默认在发布时同步投递,若在事务内发布事件,事件处理可能在事务提交前执行,若事务回滚则事件处理已执行造成不一致。@TransactionalEventListener 通过 AFTER_COMMIT 等阶段把事件投递推迟到事务提交后,保证"事务成功才投递事件",实现可靠投递与一致性。可靠投递的边界:AFTER_COMMIT 事件在提交后触发,但若事件监听器本身失败,事务已提交无法回滚,需配合补偿(如消息队列、重试)实现最终一致。事务传播影响事件触发时机,需结合事务边界设计。

事件可靠投递的核心是"与事务提交对齐"——事务提交后才投递,避免"回滚了事件还执行"。@TransactionalEventListener 正是为此设计,但需处理监听器自身失败。

#
★★

21. 事务同步(TransactionSynchronization)与回调

请说明 Spring 事务同步(TransactionSynchronization)与回调机制?

  • TransactionSynchronization 接口
  • 事务提交/回滚/完成回调
  • 注册同步与事务资源管理

TransactionSynchronization 是 Spring 事务同步回调接口,允许在事务生命周期各阶段(afterCommit、afterRollback、afterCompletion、beforeCommit、beforeCompletion)执行自定义逻辑。通过 TransactionSynchronizationManager.registerSynchronization 注册同步回调,事务提交/回滚/完成时触发。它用于在事务边界执行与事务一致的副作用(如清理缓存、发布事件、记录审计),且能感知事务状态(提交或回滚)。@TransactionalEventListener 底层即利用事务同步机制实现。

TransactionSynchronization 提供"事务生命周期钩子",让业务逻辑在事务提交/回滚时执行一致的动作,是事务与业务副作用协调的底层机制。

#
★★

22. 事务在 Spring Modulith 下的边界

请说明事务在 Spring Modulith 下的边界与约束?

  • Spring Modulith 的模块化与事件
  • 模块间事务边界
  • 事务应限制在模块内

Spring Modulith 强调模块化,模块间通过事件通信而非直接调用。事务边界方面,事务应限制在单个模块内部(模块内 Service 方法),因为模块间通过异步事件(@TransactionalEventListener)交互时,跨模块事务无法保证原子性。Spring Modulith 提供模块化事件(ApplicationModuleEvents)与发布/订阅,事件在事务提交后投递,跨模块一致性通过事件驱动与最终一致实现,而非分布式事务。边界:事务是本模块内部的操作原子性,跨模块一致靠事件与补偿,不要试图跨模块共享事务。

在 Modulith 架构下,事务边界与模块边界一致——事务内聚于模块,跨模块靠事件与最终一致。这是模块化 + 事务的实践边界。

#
★★

23. 事务失效的常见场景(异常被吞、私有方法、线程切换)

请说明事务失效的常见场景,包括异常被吞、私有方法、线程切换等?

  • 异常被吞导致不回滚
  • private/非 public 方法无法被代理
  • 线程切换导致事务上下文丢失

事务失效的常见场景:异常被吞(方法内 try-catch 捕获异常未抛出,Spring 无法感知回滚);private 或非 public 方法(AOP 代理无法拦截,事务不生效);final 方法(CGLIB 无法覆写);自调用(this.xxx() 绕过代理);线程切换(事务内新开线程执行 SQL,事务上下文不随线程传播);类未被 Spring 管理(非 Bean);传播配置错误。总之,事务失效多因"代理不起作用"或"异常未传到 Spring"。

事务失效的根因归为两类:代理失效(自调用、private、final、非 Bean)与异常未暴露(异常被吞、跨线程)。把握这两点即可排查多数失效问题。

#
★★

24. 事务方法中调用 Thread.sleep 的边界

请说明事务方法中调用 Thread.sleep 的边界与风险?

  • Thread.sleep 阻塞事务线程多久
  • 长事务风险(连接占用、锁持有)
  • 替代方案

在事务方法中调用 Thread.sleep 会延长事务持有时间,使数据库连接被长期占用、锁长期持有,可能引发连接池耗尽、锁等待与死锁,以及 UNDO 膨胀。这是长事务的通病。边界:事务内不应做任何长时间阻塞(sleep、外部调用、循环),事务应保持短小。若必须等待,应放在事务外(先提交再 sleep),或使用更合理的异步/重试机制。虚拟线程下 sleep 虽不占平台线程,但仍占用数据库连接与锁,风险不变。

Thread.sleep 在事务内相当于"人为制造长事务",放大连接占用与锁持有风险。事务应短小,等待逻辑应移出事务。

#
★★

25. 嵌套事务(PROPAGATION_NESTED)与保存点的关系

请说明 PROPAGATION_NESTED 嵌套事务与保存点(Savepoint)的关系?

  • NESTED 基于保存点实现
  • 内层回滚只回滚到保存点
  • 与 REQUIRES_NEW 的区别

PROPAGATION_NESTED 利用数据库保存点(Savepoint)实现嵌套事务:内层方法在事务内建立保存点,若内层回滚,只回滚到保存点,外层事务主体不受影响,仍可继续提交。因此 NESTED 提供"部分回滚"能力,与 REQUIRES_NEW(完全独立事务)不同——NESTED 共享外层事务的底层连接与资源,只是回滚范围由保存点划分。外层回滚时,内层保存点回滚的部分也会一并回滚。NESTED 依赖数据库保存点支持(如 MySQL/PostgreSQL 支持,某些数据库不支持)。

NESTED 的本质是"保存点划分的回滚边界"——共享外层事务但内层可局部回滚。理解保存点机制即可区分 NESTED 与 REQUIRES_NEW。

#
★★

26. @Transactional 在 Spring 6.x 的 Propagation 演进

请说明 @Transactional 的在 Spring 6.x 中 Propagation 传播行为的演进?

  • Propagation 枚举在 Spring 6 的演进
  • 传播行为语义的稳定与调整
  • 与 Reactor 的配合

Spring 6.x 中 Propagation 的语义基本保持稳定(REQUIRED、REQUIRES_NEW 等 7 种不变),主要演进是增强了对响应式(Reactive)事务的支持,以及 Constrains 相关 API 的清晰化。Spring 6 中 @Transactional 的传播行为在响应式编程(如 R2DBC)下的应用需注意,因为响应式事务依赖 TransactionalOperator 而非 ThreadLocal 绑定。整体上 Propagation 枚举本身未变,语义稳定,演进集中在与响应式、新框架的适配。开发者应关注传播语义在响应式与传统阻塞下的差异。

Spring 6 的 Propagation 演进是"稳定 + 适配"——枚举语义不变,重点是与响应式事务、新编程模型的适配。把握传播语义的核心即可。

#
★★

27. @Transactional(propagation = REQUIRES_NEW) 的连接占用

请说明 @Transactional(propagation = REQUIRES_NEW) 的连接占用问题?

  • REQUIRES_NEW 新建独立事务占用独立连接
  • 外层事务连接被挂起
  • 连接池耗尽风险

REQUIRES_NEW 会挂起外层事务并新建独立事务,因而需要单独获取一个数据库连接。这意味着在嵌套调用中,外层事务持有连接的同时,内层 REQUIRES_NEW 又占一个连接,双层即占用两个连接。若嵌套层次深或并发高,多个 REQUIRES_NEW 会同时占用多个连接,超出连接池上限时抛连接获取异常。因此 REQUIRES_NEW 应谨慎使用,避免大范围嵌套导致连接池耗尽。相比 REQUIRED(共享外层连接)更耗连接资源。

REQUIRES_NEW 的代价是"每层独立事务占用独立连接",嵌套加深会放大连接占用。评估连接池容量与嵌套深度是使用 REQUIRES_NEW 的前提。

#
★★

28. @Transactional(timeout = ...) 的边界

请说明 @Transactional(timeout = ...) 的边界与作用?

  • timeout 设置事务超时时间
  • 超时则回滚并抛异常
  • 与数据库锁等待超时区别

@Transactional(timeout = ...) 设置事务超时时间(秒),若事务执行超过该时间,Spring 会强制回滚并抛出 TransactionTimedOutException。其作用是防止长事务长时间占用连接与锁。边界:timeout 是事务级超时,与数据库的锁等待超时(lock wait timeout)不同——前者是事务总时长限制,后者是等锁的最长时间。超时后事务回滚释放资源。合理设置 timeout 可兜底长事务。

timeout 是"事务总时长上限",超时强制回滚,是长事务的兜底防控。区别于数据库锁等待超时,两者防护维度不同。

#
★★

29. 事务在 GraalVM Native Image 下的限制

请说明事务在 GraalVM Native Image 下的限制?

  • 反射与动态代理在 Native 下的限制
  • 事务 AOP 代理需 AOT 元数据
  • AOT 适配事务

在 GraalVM Native Image 下,事务依赖的 AOP 动态代理(JDK/CGLIB)与反射需要提前生成元数据(proxy-config、reflect-config)。Spring AOT 引擎会扫描并生成这些元数据,使 @Transactional 代理在 Native 下可用。限制:CGLIB 的运行时字节码增强在 Native 中受限,需依赖接口代理或 AOT 生成的代理类;动态反射(如动态解析事务属性)需显式配置。总体上,通过 Spring AOT 的支持,事务可在 Native 下工作,但需保证代码静态可分析、避免过度动态反射。

事务在 Native 下的限制源于"动态代理与反射"与 AOT 静态模型的冲突。Spring AOT 提供元数据支持,但代码需静态可分析。

#
★★

30. @Transactional 的 AOP 代理机制(JDK 动态代理 vs CGLIB)与自调用、final 方法失效的关系

请说明 @Transactional 的 AOP 代理机制(JDK 动态代理 vs CGLIB)与自调用、final 方法失效的关系?

  • JDK 动态代理(接口)与 CGLIB(子类)
  • 自调用绕过代理导致失效
  • final 方法无法被 CGLIB 覆写

@Transactional 通过 AOP 代理实现,Spring 4+ 默认使用 CGLIB(子类继承)代理,也支持 JDK 动态代理(基于接口)。代理机制决定失效边界:自调用(this.xxx())调用的是目标对象而非代理,代理拦截不到,事务失效;final 方法无法被 CGLIB 子类覆写,事务失效;private 方法无法被代理拦截,失效。JDK 动态代理要求类实现接口,只代理接口方法。因此事务生效依赖"通过代理对象调用可覆写的 public 方法"。

事务失效根因是"代理机制的限制"——自调用绕过代理、final 不可覆写、private 不可拦截。理解代理机制即可理解事务失效全集。

#
★★

31. 大事务/长事务的危害(连接占用、锁持有、UNDO 膨胀)与拆分策略

请说明大事务/长事务的危害(连接占用、锁持有、UNDO 膨胀)与拆分策略?

  • 长事务危害:连接占用、锁持有、UNDO 膨胀
  • 拆分策略
  • 事务里做太多操作的风险

大事务/长事务的危害:连接占用(长时间占用数据库连接,导致连接池耗尽);锁持有(长时间持有行/表锁,阻塞其他事务,引发锁等待与死锁);UNDO 膨胀(长事务使 undo log 无法及时清理,占用存储并影响回滚速度)。此外还会导致数据版本堆积、慢查询。拆分策略:把大事务拆成多个小事务(如批量逐批提交);把非事务操作(外部调用、文件处理)移出事务;用分页/批处理减少单事务数据量;用 REQUIRES_NEW 隔离独立步骤;控制事务内只做必要的 DB 操作。

长事务的三大危害是连接、锁、UNDO。拆分策略围绕"事务短小、非 DB 操作移出、批量处理"展开,是事务性能与稳定性的关键。

#

32. 事务失效场景汇总,自调用、非 public 方法、异常被吞、propagation 配置错误等如何?

请汇总事务失效的常见场景,包括自调用、非 public 方法、异常被吞、propagation 配置错误等?

  • 自调用、非 public、final 方法
  • 异常被吞、propagation 配置错误
  • 类未被 Spring 管理、线程切换

事务失效的常见场景汇总:自调用(this.xxx() 绕过代理);非 public 方法(代理无法拦截 private/protected);final 方法(CGLIB 无法覆写);异常被吞(try-catch 未抛出,Spring 无法感知回滚);propagation 配置错误(如 REQUIRES_NEW 与 REQUIRED 混用导致事务边界不符合预期);类未被 Spring 管理(非 Bean,无代理);线程切换(新线程执行 SQL,事务上下文不传播);DataAccessException 未正确抛出。排查时先确认"代理是否生效 + 异常是否传到 Spring"。

事务失效可归为"代理失效"与"异常未暴露"两大类。按此框架可系统排查所有失效场景,避免遗漏。

#

33. @Transactional 的 rollbackFor 与默认回滚规则,为什么运行时异常才默认回滚?

请说明 @Transactional 的 rollbackFor 与默认回滚规则,为什么默认只有运行时异常才回滚?

  • 默认回滚规则:RuntimeException 回滚、Checked 不回滚
  • 设计哲学:受检异常是业务预期
  • rollbackFor 覆盖

默认回滚规则是:抛出 RuntimeException(和 Error)时事务回滚,抛出 Checked Exception(受检异常)时事务提交。原因:受检异常通常是业务可以预期并处理的异常(如校验失败、业务规则违反),业务可能希望保留已做的操作;而运行时异常代表未预期的系统级错误(如 NPE、异常),应回滚保证数据一致性。这一设计遵循"预期异常走业务逻辑、未预期异常回滚"的哲学。rollbackFor 可指定受检异常也回滚,覆盖默认行为。

默认只回滚运行时异常,是"受检=业务预期、运行时=系统错误"的设计取舍。理解哲学,才能正确用 rollbackFor 覆盖。