Refresh and Fetch an Entity After Save in JPA – 在 JPA 中保存后刷新和获取实体

最后修改: 2024年 3月 17日

中文/混合/英文(键盘快捷键:t)

1. Introduction

1.导言

Java Persistence API (JPA) acts as a bridge between Java objects and relational databases, allowing us to persist and retrieve data seamlessly. In this tutorial, we’ll explore various strategies and techniques to effectively refresh and fetch entities after saving operations in JPA.

Java Persistence API (JPA) 是 Java 对象和关系数据库之间的桥梁,允许我们无缝地持久化和检索数据。在本教程中,我们将探讨在 JPA 中保存操作后有效刷新和获取实体的各种策略和技术。

2. Understanding Entity Management in Spring Data JPA

2.了解 Spring Data JPA 中的实体管理

In Spring Data JPA, entity management revolves around the JpaRepository interface, which serves as the primary mechanism for interacting with the database. Through the JpaRepository interface, which extends CrudRepository, Spring Data JPA offers a robust set of methods for entity persistence, retrieval, updating, and deletion.

Spring Data JPA 中,实体管理围绕 JpaRepository 接口展开,该接口是与数据库交互的主要机制。通过扩展了 CrudRepositoryJpaRepository 接口,Spring Data JPA 为实体的持久化、检索、更新和删除提供了一套强大的方法。

Furthermore, the entityManager is automatically injected into these repository interfaces by the Spring container. This component is an integral part of the JPA infrastructure embedded within Spring Data JPA, facilitating interaction with the underlying persistence context and the execution of JPA queries.

此外,entityManager 会被 Spring 容器自动注入到这些存储库接口中。该组件是 Spring Data JPA 中嵌入的 JPA 基础架构的一个组成部分,可促进与底层持久化上下文的交互和 JPA 查询的执行。

2.1. Persistence Context

2.1.持久性内涵

One crucial component within JPA is the persistence context. Imagine this context as a temporary holding area where JPA manages the state of retrieved or created entities.

JPA 中的一个关键组件是持久化上下文。将此上下文想象为 一个临时存放区,JPA 在此管理检索或创建实体的状态

It ensures that:

它确保

  • Entities are unique: Only one instance of an entity with a specific primary key exists within the context at any given time.
  • Changes are tracked: The EntityManager keeps track of any modifications made to entity properties within the context.
  • Data consistency is maintained: The EntityManager synchronizes changes made within the context with the underlying database during transactions.

2.2. Lifecycle of JPA Entities

2.2.JPA 实体的生命周期

There are four distinct lifecycle stages of JPA entities: New, Managed, Removed, and Detached.

JPA 实体有四个不同的生命周期阶段:新建、托管、移除和分离。

When we create a new entity instance using the entity’s constructor, it is in the “New” state. We can verify this by checking if the entity’s ID (primary key) is null:

当我们使用实体的构造函数创建一个新实体实例时,它处于 “新建 “状态。我们可以通过检查实体的 ID(主键)是否为空来验证这一点:

Order order = new Order();
if (order.getId() == null) {
    // Entity is in the "New" state
}

After we persist the entity using a repository’s save() method, it transitions to the “Managed” state. We can verify this by checking if the saved entity exists in the repository:

在我们使用版本库的 save() 方法持久化实体后,它将过渡到 “已托管 “状态。我们可以通过检查已保存的实体是否存在于版本库中来验证这一点:

Order savedOrder = repository.save(order);
if (repository.findById(savedOrder.getId()).isPresent()) {
    // Entity is in the "Managed" state
}

When we call the repository’s delete() method on a managed entity, it transitions to the “Removed” state. We can verify this by checking if the entity is no longer present in the database after deletion:

当我们调用版本库的delete()方法时,受管实体就会过渡到 “已删除 “状态。我们可以通过检查删除后的实体是否不再存在于数据库中来验证这一点:

repository.delete(savedOrder);
if (!repository.findById(savedOrder.getId()).isPresent()) {
    // Entity is in the "Removed" state
}

Lastly, once the entity is detached using the repository’s detach() method, the entity is no longer associated with the persistence context. Changes made to a detached entity won’t be reflected in the database unless explicitly merged back into the managed state. We can verify this by attempting to modify the entity after detaching it:

