【问题标题】:JPA OneToOne difference between cascade merge and persistJPA OneToOne 级联合并和持久化的区别
【发布时间】:2013-08-24 18:24:21
【问题描述】:

我有以下问题。我有 3 个实体,我正在使用 OneToOne 单向:

实体1

@Entity
public class Entity1 implements Serializable{

   @Id
   @GeneratedValue(strategy= GenerationType.AUTO)
   Long id;
   String name;
   String value;
}

实体2

@Entity
public class Entity2 implements Serializable {
  @Id
  @GeneratedValue(strategy=GenerationType.AUTO)
  private Long id;

  @OneToOne(cascade={CascadeType.MERGE, CascadeType.PERSIST})
  Entity1 entity1;
  public Entity1 getEntity1() {
      return entity1;
  }

  String name;
  String value;
}

实体3

@Entity
public class Entity3 implements Serializable {
  @Id
  @GeneratedValue(strategy=GenerationType.AUTO)
  Long id;

  @OneToOne(cascade={CascadeType.MERGE, CascadeType.PERSIST})
  private Entity1 entity1;

  public Entity1 getEntity1() {
      return entity1;
  }

  public void setEntity1(Entity1 entity1) {
      this.entity1 = entity1;
  }

  String name;
  String value;
}

一个小测试:

public void testApp()
   {
    EntityManager em = TestHibernateUtil.getEntityManager();
    em.getTransaction().begin();
    Entity1 entity1 = new Entity1();
    entity1.name = "Name1";
    entity1.value = "Value1";

    Entity2 entity2 = new Entity2();
    entity2.name = "Name2";
    entity2.value = "Value2";
    entity2.setEntity1(entity1);
    **em.merge(entity2);**// if change that to persist - I get one Entity1

    Entity3 entity3 = new Entity3();
    entity3.name = "Name3";
    entity3.value = "Value3";
    entity3.setEntity1(entity1);
    **em.merge(entity3);** // if change that to persist - I get one Entity1
    em.getTransaction().commit();
 }

所以查看上面的测试,如果我使用em.merge,我确实在事务提交后在持久性上下文中获得了 Entity1 的 2 个实体,如果我将其更改为 em.persist,那么我在持久性上下文中获得了 Entity1 的一个实体。谁能解释我为什么会发生这种情况或指向一些文档?

【问题讨论】:

    标签: jpa


    【解决方案1】:

    您看到的行为是两件事的结果:

    1. persist 和 merge 操作的语义(调用 em.merge(x) 不会使 x 成为托管对象,但调用 em.persist(x) 会)
    2. 您的实体 id 是由数据库生成的事实

    em.merge()破败:

    1. em.merge(entity2); 被调用
      • 合并操作级联到entity1
      • entity1 被复制到新的托管实例中,但自行管理
    2. em.merge(entity3); 被调用
      • 合并操作再次级联到entity1
      • 因为entity1 仍然是非托管的并且没有标识符,所以它无法与之前合并创建的现有托管实例匹配。结果是创建了另一个新实例
    3. 事务已提交
      • 此时,entity1 的 3 个实例存在。合并操作创建的两个托管实例和初始非托管实例
      • 两个托管实例保存在数据库中

    请注意,如果您的实体具有显式 ID,则第二次合并不会创建新实例,而是会将 entity1 复制到已存在的托管实例中。此外,如果您尝试合并已托管的实例,则第二个合并操作将被忽略。

    em.persist()破败:

    1. em.persist(entity2); 被调用
      • persist 操作级联到entity1
      • entity1 现在是托管对象
    2. em.persist(entity3); 被调用
      • persist 操作再次级联到entity1
      • 由于entity1 已被管理,因此忽略persist 操作
    3. 事务已提交
      • 此时,entity1 的实例仅存在 1 个并已被管理。
      • entity1 保存在数据库中

    此行为在JPA 2.0 Specification 部分3.2.7.1 合并分离实体状态中定义:

    应用于实体 X 的合并操作的语义为 如下:

    • 如果 X 是一个新的实体实例,则创建一个新的托管实体实例 X',并将 X 的状态复制到新的托管实体中 实例 X'。
    • 对于由来自 X 的具有级联元素值 cascade=MERGE 或 cascade=ALL 的关系引用的所有实体 Y,Y 被合并 递归为 Y'。对于 X 引用的所有此类 Y,X' 设置为 参考 Y'。 (请注意,如果 X 是托管的,则 X 与 X'。)
    • [...]

    以及3.2.2 持久化和实体实例部分

    持久化操作的语义,应用于实体 X 是 如下:

    • 如果 X 是一个新实体,它将成为托管实体。实体 X 将在事务提交时或之前输入到数据库中,或者作为 刷新操作的结果。
    • 如果 X 是预先存在的托管实体,则持久操作将忽略它。 [...]
    • [...]

    另见:When does the JPA set a @GeneratedValue @Id)

    【讨论】:

      【解决方案2】:

      我不会梦想挑战 DannyMo 的超级答案,但我想补充一点:

      持久化和合并被设计为保留某个对象的一个​​托管实例的一种方式。

      如果您使用persist,则意味着该对象尚不存在,因此将其设为唯一的托管实例并没有什么坏处。

      当您使用合并时,您会考虑到对象的托管实例可能已经存在。您不想替换那个唯一的托管实例,因为其他一些对象可能会引用它,并认为它是托管对象。

      如果你想在合并后对对象进行操作,正确的合并应该是这样的:

      managedObject = em.merge(object); // or just object = em.merge(object) //You cannot do it with persist since it returns null

      如果您尝试检查managedObjectobject 是否指向同一个对象实例,检查if(managedObject == object) 您将得到错误(当您在已管理的对象上使用合并并且操作被忽略时可能为真)。

      如果您对过时版本的对象使用合并,您将其作为参数传递给先前的合并,jpa 不知道如何找到正确的对象,因为它还没有 id。假设它是一个新对象,将创建新的托管实例。

      我还是个菜鸟。如果我在任何地方错了,请纠正我。

      【讨论】:

        猜你喜欢
        • 2012-06-17
        • 1970-01-01
        • 1970-01-01
        • 2018-05-15
        • 2014-04-27
        • 1970-01-01
        • 2011-03-12
        • 2012-01-18
        • 2016-12-13
        相关资源
        最近更新 更多