Удаление объектов с помощью Hibernate

1. Обзор

Как полнофункциональная платформа ORM, Hibernate отвечает за управление жизненным циклом постоянных объектов (объектов), включая операции CRUD, такие как read , save , update и delete .

В этой статье мы рассмотрим различные способы удаления объектов из базы данных с помощью Hibernate и объясним типичные проблемы и возможные подводные камни.

Мы используем JPA и делаем шаг назад и используем нативный API Hibernate для тех функций, которые не стандартизированы в JPA.

2. Различные способы удаления объектов

Объекты могут быть удалены в следующих случаях:

  • Используя EntityManager.remove

  • Когда удаление каскадно из других экземпляров объекта

  • Когда применяется orphanRemoval

  • Выполняя оператор delete JPQL

  • Выполняя нативные запросы

  • Применяя метод мягкого удаления (фильтрация мягко удаленных объектов

по условию в предложении @ Where )

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

3. Удаление с помощью Entity Manager

Удаление с помощью EntityManager - самый простой способ удаления экземпляра объекта:

Foo foo = new Foo("foo");
entityManager.persist(foo);
flushAndClear();

foo = entityManager.find(Foo.class, foo.getId());
assertThat(foo, notNullValue());
entityManager.remove(foo);
flushAndClear();

assertThat(entityManager.find(Foo.class, foo.getId()), nullValue());

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

void flushAndClear() {
    entityManager.flush();
    entityManager.clear();
}

После вызова метода EntityManager.remove предоставленный экземпляр переходит в состояние removed , и соответствующее удаление из базы данных происходит при следующей очистке.

Обратите внимание, что удаленный экземпляр повторно сохраняется, если к нему применена операция PERSIST . Распространенной ошибкой является игнорирование того, что операция PERSIST была применена к удаленному экземпляру (обычно, потому что он каскадируется из другого экземпляра во время сброса), потому что раздел 3.2.2 http://download.oracle . com/otndocs/jcp/persistence-2__1-fr-eval-spec/index.html[спецификация JPA]требует, чтобы такой экземпляр снова сохранялся в таком случае.

Мы проиллюстрируем это, определив ассоциацию @ ManyToOne от Foo до Bar :

@Entity
public class Foo {
    @ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private Bar bar;

   //other mappings, getters and setters
}

Когда мы удаляем экземпляр Bar , на который ссылается экземпляр Foo , который также загружается в контексте постоянства, экземпляр Bar не будет удален из базы данных:

Bar bar = new Bar("bar");
Foo foo = new Foo("foo");
foo.setBar(bar);
entityManager.persist(foo);
flushAndClear();

foo = entityManager.find(Foo.class, foo.getId());
bar = entityManager.find(Bar.class, bar.getId());
entityManager.remove(bar);
flushAndClear();

bar = entityManager.find(Bar.class, bar.getId());
assertThat(bar, notNullValue());

foo = entityManager.find(Foo.class, foo.getId());
foo.setBar(null);
entityManager.remove(bar);
flushAndClear();

assertThat(entityManager.find(Bar.class, bar.getId()), nullValue());

Если на удаленный Bar ссылается Foo , операция PERSIST каскадно переходит из Foo в Bar , поскольку ассоциация помечена как cascade = CascadeType.ALL и удаление не запланировано. Чтобы убедиться, что это происходит, мы можем включить уровень журнала трассировки для пакета org.hibernate и выполнить поиск записей, таких как un-scheduling entity deletion .

4. Каскадное удаление

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

Bar bar = new Bar("bar");
Foo foo = new Foo("foo");
foo.setBar(bar);
entityManager.persist(foo);
flushAndClear();

foo = entityManager.find(Foo.class, foo.getId());
entityManager.remove(foo);
flushAndClear();

assertThat(entityManager.find(Foo.class, foo.getId()), nullValue());
assertThat(entityManager.find(Bar.class, bar.getId()), nullValue());

