协慌网

登录 贡献 社区

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 Context ) Persistence 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:

在此输入图像描述