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

Aar*_*lla 925 merge jpa entitymanager persist java-persistence-api

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

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

Mik*_*ike 1585

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

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

合并创建实体的新实例,从提供的实体复制状态,并管理新副本.您传入的实例将不会被管理(您所做的任何更改都不会成为交易的一部分 - 除非您再次调用合并).

也许代码示例会有所帮助.

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)
Run Code Online (Sandbox Code Playgroud)

场景1和3大致相同,但在某些情况下您需要使用场景2.

  • 如果它还涵盖了持久化上下文中已经存在合并/持久化实体的情况,那么这个答案可能会得到改善(或者至少更清楚地说明它只描述了持久/合并实体尚不存在时的行为) (43认同)
  • 迈克:"合并创建一个新实例......":这并非总是如此.如果EntityManager在其上下文中找到已经管理的实体,则返回此实例(在更新字段之后).请编辑你的答案,然后我会投票支持. (5认同)
  • @dma_k:看起来你正在使用Hibernate.我不熟悉Hibernate而不是JPA - 但是在JPA中,如果你调用EntityManager.persist()并传入一个分离的实体,你将:a)立即获得EntityExistsException或b)在flush/commit时获得另一个PersistenceException.也许我在这里误解了这个问题? (3认同)
  • ids怎么样?如果我有一个`@ GeneratedId`我可以在方案2中得到它吗? (2认同)
  • 您可以在互联网上免费找到由大师解释的一种令人兴奋的解释 (2认同)

Jos*_*ero 172

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

(编辑扩大差异信息)

坚持:

  • 将新寄存器插入数据库
  • 将对象附加到实体管理器.

合并:

  • 找到具有相同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);
}
Run Code Online (Sandbox Code Playgroud)

这种方式只为实体管理器中的任何寄存器存在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;
}
Run Code Online (Sandbox Code Playgroud)

虽然如果连接到MySQL,merge()可以像使用ON DUPLICATE KEY UPDATE选项调用INSERT一样有效,但JPA是一个非常高级的编程,你不能认为这种情况在任何地方都是如此.

  • persist()可以抛出EntityExistsException.如果要确保代码正在执行插入而不是更新数据,则必须使用persist. (18认同)

Vla*_*cea 141

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

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

要了解所有这些是如何工作的,您首先应该知道Hibernate将开发人员的思维方式从SQL语句转移到实体状态转换.

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

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

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

  • 新(瞬态)

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

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

  • 持久性(管理)

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

  • 超脱

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

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

    • 重新连接

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

    • 合并

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

  • 删除

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

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

在此输入图像描述

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

在此输入图像描述


Sar*_*els 37

我注意到,当我使用时em.merge,我得到了一个SELECT声明INSERT,即使没有JPA为我生成的字段 - 主键字段是我自己设置的UUID.然后我转而接受em.persist(myEntityObject)INSERT陈述.

  • 有意义,因为你分配了ID,JPA容器不知道你从哪里得到它.对象已经存在(小)的可能性,例如在多个应用程序写入同一数据库的情况下. (3认同)

Rae*_*ald 29

JPA规范说明如下persist().

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

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

虽然规范不清楚,但persist()可能会设置@GeneratedValue @Id一个对象.merge()但是必须有一个@Id已经生成的对象.

  • 但是"_`merge()`的+1必须有一个已经生成了`@ Id`**的对象**.".每当EntityManager找不到对象ID字段的值时,它就会持久化(插入)到DB中. (4认同)

小智 17

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

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

在新实体上调用merge()时,它的行为与persist()操作类似.它将实体添加到持久性上下文中,但它不是添加原始实体实例,而是创建新副本并管理该实例.merge()操作创建的副本将保持不变,就好像在其上调用了persist()方法一样.

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

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

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


And*_*i I 17

merge和之间还有一些差异persist(我将再次枚举已在此处发布的内容):

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);
Run Code Online (Sandbox Code Playgroud)

D2.如果你删除一个实体,然后决定将实体保留回来,你可以只用persist()来做,因为merge会抛出一个IllegalArgumentException.

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

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


log*_*yer 8

我在我的实体上得到了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!!
Run Code Online (Sandbox Code Playgroud)


Ioa*_*nis 6

通过答案,有关"Cascade"和id生成的一些细节缺失.看问题

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

规格是你的朋友;)


Ray*_*lha 6

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

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

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

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

这是merge()的确切语义:

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

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


Ami*_*thi 6

JPA毫无疑问是在Java平台上构建的企业应用程序领域的一个很大的简化.作为一个不得不应对J2EE中旧实体bean复杂性的开发人员,我认为Java EE规范中包含的JPA是一个重大飞跃.然而,在深入研究JPA细节时,我发现事情并非那么容易.在本文中,我将讨论EntityManager的merge和persist方法的比较,这些方法的重叠行为不仅会导致新手混淆.此外,我提出了一种概括,即将两种方法视为更通用方法的特殊情况.

坚持实体

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

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

在此输入图像描述

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

合并实体

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

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

在此输入图像描述

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

坚持

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

合并

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


Geo*_*rou 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!!
Run Code Online (Sandbox Code Playgroud)

情景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!!
Run Code Online (Sandbox Code Playgroud)