1. 概述

本文将深入探讨 JPA 中实体的管理机制,并分析一个常见问题:当数据库发生外部变更时,持久化上下文可能无法返回最新数据的情况及解决方案。

2. 持久化上下文

每个 EntityManager 都关联着一个持久化上下文(Persistence Context),它在内存中存储所有托管实体。当我们通过 EntityManager 对实体执行任何数据操作时,该实体就会被持久化上下文管理。

当再次查询同一实体时,JPA 会直接从持久化上下文返回托管实体,而不是重新从数据库获取。这种缓存机制避免了重复查询数据库,显著提升了性能。

⚠️ 重要提示:持久化上下文在 JPA 中也被称为一级缓存(L1 缓存)

3. 场景准备

首先创建一个简单的实体类:

@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 字段。从 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);

直观上我们会认为实体名称已被更新为 *****,但实际验证时却发现:

assertThat(updatedPerson.getName()).isNotEqualTo("*****");

踩坑警告:名称仍然是 "David Jones"!这是因为 JPA 直接从持久化上下文返回了未更新的托管实体。当数据在 EntityManager 之外被修改时,JPA 无法感知这些变更,也不会更新对应的托管实体

5. 清除托管实体的解决方案

因此,当我们绕过 EntityManager 直接修改数据库数据时,必须强制清除持久化上下文中的所有托管实体,才能确保后续查询从数据库重新获取数据。

调用 EntityManagerclear() 方法即可实现:

entityManager.clear();

此操作会将所有托管实体从持久化上下文中分离,使 JPA 不再跟踪这些实体。

之后再次查询同一实体时(需在 persistence unit 配置中启用 hibernate.show_sql),控制台会显示:

Hibernate: 
    select
        p1_0.id,
        p1_0.name 
    from
        person p1_0 
    where
        p1_0.id=?

这条 SQL 语句表明 EntityManager 确实重新从数据库查询了数据。此时验证实体名称:

assertThat(updatedPerson.getName()).isEqualTo("*****");

成功解决:实体名称已正确更新为 *****

6. 总结

本文深入分析了 JPA 持久化上下文的核心机制。当绕过 EntityManager 直接修改数据库数据时,持久化上下文不会自动重新加载实体。我们需要显式调用 EntityManager.clear() 方法清除所有托管实体,强制后续查询从数据库重新获取数据。

完整示例代码可在 GitHub 仓库 中获取。


原始标题:Clear Managed Entities in JPA/Hibernate | Baeldung