实体状态
JPA 定义了以下实体状态:
新(瞬态)
从未与 Hibernate Session
(aka Persistence Context
) 关联且未映射到任何数据库表行的新创建的对象被视为处于 New (Transient) 状态。
要持久化,我们需要显式调用该EntityManager#persist
方法或使用传递持久性机制。
持久(托管)
持久性实体已与数据库表行相关联,并由当前运行的持久性上下文管理。对此类实体所做的任何更改都将被检测到并传播到数据库(在会话刷新期间)。
使用 Hibernate,我们不再需要执行 INSERT/UPDATE/DELETE 语句。Hibernate 采用事务性后写工作方式,并且在当前Session
刷新时间期间的最后一个负责时刻同步更改。
分离式
一旦当前运行的持久性上下文关闭,所有以前管理的实体都将被分离。将不再跟踪连续的更改,也不会发生自动数据库同步。
实体状态转换
您可以使用接口定义的各种方法更改实体状态EntityManager
。
为了更好地理解 JPA 实体状态转换,请考虑下图:
使用 JPA 时,要将分离的实体重新关联到活动EntityManager
,您可以使用合并操作。
使用本机 Hibernate API 时,除了merge
,您可以使用更新方法将分离的实体重新附加到活动的 Hibernate 会话,如下图所示:
合并分离的实体
合并会将分离的实体状态(源)复制到托管实体实例(目标)。
考虑我们已经持久化了以下Book
实体,现在该实体已分离,EntityManager
因为用于持久化实体的实体已关闭:
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
return book;
});
当实体处于分离状态时,我们对其进行如下修改:
_book.setTitle(
"High-Performance Java Persistence, 2nd edition"
);
现在,我们要将更改传播到数据库,因此我们可以调用该merge
方法:
doInJPA(entityManager -> {
Book book = entityManager.merge(_book);
LOGGER.info("Merging the Book entity");
assertFalse(book == _book);
});
Hibernate 将执行以下 SQL 语句:
SELECT
b.id,
b.author AS author2_0_,
b.isbn AS isbn3_0_,
b.title AS title4_0_
FROM
book b
WHERE
b.id = 1
-- Merging the Book entity
UPDATE
book
SET
author = 'Vlad Mihalcea',
isbn = '978-9730228236',
title = 'High-Performance Java Persistence, 2nd edition'
WHERE
id = 1
如果合并实体在 current 中没有等价物,EntityManager
则将从数据库中获取新的实体快照。
一旦存在托管实体,JPA 会将分离实体的状态复制到当前托管的实体上,并且在 Persistence Context 期间flush
,如果脏检查机制发现托管实体已更改,则会生成 UPDATE。
因此,当使用 时merge
,即使在合并操作之后,分离的对象实例也将继续保持分离状态。
重新附加分离的实体
Hibernate,但 JPA 不支持通过该update
方法重新附加。
HibernateSession
只能为给定的数据库行关联一个实体对象。这是因为持久性上下文充当内存缓存(一级缓存),并且只有一个值(实体)与给定的键(实体类型和数据库标识符)相关联。
仅当没有其他 JVM 对象(匹配同一数据库行)与当前 Hibernate 关联时,才能重新附加实体Session
。
考虑到我们已经持久化了Book
实体并且我们在Book
实体处于分离状态时对其进行了修改:
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
entityManager.persist(book);
return book;
});
_book.setTitle(
"High-Performance Java Persistence, 2nd edition"
);
我们可以像这样重新附加分离的实体:
doInJPA(entityManager -> {
Session session = entityManager.unwrap(Session.class);
session.update(_book);
LOGGER.info("Updating the Book entity");
});
Hibernate 将执行以下 SQL 语句:
-- Updating the Book entity
UPDATE
book
SET
author = 'Vlad Mihalcea',
isbn = '978-9730228236',
title = 'High-Performance Java Persistence, 2nd edition'
WHERE
id = 1
该update
方法需要你unwrap
到EntityManager
一个 Hibernate Session
。
与 不同merge
的是,提供的分离实体将与当前持久性上下文重新关联,并且无论实体是否已修改,都会在刷新期间安排更新。
为了防止这种情况,您可以使用@SelectBeforeUpdate
Hibernate 注释,该注释将触发一个 SELECT 语句,该语句获取加载状态,然后由脏检查机制使用。
@Entity(name = "Book")
@Table(name = "book")
@SelectBeforeUpdate
public class Book {
//Code omitted for brevity
}
当心 NonUniqueObjectException
可能发生的一个问题update
是,如果持久性上下文已经包含一个具有相同 id 和相同类型的实体引用,如下例所示:
Book _book = doInJPA(entityManager -> {
Book book = new Book()
.setIsbn("978-9730228236")
.setTitle("High-Performance Java Persistence")
.setAuthor("Vlad Mihalcea");
Session session = entityManager.unwrap(Session.class);
session.saveOrUpdate(book);
return book;
});
_book.setTitle(
"High-Performance Java Persistence, 2nd edition"
);
try {
doInJPA(entityManager -> {
Book book = entityManager.find(
Book.class,
_book.getId()
);
Session session = entityManager.unwrap(Session.class);
session.saveOrUpdate(_book);
});
} catch (NonUniqueObjectException e) {
LOGGER.error(
"The Persistence Context cannot hold " +
"two representations of the same entity",
e
);
}
现在,在执行上面的测试用例时,Hibernate 将抛出 aNonUniqueObjectException
因为第二个EntityManager
已经包含一个Book
与我们传递给的具有相同标识符的实体update
,并且持久性上下文不能包含同一实体的两个表示。
org.hibernate.NonUniqueObjectException:
A different object with the same identifier value was already associated with the session : [com.vladmihalcea.book.hpjp.hibernate.pc.Book#1]
at org.hibernate.engine.internal.StatefulPersistenceContext.checkUniqueness(StatefulPersistenceContext.java:651)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performUpdate(DefaultSaveOrUpdateEventListener.java:284)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.entityIsDetached(DefaultSaveOrUpdateEventListener.java:227)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.performSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:92)
at org.hibernate.event.internal.DefaultSaveOrUpdateEventListener.onSaveOrUpdate(DefaultSaveOrUpdateEventListener.java:73)
at org.hibernate.internal.SessionImpl.fireSaveOrUpdate(SessionImpl.java:682)
at org.hibernate.internal.SessionImpl.saveOrUpdate(SessionImpl.java:674)
结论
如果您使用乐观锁定,则首选该merge
方法,因为它可以防止丢失更新。
有update
利于批量更新,因为它可以防止merge
操作生成额外的 SELECT 语句,从而减少批量更新执行时间。