ChatGPT解决这个技术问题 Extra ChatGPT

JPA EntityManager:为什么使用persist()而不是merge()?

EntityManager.merge() 可以插入新对象并更新现有对象。

为什么要使用 persist()(它只能创建新对象)?

如果你喜欢图表。请参考:spitballer.blogspot.in/2010/04/…

c
cngzz1

无论哪种方式都会将实体添加到 PersistenceContext,不同之处在于您之后对实体执行的操作。

Persist 获取一个实体实例,将其添加到上下文中并管理该实例(即,将跟踪实体的未来更新)。

Merge 返回与状态合并的托管实例。它确实返回存在于 PersistenceContext 中的内容或创建实体的新实例。在任何情况下,它都会从提供的实体复制状态,并返回一个托管副本。您传入的实例将不会被管理(您所做的任何更改都不会成为事务的一部分 - 除非您再次调用 merge)。虽然您可以使用返回的实例(托管实例)。

也许一个代码示例会有所帮助。

MyEntity e = new MyEntity();

// scenario 1
// tran starts
em.persist(e); 
e.setSomeField(someValue); 
// tran ends, and the row for someField is updated in the database

// scenario 2
// tran starts
e = new MyEntity();
em.merge(e);
e.setSomeField(anotherValue); 
// tran ends but the row for someField is not updated in the database
// (you made the changes *after* merging)
      
// scenario 3
// tran starts
e = new MyEntity();
MyEntity e2 = em.merge(e);
e2.setSomeField(anotherValue); 
// tran ends and the row for someField is updated
// (the changes were made to e2, not e)

场景 1 和 3 大致相同,但在某些情况下您希望使用场景 2。


您可以在互联网上免费找到 MASTER 解释的涂料解释之一
J
Josep Panadero

持久化和合并有两个不同的目的(它们根本不是替代品)。

(编辑以扩展差异信息)

坚持:

在数据库中插入一个新的寄存器

将对象附加到实体管理器。

合并:

找到具有相同 id 的附加对象并更新它。

如果存在更新并返回已附加的对象。

如果不存在,则将新寄存器插入数据库。

persist() 效率:

将新寄存器插入数据库可能比 merge() 更有效。

它不会复制原始对象。

persist() 语义:

它确保您正在插入而不是错误地更新。

例子:

{
    AnyEntity newEntity;
    AnyEntity nonAttachedEntity;
    AnyEntity attachedEntity;

    // Create a new entity and persist it        
    newEntity = new AnyEntity();
    em.persist(newEntity);

    // Save 1 to the database at next flush
    newEntity.setValue(1);

    // Create a new entity with the same Id than the persisted one.
    AnyEntity nonAttachedEntity = new AnyEntity();
    nonAttachedEntity.setId(newEntity.getId());

    // Save 2 to the database at next flush instead of 1!!!
    nonAttachedEntity.setValue(2);
    attachedEntity = em.merge(nonAttachedEntity);

    // This condition returns true
    // merge has found the already attached object (newEntity) and returns it.
    if(attachedEntity==newEntity) {
            System.out.print("They are the same object!");
    }

    // Set 3 to value
    attachedEntity.setValue(3);
    // Really, now both are the same object. Prints 3
    System.out.println(newEntity.getValue());

    // Modify the un attached object has no effect to the entity manager
    // nor to the other objects
    nonAttachedEntity.setValue(42);
}

这种方式对于实体管理器中的任何寄存器只存在 1 个附加对象。

具有 id 的实体的 merge() 类似于:

AnyEntity myMerge(AnyEntity entityToSave) {
    AnyEntity attached = em.find(AnyEntity.class, entityToSave.getId());
    if(attached==null) {
            attached = new AnyEntity();
            em.persist(attached);
    }
    BeanUtils.copyProperties(attached, entityToSave);

    return attached;
}

尽管如果使用 ON DUPLICATE KEY UPDATE 选项调用 INSERT 连接到 MySQL,merge() 可能与 persist() 一样有效,但 JPA 是一种非常高级的编程,您不能假设到处都是这种情况。


您能举出用 x = em.merge(x) 替换 em.persist(x) 无效的情况吗?
persist() 可以抛出 EntityExistsException。如果您想确保您的代码正在执行插入而不是更新数据,则必须使用持久化。
merge() 也可以抛出 EntityExistsException
@None 可以,因为它是 RuntimeException,但 Javadoc 中没有提到它。
V
Vlad Mihalcea