最后,一旦使用存储库的 detach() 方法分离实体,该实体就不再与持久化上下文相关联。除非显式地合并回托管状态,否则对分离实体所做的更改不会反映在数据库中。我们可以通过在分离实体后尝试修改该实体来验证这一点:

repository.detach(savedOrder);
// Modify the entity
savedOrder.setName("New Order Name");

If we call save() on a detached entity, it re-attaches the entity to the persistence context and persists the changes to the database upon flushing the persistence context.

如果我们在分离的实体上调用 save() ,它会将实体重新连接到持久化上下文,并在刷新持久化上下文时将更改持久化到数据库。

3. Saving Entities with Spring Data JPA

3.使用 Spring Data JPA 保存实体

When we invoke save(), Spring Data JPA schedules the entity for insertion into the database upon transaction commit. It adds the entity to the persistence context, marking it as managed.

当我们调用 save() 时,Spring Data JPA 会安排在事务提交时将实体插入数据库。它会将实体添加到持久化上下文中,并将其标记为托管实体。

Here’s a simple code snippet demonstrating how to use the save() method in Spring Data JPA to persist an entity:

下面是一个简单的代码片段,演示了如何使用 Spring Data JPA 中的 save() 方法来持久化实体:

Order order = new Order();
order.setName("New Order Name");

repository.save(order);

However, it’s important to note that invoking save() doesn’t immediately trigger a database insert operation. Instead, it merely transitions the entity to the managed state within the persistence context. Thus, if other transactions read data from the database before our transaction commits, they might retrieve outdated data that doesn’t include the changes we’ve made but not yet committed.

但是,需要注意的是,调用 save() 并不会立即触发数据库插入操作。相反,它只是将实体过渡到持久化上下文中的托管状态。因此,如果其他事务在我们的事务提交之前从数据库中读取数据,它们可能会检索到过时的数据,其中不包括我们所做但尚未提交的更改。

To ensure that the data remains up-to-date, we can employ two approaches: fetching and refreshing.

为了确保数据保持最新,我们可以采用两种方法:获取和刷新。

4. Fetching Entities in Spring Data JPA

4.在 Spring Data JPA 中获取实体

When we fetch an entity, we don’t discard any modifications made to it within the persistence context. Instead, we simply retrieve the entity’s data from the database and add it to the persistence context for further processing.

当我们获取一个实体时,我们不会丢弃在持久化上下文中对其所做的任何修改。相反,我们只需从数据库中检索实体的数据,并将其添加到持久化上下文中进行进一步处理

4.1. Using findById()

4.1.使用 findById()

Spring Data JPA repositories offer convenient methods like findById() for retrieving entities. These methods always fetch the latest data from the database, regardless of the entity’s state within the persistence context. This approach simplifies entity retrieval and eliminates the need to manage the persistence context directly.

Spring Data JPA 存储库提供了诸如 findById() 等方便的方法来检索实体。无论实体在持久化上下文中的状态如何,这些方法始终会从数据库中获取最新数据。这种方法简化了实体检索,并消除了直接管理持久化上下文的需要。

Order order = repository.findById(1L).get();

4.2. Eager vs. Lazy Fetching

4.2.快速取回与懒惰取回对比

In eager fetching, all related entities associated with the main entity are retrieved from the database at the same time as the main entity. By setting fetch = FetchType.EAGER on the orderItems collection, we instruct JPA to eagerly fetch all associated OrderItem entities when retrieving an Order:

急切获取中,与主实体相关联的所有相关实体将与主实体同时从数据库中获取。通过在 orderItems 集合上设置 fetch = FetchType.EAGER ,我们将指示 JPA 在检索 Order 时急切地获取所有关联的 OrderItem 实体:

@Entity
public class Order {
    @Id
    private Long id;

    @OneToMany(mappedBy = "order", fetch = FetchType.EAGER)
    private List<OrderItem> orderItems;
}

This means that after the findById() call, we can directly access the orderItems list within the order object and iterate through the associated OrderItem entities without requiring any additional database queries:

这意味着在调用 findById() 之后,我们可以直接访问 order 对象中的 orderItems 列表,并遍历相关的 OrderItem 实体,而无需进行任何额外的数据库查询:

Order order = repository.findById(1L).get();

