«1. Обзор

В этом уроке мы проиллюстрируем момент, когда JPA присваивает значение первичному ключу. Мы поясним, что говорит спецификация JPA, а затем покажем примеры использования различных стратегий JPA для генерации первичного ключа.

2. Постановка задачи

Как мы знаем, JPA (Java Persistence API) использует EntityManager для управления жизненным циклом Entity. В какой-то момент поставщику JPA необходимо присвоить значение первичному ключу. Итак, мы можем спросить себя, когда это происходит? И где документация, в которой это указано?

Спецификация JPA гласит:

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

Итак, в этой статье мы сосредоточимся на методе EntityManager.persist().

3. Стратегия создания значения

Когда мы вызываем метод 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 ~ ~~ 3.1. GenerationType.AUTO
Let’s take a look at them one by one.

AUTO — это стратегия по умолчанию для @GeneratedValue. Если мы просто хотим иметь первичный ключ, мы можем использовать стратегию AUTO. Поставщик JPA выберет соответствующую стратегию для базовой базы данных:

3.2. GenerationType.IDENTITY

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

    @Id
    @GeneratedValue
    private Long id;

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

    // standard getters and setters
}

Стратегия IDENTITY опирается на столбец автоинкремента базы данных. База данных генерирует первичный ключ после каждой операции вставки. JPA присваивает значение первичного ключа после выполнения операции вставки или после фиксации транзакции:

Здесь мы проверяем значения id до и после фиксации транзакции:

@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
}

Стратегия IDENTITY поддерживается MySQL, SQL Server, PostgreSQL, DB2, Derby и Sybase.

@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());
}

3.3. GenerationType.SEQUENCE

Используя стратегию SEQUENCE, JPA генерирует первичный ключ, используя последовательность базы данных. Сначала нам нужно создать последовательность на стороне базы данных, прежде чем применять эту стратегию:

JPA устанавливает первичный ключ после вызова метода EntityManager.persist() и перед фиксацией транзакции.

CREATE SEQUENCE article_seq
  MINVALUE 1
  START WITH 50
  INCREMENT BY 50

Давайте определим объект Article со стратегией ПОСЛЕДОВАТЕЛЬНОСТЬ:

Последовательность начинается с 50, поэтому первым идентификатором будет следующее значение, 51.

@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
}

Теперь давайте проверим стратегию ПОСЛЕДОВАТЕЛЬНОСТЬ: ~~ ~

Стратегия SEQUENCE поддерживается Oracle, PostgreSQL и DB2.

@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();
}

3.4. GenerationType.TABLE

Стратегия 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
}

JPA присваивает значения первичного ключа после вызова EntityManager.persist() метода и перед фиксацией транзакции.

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

Давайте теперь воспользуемся генераторной таблицей со стратегией TABLE. Мы можем использовать selectionSize для предварительного выделения некоторых первичных ключей:

И id начинается с 10 000 после вызова метода persist:

@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
}

4. Заключение

@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();
}

Эта статья иллюстрирует момент, когда JPA устанавливает первичный ключ под разные стратегии. Кроме того, мы также узнали об использовании каждой из этих стратегий на примерах.

Полный код можно найти на GitHub.

«