清除 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 更新查询来屏蔽 Personname 字段(即,设置为 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 中清除所有托管的实体,这样才能再次从数据库中获取最新的实体。

为此,可以调用 EntityManagerclear() 方法:

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 将不会重新加载实体。此时,需要调用 EntityManagerclear() 方法来移除所有管理实体,并允许从数据库中重新加载最新的实体。


Ref:https://www.baeldung.com/hibernate-clear-managed-entities