清除 JPA/Hibernate 中托管的实体
1、概览
本文将带你了解 JPA 是如何托管实体的,以及 Persistence Context(持久化上下文)由于外部变化而无法返回最新数据的情况。
2、Persistence Context
每个 EntityManager
都与一个 Persistence Context 相关联,该上下文在内存中存储所管理的实体。每当通过 EntityManager
对实体执行任何数据操作时,该实体就会变成由 Persistence Context 管理的实体。
当再次检索实体时,JPA 会从 Persistence Context 返回托管实体,而不是从数据库中获取。这种缓存机制有助于提高性能,而无需从数据库中重复获取相同的数据。
Persistence Context 在 JPA 中也被称为一级(first-level,L1)缓存。
3、Demo 设置
首先,创建一个简单的实体类:
@Entity
@Table(name = "person")
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
// 构造函数、Getter、Setter 方法省略
}
接下来,创建一个 Person
实体并将其持久化到数据库中:
EntityTransaction transaction = entityManager.getTransaction();
transaction.begin();
Person person = new Person();
person.setName("David Jones");
entityManager.persist(person);
transaction.commit();
4、未清除管理的实体
测试数据插入成功后,使用 SQL 更新查询来屏蔽 Person
的 name
字段(即,设置为 N 个 *
号)。
从 EntityManager
获取一个 JDBC 连接,然后执行更新查询:
Session session = entityManager.unwrap(Session.class);
session.doWork(connection -> {
try (PreparedStatement pStmt = connection.prepareStatement("UPDATE person SET name=? WHERE id=?")) {
pStmt.setString(1, "*****");
pStmt.setLong(2, 1);
pStmt.executeUpdate();
}
});
现在,通过 EntityManager
再次检索同一个实体:
Person updatedPerson = entityManager.find(Person.class, 1);
直觉上,我们会认为检索到的 Person
实体的 name
已经因为更新查询而被屏蔽了。但是,当我们验证实体的 name
属性时,名称仍然是 “David Jones”:
assertThat(updatedPerson.getName()).isNotEqualTo("*****");
出现这种情况的原因是 JPA 从 Persistence Context 中检索托管实体,而 Persistence Context 在 JDBC 数据更新后还没有相应的更新。当数据更改发生在 EntityManager
之外时,EntityManager
并不知道这些更改,因此无法更新相应的托管实体。
5、清除托管的实体
因此,在未使用 EntityManager
的情况下对数据库数据进行任何更改时,都必须强制 JPA 从 Persistence Context 中清除所有托管的实体,这样才能再次从数据库中获取最新的实体。
为此,可以调用 EntityManager
的 clear()
方法:
entityManager.clear();
该操作会将所有托管实体从 Persistence Context 中分离,确保 JPA 不再跟踪这些实体。
随后,可以再次使用实体管理器(EntityManager
)检索同一个实体。如果在持久化单元配置(Persistence Unit Configuration)中启用 hibernate.show_sql
选项,就能从控制台日志中看到以下正在执行的 SQL:
Hibernate:
select
p1_0.id,
p1_0.name
from
person p1_0
where
p1_0.id=?
这条 SQL 语句表示 EntityManager
执行 SELECT
查询从数据库中获取新数据,不出意外这次 Person
实体的 name
属性已被屏蔽:
assertThat(updatedPerson.getName()).isEqualTo("*****");
6、总结
本文介绍了 Persistence Context 在 JPA 中的作用。如果在不涉及 EntityManager
的情况下执行数据更改,Persistence Context 将不会重新加载实体。此时,需要调用 EntityManager
的 clear()
方法来移除所有管理实体,并允许从数据库中重新加载最新的实体。
Ref:https://www.baeldung.com/hibernate-clear-managed-entities