EntityManager.merge()可以插入新对象并更新现有对象。
为什么要使用persist()(它只能创建新对象)?
EntityManager.merge()可以插入新对象并更新现有对象。
为什么要使用persist()(它只能创建新对象)?
这两种方法都会将实体添加到PersistenceContext中,区别在于之后如何处理实体。
Persist获取一个实体实例,将其添加到上下文中,并使该实例被管理(即将跟踪实体的未来更新)。
Merge返回与状态合并的托管实例。它确实返回存在于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。
我在我的实体上得到了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!!
我注意到,当我使用em.merge时,每个INSERT都有一个SELECT语句,即使JPA没有为我生成字段——主键字段是我自己设置的UUID。我切换到em.persist(myEntityObject),然后只得到INSERT语句。
JPA规范说明了关于persist()的以下内容。
如果X是一个分离对象,则在持久化时可能会抛出EntityExistsException 操作被调用,或者EntityExistsException或另一个PersistenceException可能在刷新或提交时抛出。
因此,当对象不应该是分离对象时,使用persist()是合适的。您可能更喜欢让代码抛出PersistenceException,以便快速失败。
尽管规范不清楚,persist()可能会为对象设置@GeneratedValue @Id。但是merge()必须有一个已经生成的@Id对象。
关于merge的更多细节将帮助你使用merge over persist:
Returning a managed instance other than the original entity is a critical part of the merge process. If an entity instance with the same identifier already exists in the persistence context, the provider will overwrite its state with the state of the entity that is being merged, but the managed version that existed already must be returned to the client so that it can be used. If the provider did not update the Employee instance in the persistence context, any references to that instance will become inconsistent with the new state being merged in. When merge() is invoked on a new entity, it behaves similarly to the persist() operation. It adds the entity to the persistence context, but instead of adding the original entity instance, it creates a new copy and manages that instance instead. The copy that is created by the merge() operation is persisted as if the persist() method were invoked on it. In the presence of relationships, the merge() operation will attempt to update the managed entity to point to managed versions of the entities referenced by the detached entity. If the entity has a relationship to an object that has no persistent identity, the outcome of the merge operation is undefined. Some providers might allow the managed copy to point to the non-persistent object, whereas others might throw an exception immediately. The merge() operation can be optionally cascaded in these cases to prevent an exception from occurring. We will cover cascading of the merge() operation later in this section. If an entity being merged points to a removed entity, an IllegalArgumentException exception will be thrown. Lazy-loading relationships are a special case in the merge operation. If a lazy-loading relationship was not triggered on an entity before it became detached, that relationship will be ignored when the entity is merged. If the relationship was triggered while managed and then set to null while the entity was detached, the managed version of the entity will likewise have the relationship cleared during the merge."
以上所有信息都摘自Mike Keith和Merrick Schnicariol的“Pro JPA 2掌握Java™Persistence API”。第六章。分段分离与合并。这本书实际上是作者专门介绍JPA的第二本书。这本新书比前一本有许多新信息。我非常推荐那些将认真参与JPA的人阅读这本书。我很抱歉匿名发布了我的第一个答案。
场景X:
表:spititter(1),表:Spittles(许多)(Spittles是与FK:spitter_id关系的所有者)
这个场景的结果是节省:喷壶和两个喷壶就像被同一个喷壶拥有一样。
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:
这将拯救喷壶,将拯救2喷壶,但他们不会引用同一喷壶!
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!!
持久化和合并是为了两个不同的目的(它们根本不是替代方案)。
(编辑以扩大差异信息)
坚持:
向数据库中插入一个新的寄存器 将对象附加到实体管理器。
走:
找到具有相同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);
}
这种方法对于实体管理器中的任何寄存器只存在一个附加对象。
对于带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()可以像persist()一样有效,使用一个调用INSERT和ON DUPLICATE KEY UPDATE选项,JPA是一个非常高级的编程,你不能假设这将是任何地方的情况。
merge和persist之间还有一些区别(我将再次列举已经在这里发布的区别):
D1。Merge不会使传递的实体受管理,而是返回另一个受管理的实例。在另一边持久化将使传递的实体被管理:
//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),则合并 operation将触发后续的SELECT查询,以查找具有该ID的存在实体,而persist可能不需要这些查询。
D4。有些情况下,您只是不相信调用您的代码的代码,为了确保没有数据被更新,而是被插入,您必须使用持久化。
通过回答,有一些关于“Cascade”和id生成的细节缺失。看到问题
此外,值得一提的是,您可以为合并和持久化使用单独的Cascade注释:Cascade。合并和级联。PERSIST将根据使用的方法进行处理。
规格是你的朋友;)
如果您正在使用分配的生成器,使用merge而不是持久化可能会导致冗余SQL语句,从而影响性能。
另外,对托管实体调用merge也是一个错误,因为托管实体是由Hibernate自动管理的,并且它们的状态在刷新持久性上下文时通过脏检查机制与数据库记录同步。
要理解这一切是如何工作的,首先应该知道Hibernate将开发人员的思维模式从SQL语句转换为实体状态转换。
一旦一个实体被Hibernate主动管理,所有的更改都将自动传播到数据库中。
Hibernate监视当前附加的实体。但是要使实体受到管理,它必须处于正确的实体状态。
为了更好地理解JPA状态转换,您可以将下面的图可视化:
或者如果你使用Hibernate特定的API:
如上图所示,一个实体可以处于以下四种状态之一:
新(瞬态)
如果一个新创建的对象从未与Hibernate会话(也就是持久化上下文)关联过,也没有映射到任何数据库表行,则认为该对象处于New (Transient)状态。
要成为持久化,我们需要显式地调用EntityManager#persist方法,或者使用传递持久化机制。
Persistent (Managed) A persistent entity has been associated with a database table row and it’s being managed by the currently running Persistence Context. Any change made to such an entity is going to be detected and propagated to the database (during the Session flush-time). With Hibernate, we no longer have to execute INSERT/UPDATE/DELETE statements. Hibernate employs a transactional write-behind working style and changes are synchronized at the very last responsible moment, during the current Session flush-time. Detached
一旦当前运行的持久性上下文被关闭,所有以前管理的实体将被分离。连续的更改将不再被跟踪,也不会发生自动的数据库同步。
要将分离实体关联到活动Hibernate会话,您可以选择以下选项之一:
Reattaching Hibernate (but not JPA 2.1) supports reattaching through the Session#update method. A Hibernate Session can only associate one Entity object for a given database row. This is because the Persistence Context acts as an in-memory cache (first level cache) and only one value (entity) is associated with a given key (entity type and database identifier). An entity can be reattached only if there is no other JVM object (matching the same database row) already associated with the current Hibernate Session. Merging The merge is going to copy the detached entity state (source) to a managed entity instance (destination). If the merging entity has no equivalent in the current Session, one will be fetched from the database. The detached object instance will continue to remain detached even after the merge operation. Remove Although JPA demands that managed entities only are allowed to be removed, Hibernate can also delete detached entities (but only through a Session#delete method call). A removed entity is only scheduled for deletion and the actual database DELETE statement will be executed during Session flush-time.
我发现Hibernate文档中的解释很有启发性,因为它们包含了一个用例:
The usage and semantics of merge() seems to be confusing for new users. Firstly, as long as you are not trying to use object state loaded in one entity manager in another new entity manager, you should not need to use merge() at all. Some whole applications will never use this method. Usually merge() is used in the following scenario: The application loads an object in the first entity manager the object is passed up to the presentation layer some modifications are made to the object the object is passed back down to the business logic layer the application persists these modifications by calling merge() in a second entity manager Here is the exact semantic of merge(): if there is a managed instance with the same identifier currently associated with the persistence context, copy the state of the given object onto the managed instance if there is no managed instance currently associated with the persistence context, try to load it from the database, or create a new managed instance the managed instance is returned the given instance does not become associated with the persistence context, it remains detached and is usually discarded
来自:http://docs.jboss.org/hibernate/entitymanager/3.6/reference/en/html/objectstate.html
persist(entity)应该与全新的实体一起使用,将它们添加到DB(如果实体已经存在于DB中,则会抛出EntityExistsException)。
应该使用Merge(实体),如果实体被分离并被更改,则将实体放回持久性上下文中。
可能持久是生成INSERT sql语句和合并UPDATE sql语句(但我不确定)。
JPA is indisputably a great simplification in the domain of enterprise applications built on the Java platform. As a developer who had to cope up with the intricacies of the old entity beans in J2EE I see the inclusion of JPA among the Java EE specifications as a big leap forward. However, while delving deeper into the JPA details I find things that are not so easy. In this article I deal with comparison of the EntityManager’s merge and persist methods whose overlapping behavior may cause confusion not only to a newbie. Furthermore I propose a generalization that sees both methods as special cases of a more general method combine.
持久化实体
与merge方法相比,persist方法非常简单直观。使用persist方法最常见的场景可以总结如下:
一个新创建的实体类实例被传递给持久化方法。该方法返回后,将管理实体并计划将其插入到数据库中。它可能发生在事务提交时或提交之前,也可能发生在调用flush方法时。如果实体通过标记为PERSIST级联策略的关系引用另一个实体,则此过程也应用于它。”
规范中有更多的细节,然而,记住它们并不重要,因为这些细节只涵盖了或多或少的奇异情况。
合并实体
In comparison to persist, the description of the merge's behavior is not so simple. There is no main scenario, as it is in the case of persist, and a programmer must remember all scenarios in order to write a correct code. It seems to me that the JPA designers wanted to have some method whose primary concern would be handling detached entities (as the opposite to the persist method that deals with newly created entities primarily.) The merge method's major task is to transfer the state from an unmanaged entity (passed as the argument) to its managed counterpart within the persistence context. This task, however, divides further into several scenarios which worsen the intelligibility of the overall method's behavior.
我没有从JPA规范中重复段落,而是准备了一个流程图,概要地描述了合并方法的行为:
什么时候使用持久化,什么时候使用归并?
坚持
您希望该方法总是创建一个新实体,而从不更新实体。否则,该方法将因违反主键惟一性而引发异常。 批处理流程,以有状态的方式处理实体(参见网关模式)。 性能优化
走
您希望该方法插入或更新数据库中的实体。 您希望以无状态的方式处理实体(服务中的数据传输对象) 您希望插入一个新实体,该实体可能对另一个实体有引用,但可能尚未创建(关系必须标记为MERGE)。例如,插入带有新相册或已有相册引用的新照片。
您可能来这里寻求关于何时使用持久化和何时使用合并的建议。我认为这取决于具体情况:您需要创建新记录的可能性有多大,以及检索持久数据的难度有多大。
让我们假设您可以使用自然键/标识符。
Data needs to be persisted, but once in a while a record exists and an update is called for. In this case you could try a persist and if it throws an EntityExistsException, you look it up and combine the data: try { entityManager.persist(entity) } catch(EntityExistsException exception) { /* retrieve and merge */ } Persisted data needs to be updated, but once in a while there is no record for the data yet. In this case you look it up, and do a persist if the entity is missing: entity = entityManager.find(key); if (entity == null) { entityManager.persist(entity); } else { /* merge */ }
如果你没有自然的键/标识符,你将很难弄清楚实体是否存在,或者如何查找它。
合并也可以用两种方式处理:
如果更改通常很小,请将其应用到托管实体。 如果更改很常见,则从持久化实体以及未更改的数据复制ID。然后调用EntityManager::merge()替换旧的内容。
另一个观察:
merge()只关心自动生成的id(在IDENTITY和SEQUENCE上测试),当表中已经存在这样一个id的记录时。在这种情况下,merge()将尝试更新记录。 然而,如果一个id不存在或者不匹配任何现有的记录,merge()将完全忽略它,并要求db分配一个新的。这有时是许多bug的来源。不要使用merge()强制新记录的id。
另一方面,Persist()甚至不允许将id传递给它。它会立即失效。在我的例子中,它是:
原因:org.hibernate.PersistentObjectException:分离实体 传递到持久化
Hibernate-jpa javadoc有一个提示:
抛出:javax.persistence.EntityExistsException——如果实体 已经存在。(如果实体已经存在,则 当持久化操作时,可能会抛出EntityExistsException 或者EntityExistsException或另一个PersistenceException 可能在同花顺或提交时抛出。)