如果您使用分配的生成器,使用 merge 而不是 persist 可能会导致冗余 SQL 语句,从而影响性能。

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

要了解这一切是如何工作的,您首先应该知道 Hibernate 将开发人员的思维方式从 SQL 语句转变为实体状态转换。

一旦实体由 Hibernate 主动管理,所有更改都将自动传播到数据库。

Hibernate 监视当前附加的实体。但是要使实体成为受管实体,它必须处于正确的实体状态。

为了更好地理解 JPA 状态转换,您可以可视化下图:

https://i.stack.imgur.com/Hez6p.png

或者,如果您使用 Hibernate 特定的 API:

https://i.stack.imgur.com/CpfAe.png

如上图所示,实体可以处于以下四种状态之一:

新(瞬态)

从未与 Hibernate Session(又名 Persistence Context)关联且未映射到任何数据库表行的新创建对象被视为处于新建(瞬态)状态。

要持久化,我们需要显式调用 EntityManager#persist 方法或使用传递持久性机制。

持久性(托管) 持久性实体已与数据库表行相关联,并由当前运行的持久性上下文管理。对此类实体所做的任何更改都将被检测到并传播到数据库(在会话刷新期间)。使用 Hibernate,我们不再需要执行 INSERT/UPDATE/DELETE 语句。 Hibernate 采用事务性后写工作方式,并且在当前会话刷新期间的最后一个负责时刻同步更改。

分离式

一旦当前运行的持久性上下文关闭,所有以前管理的实体都将被分离。将不再跟踪连续的更改,也不会发生自动数据库同步。

要将分离的实体关联到活动的 Hibernate Session,您可以选择以下选项之一:

重新附加 Hibernate(但不是 JPA 2.1)支持通过 Session#update 方法重新附加。 Hibernate Session 只能为给定的数据库行关联一个实体对象。这是因为持久性上下文充当内存缓存(一级缓存),并且只有一个值(实体)与给定的键(实体类型和数据库标识符)相关联。仅当没有其他 JVM 对象(匹配相同的数据库行)与当前 Hibernate Session 关联时,才能重新附加实体。

合并 合并会将分离的实体状态(源)复制到托管实体实例(目标)。如果合并实体在当前会话中没有等价物,则将从数据库中获取一个。即使在合并操作之后,分离的对象实例仍将继续保持分离状态。

