Перейти к основному содержимому

Когда JPA устанавливает первичный ключ

· 5 мин. чтения

1. Обзор

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

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

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

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

Новый экземпляр объекта становится одновременно управляемым и постоянным, вызывая для него метод persist или каскадируя операцию сохранения.

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

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

Когда мы вызываем метод EntityManager.persist() , состояние объекта изменяется в соответствии со спецификацией JPA:

Если X — новый объект, он становится управляемым. Сущность X будет введена в базу данных во время или до фиксации транзакции или в результате операции сброса.

Это означает, что существуют различные способы генерации первичного ключа. Как правило, есть два решения:

  • Предварительно выделить первичный ключ
  • Выделить первичный ключ после сохранения в базе данных

Чтобы быть более конкретным, JPA предлагает четыре стратегии для генерации первичного ключа :

  • GenerationType.AUTO
  • GenerationType.IDENTITY
  • GenerationType.SEQUENCE
  • ГенерацияТип.ТАБЛИЦА

Давайте посмотрим на них один за другим.

3.1. GenerationType.AUTO

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

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

Здесь мы проверяем значения 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());
}

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

3.3. GenerationType.SEQUENCE

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

CREATE SEQUENCE article_seq
MINVALUE 1
START WITH 50
INCREMENT BY 50

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

Давайте определим объект Article со стратегией SEQUENCE :

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

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

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

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

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

3.4. ГенерацияТип.ТАБЛИЦА

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

Затем нам нужно вставить два начальных значения в таблицу генератора:

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 присваивает значения первичного ключа после вызова метода EntityManager.persist() и перед фиксацией транзакции.

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

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

И идентификатор начинается с 10 000 после того, как мы вызовем метод persist :

@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. Вывод

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

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