Returning an Auto-Generated Id with JPA – 用JPA返回一个自动生成的ID

最后修改: 2021年 5月 17日

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

1. Introduction

1.绪论

In this tutorial, we’ll discuss how to handle auto-generated ids with JPA. There are two key concepts that we must understand before we take a look at a practical example, namely life cycle and id generation strategy.

在本教程中,我们将讨论如何用JPA处理自动生成的id。在我们看一个实际的例子之前,有两个关键的概念我们必须理解,即生命周期和id生成策略。

2. Entity Life Cycle and Id Generation

2.实体生命周期和标识生成

Each entity has four possible states during its life cycle. Those states are new, managed, detached, and removed. Our focus will be on the new and managed states. During object creation, an entity is in the new state. Consequently, EntityManager is unaware of this object. Calling the persist method on EntityManager, the object transitions from a new to managed state. This method requires an active transaction.

每个实体在其生命周期内有四种可能的状态。这些状态是管理分离移除。我们的重点将放在newmanaged状态。在对象创建期间,实体处于new状态。因此,EntityManager不知道这个对象。在EntityManager上调用persist方法,该对象从new过渡到managed状态。这个方法需要一个活动的事务。

JPA defines four strategies for id generation. We can group these four strategies into two categories:

JPA为id的生成定义了四种策略。我们可以将这四种策略分为两类。

  • Ids are pre-allocated and available to EntityManager before commit
  • Ids are allocated after transaction commit

For more details about each id generation strategy, refer to our article, When Does JPA Set the Primary Key.

关于每个id生成策略的更多细节,请参考我们的文章,JPA什么时候设置主键

3. Problem Statement

3.问题陈述

Returning an id of an object can become a cumbersome task. We need to understand the principles mentioned in the previous section to avoid issues. Depending on JPA configuration, services may return objects with id equal to zero (or null). The focus will be on service class implementation and how different modifications can provide us with a solution.

返回一个对象的id可能成为一项繁琐的工作。我们需要理解上一节中提到的原则以避免问题。取决于JPA配置,服务可能会返回id等于0(或null)的对象。重点将放在服务类的实现上,以及不同的修改如何为我们提供一个解决方案。

We’ll create a Maven module with the JPA specification and Hibernate as its implementation. For simplicity, we’ll use an H2 in-memory database.

我们将以JPA规范创建一个Maven模块,并以Hibernate作为其实现。为了简单起见,我们将使用一个H2内存数据库。

Let’s start by creating a domain entity and mapping it to a database table. For this example, we’ll create a User entity with a few basic properties:

让我们从创建一个域实体并将其映射到数据库表开始。在这个例子中,我们将创建一个具有一些基本属性的User实体。

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long id;
    private String username;
    private String password;
 
    //...
}

After the domain class, we’ll create a UserService class. This simple service will have a reference to EntityManager and a method to save User objects to the database:

在域类之后,我们将创建一个UserService类。这个简单的服务将有一个对EntityManager的引用和一个将User对象保存到数据库的方法。

public class UserService {
    EntityManager entityManager;
 
    public UserService(EntityManager entityManager) {
        this.entityManager = entityManager;
    }
 
    @Transactional
    public long saveUser(User user){
        entityManager.persist(user);
        return user.getId();
    }
}

This setup is a common pitfall that we previously mentioned. We can prove that the return value of the saveUser method is zero with a test:

这种设置是我们之前提到的一个常见的陷阱。我们可以通过测试来证明saveUser方法的返回值为零。

@Test
public void whenNewUserIsPersisted_thenEntityHasNoId() {
    User user = new User();
    user.setUsername("test");
    user.setPassword(UUID.randomUUID().toString());
 
    long index = service.saveUser(user);
 
    Assert.assertEquals(0L, index);
}

In the following sections, we’ll step back to understand why this happened, and how we can solve it.

在下面的章节中,我们将退一步了解为什么会发生这种情况,以及我们如何解决这个问题。

4. Manual Transaction Control

4.手动事务控制

After object creation, our User entity is in the new state. The entity state changes to managed after the persist method call in the saveUser method. We remember from the recap section that the managed object gets an id after the transaction commit. Since the saveUser method is still running, the transaction created by the @Transactional annotation isn’t yet committed. Our managed entity gets an id when saveUser finishes execution.

在对象创建后,我们的User实体处于new状态。在saveUser方法中调用persist方法后,实体状态变为managed。我们记得在回顾部分,被管理的对象在事务commit后得到一个id。由于 saveUser方法仍在运行,由@Transactional注解创建的事务还没有提交。当saveUser执行完毕时,我们的被管理实体会得到一个id。

One possible solution is to call the flush method on EntityManager manually. On the other hand, we can manually control transactions and guarantee that our method returns the id correctly. We can do this with EntityManager:

一个可能的解决方案是,手动调用EntityManager上的flush方法。另一方面,我们可以手动控制事务,并保证我们的方法能正确返回id。我们可以通过EntityManager来做到这一点。

@Test
public void whenTransactionIsControlled_thenEntityHasId() {
    User user = new User();
    user.setUsername("test");
    user.setPassword(UUID.randomUUID().toString());
     
    entityManager.getTransaction().begin();
    long index = service.saveUser(user);
    entityManager.getTransaction().commit();
     
    Assert.assertEquals(2L, index);
}

5. Using Id Generation Strategies

5.使用身份生成策略

Up until now, we used the second category, where id allocation occurs after the transaction commit. Pre-allocating strategies can provide us with ids before the transaction commit, since they keep a handful of ids in memory. This option isn’t always possible to implement because not all database engines support all generation strategies. Changing the strategy to GenerationType.SEQUENCE can resolve our problem. This strategy uses a database sequence instead of an auto-incrementing column as in GenerationType.IDENTITY.

到目前为止,我们使用的是第二类,即id分配发生在事务commit之后。预分配策略可以在事务commit之前为我们提供id,因为它们在内存中保留了少量的id。这个选项并不总是能够实现,因为不是所有的数据库引擎都支持所有的生成策略。将策略改为GenerationType.SEQUENCE可以解决我们的问题。这个策略使用一个数据库序列,而不是像GenerationType.IDENTITY.中的自动递增列。

To change the strategy, we edit our domain entity class:

为了改变策略,我们编辑我们的领域实体类。

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private long id;
 
    //...
}

6. Conclusion

6.结论

In this article, we covered id generation techniques in JPA. First, we did a little recap of the most important key aspects of id generation. Then we covered common configurations used in JPA, along with their advantages and disadvantages. All code referenced in this article can be found over on GitHub.

在这篇文章中,我们介绍了JPA中的id生成技术。首先,我们对id生成的最重要的关键方面做了一个小回顾。然后,我们介绍了JPA中使用的常见配置,以及它们的优点和缺点。本文中引用的所有代码都可以在GitHub上找到