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。
持久和合并有两个不同的目的(它们根本不是替代品)。
(编辑扩大差异信息)
坚持:
合并:
persist()效率:
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: