实体状态
JPA定义了以下实体状态:
新(瞬态)
如果一个新创建的对象从未与Hibernate会话(也就是持久化上下文)关联过,也没有映射到任何数据库表行,则认为该对象处于New (Transient)状态。
要成为持久化,我们需要显式地调用EntityManager#persist方法,或者使用传递持久化机制。
持久(管理)
持久化实体已与数据库表行关联,并由当前运行的持久化上下文管理。对此类实体所做的任何更改都将被检测到并传播到数据库(在会话刷新期间)。
使用Hibernate,我们不再需要执行INSERT/UPDATE/DELETE语句。Hibernate采用事务性后写工作风格,在当前会话刷新期间的最后一个负责时刻同步更改。
分离
一旦当前运行的持久性上下文被关闭,所有以前管理的实体将被分离。连续的更改将不再被跟踪,也不会发生自动的数据库同步。
实体状态转换
您可以使用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
如果合并的实体在当前EntityManager中没有对等的实体,则将从数据库中获取一个新的实体快照。
一旦有了托管实体,JPA将分离实体的状态复制到当前托管的实体上,并且在持久性上下文刷新期间,如果脏检查机制发现托管实体已更改,将生成一个UPDATE。
因此,在使用merge时,即使在merge操作之后,分离的对象实例也将继续保持分离状态。
重新连接一个分离的实体
Hibernate(而不是JPA)支持通过更新方法重新连接。
Hibernate会话只能为给定的数据库行关联一个实体对象。这是因为Persistence Context充当内存中的缓存(第一级缓存),并且只有一个值(实体)与给定的键(实体类型和数据库标识符)相关联。
只有在没有与当前Hibernate Session关联的其他JVM对象(匹配相同的数据库行)时,才可以重新附加实体。
考虑到我们已经持久化了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
更新方法要求您将EntityManager解包装为Hibernate会话。
与merge不同,所提供的分离实体将与当前持久性上下文重新关联,并且无论实体是否被修改,都将在刷新期间调度UPDATE。
为了防止这种情况,您可以使用@SelectBeforeUpdate Hibernate注释,它将触发一个SELECT语句,获取加载状态,然后由脏检查机制使用。
@Entity(name = "Book")
@Table(name = "book")
@SelectBeforeUpdate
public class Book {
//Code omitted for brevity
}
注意NonUniqueObjectException
更新时可能出现的一个问题是,如果持久性上下文已经包含了一个与下面示例中相同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将抛出一个NonUniqueObjectException,因为第二个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)
结论
如果您正在使用乐观锁定,则合并方法是首选,因为它可以防止丢失更新。
该更新适用于批量更新,因为它可以防止合并操作生成额外的SELECT语句,从而减少批量更新的执行时间。