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

Возврат автоматически сгенерированного идентификатора с помощью JPA

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

Задача: Наибольшая подстрока без повторений

Для заданной строки s, найдите длину наибольшей подстроки без повторяющихся символов. Подстрока — это непрерывная непустая последовательность символов внутри строки...

ANDROMEDA 42

1. Введение

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

2. Жизненный цикл сущности и генерация идентификатора

Каждый объект имеет четыре возможных состояния в течение своего жизненного цикла. Эти состояния являются новыми , управляемыми , отсоединенными и удаленными . Мы сосредоточимся на новых и управляемых состояниях. Во время создания объекта сущность находится в новом состоянии . Следовательно, EntityManager не знает об этом объекте. При вызове метода persist в EntityManager объект переходит из нового в управляемое состояние. Этот метод требует активной транзакции.

JPA определяет четыре стратегии генерации идентификаторов. Мы можем сгруппировать эти четыре стратегии в две категории:

  • Идентификаторы предварительно выделяются и доступны EntityManager перед фиксацией
  • Идентификаторы выделяются после фиксации транзакции

Подробнее о каждой стратегии генерации идентификаторов см. в нашей статье Когда JPA устанавливает первичный ключ .

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

Возврат идентификатора объекта может стать громоздкой задачей. Нам необходимо понять принципы, упомянутые в предыдущем разделе, чтобы избежать проблем. В зависимости от конфигурации JPA службы могут возвращать объекты с идентификатором, равным нулю (или null) . Основное внимание будет уделено реализации класса обслуживания и тому, как различные модификации могут предоставить нам решение.

Мы создадим модуль Maven со спецификацией JPA и Hibernate в качестве его реализации. Для простоты мы будем использовать базу данных H2 в памяти.

Начнем с создания объекта домена и сопоставления его с таблицей базы данных. В этом примере мы создадим сущность User с несколькими основными свойствами:

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

//...
}

После класса домена мы создаем класс 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();
}
}

Эта настройка является распространенной ошибкой, о которой мы упоминали ранее. Мы можем доказать, что возвращаемое значение метода 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);
}

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

4. Ручное управление транзакциями

После создания объекта наша сущность User находится в новом состоянии. Состояние сущности изменяется на управляемое после вызова метода persist в методе saveUser . Мы помним из раздела резюме, что управляемый объект получает идентификатор после фиксации транзакции . Поскольку метод saveUser все еще работает, транзакция, созданная аннотацией @Transactional , еще не зафиксирована. Наш управляемый объект получает идентификатор, когда saveUser завершает выполнение.

Одним из возможных решений является вызов метода flush для EntityManager вручную . С другой стороны, мы можем вручную контролировать транзакции и гарантировать, что наш метод правильно возвращает идентификатор. Мы можем сделать это с помощью 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. Использование стратегий генерации идентификаторов

До сих пор мы использовали вторую категорию, где выделение идентификатора происходит после фиксации транзакции . Стратегии предварительного распределения могут предоставить нам идентификаторы до фиксации транзакции, поскольку они сохраняют несколько идентификаторов в памяти . Этот вариант не всегда можно реализовать, поскольку не все ядра баз данных поддерживают все стратегии генерации. Изменение стратегии на GenerationType.SEQUENCE может решить нашу проблему. Эта стратегия использует последовательность базы данных вместо столбца с автоматическим увеличением, как в GenerationType.IDENTITY.

Чтобы изменить стратегию, мы редактируем наш класс сущности домена:

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

//...
}

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

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