删除 尽管 JPA 要求只允许删除托管实体,但 Hibernate 也可以删除分离的实体(但只能通过 Session#delete 方法调用)。删除的实体仅计划删除,实际的数据库 DELETE 语句将在会话刷新期间执行。


因此不可能改变 orphanremoval=true 的操作顺序?
您关于通常情况下的操作顺序的文章。我针对 orphanRemoval 的问题
查看my answer。 Hibernate 在这里没有什么神奇的。您只需要编写正确的数据访问逻辑代码。
事实是不可能用这样的图表来解释休眠。为什么分离后不能刷新会话?当您尝试保存已持久化的实体时会发生什么?为什么在保存和持久化方面刷新的行为不同?有 1000 个这样的问题,没有人有明确的逻辑。
如果您不阅读 User Guide 一点也不难,而且这并不只适用于 Hibernate。任何技术都一样。
S
Sarah Vessels

我注意到,当我使用 em.merge 时,我得到了每个 INSERTSELECT 语句,即使没有 JPA 为我生成的字段——主键字段是我自己设置的 UUID。我切换到 em.persist(myEntityObject),然后只得到 INSERT 语句。


这是有道理的,因为您分配了 ID,而 JPA 容器不知道您从哪里得到它。对象已经存在于数据库中的可能性很小,例如在多个应用程序写入同一个数据库的情况下。
我遇到了与 merge() 类似的问题。我的 PostgreSQL 数据库具有复杂的 view:视图聚合了来自多个表的数据(这些表具有相同的结构但名称不同)。所以JPA尝试做merge(),但实际上JPA首先做了SELECT(由于视图设置,数据库可能会从不同的表返回多个具有相同主键的记录!),然后JPA(Hibernate是一种实现)失败:有具有相同键 (org.hibernate.HibernateException: More than one row with the given identifier was found) 的多条记录。就我而言,persist() 帮助了我。
C
Community

JPA 规范对 persist() 进行了如下说明。

如果 X 是一个分离的对象,则在调用持久操作时可能会抛出 EntityExistsException,或者在刷新或提交时可能会抛出 EntityExistsException 或另一个 PersistenceException。

因此,当对象不应该是分离对象时,使用 persist() 是合适的。您可能更喜欢让代码抛出 PersistenceException 以便它快速失败。

尽管 the specification is unclearpersist() 可能会为对象设置 @GeneratedValue @Id。但是,merge() 必须具有已生成 @Id 的对象。


+1 表示“merge() 但是必须有一个对象,其中 @Id已经生成.”。每当 EntityManager 找不到对象 ID 字段的值时,它就会被持久化(插入)到数据库中。
我没有首先理解这一点,因为我不清楚这些状态。希望这对我有帮助。 docs.jboss.org/hibernate/core/3.6/reference/en-US/html/…
@GeneratedValue 对 merge() 和 persist() 没有不同的含义
A
Aaron Digulla

有关合并的更多详细信息将帮助您在持久性上使用合并:

返回原始实体以外的托管实例是合并过程的关键部分。如果持久化上下文中已经存在具有相同标识符的实体实例,则提供者将用正在合并的实体的状态覆盖其状态,但必须将已经存在的托管版本返回给客户端,以便它可以用过的。如果提供者没有在持久化上下文中更新 Employee 实例,那么对该实例的任何引用都将与正在合并的新状态不一致。当在新实体上调用 merge() 时,它的行为类似于 persist() 操作.它将实体添加到持久化上下文中,但不是添加原始实体实例,而是创建一个新副本并管理该实例。由 merge() 操作创建的副本被持久化,就好像对其调用了 persist() 方法一样。在存在关系的情况下,merge() 操作将尝试更新托管实体以指向被分离实体引用的实体的托管版本。如果实体与没有持久标识的对象有关系,则合并操作的结果是未定义的。一些提供者可能允许托管副本指向非持久对象,而其他提供者可能会立即抛出异常。在这些情况下,可以选择级联 merge() 操作,以防止发生异常。我们将在本节后面介绍 merge() 操作的级联。如果正在合并的实体指向已移除的实体,则会抛出 IllegalArgumentException 异常。延迟加载关系是合并操作中的一种特殊情况。如果在实体分离之前未在实体上触发延迟加载关系,则在合并实体时将忽略该关系。如果关系在托管时触发,然后在实体分离时设置为空,则实体的托管版本同样会在合并期间清除关系。”

以上所有信息均来自 Mike Keith 和 Merrick Schnicariol 的“Pro JPA 2 Mastering the Java™ Persistence API”。第 6 章节分离与合并。这本书实际上是作者致力于 JPA 的第二本书。这本新书比上一本书有许多新信息。我真的建议那些将认真参与 JPA 的人阅读这本书。我很抱歉匿名发布我的第一个答案。


V
V G

mergepersist 之间还有一些区别(我将再次列举已在此处发布的内容):

D1。 merge 不会使传递的实体受管理,而是返回另一个受管理的实例。另一边的 persist 将使传递的实体托管:

//MERGE: passedEntity remains unmanaged, but newEntity will be managed
Entity newEntity = em.merge(passedEntity);

//PERSIST: passedEntity will be managed after this
em.persist(passedEntity);

D2。如果您删除一个实体,然后决定将实体保留回来,您只能使用persist() 执行此操作,因为merge 会抛出一个IllegalArgumentException

D3。如果您决定手动处理您的 ID(例如通过使用 UUID),那么 merge 操作将触发后续 SELECT 查询以查找具有该 ID 的现有实体,而 persist 可能不需要这些查询.

D4。在某些情况下,您根本不信任调用您的代码的代码,并且为了确保没有数据被更新而是被插入,您必须使用 persist


A
Aaron Digulla

JPA 无疑是在 Java 平台上构建的企业应用程序领域的一个极大简化。作为一个必须应对 J2EE 中旧实体 bean 的复杂性的开发人员,我认为将 JPA 包含在 Java EE 规范中是一个巨大的飞跃。然而,在深入研究 JPA 细节时,我发现事情并不那么容易。在本文中,我将比较 EntityManager 的 merge 和 persist 方法,它们的重叠行为可能不仅会引起新手的困惑。此外,我提出了一个概括,将这两种方法视为更通用方法组合的特殊情况。

持久实体

与 merge 方法相比,persist 方法非常简单直观。最常见的persist方法的使用场景可以总结如下:

“一个新创建的实体类实例被传递给persist方法。该方法返回后,管理实体并计划插入数据库。它可能发生在事务提交时或之前,或者调用flush方法时。如果实体通过标有 PERSIST 级联策略的关系引用另一个实体,则此过程也适用于它。”

https://i.stack.imgur.com/yJHiE.png

该规范更详细地介绍了细节,但是记住它们并不重要,因为这些细节仅涵盖或多或少的奇异情况。

合并实体

与persist相比,merge的行为描述就没有那么简单了。没有主要场景,就像在持久化的情况下一样,程序员必须记住所有场景才能编写正确的代码。在我看来,JPA 设计者希望有一些方法,其主要关注点是处理分离的实体(与主要处理新创建的实体的持久方法相反)。合并方法的主要任务是将状态从非托管实体(作为参数传递)到持久性上下文中的托管实体。然而,这项任务进一步分为几个场景,这些场景会恶化整个方法行为的可理解性。

我没有重复 JPA 规范中的段落,而是准备了一个流程图,该流程图示意性地描述了合并方法的行为:

https://i.stack.imgur.com/9QaeA.png

那么,我应该什么时候使用persist,什么时候合并?

坚持

您希望该方法始终创建一个新实体并且从不更新实体。否则,该方法会因违反主键唯一性而引发异常。

批处理,以有状态的方式处理实体(请参阅网关模式)。

性能优化

合并

您希望该方法在数据库中插入或更新实体。

您希望以无状态方式处理实体(服务中的数据传输对象)

您想要插入一个新实体,该实体可能引用另一个可能但可能尚未创建的实体(关系必须标记为 MERGE)。例如,插入一张新照片并引用新的或现有的相册。


is E managed 和 Does PC contains an managed version of E 有什么区别?
A
Aaron Digulla

我在实体上遇到了lazyLoading 异常,因为我试图访问会话中的延迟加载集合。

我要做的是在一个单独的请求中,从会话中检索实体,然后尝试访问我的 jsp 页面中存在问题的集合。

为了缓解这种情况,我更新了控制器中的相同实体并将其传递给我的 jsp,尽管我想当我在会话中重新保存时,它也可以通过 SessionScope 访问而不是抛出 LazyLoadingException,修改示例 2:

以下对我有用:

// scenario 2 MY WAY
// tran starts
e = new MyEntity();
e = em.merge(e); // re-assign to the same entity "e"

//access e from jsp and it will work dandy!!

R
Ray Hulha

我从 Hibernate 文档中发现了这个解释很有启发性,因为它们包含一个用例:

新用户似乎对 merge() 的用法和语义感到困惑。首先,只要您不尝试在另一个新实体管理器中使用加载在一个实体管理器中的对象状态,则根本不需要使用 merge()。一些完整的应用程序永远不会使用这种方法。通常 merge() 用于以下场景: 应用程序在第一个实体管理器中加载对象 对象被向上传递到表示层 对对象进行一些修改 对象被向下传递回业务逻辑层 应用程序通过在第二个实体管理器中调用 merge() 来持久化这些修改 下面是 merge() 的确切语义:如果存在具有当前与持久性上下文关联的相同标识符的托管实例,则将给定对象的状态复制到托管instance 如果当前没有与持久性上下文关联的托管实例,请尝试从数据库加载它,或创建一个新的托管实例 返回托管实例 给定实例未与持久性上下文关联,它保持分离状态通常被丢弃

来自:http://docs.jboss.org/hibernate/entitymanager/3.6/reference/en/html/objectstate.html


C
Community

通过答案,缺少有关“级联”和 id 生成的一些细节。 See question

此外,值得一提的是,您可以有单独的 Cascade 注释用于合并和持久化:Cascade.MERGECascade.PERSIST 将根据使用的方法进行处理。

规范是你的朋友;)


