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

末鹿安然 提交于 2019-12-15 18:11:32

【推荐】2019 Java 开发者跳槽指南.pdf(吐血整理) >>>

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

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


#1楼

无论哪种方式都会将实体添加到PersistenceContext中,区别在于您之后对实体执行的操作。

Persist接受实体实例,将其添加到上下文并使该实例得到管理(即将跟踪对该实体的未来更新)。

合并创建实体的新实例,从提供的实体复制状态,并管理新副本。 您传入的实例将不会被管理(您所做的任何更改都不会成为交易的一部分 - 除非您再次调用合并)。

也许代码示例会有所帮助。

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。


#2楼

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

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

在新实体上调用merge()时,它的行为与persist()操作类似。 它将实体添加到持久性上下文中,但它不是添加原始实体实例,而是创建新副本并管理该实例。 merge()操作创建的副本将保持不变,就好像在其上调用了persist()方法一样。

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

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

所有上述信息均来自Mike Keith和Merrick Schnicariol的“Pro JPA 2掌握Java™持久性API”。 第6章部分分离和合并。 这本书实际上是作者专门撰写JPA的第二本书。 这本新书有许多新信息,然后是前一本。 我真的建议您阅读本书,了解那些认真参与JPA的人。 我很抱歉无意中发布了我的第一个答案。


#3楼

场景X:

表:Spitter(一),表:Spittles(很多)(Spittles是与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!!

#4楼

持久和合并有两个不同的目的(它们根本不是替代品)。

(编辑扩大差异信息)

坚持:

  • 将新寄存器插入数据库
  • 将对象附加到实体管理器。

合并:

  • 找到具有相同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);
}

这种方式只为实体管理器中的任何寄存器存在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;
}

虽然如果连接到MySQL,merge()可以像使用INSERT with ON DUPLICATE KEY UPDATE选项调用persist()一样高效,但JPA是一个非常高级的编程,你不能认为这种情况在任何地方都是如此。


#5楼

mergepersist化之间还有一些差异(我将再次枚举已在此处发布的那些):

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

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!