JPA EntityManager:为什么在merge()上使用persist()?

时间:2009-07-01 16:04:00

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

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

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

15 个答案:

答案 0 :(得分:1535)

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

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

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.

答案 1 :(得分:163)

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

(编辑以扩展差异信息)

坚持:

  • 将新注册表插入数据库
  • 将对象附加到实体管理器。

合并:

  • 找到具有相同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;
}

虽然如果连接到MySQL merge()可以像使用ON DUPLICATE KEY UPDATE选项调用INSERT一样高效,那么JPA是一个非常高级的编程,你不能认为这将是无处不在。

答案 2 :(得分:133)

如果您使用指定的生成器using merge instead of persist can cause a redundant SQL statement,则会影响性能。

此外,calling merge for managed entities也是一个错误,因为管理实体由Hibernate自动管理,并且dirty checking mechanismflushing the Persistence Context上状态与数据库记录同步。

要了解所有这些是如何工作的,首先应该知道Hibernate将开发人员的思维方式从SQL语句转移到entity state transitions

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

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

首先,我们必须定义所有实体状态:

  • 新(瞬态)

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

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

  • 持久(管理)

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

  • 独立式

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

    要将分离的实体与活动的Hibernate会话相关联,您可以选择以下选项之一:

    • 再附

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

    • 合并

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

  • 删除

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

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

enter image description here

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

enter image description here

答案 3 :(得分:37)

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

答案 4 :(得分:28)

JPA规范说明了以下persist()

  

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

因此,当对象不应成为分离对象时,使用persist()将是合适的。您可能希望让代码抛出PersistenceException,以便快速失败。

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

答案 5 :(得分:17)

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

答案 6 :(得分:16)

有关合并的更多详细信息将帮助您使用merge over persist:

  

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

     

在新实体上调用merge()时,它的行为与persist()操作类似。它补充道   实体到持久化上下文,但它不是添加原始实体实例,而是创建一个新实体   而是复制和管理该实例。 merge()操作创建的副本是持久的   好像在它上面调用了persist()方法。

     

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

     

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

以上所有信息均来自Mike Keith和Merrick Schnicariol的“Pro JPA 2掌握Java™持久性API”。第6章部分分离和合并。这本书实际上是作者专门撰写JPA的第二本书。这本新书有许多新信息,然后是前一本。我真的建议您阅读本书,了解那些认真参与JPA的人。我很抱歉无意中发布了我的第一个答案。

答案 7 :(得分:8)

我在我的实体上得到了懒惰的加载异常,因为我试图访问一个在会话中的延迟加载的集合。

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

为了缓解这个问题,我更新了控制器中的同一个实体,并将其传递给了我的jsp,虽然我想当我在会话中重新保存时它也可以通过SessionScope访问而不是抛出{{ 1}},对示例2的修改:

以下对我有用:

LazyLoadingException

答案 8 :(得分:6)

通过答案,有一些关于“Cascade'和id生成。 See question

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

规范是你的朋友;)

答案 9 :(得分:6)

我从Hibernate docs中发现了这个解释,因为它们包含一个用例:

  

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

     

通常在以下场景中使用merge():

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

这是merge()的确切语义:

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

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

答案 10 :(得分:6)

  

JPA毫无疑问是企业领域的一个很大的简化   在Java平台上构建的应用程序。作为一个不得不的开发人员   应对J2EE中旧实体bean的复杂性我看到了   在Java EE规范中包含JPA是一个重大的飞跃   向前。然而,在深入研究JPA细节的同时,我发现了   事情并不那么容易。在本文中我将讨论比较   EntityManager的重合和持久化方法重叠   行为可能不仅会给新手造成混淆。而且我   提出了将两种方法视为a的特例的概括   更通用的方法结合。

坚持实体

与合并方法相比,持久化方法非常简单直观。持久化方法的最常见用法可以总结如下:

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

enter image description here

规范更详细,但记住它们并不重要,因为这些细节仅涵盖或多或少的异国情调。

合并实体

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

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

enter image description here

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

<强>持续

  • 您希望该方法始终创建新实体,并且永远不会更新实体。否则,该方法会因主键唯一性违规而抛出异常。
  • 批处理,以有状态方式处理实体(请参阅网关模式)。
  • 效果优化

<强>合并

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

答案 11 :(得分:5)

情景X:

表:Spitter(一),表:Spittles(很多)(Spittles是与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!!

答案 12 :(得分:1)

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

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

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

    尝试{entityManager.persist(entity)}

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

  • 需要更新持久数据,但偶尔也没有数据记录。在这种情况下,您可以查找它,并在实体丢失时执行持久性操作:

    entity = entityManager.find(key);

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

    else {/ * merge * /}

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

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

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

答案 13 :(得分:1)

另一个观察结果:

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

另一方面,

persist()永远不会让你传递一个id。它会立即失败。就我而言,它是:

  

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

hibernate-jpa javadoc有一个提示:

  

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

答案 14 :(得分:0)

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

应使用

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

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