JPA中@Transactional方法执行后触发器生成字段无法同步问题
核心原因
你遇到的问题本质是JPA(Hibernate)**一级缓存(Session缓存)**的特性导致的:
当在@Transactional方法内执行saveAndFlush()时,数据会被刷入数据库,触发器也会生成column4的值,但Session缓存里的实体对象还是保存着插入时的快照(column4为null)。此时调用findById()会直接返回缓存里的旧数据,不会去数据库读最新值;而单独调用查询API时,是新的Session,自然能拿到数据库里的真实数据。
另一张表因为触发器函数执行快就正常,其实是巧合——它同样存在缓存不一致的隐患,只是某些场景下缓存被意外刷新了,不是可靠的解决方式。
针对你的疑问逐一解答
1. @Transactional是否会执行提交操作?
会。@Transactional默认在方法正常执行完成后自动提交事务;如果方法抛出未捕获的非受检异常(可通过rollbackFor/noRollbackFor配置规则),则会触发回滚。
2. 带有@Transactional注解的方法执行完成时,事务提交是否已完成?
是的。Spring事务管理器会在@Transactional方法执行完毕(不管是正常返回还是异常终止)后,处理事务的提交或回滚。但要注意:如果在事务方法内部调用其他非事务方法,只要没触发新的事务传播,所有操作都在同一个事务里,直到当前事务方法结束才会提交。
3. 为何执行findById时,实体记录与数据库记录不一致?
这是JPA一级缓存的优化机制:
- 调用
saveAndFlush()后,数据库里的数据已经更新,但Session缓存中的实体还是插入时的状态(column4为null)。 findById()会先检查Session缓存里有没有对应实体,有就直接返回,不会去数据库重新查询。- 单独调用查询API时,是新的请求上下文,Session缓存是空的,所以会从数据库读取最新数据。
可行解决方案
方案1:手动刷新实体,强制同步数据库数据
在saveAndFlush()之后,用EntityManager.refresh()强制从数据库拉取最新数据,更新缓存里的实体:
@Service public class MyServiceImpl implements MyService{ @Autowired MyTableRepository myTableRepo; @PersistenceContext private EntityManager entityManager; // 注入EntityManager @Override @Transactional public Long saveRecord(MyTableDto myTableDto) { MyTableEntity myTableEntity, storedDetails; Long returnValue; myTableEntity = convertToEntity(myTableDto); storedDetails = myTableRepo.saveAndFlush(myTableEntity); // 刷新实体,获取触发器生成的column4 entityManager.refresh(storedDetails); returnValue = storedDetails.getColumn4(); return returnValue; } }
方案2:事务提交后再查询
利用@Transactional方法执行完毕自动提交的特性,把查询操作放在事务方法外面。你的Controller代码其实已经是这么写的——只要saveRecord()方法执行完,事务就提交了,此时调用getRecord()就能拿到最新数据:
@PostMapping public MyTableDto saveRecord (@RequestBody MyTableDto myTableDto) throws MyException { //.. validations .. // Long pk = myService.saveRecord(myTableDto); // 事务在此处提交 if (pk == null) throw new MyException("Error in saving Request for Quotation Package"); MyTableDto returnValue = myService.getRecord(pk); // 此时查询的是数据库最新数据 return returnValue; }
这种方式不需要修改服务层代码,是最简洁的解决方案。
方案3:禁用实体一级缓存(不推荐)
可以给实体类加@Cacheable(false)或者设置缓存策略为NONE,但这会牺牲JPA的缓存优化,影响性能,不建议在高频操作的实体上使用。
关键注意事项
saveAndFlush()只是把数据刷入数据库,不会自动刷新Session缓存里的实体。- 你已经正确把
column4标记为insertable=false, updatable=false,这很重要,避免JPA尝试修改这个由触发器维护的字段。 - 事务内优先用缓存是JPA的默认优化,不是BUG,需要根据业务场景手动处理缓存同步。
内容的提问来源于stack exchange,提问作者A Aragon

