在 JPA 上是不是绕过托管实体

Posted

技术标签:

【中文标题】在 JPA 上是不是绕过托管实体【英文标题】:Bypass managed entities or not on JPA在 JPA 上是否绕过托管实体 【发布时间】:2015-09-29 22:29:17 【问题描述】:

为什么JPA EntityManager 执行可变查询,例如UPDATE 直接针对 DB,但选择首先针对下面的持久性上下文(又名二级缓存)执行SELECT 查询(使用Hibernate 5 测试)?

JPA spec 似乎没有规定这样做。是否只是为了在不一致的风险下获得更好的性能?

/* ... */

String name = em.find(Person.class, 1).getName();

em.getTransaction().begin();
em.createQuery("update Person set name='new' where id=1")
  .executeUpdate(); 
em.getTransaction().commit(); // DB updated but the entity not

Person p = em
  .createQuery("select p from Person p where id=1", Person.class)
  .getSingleResult(); // the stale entity returned

assertEqual(name, p.getName()); // true

【问题讨论】:

因为规范是这样说的。 UPDATE/DELETE 查询仅针对数据库,不尊重级联语义或内存实例中的字段更新 【参考方案1】:

可能出于性能原因,如果 JPQL 查询与内存中已加载到实体管理器中的任何实体匹配,则规范不需要用数据库中的当前状态刷新内存中的实体。 p>

在您的示例中,这就是在后台发生的情况 - 从数据库中检索 id 为 1 的人员实体(它现在存在于 EntityManager 缓存中) - 人名通过 JPQL 在 DB 中更新(实体保持不变) - id 为 1 的实体人将从数据库中检索 -- 但 id 为 1 的实体已存在于 EntityManager 中,因此返回此实例(名称中有旧值)- 实体未与 DB 合并。如需更多信息,另请参阅回复this question。

此行为符合 JPA 要求,即在单个 EntityManager 中,DB 中的单行不能有 2 个实体实例。解决方法只有两种,一种是将现有实体与数据库中的状态合并,另一种是忽略数据库中的数据。 JPA 规范选择了第二个选项。

如果您不检索一开始 id 为 1 的人,它将在选择查询中检索具有新值的新实体。

在实体中拥有新数据的解决方案可能是在 updateselect 查询之间使用 em.clean(),但请注意,它会从缓存中清除所有实体,而不仅仅是 person(id=1)实体,它可能有其他副作用。

但是,更安全的解决方案是在使用更新脚本更改实体后使用em.refresh()

【讨论】:

这个 SO 答案也可能有帮助:***.com/questions/5295386/…

以上是关于在 JPA 上是不是绕过托管实体的主要内容,如果未能解决你的问题,请参考以下文章

看我如何绕过某托管公司域上的强大XSS过滤器

JPA 中的哪些操作绕过持久性上下文/缓存并直接在数据库中调用?

绕过 HTML 实体

绕过 Chrome 的恶意文件警告

JPA 混淆(托管与非托管实体)

通过实体框架更新时如何绕过唯一键约束(使用 dbcontext.SaveChanges())