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

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

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

1. Обзор

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

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

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

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

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

  • Используя EntityManager.remove
  • Когда удаление каскадируется из других экземпляров объекта
  • Когда применяется orphanRemoval
  • Выполняя оператор удаления 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 предоставленный экземпляр переходит в удаленное состояние, и связанное с ним удаление из базы данных происходит при следующей очистке.

Обратите внимание, что удаленный экземпляр повторно сохраняется, если к нему применяется операция PERSIST . Распространенной ошибкой является игнорирование того факта, что операция PERSIST была применена к удаленному экземпляру (обычно потому, что он каскадируется из другого экземпляра во время сброса), потому что раздел 3.2.2 спецификации 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 и выполнить поиск таких записей, как незапланированное удаление объекта .

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 объявляет, что связанные экземпляры объектов должны быть удалены, когда они отсоединяются от родителя, или, что то же самое, при удалении родителя.

Покажем это, определив такую ассоциацию от Бара до База:

@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 доступна на Github . Это проект на основе Maven, поэтому его легко импортировать и запускать как есть.