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


Answers:


1615

两种方法都会将一个实体添加到PersistenceContext,不同之处在于之后您对实体的处理方式。

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。


3
@dma_k:看起来您正在使用Hibernate。与JPA相比,我对Hibernate不太熟悉-但是在JPA中,如果您调用EntityManager.persist()并传递一个分离的实体,您将:a)立即获得EntityExistsException或b)在刷新/提交时获得另一个PersistenceException。也许我在这里误解了这个问题?
迈克(Mike)2010年

49
如果该回答还涵盖了在持久上下文中已经存在要合并/持久化的实体的情况(或者至少更清楚地表明,它仅描述了持久/合并实体不存在时的行为),则可能会改善此答案
亨利

2
一种方法更有效吗?也许merge在管理对象之前,对象的完整副本会影响性能?
凯文·梅瑞迪斯

2
身份证呢?如果我@GeneratedId可以在场景2中得到它?
rascio

7
迈克:“合并创建了一个新实例...”:并非总是如此。如果EntityManager在其上下文中找到已经被管理的实体,它将返回该实例(在更新字段之后)。请修改您的答案,然后我将投票给它。
2015年

181

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

(编辑以扩展差异信息)

坚持:

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

合并:

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


你能说出它是不是有效的替换的情况下em.persist(x)x = em.merge(x)
亚伦·迪古拉

20
persist()可以抛出EntityExistsException。如果要确保代码正在执行插入操作而不是对数据进行更新,则必须使用persist。
Josep Panadero 2013年

1
merge()也可以EntityExistsException
肖恩

1
@None可能是因为它是一个RuntimeException,但是Javadoc中没有提到它。
马丁

154

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

同样,为托管实体调用合并也是一个错误,因为托管实体由Hibernate自动管理,并且在刷新Persistence Context时,脏检查机制会将它们的状态与数据库记录同步。

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

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

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

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

JPA实体状态转换

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

休眠实体状态转换

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

  • 新(瞬态)

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

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

  • 持久(托管)

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

  • 独立式

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

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

    • 重新连接

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

    • 合并中

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

  • 已移除

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



@gstackoverflow您得到的答案是正确的。有关更多详细信息,请查阅本文或我的书《高性能Java持久性》
Vlad Mihalcea

因此,不可能更改orphanremoval = true吗?
gstackoverflow

您关于正常情况下的操作顺序的文章。我的问题专门针对orphanRemoval
gstackoverflow

1
事实是不可能用这样的图来解释休眠。为什么分离后无法刷新会话?当您尝试保存一个已经存在的实体时会发生什么?为什么保存和持久保存时刷新的行为不同?有1000个这样的问题,没有人有明确的逻辑。
GingerBeer

37

我注意到,当我使用时,即使没有JPA为我生成的字段,我也会为每个语句em.merge得到一个SELECT声明INSERT-主键字段是我自己设置的UUID。我转而去em.persist(myEntityObject)只是那INSERT句话。


3
这是有道理的,因为您分配了ID,而JPA容器不知道从何处获得ID。数据库中已经存在对象的可能性很小,例如,在多个应用程序写入同一数据库的情况下。
亚伦·迪古拉

我也曾遇到过类似的问题merge()。我的PostgreSQL数据库具有复杂的视图:该视图聚合了多个表的数据(这些表具有相同的结构,但名称不同)。因此,JPA尝试这样做merge(),但实际上是先创建了JPA SELECT(由于视图设置,数据库可能会从不同的表返回具有相同主键的多个记录!),然后JPA(Hibernate是一个实现)失败:有多个具有相同键的记录(org.hibernate.HibernateException: More than one row with the given identifier was found)。就我而言persist()帮助了我。
flaz14

29

JPA规范说明了以下内容persist()

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

因此,persist()当对象不应该是分离的对象时,使用将是合适的。您可能更喜欢使代码抛出错误,PersistenceException因此它很快就会失败。

尽管规格不清楚,但persist()可以@GeneratedValue @Id为对象设置。merge()但是必须有一个@Id已经生成的对象。


5
+1表示“ merge()但是必须具有@Id 已生成的对象。 ”。每当EntityManager在对象ID的字段中找不到值时,它将被持久化(插入)到数据库中。
奥马尔