G
George Papatheodorou

情景十:

Table:Spitter (One) ,Table: Spittles (Many) (Spitters 是与 FK:spitter_id 的关系的所有者)

这种情况会导致保存: Spitter 和两个 Spittles 就好像由 Same Spitter 拥有一样。

        Spitter spitter=new Spitter();  
    Spittle spittle3=new Spittle();     
    spitter.setUsername("George");
    spitter.setPassword("test1234");
    spittle3.setSpittle("I love java 2");       
    spittle3.setSpitter(spitter);               
    dao.addSpittle(spittle3); // <--persist     
    Spittle spittle=new Spittle();
    spittle.setSpittle("I love java");
    spittle.setSpitter(spitter);        
    dao.saveSpittle(spittle); //<-- merge!!

场景 Y:

这将保存 Spitter,将保存 2 个 Spittles 但它们不会引用同一个 Spitter!

        Spitter spitter=new Spitter();  
    Spittle spittle3=new Spittle();     
    spitter.setUsername("George");
    spitter.setPassword("test1234");
    spittle3.setSpittle("I love java 2");       
    spittle3.setSpitter(spitter);               
    dao.save(spittle3); // <--merge!!       
    Spittle spittle=new Spittle();
    spittle.setSpittle("I love java");
    spittle.setSpitter(spitter);        
    dao.saveSpittle(spittle); //<-- merge!!

