When Does JPA Set the Primary Key – JPA何时设置主键

最后修改: 2020年 6月 12日

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

1. Overview

1.概述

In this tutorial, we’ll illustrate the moment when JPA assigns a value to the primary key. We’ll clarify what the JPA specification says, and then, we’ll show examples using various JPA strategies for primary key generation.

在本教程中,我们将说明JPA为主键赋值的时刻。我们将阐明JPA规范的内容,然后,我们将展示使用各种JPA策略生成主键的例子。

2. Problem Statement

2 问题陈述

As we know, JPA (Java Persistence API) uses the EntityManager to manage the lifecycle of an Entity. At some point, the JPA provider needs to assign a value to the primary key. So, we may find ourselves asking, when does this happen? And where is the documentation that states this?

正如我们所知,JPA(Java Persistence API)使用EntityManager来管理Entity的生命周期。在某些时候,JPA提供者需要给主键分配一个值。因此,我们可能会发现自己在问,什么时候会发生这种情况?文档中又是怎么说的呢?

The JPA specification says:

JPA规范中说。

A new entity instance becomes both managed and persistent by invoking the persist method on it or by cascading the persist operation.

通过对其调用persist方法或通过级联的persist操作,一个新的实体实例就会变得既可管理又可持久化。

So, we’ll focus on the EntityManager.persist() method in this article.

因此,我们将在本文中重点讨论EntityManager.persist()方法。

3. The Generate Value Strategy

3.创造价值战略

When we invoke the EntityManager.persist() method, the entity’s state is changed according to the JPA specification:

当我们调用EntityManager.persist()方法时,根据JPA规范,实体的状态被改变。

If X is a new entity, it becomes managed. The entity X will be entered into the database at or before transaction commit or as a result of the flush operation.

This means there are various ways to generate the primary key. Generally, there are two solutions:

这意味着有各种方法来生成主键。一般来说,有两种解决方案。

  • Pre-allocate the primary key
  • Allocate primary key after persisting in the database
To be more specific, JPA offers four strategies to generate the primary key:
  • GenerationType.AUTO
  • GenerationType.IDENTITY
  • GenerationType.SEQUENCE
  • GenerationType.TABLE
Let’s take a look at them one by one.

3.1. GenerationType.AUTO

3.1.GenerationType.AUTO

AUTO is the default strategy for @GeneratedValue. If we just want to have a primary key, we can use the AUTO strategy. The JPA provider will choose an appropriate strategy for the underlying database:

AUTO@GeneratedValue默认策略。如果我们只想拥有一个主键,我们可以使用AUTO策略。JPA提供者将为底层数据库选择一个合适的策略。

@Entity
@Table(name = "app_admin")
public class Admin {

    @Id
    @GeneratedValue
    private Long id;

    @Column(name = "admin_name")
    private String name;

    // standard getters and setters
}

3.2. GenerationType.IDENTITY

3.2.GenerationType.IDENTITY

The IDENTITY strategy relies on the database auto-increment column. The database generates the primary key after each insert operation. JPA assigns the primary key value after performing the insert operation or upon transaction commit:

IDENTITY策略依赖于数据库的自动递增列。数据库在每次插入操作后都会生成主键。JPA在执行插入操作后或事务提交时分配主键值。

@Entity
@Table(name = "app_user")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name = "user_name")
    private String name;

    // standard getters and setters
}

Here, we verify the id values before and after the transaction commit:

在这里,我们验证事务提交前后的id值。

@Test
public void givenIdentityStrategy_whenCommitTransction_thenReturnPrimaryKey() {
    User user = new User();
    user.setName("TestName");
        
    entityManager.getTransaction().begin();
    entityManager.persist(user);
    Assert.assertNull(user.getId());
    entityManager.getTransaction().commit();

    Long expectPrimaryKey = 1L;
    Assert.assertEquals(expectPrimaryKey, user.getId());
}

The IDENTITY strategy is supported by MySQL, SQL Server, PostgreSQL, DB2, Derby, and Sybase.

MySQL、SQL Server、PostgreSQL、DB2、Derby和Sybase都支持IDENTITY策略。

3.3. GenerationType.SEQUENCE

3.3.GenerationType.SEQUENCE

By using the SEQUENCE strategy, JPA generates the primary key using a database sequence. We first need to create a sequence on the database side before applying this strategy:

