如何在同一事务中获取flush()后的更新对象(Hibernate/Spring Boot)
问题解答:JPQL Update后查询返回旧数据的原因与解决办法
这当然是可以实现的!你遇到的这个问题其实是Hibernate持久化上下文(也就是一级缓存)的特性导致的,我来给你拆解清楚:
为什么Delete正常,Update却不行?
- 当你调用
executeUpdate()执行JPQL DELETE时,Hibernate除了向数据库发送删除SQL,还会把EntityManager缓存里对应的实体标记为removed状态。所以后续查询时,Hibernate会自动过滤掉这些已标记删除的对象,你看到的结果就是正确的。 - 但执行JPQL UPDATE时,Hibernate只会直接向数据库发送更新SQL,不会同步更新已经加载在EntityManager缓存里的实体对象。哪怕你调用了
em.flush(),也只是把EntityManager中原本的实体变更(比如你手动set的属性)同步到数据库,根本管不到这次绕过缓存的JPQL Update操作。所以后续查询会优先从缓存里取旧的对象数据,自然看不到更新后的结果。
解决办法(按推荐程度排序)
1. 清空EntityManager缓存(最适合批量更新场景)
在em.flush()之后调用em.clear(),直接清空整个一级缓存。这样后续的JPA查询就会从数据库重新加载最新数据,自然能拿到更新后的结果。
修改后的代码示例:
@Transactional void test() { //... em.createQuery("delete from StorefrontProduct sp where sp in (:storefrontProducts)") .setParameter("storefrontProducts", storefrontProductsToDelete) .executeUpdate(); // example em.createQuery("update StorefrontProduct sp set sp.orderIndex=0 where sp.id=90") .executeUpdate(); em.flush(); em.clear(); // 新增这行,清空一级缓存 //Simple JPA query List<StorefrontProduct> result = repository.findAllByPreviousOrderIndexIsNotNull(); //additional code.... }
注意:
clear()会清空所有缓存的实体,如果后续代码还要用到之前加载的实体,需要重新通过find()或merge()获取。对于你提到的10000个对象的批量更新场景,这是最省事的方案。
2. 手动更新缓存中的实体(适合少量实体更新)
如果你明确知道哪些实体被更新了,可以手动修改缓存里的实体属性,让它和数据库状态保持一致。比如针对你的示例:
// 在executeUpdate之后,手动找到缓存里的实体并更新 StorefrontProduct updatedProduct = em.find(StorefrontProduct.class, 90); if (updatedProduct != null) { updatedProduct.setOrderIndex(0); } em.flush();
这种方法适合单个或少量实体的更新,批量更新时操作成本太高,不推荐。
3. 强制刷新单个实体(适合已知具体实体的场景)
如果能拿到被更新的实体实例,可以调用em.refresh(entity)强制从数据库重新加载该实体的最新状态:
StorefrontProduct product = em.find(StorefrontProduct.class, 90); em.createQuery("update StorefrontProduct sp set sp.orderIndex=0 where sp.id=90") .executeUpdate(); em.flush(); em.refresh(product); // 刷新该实体的状态
这样后续使用这个实体时,就是最新的数据了,但同样只适合少量实体的情况。
4. 拆分事务(不推荐,除非必要)
把Update操作和查询操作放到不同的事务中,事务结束后EntityManager缓存会被自动清空,下一个事务的查询就会从数据库获取最新数据。但这种方式会增加事务管理的复杂度,可能带来一致性问题,一般不建议使用。
内容的提问来源于stack exchange,提问作者Michael Bat
相关产品推荐
相关产品推荐

