Как работает Flush в Hibernate

В этой статье рассмотрим, что делает метод flush(), и когда он вызывается неявно.

Что такое flush()

Этот метод есть как у JPA EntityManager:

em.flush();

так и у Hibernate Session:

session.flush();

Он транслирует изменения отслеживаемых сущностей в базу данных, то есть выполняет накопившиеся SQL-команды.

Режим FlushMode.AUTO

Дело в том, что когда мы переводим JPA сущности из одного состояния в другое, то есть вызываем для них методы сохранения, удаления (persist(), merge(), remove()), немедленного выполнения SQL-команд не происходит. SQL-команды накапливаются, а выполнение их откладывается на потом, до необходимого момента, а именно:

  1. до подтверждения транзакции (commit()),
  2. до выполнения JPQL и HQL запросов,
  3. до выполнения  native SQL запросов.
  4. либо до метода flush() – с помощью него мы можем явно выполнить накопившиеся SQL-команды.

С п.1 все ясно – в конце транзакции изменения всё же должны попасть в базу данных.

Что касается п. 2 и 3 – если перед select не сбросить изменения в базу, то select их не увидит, поэтому 2 и 3 тоже считаются необходимыми моментами. Транзакция должна видеть изменения, сделанные в ней самой.

Именно так работает автоматический режим сброса изменений в базу FlushMode.AUTO. Специально его устанавливать не надо, он и так стоит.

Но иногда все-таки нужно сбросить изменения в базу вручную методом flush().

Пример использования flush()

Дело в том, что автоматически изменения сбрасываются в базу еще и в определенном порядке, и persist() выполняется перед remove().

Рассмотрим пример. Пусть у нас есть класс City с уникальным названием:

@Data
@NoArgsConstructor
@Entity
public class City {
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private long id;
    @Column(unique = true)
    private String name;

    public City(String name) {
        this.name = name;
    }
}

Попробуем сохранить в базу город, затем удалить его и снова сохранить город с тем же названием. Несмотря на то, что название уникально, второе сохранение должно сработать, ведь мы удалили город. Но на самом деле без flush() возникнет ошибка, потому что сброс изменений запланирован на конец транзакции, и мало того, удаление идет в самом конце.

@Test
@DisplayName("")
public void whenPersistAndRemove_thenPersistsAreFisrt() {

    City city=new City("Moscow");
    em.persist(city);
    em.remove(city);
    //Без flush имеем  org.hibernate.exception.ConstraintViolationException : Нарушение уникального индекса или первичного ключа
    em.flush();

    em.persist(new City("Moscow"));
}

Благодаря em.flush() после em.remove() удается сразу протолкнуть в базу удаление, до того, как будет выполнен повторный persist(). И избежать исключения.

Код выше приведен только для демонстрации смысла flush().

Еще раз, без flush() операции выполняются (точнее, пытаются выполниться) в конце транзакции в таком порядке, что вызывает исключение:

Hibernate: call next value for hibernate_sequence
Hibernate: call next value for hibernate_sequence
Hibernate: insert into city (name, id) values (?, ?)
Hibernate: insert into city (name, id) values (?, ?)

До delete не доходит, нарушение уникальности вызывает второй insert.

А такой порядок операций с flush():

Hibernate: call next value for hibernate_sequence
Hibernate: insert into city (name, id) values (?, ?)
Hibernate: delete from city where id=?
Hibernate: call next value for hibernate_sequence
Hibernate: insert into city (name, id) values (?, ?)

Тут методом flush() мы принудительно вызываем сброс накопившихся изменений (первый persist() и remove()) в базу, так что remove() проходит.

Порядок сброса операций в базу

Вообще при сбросе изменений в базу операции выполняются в таком порядке:

  • OrphanRemovalAction
  • AbstractEntityInsertAction
  • EntityUpdateAction
  • QueuedOperationCollectionAction
  • CollectionRemoveAction
  • CollectionUpdateAction
  • CollectionRecreateAction
  • EntityDeleteAction

Удаление идет в самом конце, что и случается в предыдущем примере.

Итоги

Код доступен на GitHub.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *