Spring 事务管理与数据访问详解

Spring 事务管理与数据访问详解

定位:讲透 Spring 事务抽象、七种传播行为、隔离级别与回滚规则、事务失效全景与 JdbcTemplate 数据访问

适用版本:Spring Framework 6.x(JDK 17+)

说明:本篇侧重事务语义;数据库隔离级别原理归数据库方面的知识


目录


一、事务抽象

1.1 统一事务编程模型

底层事务 API 各不相同(JDBC 的 Connection、JPA 的 EntityTransaction、JTA 的 UserTransaction),Spring 用 PlatformTransactionManager 统一抽象:

复制代码
PlatformTransactionManager
├── DataSourceTransactionManager   ← JDBC/MyBatis
├── JpaTransactionManager          ← JPA/Hibernate
└── JtaTransactionManager          ← 分布式/XA

业务代码面向统一接口,换持久层技术不改事务代码。

1.2 声明式与编程式

方式 用法 适用
声明式 @Transactional(AOP 织入,FW-03) 主流:方法级事务
编程式 TransactionTemplate 需要精确控制范围(如循环内分段提交、只包一小段)
java 复制代码
transactionTemplate.execute(status -> {
    // 事务范围仅此 lambda
    return dao.update(...);
});

TransactionTemplate 常被忽视的价值:把事务范围收到最小------大方法里只有两行需要事务时,用它比整个方法 @Transactional 更合理(长事务是性能杀手)。

1.3 事务定义四要素

复制代码
传播行为(与已有事务的关系)
隔离级别(并发可见性)
超时(超时自动回滚,防长事务)
只读(只读优化提示)

二、传播行为

传播行为回答:"方法被调用时已有事务怎么办?"七种,按三组记忆:

2.1 支持当前事务(最常用)

行为 有事务 无事务
REQUIRED(默认) 加入 新建
SUPPORTS 加入 非事务执行
MANDATORY 加入 抛异常

2.2 另起事务/挂起

行为 行为
REQUIRES_NEW 挂起当前,新建独立事务(独立提交/回滚)
NOT_SUPPORTED 挂起当前,非事务执行
NEVER 有事务则抛异常

2.3 嵌套

行为 语义
NESTED 在当前事务内开保存点(savepoint),嵌套部分可独立回滚,但整体提交仍受外层控制

2.4 REQUIRES_NEW 与 NESTED 的辨析(高频)

复制代码
REQUIRES_NEW:两个独立事务
   内层提交后不受外层回滚影响(已独立提交)
   典型:操作日志/审计------主业务回滚了日志也要留

NESTED:一个事务 + 保存点
   内层回滚只回滚到保存点;外层回滚则一起回滚
   外层提交前,内层修改并未真正提交

一句话:独立提交选 REQUIRES_NEW,部分可撤销选 NESTED。

2.5 传播行为的常见坑

  • SUPPORTS + 写操作:无事务时没有原子性保证,写操作用它等于裸奔;
  • REQUIRES_NEW 内部调用同类方法仍可能自调用失效(还是代理问题);
  • 只读事务(readOnly = true)里做写操作,部分实现会拒绝/无优化效果。

三、隔离级别与回滚规则

3.1 隔离级别

复制代码
默认:沿用数据库默认(MySQL InnoDB 为 REPEATABLE_READ)
显式:@Transactional(isolation = Isolation.READ_COMMITTED)

常见选择:多数互联网业务用 READ_COMMITTED(防脏读,性能与一致性平衡);依赖间隙锁防幻读的场景保持 RR。隔离级别原理(脏读/不可重复读/幻读)归数据库知识。

3.2 回滚规则(必记)

复制代码
默认:只回滚 RuntimeException 与 Error
受检异常(IOException 等)默认【不回滚】,事务照常提交
定制:
  @Transactional(rollbackFor = Exception.class)      ← 常用,所有异常都回滚
  @Transactional(noRollbackFor = BizWarnException.class) ← 特定异常不回滚

建议默认配置 rollbackFor = Exception.class------"业务失败就回滚"符合直觉,避免受检异常导致的数据不一致。

3.3 事务内异常的正确姿势

复制代码
要回滚:让异常抛出事务方法(或在 catch 里手动 setRollbackOnly)
要吞掉但要回滚:
    try { ... } catch (Exception e) {
        TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
        // 再返回降级结果
    }

吞异常又不标记回滚 = 数据不一致的温床。


四、事务失效场景

汇总:

