【问题标题】:JPA EntityManager: Why use persist() over merge()?JPA EntityManager:为什么使用persist()而不是merge()?
【发布时间】:2010-11-07 09:16:07
【问题描述】:

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

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

【问题讨论】:

标签: jpa merge entitymanager persist java-persistence-api


【解决方案1】:

无论哪种方式都会将实体添加到 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 解释的网上免费解释之一
【解决方案2】:

我的实体出现了延迟加载异常,因为我试图访问会话中的延迟加载集合。

我要做的是在一个单独的请求中,从会话中检索实体,然后尝试访问我的 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!!

【讨论】:

    【解决方案3】:

    我注意到,当我使用 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() 帮助了我。
    【解决方案4】:

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

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

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

    虽然the specification is unclearpersist() 可能会为对象设置@GeneratedValue @Idmerge() 但是必须有一个已生成 @Id 的对象。

    【讨论】:

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

    有关合并的更多详细信息将帮助您使用合并而不是持久化:

    返回原始实体以外的托管实例是合并的关键部分 过程。如果持久化上下文中已经存在具有相同标识符的实体实例,则 提供者将用正在合并的实体的状态覆盖其状态,但托管 已经存在的版本必须返回给客户端,以便它可以使用。如果提供者没有 在持久化上下文中更新 Employee 实例,对该实例的任何引用都将变为 与正在合并的新状态不一致。

    当对新实体调用 merge() 时,它的行为类似于 persist() 操作。它增加了 实体添加到持久化上下文中,但不是添加原始实体实例,而是创建一个新的实体实例 而是复制和管理该实例。由 merge() 操作创建的副本被持久化 就好像对其调用了 persist() 方法一样。

    在存在关系的情况下,merge() 操作将尝试更新托管实体 指向被分离实体引用的实体的托管版本。如果实体有 与没有持久标识的对象的关系,合并操作的结果是 不明确的。一些提供者可能允许托管副本指向非持久对象, 而其他人可能会立即抛出异常。 merge() 操作可以是可选的 在这些情况下级联以防止发生异常。我们将介绍 merge() 的级联 本节后面的操作。如果正在合并的实体指向已移除的实体,则 会抛出 IllegalArgumentException 异常。

    延迟加载关系是合并操作中的一种特殊情况。如果延迟加载 在实体分离之前未在实体上触发关系,该关系将是 合并实体时忽略。如果关系在托管时触发,然后在实体分离时设置为 null,则实体的托管版本同样会在合并期间清除关系。"

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

    【讨论】:

      【解决方案6】:

      场景 X:

      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 有很多 spittle 意味着他有一个字符串列表。
      • 如果不阅读 Spring in Action,您可以使用一个更具可读性的示例......
      • 你实际上不需要知道什么是 spittle 或 spitter,因为上面写着 Spitter 是一张桌子, spitter 是另一张桌子,拥有这个和那个......跨度>
      【解决方案7】:

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

      (已编辑以扩展差异信息)

      坚持:

      • 在数据库中插入一个新的寄存器
      • 将对象附加到实体管理器。

      合并:

      • 找到具有相同 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 个附加对象。

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

      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中没有提到。
      【解决方案8】:

      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

      【讨论】:

        【解决方案9】:

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

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

        规范是你的朋友;)

        【讨论】:

          【解决方案10】:

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

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

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

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

          Hibernate 监控当前连接的实体。但要使实体成为托管实体,它必须处于正确的实体状态。

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

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

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

          • 新建(瞬态)

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

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

          • 持久(托管)

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

          • 分离

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

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

          • 重新连接

            Hibernate(但不是 JPA 2.1)支持通过 Session#update 方法重新附加。

            Hibernate Session 只能为给定的数据库行关联一个 Entity 对象。这是因为 Persistence Context 充当内存缓存(一级缓存),只有一个值(实体)与给定的键(实体类型和数据库标识符)相关联。

            只有在没有与当前 Hibernate Session 关联的其他 JVM 对象(匹配同一数据库行)时,才能重新附加实体。

          • 合并

            合并会将分离的实体状态(源)复制到托管实体实例(目标)。如果合并实体在当前 Session 中没有等价物,则从数据库中获取一个。

            即使在合并操作之后,分离的对象实例也将继续保持分离状态。

          • 删除

            虽然 JPA 要求只允许删除托管实体,但 Hibernate 也可以删除分离的实体(但只能通过 Session#delete 方法调用)。

            已删除的实体仅计划删除,实际的数据库 DELETE 语句将在 Session 刷新期间执行。

          【讨论】:

          • 这样orphanremoval=true就无法更改操作顺序?
          • 你的文章一般情况下的操作顺序。我的问题是 orphanRemoval
          • 查看my answer。 Hibernate 在这里没有什么神奇的。您只需要编写正确的数据访问逻辑代码。
          • 事实是不可能用这样的图来解释hibernate。为什么分离后不能刷新会话?当您尝试保存已持久化的实体时会发生什么?为什么在保存和持久化方面刷新的行为不同?有 1000 个这样的问题,没有人有明确的逻辑。
          • 不看User Guide一点也不难,而且这并不只适用于Hibernate。任何技术都一样。
          【解决方案11】:

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

          merge() 的用法和语义似乎让新用户感到困惑。首先,只要您不尝试在另一个新实体管理器中使用加载在一个实体管理器中的对象状态,您应该根本不需要使用 merge()。一些完整的应用程序永远不会使用这种方法。

          通常merge()用于以下场景:

          • 应用程序在第一个实体管理器中加载一个对象
          • 对象被向上传递到表示层
          • 对对象进行了一些修改
          • 对象被传回业务逻辑层
          • 应用程序通过在第二个实体管理器中调用 merge() 来保留这些修改

          下面是 merge() 的确切语义:

          • 如果存在具有相同标识符的托管实例当前与持久性上下文相关联,则将给定对象的状态复制到托管实例上
          • 如果当前没有与持久性上下文关联的托管实例,请尝试从数据库中加载它,或者创建一个新的托管实例
          • 托管实例返回
          • 给定的实例没有与持久化上下文相关联,它保持分离状态并且通常被丢弃

          发件人:http://docs.jboss.org/hibernate/entitymanager/3.6/reference/en/html/objectstate.html

          【讨论】:

            【解决方案12】:

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

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

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

            【讨论】:

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

            JPA 无疑是企业领域的一个伟大的简化 建立在 Java 平台上的应用程序。作为一个不得不 处理 J2EE 中旧实体 bean 的复杂性我看到了 将 JPA 纳入 Java EE 规范是一大飞跃 向前。然而,在深入研究 JPA 细节的同时,我发现 不那么容易的事情。在这篇文章中,我处理比较 EntityManager 的 merge 和 persist 方法重叠 行为可能不仅对新手造成混淆。此外我 提出一个概括,将这两种方法都视为一个特殊情况 更通用的方法组合。

            持久化实体

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

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

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

            合并实体

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

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

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

            坚持

            • 您希望该方法始终创建一个新实体并且从不更新实体。否则,该方法会因违反主键唯一性而引发异常。
            • 批处理,以有状态的方式处理实体(请参阅网关模式)。
            • 性能优化

            合并

            • 您希望该方法在数据库中插入或更新实体。
            • 您希望以无状态方式处理实体(服务中的数据传输对象)
            • 您想要插入一个新实体,该实体可能引用另一个可能但可能尚未创建的实体(关系必须标记为 MERGE)。例如,插入新照片并引用新相册或现有相册。

            【讨论】:

            • is E managed和Does PC contains a managed version of E有什么区别?
            【解决方案14】:

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

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

            • 数据需要持久化,但有时会存在记录并需要更新。在这种情况下,您可以尝试持久化,如果它抛出 EntityExistsException,则查找并组合数据:

              试试 { entityManager.persist(entity) }

              catch(EntityExistsException exception) { /* 检索并合并 */ }

            • 持久化数据需要更新,但有时还没有数据记录。在这种情况下,您查找它,如果实体丢失,请执行持久化:

              entity = entityManager.find(key);

              if (entity == null) { entityManager.persist(entity); }

              else { /* 合并 */ }

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

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

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

            【讨论】:

              【解决方案15】:

              另一个观察:

              merge() 只关心自动生成的 id(在 IDENTITYSEQUENCE 上测试),当您的表中已经存在具有此类 id 的记录时。在这种情况下,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 的东西时才会抱怨。
              猜你喜欢
              • 2012-05-25
              • 2019-03-30
              • 2011-05-29
              • 2020-11-26
              • 2016-07-14
              • 2011-06-19
              • 2014-11-19
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多