Здесь bar удаляется, потому что удаление связано с foo , так как объявляется, что ассоциация каскадирует все операции жизненного цикла от Foo до Bar .

Обратите внимание, что это почти всегда ошибка каскадной операции REMOVE в ассоциации @ ManyToMany , поскольку это приведет к удалению дочерних экземпляров, которые могут быть связаны с другими родительскими экземплярами. Это также относится к CascadeType.ALL , поскольку это означает, что все операции должны быть каскадными, включая операцию REMOVE .

5. Удаление сирот

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

Мы показываем это, определяя такую ​​связь от Bar до Baz:

@Entity
public class Bar {
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    private List<Baz> bazList = new ArrayList<>();

   //other mappings, getters and setters
}

Затем экземпляр Baz удаляется автоматически, когда он удаляется из списка родительского экземпляра Bar :

Bar bar = new Bar("bar");
Baz baz = new Baz("baz");
bar.getBazList().add(baz);
entityManager.persist(bar);
flushAndClear();

bar = entityManager.find(Bar.class, bar.getId());
baz = bar.getBazList().get(0);
bar.getBazList().remove(baz);
flushAndClear();

assertThat(entityManager.find(Baz.class, baz.getId()), nullValue());
  • Семантика операции orphanRemoval полностью аналогична операции REMOVE , применяемой непосредственно к затронутым дочерним экземплярам ** , что означает, что операция REMOVE далее каскадируется для вложенных дочерних элементов. Как следствие, вы должны убедиться, что другие экземпляры не ссылаются на удаленные (в противном случае они сохраняются повторно).

6. Удаление с использованием оператора JPQL

Hibernate поддерживает операции удаления в стиле DML:

Foo foo = new Foo("foo");
entityManager.persist(foo);
flushAndClear();

entityManager.createQuery("delete from Foo where id = :id")
  .setParameter("id", foo.getId())
  .executeUpdate();

assertThat(entityManager.find(Foo.class, foo.getId()), nullValue());

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

7. Удаление с использованием собственных запросов

Иногда нам нужно обратиться к собственным запросам, чтобы достичь чего-то, что не поддерживается Hibernate или специфично для поставщика базы данных.

Мы также можем удалить данные в базе данных с помощью собственных запросов:

Foo foo = new Foo("foo");
entityManager.persist(foo);
flushAndClear();

entityManager.createNativeQuery("delete from FOO where ID = :id")
  .setParameter("id", foo.getId())
  .executeUpdate();

assertThat(entityManager.find(Foo.class, foo.getId()), nullValue());

Та же рекомендация применяется к собственным запросам, что и к операторам JPA-стиля DML, т. Е. Собственные запросы не влияют ни на состояние, ни на жизненный цикл экземпляров сущностей, которые загружаются в контекст постоянства до выполнения запросов .

8. Мягкое удаление

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

Во избежание большого количества избыточных условий в предложениях where во всех запросах, которые читают мягко удаляемые объекты, Hibernate предоставляет аннотацию @ Where , которая может быть размещена на объекте и которая содержит фрагмент SQL, который автоматически добавляется к сгенерированным запросам SQL для этого лица.

Чтобы продемонстрировать это, мы добавляем аннотацию @ Where и столбец с именем DELETED к сущности Foo :

@Entity
@Where(clause = "DELETED = 0")
public class Foo {
   //other mappings

    @Column(name = "DELETED")
    private Integer deleted = 0;

   //getters and setters

    public void setDeleted() {
        this.deleted = 1;
    }
}

Следующий тест подтверждает, что все работает как положено:

Foo foo = new Foo("foo");
entityManager.persist(foo);
flushAndClear();

foo = entityManager.find(Foo.class, foo.getId());
foo.setDeleted();
flushAndClear();

assertThat(entityManager.find(Foo.class, foo.getId()), nullValue());

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

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

Реализация этого Руководства по удалению объектов с помощью Hibernate доступна на over на Github . Это проект, основанный на Maven, поэтому его легко импортировать и запускать как есть.