通过使用SEQUENCE策略,JPA使用一个数据库序列生成主键。在应用这个策略之前,我们首先需要在数据库端创建一个序列。

CREATE SEQUENCE article_seq
  MINVALUE 1
  START WITH 50
  INCREMENT BY 50

JPA sets the primary key after we invoke the EntityManager.persist() method and before we commit the transaction.

JPA在调用EntityManager.persist()方法之后,在提交事务之前设置主键。

Let’s define an Article entity with the SEQUENCE strategy:

让我们用SEQUENCE策略定义一个Article实体。

@Entity
@Table(name = "article")
public class Article {
    
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "article_gen")
    @SequenceGenerator(name="article_gen", sequenceName="article_seq")
    private Long id;

    @Column(name = "article_name")
    private String name

    // standard getters and setters
}

The sequence starts from 50, so the first id will be the next value, 51.

序列从50开始,所以第一个id将是下一个值,51。

Now, let’s test the SEQUENCE strategy:

现在,让我们测试一下SEQUENCE策略。

@Test
public void givenSequenceStrategy_whenPersist_thenReturnPrimaryKey() {
    Article article = new Article();
    article.setName("Test Name");

    entityManager.getTransaction().begin();
    entityManager.persist(article);
    Long expectPrimaryKey = 51L;
    Assert.assertEquals(expectPrimaryKey, article.getId());

    entityManager.getTransaction().commit();
}

The SEQUENCE strategy is supported by Oracle, PostgreSQL, and DB2.

SEQUENCE策略被Oracle、PostgreSQL和DB2支持。

3.4. GenerationType.TABLE

3.4.GenerationType.TABLE

The TABLE strategy generates the primary key from a table and works the same regardless of the underlying database.

TABLE策略从一个表中生成主键,无论底层数据库如何,其作用都是一样的。

We need to create a generator table on the database side to generate the primary key. The table should at least have two columns: one column to represent the generator’s name and another to store the primary key value.

我们需要在数据库端创建一个生成器表来生成主键。该表至少应该有两列:一列代表生成器的名称,另一列存储主键值。

Firstly, let’s create a generator table:

首先,让我们创建一个生成器表。

@Table(name = "id_gen")
@Entity
public class IdGenerator {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "gen_name")
    private String gen_name;

    @Column(name = "gen_value")
    private Long gen_value;

    // standard getters and setters
}

Then, we need to insert two initial values to the generator table:

然后,我们需要在生成器表中插入两个初始值。

INSERT INTO id_gen (gen_name, gen_val) VALUES ('id_generator', 0);
INSERT INTO id_gen (gen_name, gen_val) VALUES ('task_gen', 10000);

JPA assigns the primary key values after calling EntityManager.persist() method and before the transaction commit.

JPA在调用EntityManager.persist()方法后,在事务提交前分配了主键值。

Let’s now use the generator table with the TABLE strategy. We can use allocationSize to pre-allocate some primary keys:

现在让我们用TABLE策略使用生成器表。我们可以使用allocationSize来预先分配一些主键。

@Entity
@Table(name = "task")
public class Task {
    
    @TableGenerator(name = "id_generator", table = "id_gen", pkColumnName = "gen_name", valueColumnName = "gen_value",
        pkColumnValue="task_gen", initialValue=10000, allocationSize=10)
    @Id
    @GeneratedValue(strategy = GenerationType.TABLE, generator = "id_generator")
    private Long id;

    @Column(name = "name")
    private String name;

    // standard getters and setters
}

And the id starts from 10,000 after we invoke the persist method:

而在我们调用persist方法后,id从10,000开始。

@Test
public void givenTableStrategy_whenPersist_thenReturnPrimaryKey() {
    Task task = new Task();
    task.setName("Test Task");

    entityManager.getTransaction().begin();
    entityManager.persist(task);
    Long expectPrimaryKey = 10000L;
    Assert.assertEquals(expectPrimaryKey, task.getId());

    entityManager.getTransaction().commit();
}

4. Conclusion

4.总结

This article illustrates the moment when JPA sets the primary key under different strategies. In addition, we also learned about the usage of each of these strategies through examples.

这篇文章说明了JPA在不同策略下设置主键的时刻。此外,我们还通过实例了解了这些策略中每一种的用法。

The complete code can be found over on GitHub.

完整的代码可以在GitHub上找到over