场景 根因类别
同类自调用 代理:绕过代理对象
方法非 public 代理:默认只拦 public
异常被吞/受检异常未配 回滚规则
静态方法、final 方法 代理:拦不到
数据库引擎不支持(MyISAM) 基础设施
传播行为误用(SUPPORTS 写) 语义误用
调用方不在 Spring 管理 代理:对象不是 Bean

排查顺序:先看是否经代理(自调用/非 Bean)→ 再看回滚规则 → 再看传播行为与引擎。


五、JdbcTemplate 数据访问

5.1 定位

JDBC 的薄封装:免手动关资源(try-with-resources 由框架处理)、参数绑定防注入、异常翻译。适合简单 SQL、运维脚本、不适合/不想上 ORM 的场景。

5.2 核心用法

java 复制代码
// 增删改
jdbcTemplate.update("INSERT INTO t_user(name, age) VALUES (?, ?)", name, age);

// 批量
jdbcTemplate.batchUpdate(sql, batchArgs);

// 查询
User u = jdbcTemplate.queryForObject(
        "SELECT id, name, age FROM t_user WHERE id = ?",
        new BeanPropertyRowMapper<>(User.class), id);

List<User> users = jdbcTemplate.query(
        "SELECT id, name, age FROM t_user WHERE age > ?",
        new BeanPropertyRowMapper<>(User.class), 18);

5.3 异常翻译

复制代码
SQLException(受检、厂商相关)
   ↓ PersistenceExceptionTranslator
DataAccessException 体系(非受检、统一语义)
   如 DuplicateKeyException / DeadlockLoserDataAccessException

价值:上层不再写厂商相关的 SQLException 判断,异常语义跨持久层统一。

5.4 与 ORM 的分工

场景 工具
领域对象存取、状态管理 ORM(Hibernate/JPA)
动态 SQL、复杂查询 MyBatis / jOOQ
简单 SQL、DDL、脚本、统计 JdbcTemplate

同一项目可共存(共享 DataSource 与事务管理器),见 ORM 选型篇。


六、总结

  1. 事务抽象:PlatformTransactionManager 统一 JDBC/ORM/JTA;声明式 @Transactional 为主流,TransactionTemplate 用于把事务范围收到最小。
  2. 传播行为七种三组:支持当前(REQUIRED 默认/SUPPORTS/MANDATORY)、挂起另起(REQUIRES_NEW/NOT_SUPPORTED/NEVER)、嵌套(NESTED);REQUIRES_NEW 独立提交、NESTED 保存点,是最高频辨析。
  3. 回滚规则 :默认只回滚运行时异常;受检异常要配 rollbackFor = Exception.class;吞异常必须手动 setRollbackOnly。
  4. 失效排查序:代理(自调用/非 public/非 Bean)→ 回滚规则 → 传播行为与引擎。
  5. JdbcTemplate:薄封装、免关资源、异常翻译为 DataAccessException;与 ORM 按场景分工共存。

七、常见高频面试题

1. Spring 的事务抽象是怎么设计的?

要点:PlatformTransactionManager 统一不同底层(DataSourceTransactionManager 管 JDBC/MyBatis,JpaTransactionManager 管 JPA,JtaTransactionManager 管分布式),业务代码面向统一接口,切换持久层不改事务代码。使用分声明式(@Transactional,AOP 织入,主流)与编程式(TransactionTemplate,精确控制范围)。事务定义四要素:传播行为、隔离级别、超时、只读。

2. 详细说说 Spring 的事务传播行为。

要点:七种分三组。支持当前事务:REQUIRED(默认,有则加入无则新建)、SUPPORTS(有则加入无则非事务)、MANDATORY(必须有否则抛异常);挂起另起:REQUIRES_NEW(挂起当前新建独立事务)、NOT_SUPPORTED(挂起当前非事务执行)、NEVER(有事务则抛异常);嵌套:NESTED(保存点)。最常用 REQUIRED 与 REQUIRES_NEW;SUPPORTS 配写操作是典型误用。

3. REQUIRES_NEW 和 NESTED 的区别?

要点:REQUIRES_NEW 是真正的两个独立事务------挂起外层新开事务,内层提交后不受外层回滚影响,典型场景是审计日志(主业务回滚日志也要留)。NESTED 仍是一个事务------在当前事务内开保存点,嵌套部分可独立回滚到保存点,但外层回滚会连它一起回滚,外层提交前内层修改未真正提交。记忆:独立提交选 REQUIRES_NEW,部分可撤销选 NESTED。

