«1. Обзор

В этом кратком руководстве мы узнаем, как выполнять оператор INSERT для объектов JPA.

Для получения дополнительной информации о Hibernate в целом ознакомьтесь с нашим исчерпывающим руководством по JPA с Spring и введением в Spring Data с JPA для более глубокого погружения в эту тему.

2. Сохранение объектов в JPA

В JPA каждая сущность, переходящая из переходного состояния в управляемое, автоматически обрабатывается EntityManager.

EntityManager проверяет, существует ли данный объект, а затем решает, следует ли его вставить или обновить. Из-за этого автоматического управления единственными операторами, разрешенными JPA, являются SELECT, UPDATE и DELETE.

В приведенных ниже примерах мы рассмотрим различные способы управления и обхода этого ограничения.

3. Определение общей модели

Теперь давайте начнем с определения простого объекта, который мы будем использовать в этом руководстве:

@Entity
public class Person {

    @Id
    private Long id;
    private String firstName;
    private String lastName;

    // standard getters and setters, default and all-args constructors
}

Кроме того, давайте определим класс репозитория, который мы будем использовать для нашего реализации:

@Repository
public class PersonInsertRepository {

    @PersistenceContext
    private EntityManager entityManager;

}

Кроме того, мы применим аннотацию @Transactional для автоматической обработки транзакций с помощью Spring. Таким образом, нам не придется беспокоиться о создании транзакций с нашим EntityManager, фиксации наших изменений или выполнении отката вручную в случае исключения.

4. createNativeQuery

Для запросов, созданных вручную, мы можем использовать метод EntityManager#createNativeQuery. Это позволяет нам создавать SQL-запросы любого типа, а не только поддерживаемые JPA. Давайте добавим новый метод в наш класс репозитория:

@Transactional
public void insertWithQuery(Person person) {
    entityManager.createNativeQuery("INSERT INTO person (id, first_name, last_name) VALUES (?,?,?)")
      .setParameter(1, person.getId())
      .setParameter(2, person.getFirstName())
      .setParameter(3, person.getLastName())
      .executeUpdate();
}

При таком подходе нам нужно определить литеральный запрос, включая имена столбцов и установить их соответствующие значения.

Теперь мы можем протестировать наш репозиторий:

@Test
public void givenPersonEntity_whenInsertedTwiceWithNativeQuery_thenPersistenceExceptionExceptionIsThrown() {
    Person person = new Person(1L, "firstname", "lastname");

    assertThatExceptionOfType(PersistenceException.class).isThrownBy(() -> {
        personInsertRepository.insertWithQuery(PERSON);
        personInsertRepository.insertWithQuery(PERSON);
    });
}

В нашем тесте каждая операция пытается вставить новую запись в нашу базу данных. Поскольку мы попытались вставить два объекта с одним и тем же идентификатором, вторая операция вставки завершается сбоем, вызывая исключение PersistenceException.

Принцип здесь тот же, если мы используем @Query Spring Data.

5. persist

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

Вместо этого мы можем использовать метод persist из EntityManager.

Как и в предыдущем примере, давайте расширим наш класс репозитория с помощью пользовательского метода:

@Transactional
public void insertWithEntityManager(Person person) {
    this.entityManager.persist(person);
}

Теперь мы можем снова протестировать наш подход:

@Test
public void givenPersonEntity_whenInsertedTwiceWithEntityManager_thenEntityExistsExceptionIsThrown() {
    assertThatExceptionOfType(EntityExistsException.class).isThrownBy(() -> {
        personInsertRepository.insertWithEntityManager(new Person(1L, "firstname", "lastname"));
        personInsertRepository.insertWithEntityManager(new Person(1L, "firstname", "lastname"));
    });
}

В отличие от использования собственных запросов, мы не не нужно указывать имена столбцов и соответствующие значения. Вместо этого EntityManager обрабатывает это за нас.

В приведенном выше тесте мы также ожидаем, что EntityExistsException будет выброшено вместо его суперкласса PersistenceException, которое является более специализированным и выбрасывается с помощью persist.

С другой стороны, в этом примере мы должны убедиться, что мы вызываем наш метод вставки каждый раз с новым экземпляром Person. В противном случае он уже будет управляться EntityManager, что приведет к операции обновления.

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

В этой статье мы проиллюстрировали способы выполнения операций вставки над объектами JPA. Мы рассмотрели примеры использования собственного запроса, а также использования EntityManager#persist для создания пользовательских операторов INSERT.

Как всегда, полный код, использованный в этой статье, доступен на GitHub.