Spring Data JPA save后未flush,原生更新为何能命中刚存实体?
关于Spring Data JPA中save与saveAndFlush的行为差异、测试用例预期不符的问题解析
核心结论
你测试用例能通过,不是因为save方法上的@Transactional注解提前提交了INSERT语句,本质是两个JPA运行的底层机制在起作用:
- Hibernate的自动FlushMode机制
- Repository方法上@Transactional注解的默认传播级别规则
先澄清对save与saveAndFlush的基础认知偏差
你最初的理解里,有个关键的概念混淆:SQL的执行时机和事务的提交时机是完全解耦的
- 不管是save还是saveAndFlush,都不会触发事务提交
- save:仅把实体的状态写入持久化上下文(一级缓存),不会主动触发flush动作把SQL发送到数据库,后续SQL的执行完全由flush的触发时机决定
- saveAndFlush:在把实体写入一级缓存后,主动触发一次持久化上下文的flush,把暂存的SQL同步发送到数据库执行,但依然不会提交事务
- Flush 只是把内存里暂存的SQL发到数据库执行,修改依然处在事务范围内,其他未关联的事务看不到这些数据;只有事务Commit的时候,才会把这些已执行的修改永久落盘
测试用例实际执行流程拆解
你的测试能通过,分两种常见的Spring Boot Test场景,逻辑都是一致的:
场景1:测试类没有标注@Transactional(测试方法本身无外层事务)
- 调用
classRepository.save(classA)时:- SimpleJpaRepository里的save方法标注的@Transactional,默认传播级别是
PROPAGATION_REQUIRED——因为当前没有绑定事务,所以会新建一个独立事务 - 事务执行过程中,把classA状态写入一级缓存
- 这个独立事务提交前,会强制触发一次flush,执行对应的INSERT SQL
- 事务提交,INSERT数据永久落库
- SimpleJpaRepository里的save方法标注的@Transactional,默认传播级别是
- 调用
classRepository.updateClassNative(...)时:- 这个带@Modifying的方法同样因为没有外层事务,新建独立事务
- 此时之前的INSERT已经提交落库,所以UPDATE语句能匹配到对应记录,返回影响行数1,断言自然通过
场景2:测试类标注了@Transactional(Spring Boot Test默认的测试事务场景)
- 测试方法启动时,已经开启了一个外层的测试事务(默认配置下测试结束会自动回滚,不会真的修改数据库)
- 调用
classRepository.save(classA)时:- save方法上的@Transactional检测到当前已有事务,直接加入现有事务,根本不会新建独立事务,也就不存在提前提交的可能
- 仅把classA状态写入一级缓存,不主动触发flush,此时还没有执行任何INSERT SQL
- 调用
classRepository.updateClassNative(...)时:- 这个@Modifying方法同样加入现有外层事务,不新建
- 按照Hibernate默认的
FlushMode.AUTO规则,执行任何查询(包括带@Modifying的UPDATE/DELETE语句)之前,必须先把持久化上下文里所有未同步的变更flush到数据库,避免出现内存状态和数据库查询结果不一致的问题 - 这一步会自动触发flush,把之前save的classA对应的INSERT SQL先发到数据库执行(依然在当前事务内,没有提交)。你配置的
clearAutomatically = true,只会在flush、执行完UPDATE之后清空一级缓存的实体状态,不会改变前置flush的触发逻辑 - 再执行原生UPDATE语句,此时同事务内的INSERT已经执行,所以能匹配到记录,返回影响行数1,断言通过
常见的自动Flush触发点
你不需要手动调用saveAndFlush,只要满足以下任意条件,暂存的SQL就会被自动执行:
- 事务提交前的强制flush(不管设置什么FlushMode都会执行)
- 调用带@Modifying标注的Repository查询方法前
- 执行任意SELECT查询前(按FlushMode.AUTO规则判断是否需要flush)
- 主动调用
EntityManager.flush()或saveAndFlush()方法
关于你提到的“save方法的@Transactional是不是提前提交INSERT的原因”
答案是否定的:
- save方法上的@Transactional只是为了给无外层事务的场景提供基础的事务边界,默认传播级别是REQUIRED,不会在已有外层事务的情况下新建独立事务,也就不可能提前提交INSERT
- 你观察到的测试结果,核心是Hibernate自动flush机制的作用,不是事务注解导致的提前提交
内容的提问来源于stack exchange,提问作者ashley
相关产品推荐
相关产品推荐

