JPA:分离后持久化(用于创建实体副本)会混淆EntityManager缓存

时间:2017-07-13 07:22:32

标签: java jpa eclipselink clone

我使用此代码制作实体的副本:

EntityClass obj = em.find(...);
em.detach(obj);
obj.setId(null);
obj.setName("New");
em.persist(obj);
em.flush();

所以问题是 - 如果我从这个创建的副本中创建一个新副本,它们都指向实体管理器缓存中最后创建的副本!

// Call#1 copy method
Entity obj = em.find(Entity.class, 1); // old object, id = 1
em.detach(obj);
obj.setId(null);
em.persist(obj); // created new object with id = 2
em.flush();

// Call#2 copy method
Entity obj2 = em.find(Entity.class, 2); // our copy, id = 2
em.detach(obj2);
obj2.setId(null);
em.persist(obj2); // created new object with id = 3
em.flush();

// Call another method
Entity someObj = em.find(Entity.class, 2); // returns last copy with id=3!
// it's like after persist obj2 (id=2) points 
// to the same memory address as the new copy with id = 3
复制方法执行后的evictAll()使其颠倒 - 现在id = 2和id = 3都指向id = 2的原始副本。 我认为这与某种事实相关,即在Java中我们不使用构造函数创建新对象,并且变量保持不变,而在数据库中存在两个实体。

1 个答案:

答案 0 :(得分:1)

问题的根源是你和JPA之间的主要冲突:

  • JPA做了很多工作来抽象出“对象标识”与“具有相同主键的对象”不同的事实。 JPA 90%的复杂性源于引入此模型。
  • 您正试图根据JPA试图摘要的行为显式构建您的逻辑。

因此,不是使用JPA,而是在与它作斗争。

如果你试着追随这件衣服,你会得到一些脆弱和丑陋的东西 - 而且你也会觉得JPA在某种程度上让你失望(当你试图用螺丝刀锤击钉子时会有同样的感觉)。< / p>

要使其顺利运作,您需要做的是:

  • 在每次业务操作后丢弃EntityManager(这是EE Web应用程序中的默认行为 - EntityManager仅适用于单个事务并被丢弃)
  • 如果要复制对象,只需执行此操作:复制对象。编写(或生成)Java代码来执行此操作。

您最终可能会得到优雅,可测试的代码,这些代码没有显式的缓存操作,没有刷新,没有“合并”,也没有“分离”。

抱歉,我知道这不是您想听到的建议类型。如果你想进入JPA思维模式并看到JPA行为的基本原理,请查看经典的Fowler企业模式,特别是工作单元身份地图