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

JPA EntityManager:为什么在merge()上使用persist()?,jpa,merge,entitymanager,persist,java-persistence-api,Jpa,Merge,Entitymanager,Persist,Java Persistence Api,EntityManager.merge()可以插入新对象并更新现有对象 为什么要使用persist()(它只能创建新对象)?任何一种方法都会将一个实体添加到PersistenceContext中,区别在于之后如何处理该实体 Persist获取一个实体实例,将其添加到上下文中,并对该实例进行管理(即跟踪实体的未来更新) Merge返回状态合并到的托管实例。它确实返回PersistenceContext中存在的内容或创建实体的新实例。在任何情况下,它都将从提供的实体复制状态,并返回托管副本。您传入的

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。

我的实体上出现懒散加载异常,因为我试图访问会话中的懒散加载集合

我要做的是在一个单独的请求中,从会话中检索实体,然后尝试访问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
调用操作,或者在刷新或提交时抛出
EntityExistSexException
或另一个
持久异常

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


虽然,
persist()
可能会为对象设置
@GeneratedValue
@Id
merge()
但是必须已经生成了一个具有
@Id
的对象。

有关merge的更多详细信息,这将帮助您使用merge over persist:

返回原始实体以外的托管实例是合并的关键部分 过程如果持久性上下文中已存在具有相同标识符的实体实例,则 提供程序将用正在合并的实体的状态覆盖其状态,但托管 必须将已存在的版本返回给客户端,以便使用。如果提供者没有 在持久性上下文中更新Employee实例,对该实例的任何引用都将成为 与正在合并的新状态不一致

对新实体调用merge()时,其行为类似于persist()操作。它补充道 将实体添加到持久性上下文中,但不是添加原始实体实例,而是创建新的实体实例 而是复制并管理该实例。由merge()操作创建的副本将被持久化 就好像对它调用了persist()方法一样

存在关系时,merge()操作将尝试更新托管实体 指向分离实体引用的实体的托管版本。如果该实体有 与没有持久标识的对象的关系,合并操作的结果是 未定义。某些提供程序可能允许托管副本指向非持久对象, 而其他人可能会立即抛出异常。merge()操作可以是可选的 在这些情况下级联以防止发生异常。我们将讨论merge()的级联 操作将在本节后面介绍。如果要合并的实体指向删除的实体,则 将引发IllegalArgumentException异常

延迟加载关系是合并操作中的一种特殊情况。如果延迟加载 关系在实体分离之前未在其上触发,该关系将 合并实体时忽略。如果在管理时触发关系,然后在分离实体时将其设置为null,则该实体的管理版本同样会在合并过程中清除该关系。”

以上所有信息均取自“精通Java的Pro JPA 2”™ 持久性API由迈克·基思和梅里克·施尼卡里奥创作。第六章。分段分离合并。这本书实际上是第二本由作者撰写的关于JPA的书。这本新书比前一本有许多新信息。我真的建议那些将认真参与JPA的人读这本书。很抱歉,我无意中发布了我的第一个答案。

场景X:

表:Spitter(一个),表: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!!
Spitter Spitter=新Spitter();
Spitle spittle3=新的Spittle();
spitter.setUsername(“乔治”);
设置密码(“test1234”);
setSpittle(“我爱Java2”);
痰盂3.固定式痰盂(痰盂);
dao.addSpittle(spittle3);// 波斯
        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!!
{
    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);
}
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;
}
//MERGE: passedEntity remains unmanaged, but newEntity will be managed
Entity newEntity = em.merge(passedEntity);

//PERSIST: passedEntity will be managed after this
em.persist(passedEntity);