关于hibernate:是否在JPA上绕过托管实体

 2021-04-27 

Bypass managed entities or not on JPA

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
/* ... */

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


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

在您的示例中,这就是后台发生的情况
-从数据库检索ID为1的人员实体(现在存在于EntityManager缓存中)
-人名通过JPQL在数据库中更新(实体保持不变)
-从数据库中检索ID为1的实体人
-但ID为1的实体已存在于EntityManager中,因此将返回此实例(名称中具有旧值)-该实体未与DB合并。有关更多信息,请参见此问题的答案。

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

如果您一开始不检索ID为1的Person,它将在select查询中检索具有新值的新实体。

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

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