JPA OneToOne级联合并和持久化之间的区别

时间:2013-08-22 06:42:05

标签: jpa

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

ENTITY1

@Entity
public class Entity1 implements Serializable{

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

ENTITY2

@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;
}

ENTITY3

@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,我会在事务提交后在持久化上下文中获得2个Entity1实体,如果我将其更改为em.persist,那么我将获得一个Entity1实体持久化上下文。任何人都可以解释为什么会发生这种情况或指向某些文档吗?

2 个答案:

答案 0 :(得分:24)

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

  1. 持久化和合并操作的语义(调用em.merge(x)不会使x成为托管对象,但调用em.persist(x)将会是
  2. 您的实体的ID由数据库生成
  3. em.merge()纲要:

    1. em.merge(entity2);被调用
      • 合并操作级联到entity1
      • entity1已复制到新的托管实例中,但 自行管理
    2. em.merge(entity3);被调用
      • 合并操作再次级联到entity1
      • 由于entity1仍未受管理且没有标识符,因此无法与上一次合并创建的现有托管实例匹配。结果是创建了另一个新实例
    3. 交易已提交
      • 此时,存在entity1的3个实例。合并操作和初始非托管实例创建的两个托管实例
      • 两个托管实例保存在数据库中
    4. 请注意,如果您的实体具有显式ID,则第二次合并将不会创建新实例,而是将entity1复制到已存在的托管实例中。此外,如果您尝试合并已托管的实例,则第二次合并操作将被忽略。

      em.persist()纲要:

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

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

          

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

             
            
        • 如果X是新的实体实例,则创建新的托管实体实例X',并将X的状态复制到新的托管实体中   实例X'。
        •   
        • 对于具有级联元素值cascade = MERGE或cascade = ALL的X关系引用的所有实体Y,Y被合并   递归地为Y'。对于由X引用的所有这样的Y,X'被设置为   参考Y'。 (注意,如果X被管理,那么X是与之相同的对象   X”)
        •   
        • [...]
        •   

        3.2.2持久性和实体实例部分:

          

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

             
            
        • 如果X是一个新实体,它就会被管理。实体X将在事务提交时或之前输入数据库,或者作为a   冲洗操作的结果。
        •   
        • 如果X是预先存在的托管实体,则persist操作会忽略它。 [...]
        •   
        • [...]
        •   

        参见:When does the JPA set a @GeneratedValue @Id

答案 1 :(得分:13)

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

Persist和merge旨在保留某个对象的托管实例。

如果使用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)的同一对象实例,则会出现错误(当您在已经管理的对象上使用合并时,可以使用true操作被忽略了。)

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

我是个新手。如果我在任何地方都错了,请纠正我。