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

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

有帮助吗?

解决方案

无论哪种方式,将实体添加到一个PersistenceContext,不同的是在你做什么与实体之后。

坚持采用实体实例,将其添加到上下文并使该实例管理(即未来的实体更新将被跟踪)。

合并创造你的实体的一个新实例,复制从提供的实体的状态,使得新副本进行管理。您传递的情况下将无法管理(你所做的任何更改将不会成为交易的一部分 - 除非你再次调用合并)。

也许一个代码示例将帮助。

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.

其他提示

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

(编辑以扩大差异信息)

坚持:

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

合并:

  • 找到具有相同 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 是一种非常高级的编程,您不能假设到处都是这种情况。

如果您使用指定的发电机, 使用 merge 而不是 persist 会导致多余的 SQL 语句, ,从而影响性能。

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

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

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

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

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

  • 新的(暂时的)

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

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

  • 持久(托管)

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

  • 独立的

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

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

    • 重新连接

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

    • 合并

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

  • 已删除

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

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

enter image description here

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

enter image description here

我注意到,当我用em.merge,我得到了SELECT语句每INSERT,即使没有字段JPA是产生了我 - 主键字段是我给自己设定了一个UUID。我切换到em.persist(myEntityObject)和仅获得INSERT语句的话。

JPA规范说,大约persist()以下。

  

如果 X 是一个分离的对象,则EntityExistsException可以当坚持抛出   操作被调用时,或EntityExistsException或另一PersistenceException可以在冲洗被抛出或提交时间。

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

虽然说明书不清楚persist()可以设置该@GeneratedValue @Id为一个对象。然而merge()必须与已经生成的@Id的对象。

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。如果你删除一个实体,然后决定要坚持实体回来,你可以做到这一点只有坚持(),因为merge将抛出IllegalArgumentException

D3。如果您决定手动照顾你的ID(例如通过使用UUID),然后merge 操作将触发后续SELECT查询,以便寻找与该ID存在的实体,而persist可能不需要这些查询。

D4。有些时候,你根本不相信,叫你的代码的代码,以确保没有数据被更新,而是被插入的情况下,你必须使用persist

有关合并更多的细节,这将有助于您使用合并在坚持:

  

返回一个托管实例以外的原始实体是合并的关键部分   处理。如果具有相同标识符的实体实例已经存在于持久化上下文中,   供应商将覆盖其状态与被合并实体的状态,但管理   已经存在的版本必须返回到客户端,以便它可以被使用。如果供应商没有   更新持久化上下文的Employee实例,该实例的引用将成为   用新状态不一致被合并英寸

     

当合并()是在一个新的实体调用时,它的行为类似于坚持()操作。它增加了   实体的持久化上下文但是,代替将原有实体实例,它会创建一个新的   复制并管理实例,而不是。由合并()操作所创建的副本坚持   如同persist()方法被调用它。

     

在关系的存在,合并()操作将尝试更新管理实体   指向由分离的实体引用的实体的管理版本。如果实体有一个   为不具有持久化标识对象的关系,合并操作的结果是   未定义。一些供应商可能会允许托管副本指向非持久性对象,   而其他人可能会立即抛出异常。合并()操作可以是任选   在这些情况下级联,以防止异常的发生。我们将介绍合并的级联()   操作在本节后面。如果一个实体被合并指向去除实体,   抛出:IllegalArgumentException会抛出异常。

     

延迟加载关系在合并操作的特例。如果延迟加载   关系不触发它已分离之前的实体,这种关系会   当实体合并忽略。如果在管理,然后设置为空,而该实体分离的关系被触发,该实体的托管版本将同样有合并过程中被清除的关系。“

所有上述信息是从拍摄“临JPA 2掌握的Java™持久性API”由Mike Keith和梅里克Schnicariol。第6章第支队和合并。这本书实际上是第二本书的作者专门JPA。这本新书有许多新的信息,则前者。我真的很推荐阅读这本书的人谁都会认真参与JPA。我很抱歉anonimously张贴我的第一个答案。

我在我的实体越来越惰性加载异常,因为我试图访问延迟加载的集合,这是在会话。

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

浏览答案时,缺少一些有关“级联”和 id 生成的细节。 查看问题

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

规格是你的朋友;)

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

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

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

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

这是 merge() 的确切语义:

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

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

毫无疑问,JPA是在Java平台上构建的企业应用程序领域中的一个很好的简化。作为一名开发人员,必须应对J2EE中旧实体豆的复杂性,我认为将JPA纳入Java EE规格中是一个很大的飞跃。但是,在深入研究JPA细节的同时,我发现的东西并不容易。在本文中,我处理了实体的合并和持久方法的比较,其重叠行为不仅会引起新手的困惑。此外,我提出了一种概括,将这两种方法视为一种更通用方法的特殊情况。

持久化实体

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

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

enter image description here

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

合并实体

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

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

enter image description here

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

坚持

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

合并

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

方案X:

表:Spitter(一),表:Spittles(很多)(Spittles是具有FK的关系的拥有者:spitter_id)

这个场景结果在省电:该Spitter和两个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.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!!

另一个观察:

merge()将只关心一个自动生成的ID(在IDENTITYSEQUENCE测试)在与这样的ID记录在你的表已经存在。在这种情况下merge()将尝试更新记录。 但是,如果一个ID是不存在或不匹配任何现有的记录,merge()会完全忽略它,并问DB分配一个新的。有时,这是一个很大的错误的来源。不要使用merge()迫使一个id为一个新的记录。

在另一方面persist()永远不会让你即使通过一个ID给它。它会立即失效。对我来说,它是:

  

产生的原因:org.hibernate.PersistentObjectException:独立实体   被persist

冬眠-JPA的Javadoc具有一个提示:

  

<强>抛出:javax.persistence.EntityExistsException - 如果实体   已经存在。 (如果实体已经存在,   当坚持操作EntityExistsException可能会抛出   调用的,或EntityExistsException或另一个的PersistenceException   可以在冲洗或提交时被抛出。)

您可能来这里寻求有关何时使用的建议 坚持 以及何时使用 合并. 。我认为这要看具体情况:您需要创建新记录的可能性有多大,以及检索持久数据有多困难。

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

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

    尝试 {entityManager.persist(entity) }

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

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

    实体=entityManager.find(key);

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

    else { /* 合并 */ }

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

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

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

坚持(实体)应与全新的实体使用,将它们添加到数据库(如果实体在DB已经存在会有EntityExistsException掷)。

合并(实体)应被使用,把实体回持久性上下文表示实体是否分离并已被更改。

可能一直存在正在生成INSERT SQL语句和合并UPDATE SQL语句(但我不知道)。

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top