吐痰器是取自 Graig Walls 的《Spring in Action》第三版一书中的一个对象。 Spitters 是说某事的人,而他们的 Spittle 就是他们实际上在说什么。所以一个 Spitter 有很多 spitter 意味着他有一个字符串列表。
您可以使用一个更具可读性的示例,而无需阅读 Spring in Action ...
您实际上不需要知道什么是 spittle 或 spitter,因为在顶部写着 Spitter 是一张桌子, spitter 是另一张拥有..这个和那个的桌子......
y
yuranos

另一个观察:

只有当您的表中已存在具有此类 ID 的记录时,merge() 才会关心自动生成的 ID(在 IDENTITYSEQUENCE 上测试)。在这种情况下,merge() 将尝试更新记录。但是,如果 id 不存在或与任何现有记录不匹配,merge() 将完全忽略它并要求 db 分配一个新记录。这有时是很多错误的来源。不要使用 merge() 来强制新记录的 id。

另一方面,persist() 绝不会让您将 id 传递给它。它会立即失败。就我而言,它是:

引起:org.hibernate.PersistentObjectException:分离的实体传递给持久化

hibernate-jpa javadoc 有一个提示:

抛出: javax.persistence.EntityExistsException - 如果实体已经存在。 (如果实体已经存在,在调用持久化操作时可能会抛出 EntityExistsException,或者在刷新或提交时可能会抛出 EntityExistsException 或另一个 PersistenceException。)


如果您没有使用自动生成的 ID,则必须手动为您的新实体提供一个 ID。 persist() 不会抱怨它有一个 ID,它只会在具有相同 ID 的东西已经在数据库中时才会抱怨。
P
Peter Willems

您可能来这里是为了获得关于何时使用persist 以及何时使用merge 的建议。我认为这取决于情况:您需要创建新记录的可能性有多大,以及检索持久数据的难度有多大。

假设您可以使用自然键/标识符。

数据需要持久化,但有时会存在记录并需要更新。在这种情况下,您可以尝试持久化,如果它抛出 EntityExistsException,则查找并合并数据:try { entityManager.persist(entity) } catch(EntityExistsException exception) { /* 检索和合并 */ }

持久化数据需要更新,但有时还没有数据记录。在这种情况下,您查找它,如果实体丢失,请执行持久化: entity = entityManager.find(key); if (entity == null) { entityManager.persist(entity); } else { /* 合并 */ }

如果您没有自然键/标识符,您将很难确定实体是否存在,或者如何查找它。

也可以通过两种方式处理合并:

如果更改通常很小,请将它们应用于托管实体。如果更改很常见,请从持久化实体复制 ID 以及未更改的数据。然后调用 EntityManager::merge() 替换旧内容。


K
Krystian

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

应该使用 merge(entity),如果实体被分离并被更改,则将实体放回持久性上下文。

可能持久化正在生成 INSERT sql 语句和合并 UPDATE sql 语句(但我不确定)。


这是不正确的。如果你在一个新的 e 上调用 merge(e),它必须被持久化。
来自 JPA 规范版本 2.1,第 3.2.7.1 节,第二个项目符号:“如果 X 是一个新的实体实例,则创建一个新的托管实体实例 X',并将 X 的状态复制到新的托管实体实例 X'。”