我不了解这一点,因为我不清楚各州。希望这能对我有所帮助。docs.jboss.org/hibernate/core/3.6/reference/en-US/html/…–
RBz

1
@GeneratedValue对merge()和persist()没有任何不同的含义
SandeepGodara

17

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

返回原始实体以外的托管实例是合并过程的关键部分。如果持久性上下文中已经存在具有相同标识符的实体实例,则提供程序将使用要合并的实体的状态覆盖其状态,但是必须将已经存在的托管版本返回给客户端,以便可以用过的。如果提供程序未在持久性上下文中更新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的人阅读本书。对不起,我无意间发布了我的第一个答案。


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);

D2。如果删除实体然后决定将实体持久保存,则只能使用persist(),因为merge会抛出IllegalArgumentException

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

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


8

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

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

为了缓解这种情况,我在控制器中更新了相同的实体并将其传递给我的jsp,尽管我想象当我在会话中重新保存时,尽管它也可以访问SessionScope,而不抛出a 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!!

7

我从启发性的Hibernate文档中找到了这种解释,因为它们包含一个用例:

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

通常在以下情况下使用merge():

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

这是merge()的确切语义:

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

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


6

通过答案,有一些有关“级联”和ID生成的细节缺少。见问题

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

该规范是您的朋友;)


6

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

持久实体

与merge方法相比,persist方法非常简单直观。持久化方法使用的最常见情况可以总结如下:

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

在此处输入图片说明

规范中更多地涉及细节,但是记住它们并不是至关重要的,因为这些细节仅涉及或多或少的特殊情况。

合并实体

与持久化相比,合并行为的描述不是那么简单。没有持久性情况下的主要方案,程序员必须记住所有方案才能编写正确的代码。在我看来,JPA设计人员想要某种方法,其主要关注点是处理分离的实体(这与主要处理新创建的实体的persist方法相反)。merge方法的主要任务是从非托管实体(作为参数传递)到持久化上下文中的托管对等实体。但是,此任务又分为几种情况,这些情况使整个方法的行为的可理解性变差。

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

在此处输入图片说明

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

坚持

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

合并

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

E受管理和PC是否包含E的受管版本有什么区别?
GingerBeer

5

方案X:

表:Spitter(一个),表:Spitttles(许多)(Spittles是具有FK:spitter_id的关系的所有者)

这种情况下可以节省成本:Spitter和两个Spittle都由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!!

1
飞溅物是从Graig Walls的《 Spring in Action》第三版中摘取的。Spitters是说什么的人,Spttlet是他们实际上在说的话。因此Spitter有许多spittles,意味着他有一个Strings列表。
乔治·帕帕特奥多鲁

1
如果不阅读Spring in Action,您可能会使用一个更具可读性的示例...
wonderb0lt

1
您实际上不需要知道什么是唾沫或飞溅物,因为在上面写着,飞溅物是一个桌子,飞溅物是另一个拥有的桌子..这个和那个……
George Papatheodorou

3

另一观察:

merge()仅当表中已存在具有此类ID的记录时,才会关心自动生成的ID(在IDENTITY和上测试过SEQUENCE)。在这种情况下,merge()将尝试更新记录。但是,如果缺少一个ID或与任何现有记录都不匹配,merge()则将其完全忽略,并要求数据库分配一个新的ID 。有时这是许多错误的来源。请勿merge()用于强制为新记录创建ID。

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

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

hibernate-jpa javadoc有一个提示:

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


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

1

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

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

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

    尝试{EntityManager.persist(entity)}

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

  • 持久性数据需要更新,但有时还没有该数据的记录。在这种情况下,您可以查找它,如果缺少实体,请执行以下操作:

    实体= entityManager.find(key);

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

    否则{/ *合并* /}

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

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

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

0

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

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

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


这是不正确的。如果在新e上调用merge(e),则必须将其保留。
PedroLamarão2015年


从JPA规范版本2.1,第3.2.7.1节,第二个项目符号:“如果X是新的实体实例,则将创建新的管理实体实例X',并将X的状态复制到新的管理实体实例X'中。”
PedroLamarão
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.