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

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


当前回答

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)。例如,插入带有新相册或已有相册引用的新照片。

其他回答

这两种方法都会将实体添加到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。

我发现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

我在我的实体上得到了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!!

通过回答,有一些关于“Cascade”和id生成的细节缺失。看到问题

此外,值得一提的是,您可以为合并和持久化使用单独的Cascade注释:Cascade。合并和级联。PERSIST将根据使用的方法进行处理。

规格是你的朋友;)

场景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!!