由于这是一个非常常见的问题,因此我写了
这篇文章,此答案基于该文章。
实体状态
JPA定义以下实体状态:
新(瞬态)
从未与Hibernate关联的新创建的对象Session
(又名Persistence Context
)并且未映射到任何数据库表行被视为处于New(瞬态)状态。
要坚持下去,我们需要明确地调用 EntityManager#persist
方法或使用可传递持久性机制。
持久(托管)
持久性实体已与数据库表行关联,并由当前运行的持久性上下文进行管理。对此类实体所做的任何更改都将被检测到并传播到数据库(在会话刷新期间)。
使用Hibernate,我们不再需要执行INSERT / UPDATE / DELETE语句。Hibernate采用事务后写式工作方式,并且在当前的最后一个负责时刻对更改进行同步。Session
刷新时间的。
独立式
一旦当前正在运行的持久性上下文关闭,所有先前管理的实体都将分离。连续的更改将不再被跟踪,并且不会自动进行数据库同步。
实体状态转换
您可以使用以下方法定义的各种方法来更改实体状态: EntityManager
接口。
为了更好地了解JPA实体状态转换,请考虑下图:
使用JPA时,要将分离的实体重新关联到active EntityManager
,可以使用merge操作。
使用本地Hibernate API时,除了merge
,您还可以使用update方法将分离的实体重新连接到活动的Hibernate Session,如下图所示:
合并独立实体
合并将把分离的实体状态(源)复制到托管实体实例(目标)。
考虑到我们已经持久化了以下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
如果合并实体在当前没有等效项 EntityManager
将从数据库中获取一个新的实体快照。
一旦存在托管实体,JPA会将分离实体的状态复制到当前管理的实体上,并且在持久性上下文flush
期间,如果脏检查机制发现托管实体已更改,则将生成UPDATE 。
因此,当使用时merge
,即使在合并操作之后,分离的对象实例仍将继续保持分离状态。
重新附加独立实体
休眠但JPA不支持通过该update
方法进行重新连接。
冬眠 Session
只能将一个实体对象与给定的数据库行关联。这是因为持久性上下文充当内存中的缓存(第一级缓存),并且只有一个值(实体)与给定的键(实体类型和数据库标识符)相关联。
仅当没有其他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
一个休眠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将抛出a,NonUniqueObjectException
因为第二个EntityManager
已经包含Book
与我们传递给它的标识符具有相同标识符的实体update
,并且Persistence Context无法容纳同一实体的两个表示形式。
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
,因为它可以防止由产生的额外的SELECT语句有利于批量更新merge
操作,从而降低了批量更新的执行时间。
refresh()
在分离的实体上使用的原因?查看2.0规范,我看不出任何理由。只是不允许这样做。