«1. Обзор

В этом руководстве мы рассмотрим причину ошибки TransactionRequiredException и способы ее устранения.

2. TransactionRequiredException

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

Например, попытка обновить запись без транзакции:

Query updateQuery
  = session.createQuery("UPDATE Post p SET p.title = ?1, p.body = ?2 WHERE p.id = ?3");
updateQuery.setParameter(1, title);
updateQuery.setParameter(2, body);
updateQuery.setParameter(3, id);
updateQuery.executeUpdate();

Вызовет исключение с сообщением следующего содержания:

...
javax.persistence.TransactionRequiredException: Executing an update/delete query
  at org.hibernate.query.internal.AbstractProducedQuery.executeUpdate(AbstractProducedQuery.java:1586)
...

3. Предоставление транзакции

Очевидное решение заключается в заключении любой операции по изменению базы данных в транзакцию:

Transaction txn = session.beginTransaction();
Query updateQuery
  = session.createQuery("UPDATE Post p SET p.title = ?1, p.body = ?2 WHERE p.id = ?3");
updateQuery.setParameter(1, title);
updateQuery.setParameter(2, body);
updateQuery.setParameter(3, id);
updateQuery.executeUpdate();
txn.commit();

В приведенном выше фрагменте кода мы вручную инициируем и фиксируем транзакцию. Хотя в среде Spring Boot мы можем добиться этого, используя аннотацию @Transactional.

4. Поддержка транзакций в Spring

Если нам нужен более детальный контроль, мы можем использовать Spring TransactionTemplate. Потому что это позволяет программисту запускать персистентность объекта непосредственно перед тем, как приступить к выполнению кода метода.

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

public void update() {
    entityManager.createQuery("UPDATE Post p SET p.title = ?2, p.body = ?3 WHERE p.id = ?1")
      // parameters
      .executeUpdate();
    sendEmail();
}

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

Таким образом, обновление сообщения в TransactionTemplate предотвратит этот сценарий, так как он немедленно зафиксирует операцию:

public void update() {
    transactionTemplate.execute(transactionStatus -> {
        entityManager.createQuery("UPDATE Post p SET p.title = ?2, p.body = ?3 WHERE p.id = ?1")
          // parameters
          .executeUpdate();
        transactionStatus.flush();
        return null;
    });
    sendEmail();
}

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

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