JPA OneToOne中cascade merge和persist的区别

27
我有以下问题。我有3个实体,并使用一对一单向关系: 实体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,在事务提交后持久化上下文中确实会有2个Entity1实体,如果我将其更改为em.persist,那么持久化上下文中就只会有一个Entity1实体。有人能解释一下这是为什么,或者指向一些相关文档吗?

2个回答

31
你看到的行为是由以下两个原因造成的:
  1. 持久化和合并操作的语义(调用 em.merge(x) 不会使 x 成为托管对象,但调用 em.persist(x) 会)
  2. 实体的 ID 是由数据库生成的

em.merge() 概述:

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

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

em.persist()简介:

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

这种行为在JPA 2.0规范3.2.7.1合并分离的实体状态一节中定义:

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

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

3.2.2持久化和实体实例一节:

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

  • 如果 X 是一个新实体,则它将变为托管状态。实体 X 将在事务提交时或通过刷新操作的结果之前或之后进入数据库。
  • 如果 X 是一个已存在的托管实体,则持久化操作将忽略它。[...]
  • [...]

另请参阅:JPA 何时设置 @GeneratedValue @Id)


24

我不敢挑战DannyMo的终极答案,但我想做一个补充:

Persist和Merge被设计为保持某个对象的唯一托管实例的方法。

如果您使用Persist,意味着该对象尚不存在,因此使其成为唯一的托管实例并不会有害。

当您使用Merge时,应考虑对象的托管实例可能已经存在。您不希望替换那个唯一的托管实例,因为其他对象可能引用它,认为它是受管理的对象。

如果您想在合并后对对象进行操作,则正确的合并应如下所示:

managedObject = em.merge(object); //或者只是 object=em.merge(object) //由于Persist返回null,所以您不能使用它

如果您尝试检查managedObjectobject是否指向同一个对象实例,请检查if(managedObject == object),结果将为false(当您在已管理的对象上使用merge并且操作被忽略时可能会出现true)。

如果您在过时的对象版本上使用merge,这些对象被传递为先前合并的参数,jpa不知道如何找到正确的对象,因为它们还没有ID。假定它是一个新对象,并将创建新的托管实例。

我是一个初学者。如果我有任何错误,请纠正我。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接