// Accessing OrderItems directly after fetching the Order
if (order != null) {
    for (OrderItem item : order.getOrderItems()) {
        System.out.println("Order Item: " + item.getName() + ", Quantity: " + item.getQuantity());
    }
}

On the other hand, by setting fetch = FetchType.LAZY, related entities will not retrieved from the database until they are explicitly accessed within the code:

另一方面,通过设置 fetch = FetchType.LAZY,在代码中明确访问相关实体之前,不会从数据库中检索这些实体:

@Entity
public class Order {
    @Id
    private Long id;

    @OneToMany(mappedBy = "order", fetch = FetchType.LAZY)
    private List<OrderItem> orderItems;
}

When we call order.getOrderItems(), a separate database query is executed to fetch the associated OrderItem entities for that order. This additional query is only triggered because we explicitly accessed the orderItems list:

当我们调用order.getOrderItems()时,将执行一个单独的数据库查询,以获取该订单的相关OrderItem实体。只有当我们显式访问 orderItems 列表时,才会触发该额外查询:

Order order = repository.findById(1L).get();

if (order != null) {
    List<OrderItem> items = order.getOrderItems(); // This triggers a separate query to fetch OrderItems
    for (OrderItem item : items) {
        System.out.println("Order Item: " + item.getName() + ", Quantity: " + item.getQuantity());
    }
}

4.3. Fetching with JPQL

4.3.使用 JPQL 获取

Java Persistence Query Language (JPQL) allows us to write SQL-like queries that target entities instead of tables. It provides flexibility for retrieving specific data or entities based on various criteria.

Java 持久性查询语言 (JPQL) 允许我们针对实体而不是表编写类似 SQL 的查询。它为根据各种条件检索特定数据或实体提供了灵活性。

Let’s see an example of fetching orders by customer name and with the order date falling within a specified range:

让我们来看一个按客户名称和订单日期在指定范围内获取订单的示例:

