You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

内存H2数据库删除实体后EntityManager.find仍返回该实体的问题

问题原因分析

这是Hibernate一级缓存(持久化上下文)的特性导致的:

  • EntityManager.find()方法会优先从**一级缓存(当前EntityManager实例维护的内存缓存)**中查找实体,只有缓存中不存在时才会去数据库查询。
  • 你使用JPQL的delete语句执行删除操作时,该操作会直接修改数据库中的数据,但不会同步更新EntityManager的一级缓存——JPQL批量操作绕过了持久化上下文的实体管理机制,缓存中依然保留着之前加载的实体对象。

在你的测试流程中:

  1. 第一次persist操作将实体存入了一级缓存;
  2. 后续的findByReportId1直接从缓存中获取实体,验证通过;
  3. 调用deleteByReportId执行JPQL删除,仅删除了数据库中的记录,但缓存里的实体并未被清除或标记为删除;
  4. 事务提交后再次调用find方法,EntityManager依然从缓存中返回旧的实体对象,导致断言失败。

而DAO的getByReportId使用JPQL查询,该查询会直接从数据库获取最新数据,因此能正确返回空结果。

解决方案

可以通过以下几种方式解决这个问题:

1. 清除EntityManager的一级缓存

在删除操作提交事务后,调用em.clear()清除缓存,这样后续的find方法会强制去数据库查询:

tx.commit();
em.clear(); // 清除一级缓存
tx.begin();

var getByReportId2 = blobInfoDao.getByReportId(reportId);
assertFalse(getByReportId2.isPresent());

BlobInfoDBVO findByReportId2 = em.find(BlobInfoDBVO.class, reportId);
assertNull(findByReportId2); // 现在会通过

2. 使用EntityManager.remove()删除实体

放弃JPQL批量删除,改为先通过find获取实体,再调用remove方法删除,这样Hibernate会同步更新一级缓存:

// 修改DAO的deleteByReportId方法
public void deleteByReportId(long reportId) {
    BlobInfoDBVO blobInfo = em.find(BlobInfoDBVO.class, reportId);
    if (blobInfo != null) {
        em.remove(blobInfo);
    }
}

这种方式会同时删除数据库记录并清除缓存中的实体,后续find方法会返回null。

3. 强制find方法从数据库刷新数据

调用find时指定刷新模式,强制绕过缓存直接查询数据库:

BlobInfoDBVO findByReportId2 = em.find(BlobInfoDBVO.class, reportId, LockModeType.READ);
// 或者设置EntityManager的刷新模式
em.setFlushMode(FlushModeType.COMMIT);
BlobInfoDBVO findByReportId2 = em.find(BlobInfoDBVO.class, reportId);

内容的提问来源于stack exchange,提问作者zolv

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 04:25:32