JPA和Hibernate中的persist()和merge()有什么区别?

134

persist()merge()在Hibernate中有什么区别?

persist()可以创建INSERT和UPDATE查询,例如:

SessionFactory sef = cfg.buildSessionFactory();
Session session = sef.openSession();
A a=new A();
session.persist(a);
a.setName("Mario");
session.flush();
在这种情况下,查询语句将生成如下:
Hibernate: insert into A (NAME, ID) values (?, ?)
Hibernate: update A set NAME=? where ID=?

persist() 方法可以生成一个 Insert 和一个 Update 操作。

现在来看 merge() 方法:

SessionFactory sef = cfg.buildSessionFactory();
Session session = sef.openSession();
Singer singer = new Singer();
singer.setName("Luciano Pavarotti");
session.merge(singer);
session.flush();

这是我在数据库中看到的:

SINGER_ID   SINGER_NAME
1           Ricky Martin
2           Madonna
3           Elvis Presley
4           Luciano Pavarotti

现在使用merge()更新记录。

SessionFactory sef = cfg.buildSessionFactory();
Session session = sef.openSession();
Singer singer = new Singer();
singer.setId(2);
singer.setName("Luciano Pavarotti");
session.merge(singer);
session.flush();

这是我在数据库中看到的内容:

SINGER_ID   SINGER_NAME
1           Ricky Martin
2           Luciano Pavarotti
3           Elvis Presley

7
Java文档非常明确地阐述了它们的功能以及差异。你是否已经阅读并理解了它? - skaffman
1
请查看以下链接:https://dev59.com/d3VC5IYBdhLWcg3w21Iq - jmj
https://dev59.com/EXNA5IYBdhLWcg3wKacx - Bozho
4个回答

159

JPA规范中对这些操作的语义描述非常精确,比JavaDoc更好:

persist操作应用于实体X的语义如下:

  • 如果X是一个新实体,则它将变为托管状态。实体X将在事务提交之前或通过flush操作结果被输入到数据库中。

  • 如果X是预先存在的托管实体,则persist操作将忽略它。但是,如果从X到其他实体的关系使用cascade=PERSISTcascade=ALL注释元素值或指定等效的XML描述符元素,则persist操作将级联到X引用的实体。

  • 如果X是一个已删除的实体,则它将变为托管状态。

  • 如果X是一个分离对象,在调用persist操作时可能会抛出EntityExistsException,或者在flush或提交时可能抛出EntityExistsException或其他PersistenceException异常。

  • 对于所有被X引用的实体Y,如果从X到Y的关系已经使用cascade=PERSISTcascade=ALL注释元素值或指定等效的XML描述符元素,则persist操作将应用于Y。


merge操作应用于实体X的语义如下:

  • 如果X是一个分离实体,则将X的状态复制到同一类的预先存在的托管实体实例X'上,并返回该托管实体。如果该实体不存在,则创建一个新的托管实体,并返回该实体。

  • 如果X是一个新实体,则该方法将X的状态复制到一个新的托管实体,然后返回该实体。

  • 如果X是一个预先存在的托管实体,则该方法将忽略它并返回该实体。

  • 如果X是一个已删除的实体,则这个方法将抛出IllegalArgumentException异常。如果关联的实体也被删除,则这些实体的状态不会被合并。

  • 对于所有被X引用的实体Y,如果从X到Y的关系已经使用cascade=MERGEcascade=ALL注释元素值或指定等效的XML描述符元素,则该操作将递归地应用于Y。

  • 如果X是一个托管实体,那么将使用X的标识符在持久化上下文中查找它。

  • 如果找到X,则将其状态复制到托管副本X'中。

  • 如果未找到X且X不是新实例,则抛出IllegalArgumentException异常。

  • 如果未找到X且X是新实例,则创建一个新的托管实体实例X',并将X的状态复制到其中。

  • 如果X是已删除的实体实例,则合并操作将抛出IllegalArgumentException异常(或事务提交将失败)。

  • 如果X是托管实体,则忽略它,并对从X引用的关系进行级联合并操作,如果这些关系被注释为级联元素值为cascade=MERGEcascade=ALL

  • 对于所有从X引用级联元素值为cascade=MERGEcascade=ALL的关系实体Y,都会递归地合并为Y'。 对于所有这样的Y,X'被设置为引用Y'。(请注意,如果X是托管实体,则X与X'是同一个对象。)

  • 如果将实体X合并到X',并且引用了另一个实体Y,其中cascade=MERGEcascade=ALL未指定,则从X'导航到相同的关联会产生对具有与Y相同持久化标识符的托管对象Y'的引用。


  • 谢谢提供信息。我理解了两个定义的语义。但问题是它们之间的区别。也许可以列出状态列表,并为每个不同的persistmerge行为提供2个子部分? - AlikElzin-kilaka
    如果实体已被管理,即已保存或从数据库中检索出并进行更改但未分离,则应使用merge还是persist - samshers
    1
    如果我们想要阅读文档页面,我们就不会看Stackoverflow的答案...-1 - Krusty the Clown

    36

    这是来自JPA的内容。简单来说:

    • persist(entity) 应该与全新的实体一起使用,将它们添加到数据库中(如果实体已经存在于数据库中,则会抛出 EntityExistsException 异常)。

    • merge(entity) 应该用于将从持久化上下文中分离并已更改的实体放回去。


    请问您能否在您的解释中添加一个来源?谢谢。 - AlikElzin-kilaka
    @AlikElzin-kilaka,我记得这样的解释是在《Java EE 7入门》一书中发现的。 - Krystian

    21

    Persist只应用于新实体,而merge用于重新附加已分离的实体。

    如果您正在使用已分配的生成器,则使用merge而不是persist可能会导致冗余的SQL语句。

    此外,对于托管实体调用合并也是错误的,因为Hibernate自动管理托管实体,并且在刷新持久性上下文时,它们的状态通过脏检查机制与数据库记录同步。


    1
    如果实体已经被管理,就不应该调用任何方法。只需让Hibernate自动执行更新操作即可。 - Vlad Mihalcea
    tx. 这是否意味着在提交时或会话关闭时,所有托管实体都将自动保存到数据库中。 - samshers
    2
    是的,在提交之前,会自动刷新,除非您正在使用只读会话或Tx。 - Vlad Mihalcea
    我理解上述内容+“如果您正在使用分配的生成器,则使用合并而不是持久化可能会导致冗余的SQL语句”。谢谢。但是Spring Data JPA [save](https://github.com/spring-projects/spring-data-jpa/blob/main/spring-data-jpa/src/main/java/org/springframework/data/jpa/repository/support/SimpleJpaRepository.java#L605)在除实体为新时的所有其他情况下都使用`merge`,即使实体已被管理。为什么会这样?用户可能会无意中在已管理的实体上调用保存,从而触发合并(同样是无意识的),没有任何阻止他们的措施。 - samshers
    1
    Spring Data的JpaRepository出了问题。最好使用HibernateRepository,因为它修复了save问题。 - Vlad Mihalcea
    显示剩余2条评论

    2
    最重要的区别在于:
    • 对于persist方法,如果要在持久化上下文中管理的实体已经存在于持久化上下文中,则会忽略新实体。(基本上,什么也不会发生)

    • 但是,在merge方法的情况下,已经在持久化上下文中管理的实体将被新实体替换(简单地说,它会得到更新),并且这个更新后的实体的副本将返回。(因此,如果想要反映更改,请从现在开始对此返回的实体进行更改)


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