4. Spring 事务默认对哪些异常回滚?怎么改?

要点:默认只回滚 RuntimeException 与 Error;受检异常(如 IOException、自定义 extends Exception)默认不回滚,事务照常提交------这是数据不一致的常见来源。定制:@Transactional(rollbackFor = Exception.class) 让所有异常回滚(推荐作为默认姿势),或 noRollbackFor 指定例外。吞异常的场景要手动 TransactionAspectSupport.currentTransactionStatus().setRollbackOnly()。

5. @Transactional 失效的场景有哪些?

要点:① 同类自调用绕过代理;② 方法非 public;③ 异常被吞或受检异常未配回滚;④ 静态方法、final 方法代理拦不到;⑤ 数据库引擎不支持事务(MyISAM);⑥ 传播行为误用(如 SUPPORTS 配写操作);⑦ 对象不是 Spring Bean。排查顺序:先代理问题(自调用/非 public/非 Bean),再回滚规则,再传播行为与引擎。

6. 什么场景用编程式事务(TransactionTemplate)?

要点:需要精确控制事务范围时。典型:大方法中只有几行需要事务(避免整方法 @Transactional 造成长事务);循环中分段提交(每批一个事务,避免单事务过大);条件性地开启事务。编程式把事务边界显式写进代码,比声明式更灵活可控;但日常业务仍以声明式为主。

7. 长事务有什么危害?如何避免?

要点:长事务持有连接与行锁时间长,导致连接池耗尽、锁等待扩散、主从延迟加大、超时连锁。避免:事务方法内不做远程调用与耗时计算(移出事务或先准备后落库);用 TransactionTemplate 把范围收到最小;拆分大批量为分段提交;设置超时(timeout)兜底。事务边界应与业务原子操作对齐,短而原子。

8. 只读事务(readOnly)有什么作用?

要点:声明事务只读,框架与底层可做优化:如 Hibernate 场景下实体不进脏检查、数据库可能路由到从库或优化锁策略。注意它是"提示"不是强制:只读事务里写数据,不同实现表现不一(报错/忽略/照写);读写混合方法不要标只读。读多场景(报表、查询服务)开启可获得真实收益。

9. JdbcTemplate 相比原生 JDBC 好在哪?

要点:① 免资源管理:连接/语句/结果集的获取与关闭由框架处理;② 参数绑定防 SQL 注入;③ 异常翻译:厂商 SQLException 统一转为 DataAccessException 非受检体系(如 DuplicateKeyException),上层无需厂商判断;④ 提供 RowMapper/BeanPropertyRowMapper 简化映射、batchUpdate 批量。定位是轻封装,不做对象状态管理,与 ORM 按场景分工。

10. 一个事务方法调用另一个事务方法,传播行为如何生效?

要点:传播行为只在"经过代理调用"时生效。外部调用 B 的 @Transactional 方法:B 的代理拦截,按其传播行为决定加入当前事务还是新建(如 REQUIRED 加入、REQUIRES_NEW 挂起新建)。若 A 同类内部调用 B,绕过代理,B 的事务注解完全失效(等于普通方法在 A 的事务里执行)。跨类调用还要注意:B 抛异常回滚后若被 A 捕获不再抛出,A 的事务可能已标脏(同一事务时抛 TransactionSystemException/UnexpectedRollbackException)。

相关推荐
萧瑟余晖6 小时前
Spring 事件机制与条件装配详解
spring
Wx-bishekaifayuan7 小时前
springboot陨石鉴收系统96265-计算机课程设计、毕业设计
java·vue.js·spring boot·后端·python·spring·课程设计
x-Achan8 小时前
Redis实现方案:更新策略、穿透、雪崩、击穿、工具封装
java·spring boot·redis·spring·bootstrap·mybatis
步行cgn19 小时前
Spring 全注解开发详解
java·后端·spring
ZealSinger1 天前
Spring7 RestTemplate弃用迁移
java·spring·后端开发·restclient
萧瑟余晖1 天前
Spring 资源与环境配置体系详解
spring
不才不才不不才1 天前
Spring 源码系列(29): 20 道高频面试题源码级解析合集
java·后端·spring
卓怡学长1 天前
w192基于springboot“考研情报站”微信小程序设计与实现
java·spring boot·spring·微信小程序·intellij-idea
步行cgn1 天前
Spring AOP 切面的先后顺序详解
java·spring