@Query("SELECT o FROM Order o WHERE o.customerName = :customerName AND 
  o.orderDate BETWEEN :startDate AND :endDate")
List<Order> findOrdersByCustomerAndDateRange(@Param("customerName") String customerName, 
  @Param("startDate") LocalDate startDate, @Param("endDate") LocalDate endDate);

4.4. Fetching with Criteria API

4.4.使用标准 API 抓取

The Criteria API in Spring Data JPA provides a reliable and flexible method to create queries dynamically. It allows us to construct complex queries safely using method chaining and criteria expressions, ensuring that our queries are error-free at compile time.

Spring Data JPA 中的 Criteria API 提供了一种可靠而灵活的方法来动态创建查询。它允许我们使用方法链和标准表达式安全地构建复杂的查询,确保我们的查询在编译时不会出错。

Let’s consider an example where we use the Criteria API to fetch orders based on a combination of criteria, such as customer name and order date range:

举个例子,我们使用标准 API 根据客户名称和订单日期范围等标准组合来获取订单:

CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Order> criteriaQuery = criteriaBuilder.createQuery(Order.class);
Root<Order> root = criteriaQuery.from(Order.class);

Predicate customerPredicate = criteriaBuilder.equal(root.get("customerName"), customerName);
Predicate dateRangePredicate = criteriaBuilder.between(root.get("orderDate"), startDate, endDate);

criteriaQuery.where(customerPredicate, dateRangePredicate);

return entityManager.createQuery(criteriaQuery).getResultList();

5. Refreshing Entities with Spring Data JPA

5.使用 Spring Data JPA 刷新实体

Refreshing entities in JPA ensures that the in-memory representation of entities in the application stays synchronized with the latest data stored in the database. When other transactions modify or update entities, the data within the persistence context might become outdated. Refreshing entities allow us to retrieve the most recent data from the database, preventing inconsistencies and maintaining data accuracy.

在 JPA 中刷新实体可确保应用程序中实体的内存表示与数据库中存储的最新数据保持同步。当其他事务修改或更新实体时,持久化上下文中的数据可能会过时。刷新实体允许我们从数据库中检索最新数据,从而防止不一致并保持数据的准确性。

5.1. Using refresh()

5.1.使用 refresh()

In JPA, we achieve entity refreshing using the refresh() method provided by the EntityManager. Invoking refresh() on a managed entity discards any modifications made to the entity within the persistence context. It reloads the entity’s state from the database, effectively replacing any modifications made since the entity was last synchronized with the database.

在 JPA 中,我们使用 EntityManager 提供的 refresh() 方法来实现实体刷新。在受管实体上调用 refresh() 会丢弃持久化上下文中对实体所做的任何修改。它将从数据库重新加载实体的状态,从而有效地替换自实体上次与数据库同步以来所做的任何修改。

However, it’s important to note that Spring Data JPA repositories do not offer a built-in refresh() method.

不过,需要注意的是,Spring Data JPA 存储库并不提供内置的 refresh() 方法。

Here’s how to refresh an entity using the EntityManager:

以下是使用 EntityManager 刷新实体的方法:

@Autowired
private EntityManager entityManager;

entityManager.refresh(order);

5.2. Handling OptimisticLockException

5.2.处理 OptimisticLockException 异常

The @Version annotation in Spring Data JPA serves the purpose of implementing optimistic locking. It helps ensure data consistency when multiple transactions might try to update the same entity concurrently. When we use @Version, JPA automatically creates a special field (often named version) on our entity class.

Spring Data JPA 中的 @Version 注解用于实现乐观锁定。当多个事务尝试并发更新同一实体时,它有助于确保数据一致性。当我们使用 @Version 时,JPA 会自动在实体类上创建一个特殊字段(通常命名为 版本)。

This field stores an integer value representing the version of the entity in the database:

该字段存储一个整数值,代表数据库中实体的版本:

@Entity
public class Order {
    @Id
    @GeneratedValue
    private Long id;
    
    @Version
    private Long version;
}

When retrieving an entity from the database, JPA actively fetches its version. Upon updating the entity, JPA compares the version of the entity in the persistence context with the version stored in the database. If the versions of the entity differ, it indicates that another transaction has modified the entity, potentially leading to data inconsistency.

从数据库检索实体时,JPA 会主动获取其版本。更新实体时,JPA 会将持久化上下文中实体的版本与数据库中存储的版本进行比较。如果实体的版本不同,则表明另一个事务修改了该实体,从而可能导致数据不一致。

In such cases, JPA throws an exception, often an OptimisticLockException, to indicate the potential conflict. Therefore, we can call the refresh() method within the catch block to reload the entity’s state from the database.

在这种情况下,JPA 会抛出一个异常,通常是 OptimisticLockException 异常,以指示潜在的冲突。因此,我们可以在 catch 块中调用 refresh() 方法,从数据库中重新加载实体的状态。

Let’s see a brief demonstration of how this approach works:

让我们来看看这种方法的简单演示:

Order order = orderRepository.findById(orderId)
  .map(existingOrder -> {
      existingOrder.setName(newName);
      return existingOrder;
  })
  .orElseGet(() -> {
      return null;
  });

if (order != null) {
    try {
        orderRepository.save(order);
    } catch (OptimisticLockException e) {
        // Refresh the entity and potentially retry the update
        entityManager.refresh(order);
        // Consider adding logic to handle retries or notify the user about the conflict
    }
}

Additionally, it’s worth noting that refresh() may throw javax.persistence.EntityNotFoundException if the entity being refreshed has been removed from the database by another transaction since it was last retrieved.

此外,值得注意的是,如果被刷新的实体在上次检索后已被其他事务从数据库中删除,refresh() 可能会抛出 javax.persistence.EntityNotFoundException 异常。

6. Conclusion

6.结论

In this article, we learned the distinction between refreshing and fetching entities in Spring Data JPA. Fetching involves retrieving the latest data from the database when needed. Refreshing involves updating the entity’s state in the persistence context with the most recent data from the database.

在本文中,我们学习了 Spring Data JPA 中刷新实体和获取实体之间的区别。获取涉及在需要时从数据库中检索最新数据。刷新则是用数据库中的最新数据更新持久化上下文中实体的状态。

By utilizing these approaches strategically, we can maintain data consistency and ensure that all transactions operate on the latest data.

通过战略性地利用这些方法,我们可以保持数据的一致性,并确保所有交易都在最新数据的基础上运行。

As always, the source code for the examples is available over on GitHub.

与往常一样,这些示例的源代码可在 GitHub 上获取。