EntityManager.merge()
可以插入新对象并更新现有对象。
为什么要使用persist()
(只能创建新对象)?
EntityManager.merge()
可以插入新对象并更新现有对象。
为什么要使用persist()
(只能创建新对象)?
无论哪种方式都会将实体添加到 PersistenceContext,不同之处在于您之后对实体执行的操作。
Persist 获取一个实体实例,将其添加到上下文中并管理该实例(即,将跟踪实体的未来更新)。
合并返回状态合并到的托管实例。它确实会返回存在于 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。
持久化和合并有两个不同的目的(它们根本不是替代品)。
(编辑以扩展差异信息)
坚持:
合并:
persist() 效率:
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 是一种非常高级的编程,您不能假设到处都是这种情况。
如果您使用分配的生成器,使用merge
而不是persist
可能会导致冗余 SQL 语句,从而影响性能。
此外,调用merge
托管实体也是一个错误,因为托管实体由 Hibernate 自动管理,并且它们的状态在刷新持久性上下文时通过脏检查机制与数据库记录同步。
要了解这一切是如何工作的,您首先应该知道 Hibernate 将开发人员的思维方式从 SQL 语句转变为实体状态转换。
一旦实体由 Hibernate 主动管理,所有更改都将自动传播到数据库。
Hibernate 监视当前附加的实体。但是要使实体成为受管实体,它必须处于正确的实体状态。
为了更好地理解 JPA 状态转换,您可以可视化下图:
或者,如果您使用 Hibernate 特定的 API:
如上图所示,实体可以处于以下四种状态之一:
从未与 Hibernate Session
(aka Persistence Context
) 关联且未映射到任何数据库表行的新创建的对象被视为处于 New (Transient) 状态。
要持久化,我们需要显式调用该EntityManager#persist
方法或使用传递持久性机制。
持久(托管)
持久性实体已与数据库表行相关联,并由当前运行的持久性上下文管理。对此类实体所做的任何更改都将被检测到并传播到数据库(在会话刷新期间)。使用 Hibernate,我们不再需要执行 INSERT/UPDATE/DELETE 语句。Hibernate 采用事务性后写工作方式,并且在当前Session
刷新时间期间的最后一个负责时刻同步更改。
分离式
一旦当前运行的持久性上下文关闭,所有以前管理的实体都将被分离。将不再跟踪连续的更改,也不会发生自动数据库同步。
要将分离的实体关联到活动的 Hibernate Session,您可以选择以下选项之一:
重新连接
Hibernate(但不是 JPA 2.1)支持通过 Session#update 方法重新附加。
Hibernate Session 只能为给定的数据库行关联一个实体对象。这是因为持久性上下文充当内存缓存(一级缓存),并且只有一个值(实体)与给定的键(实体类型和数据库标识符)相关联。
仅当没有其他 JVM 对象(匹配相同的数据库行)与当前 Hibernate Session 关联时,才能重新附加实体。
合并
合并会将分离的实体状态(源)复制到托管实体实例(目标)。如果合并实体在当前会话中没有等价物,则将从数据库中获取一个。
即使在合并操作之后,分离的对象实例仍将继续保持分离状态。
消除
尽管 JPA 要求只允许删除托管实体,但 Hibernate 也可以删除分离的实体(但只能通过 Session#delete 方法调用)。
删除的实体仅计划删除,实际的数据库 DELETE 语句将在会话刷新期间执行。
我注意到,当我使用 时,即使没有 JPA 为我生成的字段em.merge
,我也得到了SELECT
每个 的声明——主键字段是我自己设置的 UUID。INSERT
然后我切换到em.persist(myEntityObject)
并得到了INSERT
陈述。
JPA 规范对persist()
.
如果X是一个分离的对象,则
EntityExistsException
可能会在调用持久操作时抛出 ,或者可能会在刷新或提交时抛出theEntityExistsException
或另一个。PersistenceException
因此,当对象不应该是分离persist()
的对象时,使用将是合适的。您可能更喜欢让代码抛出,以便它快速失败。PersistenceException
虽然规范不清楚,但persist()
可能会@GeneratedValue
@Id
为一个对象设置 。merge()
但是必须有一个@Id
已经生成的对象。
有关合并的更多详细信息将帮助您在持久性上使用合并:
返回原始实体以外的托管实例是合并过程的关键部分。如果持久化上下文中已经存在具有相同标识符的实体实例,则提供者将使用正在合并的实体的状态覆盖其状态,但必须将已经存在的托管版本返回给客户端,以便它可以用过的。如果提供者没有在持久化上下文中更新 Employee 实例,那么对该实例的任何引用都将与正在合并的新状态不一致。
当在新实体上调用 merge() 时,它的行为类似于 persist() 操作。它将实体添加到持久化上下文中,但不是添加原始实体实例,而是创建一个新副本并管理该实例。由 merge() 操作创建的副本被持久化,就好像对其调用了 persist() 方法一样。
在存在关系的情况下,merge() 操作将尝试更新托管实体以指向被分离实体引用的实体的托管版本。如果实体与没有持久标识的对象有关系,则合并操作的结果是未定义的。一些提供者可能允许托管副本指向非持久对象,而其他提供者可能会立即抛出异常。在这些情况下,可以选择级联 merge() 操作,以防止发生异常。我们将在本节后面介绍 merge() 操作的级联。如果正在合并的实体指向已移除的实体,则会抛出 IllegalArgumentException 异常。
延迟加载关系是合并操作中的一种特殊情况。如果在实体分离之前未在实体上触发延迟加载关系,则在合并实体时将忽略该关系。如果关系在托管时触发,然后在实体分离时设置为空,则实体的托管版本同样会在合并期间清除关系。”
以上所有信息均来自 Mike Keith 和 Merrick Schnicariol 的“Pro JPA 2 Mastering the Java™ Persistence API”。第 6 章节分离与合并。这本书实际上是作者致力于 JPA 的第二本书。这本新书比上一本书有许多新信息。我真的建议那些将认真参与 JPA 的人阅读这本书。我很抱歉匿名发布我的第一个答案。
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
.
JPA 无疑是在 Java 平台上构建的企业应用程序领域的一个极大简化。作为一个必须应对 J2EE 中旧实体 bean 的复杂性的开发人员,我认为将 JPA 包含在 Java EE 规范中是一个巨大的飞跃。然而,在深入研究 JPA 细节时,我发现事情并不那么容易。在本文中,我将比较 EntityManager 的 merge 和 persist 方法,它们的重叠行为可能不仅会引起新手的困惑。此外,我提出了一个概括,将这两种方法视为更通用方法组合的特殊情况。
持久实体
与 merge 方法相比,persist 方法非常简单直观。最常见的persist方法的使用场景可以总结如下:
“一个新创建的实体类实例被传递给persist方法。该方法返回后,管理实体并计划插入数据库。它可能发生在事务提交时或之前,或者调用flush方法时。如果实体通过标有 PERSIST 级联策略的关系引用另一个实体,则此过程也适用于它。”
该规范更详细地介绍了细节,但是记住它们并不重要,因为这些细节仅涵盖或多或少的奇异情况。
合并实体
与persist相比,merge的行为描述就没有那么简单了。没有主要场景,就像在持久化的情况下一样,程序员必须记住所有场景才能编写正确的代码。在我看来,JPA 设计者希望有一些方法,其主要关注点是处理分离的实体(与主要处理新创建的实体的持久方法相反)。合并方法的主要任务是将状态从非托管实体(作为参数传递)到持久性上下文中的托管实体。然而,这项任务进一步分为几个场景,这些场景会恶化整个方法行为的可理解性。
我没有重复 JPA 规范中的段落,而是准备了一个流程图,该流程图示意性地描述了合并方法的行为:
那么,我应该什么时候使用persist,什么时候合并?
坚持
合并
我在实体上遇到了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!!
我从 Hibernate 文档中发现了这个解释很有启发性,因为它们包含一个用例:
新用户似乎对 merge() 的用法和语义感到困惑。首先,只要您不尝试在另一个新实体管理器中使用加载在一个实体管理器中的对象状态,则根本不需要使用 merge()。一些完整的应用程序永远不会使用这种方法。
通常merge()用于以下场景:
- 应用程序在第一个实体管理器中加载一个对象
- 对象被传递到表示层
- 对对象进行了一些修改
- 对象被传递回业务逻辑层
- 应用程序通过在第二个实体管理器中调用 merge() 来保留这些修改
下面是 merge() 的确切语义:
- 如果存在与当前与持久性上下文关联的具有相同标识符的托管实例,则将给定对象的状态复制到托管实例上
- 如果当前没有与持久性上下文关联的托管实例,请尝试从数据库中加载它,或者创建一个新的托管实例
- 返回托管实例
- 给定的实例没有与持久化上下文相关联,它保持分离并且通常被丢弃
来自:http ://docs.jboss.org/hibernate/entitymanager/3.6/reference/en/html/objectstate.html
通过答案,缺少有关“级联”和 id 生成的一些细节。查看问题
此外,值得一提的是,您可以有单独Cascade
的注释用于合并和持久化:Cascade.MERGE
并将Cascade.PERSIST
根据使用的方法进行处理。
规范是你的朋友;)
情景十:
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!!
另一个观察:
merge()
仅当您的表中已存在具有此类 id 的记录时,才会关心自动生成的 id(在IDENTITY
and上测试)。SEQUENCE
在这种情况下merge()
将尝试更新记录。但是,如果 id 不存在或与任何现有记录不匹配,merge()
将完全忽略它并要求 db 分配一个新记录。这有时是很多错误的来源。不要merge()
用于强制新记录的 id。
persist()
另一方面,永远不会让您甚至将 id 传递给它。它会立即失败。就我而言,它是:
引起:org.hibernate.PersistentObjectException:分离的实体传递给持久化
hibernate-jpa javadoc 有一个提示:
抛出: javax.persistence.EntityExistsException - 如果实体已经存在。(如果实体已经存在,在调用持久化操作时可能会抛出 EntityExistsException,或者在刷新或提交时可能会抛出 EntityExistsException 或另一个 PersistenceException。)
您可能来这里是为了获得有关何时使用persist和何时使用merge的建议。我认为这取决于情况:您需要创建新记录的可能性有多大,以及检索持久数据的难度有多大。
假设您可以使用自然键/标识符。
数据需要持久化,但有时会存在记录并需要更新。在这种情况下,您可以尝试持久化,如果它抛出 EntityExistsException,则查找并组合数据:
尝试 { entityManager.persist(entity) }
catch(EntityExistsException exception) { /* 检索并合并 */ }
持久化数据需要更新,但有时还没有数据记录。在这种情况下,您查找它,如果实体丢失,请执行持久化:
实体 = entityManager.find(key);
if (entity == null) { entityManager.persist(entity); }
else { /* 合并 */ }
如果您没有自然键/标识符,您将很难确定实体是否存在,或者如何查找它。
也可以通过两种方式处理合并:
persist(entity) 应该与全新的实体一起使用,以将它们添加到数据库中(如果实体已经存在于数据库中,则会抛出 EntityExistsException)。
应该使用 merge(entity),如果实体被分离并被更改,则将实体放回持久性上下文。
可能持久化正在生成 INSERT sql 语句和合并 UPDATE sql 语句(但我不确定)。