协慌网

登录 贡献 社区

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

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

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

答案

无论哪种方式都会将实体添加到 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。

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

(编辑扩大差异信息)

坚持:

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

合并:

  • 找到具有相同 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()可以像使用 ON DUPLICATE KEY UPDATE 选项调用 INSERT 一样有效,但 JPA 是一个非常高级的编程,你不能认为这种情况在任何地方都是如此。

如果您使用的是已分配的生成器,则使用 merge 而不是 persist 会导致冗余的 SQL 语句 ,从而影响性能。

此外, 为托管实体调用合并也是一个错误,因为托管实体由 Hibernate 自动管理,并且在刷新持久性上下文时,它们的状态通过脏检查机制与数据库记录同步。

要了解所有这些是如何工作的,您首先应该知道 Hibernate 将开发人员的思维方式从 SQL 语句转移到实体状态转换

一旦 Hibernate 主动管理实体,所有更改将自动传播到数据库。

Hibernate 监视当前附加的实体。但是对于要管理的实体,它必须处于正确的实体状态。

首先,我们必须定义所有实体状态:

  • 新(瞬态)

    尚未与 Hibernate Session (也称为Persistence ContextPersistence Context且未映射到任何数据库表行的新创建的对象被视为处于新(暂停)状态。

    要成为持久化,我们需要显式调用EntityManager#persist方法或使用传递持久性机制。

  • 持久性(管理)

    持久化实体已与数据库表行关联,并由当前运行的持久性上下文管理。对此类实体所做的任何更改都将被检测并传播到数据库(在会话刷新时间内)。使用 Hibernate,我们不再需要执行 INSERT / UPDATE / DELETE 语句。 Hibernate 采用事务性的后写工作方式,并且在当前的Session刷新时间内,在最后一个负责时刻同步更改。

  • 超脱

    一旦当前运行的持久性上下文关闭,所有先前管理的实体都将分离。将不再跟踪连续更改,也不会发生自动数据库同步。

    要将分离的实体与活动的 Hibernate 会话相关联,您可以选择以下选项之一:

    • 重新连接

      Hibernate(但不是 JPA 2.1)支持通过 Session#update 方法重新附加。 Hibernate 会话只能为给定的数据库行关联一个 Entity 对象。这是因为持久性上下文充当内存缓存(第一级缓存),并且只有一个值(实体)与给定密钥(实体类型和数据库标识符)相关联。仅当没有与当前 Hibernate 会话关联的其他 JVM 对象(匹配相同的数据库行)时,才能重新附加实体。

    • 合并

    合并将将分离的实体状态(源)复制到托管实体实例(目标)。如果合并实体在当前会话中没有等效项,则将从数据库中获取一个。即使在合并操作之后,分离的对象实例仍将继续保持分离状态。

  • 删除

    尽管 JPA 要求仅允许删除托管实体,但 Hibernate 还可以删除分离的实体(但只能通过 Session#delete 方法调用)。删除的实体仅计划删除,实际的数据库 DELETE 语句将在会话刷新时执行。

要更好地理解 JPA 状态转换,您可以可视化以下图表:

在此输入图像描述

或者,如果您使用 Hibernate 特定的 API:

在此输入图像描述