//MERGE: passedEntity remains unmanaged, but newEntity will be managed
Entity newEntity = em.merge(passedEntity);
//PERSIST: passedEntity will be managed after this
em.persist(passedEntity);
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)
// 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!!
{
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);
}
15 回答
如果您使用的是指定的生成器using merge instead of persist can cause a redundant SQL statement,则会影响性能 .
此外,calling merge for managed entities也是一个错误,因为管理实体由Hibernate自动管理,并且dirty checking mechanism在flushing the Persistence Context时它们的状态与数据库记录同步 .
要了解这一切是如何工作的,你应该首先知道Hibernate会改变它开发人员的心态从SQL语句到entity state transitions .
一旦Hibernate主动管理实体,所有更改将自动传播到数据库 .
Hibernate监视当前附加的实体 . 但是对于要管理的实体,它必须处于正确的实体状态 .
首先,我们必须定义所有实体状态:
未与Hibernate
Session
(a.k.aPersistence 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状态转换,您可以可视化以下图表:
或者,如果您使用Hibernate特定的API:
另一个观察:
merge()
只关心表中已存在具有此ID的记录时自动生成的ID(在IDENTITY
和SEQUENCE
上测试) . 在这种情况下merge()
将尝试更新记录 . 但是,如果id不存在或者与任何现有记录不匹配,merge()
将完全忽略它并要求db分配新记录 . 这有时是很多错误的根源 . 不要使用merge()
来强制新记录的ID .另一方面,
persist()
永远不会让你甚至传递一个id . 它会立即失败 . 就我而言,它是:hibernate-jpa javadoc有一个提示:
JPA规范说明了以下关于
persist()
的内容 .因此,当对象不应该是分离对象时,使用
persist()
将是合适的 . 您可能希望让代码抛出PersistenceException
,以便快速失败 .虽然the specification is unclear,
persist()
可能会为对象设置@GeneratedValue
@Id
.merge()
但是必须有一个已生成@Id
的对象 .场景X:
表:Spitter(一),表:Spittles(很多)(Spittles是与FK的关系的所有者:spitter_id)
这种情况导致节省:Spitter和Spittles都好像拥有Same Spitter一样 .
情景Y:
这将节省Spitter,将节省2 Spittles但他们不会引用相同的Spitter!
我从Hibernate docs中发现了这个解释,因为它们包含一个用例:
来自:http://docs.jboss.org/hibernate/entitymanager/3.6/reference/en/html/objectstate.html
我注意到当我使用
em.merge
时,我为每个INSERT
得到一个SELECT
语句,即使没有JPA为我生成的字段 - 主键字段是我自己设置的UUID . 我切换到em.persist(myEntityObject)
然后只得到INSERT
语句 .merge
和persist
之间还有一些差异(我将再次枚举已在此处发布的内容):D1 .
merge
不会使传递的实体受管,而是返回另一个受管实例 . 另一方面persist
将使传递的实体得到管理:D2 . 如果删除实体然后决定将实体保留回来,则只能使用persist(),因为
merge
将抛出IllegalArgumentException
.D3 . 如果您决定手动处理您的ID(例如,通过使用UUID),则
merge
操作将触发后续的SELECT
查询,以便查找具有该ID的现有实体,而persist
可能不需要这些查询 .D4 . 有些情况下,您根本不信任调用代码的代码,并且为了确保没有数据更新,而是插入,您必须使用
persist
.无论哪种方式都会将实体添加到PersistenceContext,区别在于您之后对实体执行的操作 .
Persist接受一个实体实例,将其添加到上下文并使该实例得到管理(即将跟踪该实体的未来更新) .
合并创建实体的新实例,从提供的实体复制状态,并管理新副本 . 您传入的实例将不会被管理(您所做的任何更改都不会成为事务的一部分 - 除非您再次调用merge) .
也许代码示例会有所帮助 .
场景1和3大致相同,但在某些情况下您需要使用场景2 .
您可能来这里是关于何时使用 persist 以及何时使用 merge 的建议 . 我认为这取决于具体情况:您需要创建新记录的可能性有多大,以及检索持久数据有多难 .
我们假设您可以使用自然键/标识符 .
试试{entityManager.persist(entity)}
catch(EntityExistsException异常){/ 检索并合并 /}
entity = entityManager.find(key);
if(entity == null){entityManager.persist(entity); }
别的{/ 合并 /}
如果您没有自然键/标识符,那么您将很难确定实体是否存在,或者如何查找实体 .
合并也可以通过两种方式处理:
如果更改通常很小,请将它们应用于托管实体 .
如果更改很常见,请复制持久化实体中的ID以及未更改的数据 . 然后调用EntityManager :: merge()来替换旧内容 .
Persisting entities
与合并方法相比,持久化方法非常简单直观 . 持久化方法的最常见用法可以总结如下:
“将新创建的实体类实例传递给persist方法 . 此方法返回后,将管理并计划实体插入数据库 . 可能发生在事务提交之前或之前或调用flush方法时 . 如果实体通过标有PERSIST级联策略的关系引用另一个实体,则该程序也适用于此 . “
规范更详细,但是,记住它们并不重要,因为这些细节仅涵盖或多或少的异国情况 .
Merging entities
与持久化相比,合并行为的描述并不那么简单 . 没有主要场景,因为它是持久化的,并且程序员必须记住所有场景才能编写正确的代码 . 在我看来,JPA设计者希望有一些方法,其主要关注点是处理分离的实体(与主要处理新创建的实体的持久化方法相反 . )合并方法的主要任务是从状态转移状态 . 非托管实体(作为参数传递)到持久化上下文中的托管对应方 . 然而,这个任务进一步分为几个场景,这些场景恶化了整个方法行为的可懂度 .
我没有重复JPA规范中的段落,而是准备了一个流程图,该流程图示意性地描述了合并方法的行为:
So, when should I use persist and when merge?
persist
您希望该方法始终创建一个新实体,并且永远不会更新实体 . 否则,该方法会因主键唯一性违规而抛出异常 .
批处理,以有状态方式处理实体(请参阅网关模式) .
性能优化
merge
您希望该方法在数据库中插入或更新实体 .
您希望以无状态方式处理实体(服务中的数据传输对象)
您想要插入一个新实体,该实体可能具有对可能但尚未创建的另一个实体的引用(关系必须标记为MERGE) . 例如,插入新照片时引用新的或预先存在的相册 .
我在我的实体上得到了lazyLoading异常,因为我试图访问会话中的延迟加载集合 .
我要做的是在一个单独的请求中,从会话中检索实体,然后尝试访问我的jsp页面中的一个有问题的集合 .
为了缓解这种情况,我在我的控制器中更新了同一个实体并将其传递给了我的jsp,虽然我想当我在会话中重新保存时它也可以通过
SessionScope
访问并且不会抛出LazyLoadingException
,这是示例2的修改:以下对我有用:
通过答案,有关“Cascade”和id生成的一些细节缺失 . See question
此外,值得一提的是,您可以使用单独的
Cascade
注释进行合并和持久化:Cascade.MERGE
和Cascade.PERSIST
将根据使用的方法进行处理 .规格是你的朋友;)
持久化和合并有两个不同的目的(它们根本不是替代品) .
(编辑扩大差异信息)
坚持:
将新寄存器插入数据库
将对象附加到实体管理器 .
合并:
查找具有相同ID的附加对象并进行更新 .
如果存在更新并返回已附加的对象 .
如果不存在,则将新寄存器插入数据库 .
persist()效率:
将新注册表插入数据库比使用merge()更有效 .
它不会复制原始对象 .
persist()语义:
例:
这种方式只为实体管理器中的任何寄存器存在1个附加对象 .
merge()对于具有id的实体是这样的:
虽然如果连接到MySQL,merge()可以像使用ON DUPLICATE KEY UPDATE选项调用INSERT一样有效,但JPA是一个非常高级的编程,你不能认为这种情况在任何地方都是如此 .
有关合并的更多详细信息将对您有所帮助使用merge over persist:
所有上述信息均来自Mike Keith和Merrick Schnicariol的“Pro JPA 2掌握Java™Persistence API” . 第6章部分分离和合并 . 这本书实际上是第二本专着JPA作者的书 . 这本新书有许多新信息,然后是前一本 . 我真的建议那些将认真参与JPA的人阅读本书 . 我很抱歉无意中发布了我的第一个答案 .
persist(entity)应该与全新的实体一起使用,将它们添加到DB中(如果实体已经存在于DB中,则会抛出EntityExistsException) .
应该使用merge(entity),如果实体被分离并被更改,则将实体放回到持久化上下文 .
可能持久化是生成INSERT sql语句并合并UPDATE sql语句(但我不确定) .