1. Обзор
Как полнофункциональная платформа ORM, Hibernate отвечает за управление жизненным циклом постоянных объектов (сущностей), включая операции CRUD, такие как read , save , update и delete .
В этой статье мы исследуем различные способы удаления объектов из базы данных с помощью Hibernate и объясняем общие проблемы и подводные камни, которые могут возникнуть.
Мы используем JPA и только отступаем назад и используем Hibernate native API для тех функций, которые не стандартизированы в JPA.
2. Различные способы удаления объектов
Объекты могут быть удалены в следующих сценариях:
- С помощью EntityManager.remove
- Когда удаление каскадируется из других экземпляров сущности
- Когда применяется orphanRemoval
- Выполнив инструкцию delete JPQL
- Путем выполнения собственных запросов
- Применяя метод мягкого удаления (фильтрация мягко удаленных сущностей по условию в предложении @Where )
В оставшейся части статьи мы подробно рассмотрим эти моменты.
3. Удаление С помощью Диспетчера сущностей
Удаление с помощью 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 из спецификации 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());
Если на удаленную Кору ссылается Foot , то операция PERSIST каскадируется из Foo в Bar , поскольку ассоциация помечена cascade.ВСЕ и удаление незапланированное. Чтобы убедиться, что это происходит, мы можем включить уровень журнала трассировки для пакета org.hibernate и выполнить поиск таких записей, как un-scheduling entity deletation .
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 .
Обратите внимание , что это почти всегда ошибка каскадирования УДАЛЕНИЯ операции в @ManyToMany ассоциации , потому что это вызовет удаление дочерних экземпляров, которые могут быть связаны с другими родительскими экземплярами. Это также относится к CascadeType.ВСЕ , так как это означает, что все операции должны быть каскадированы, включая операцию REMOVE .
5. Изъятие детей-сирот
Директива orphanRemoval объявляет, что связанные экземпляры сущностей должны быть удалены, когда они не связаны с родительским объектом или, что эквивалентно, когда родительский объект удален.
Мы покажем это, определив такую ассоциацию от Bar до Baz:
@Entity public class Bar { @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) private ListbazList = 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, поэтому его должно быть легко импортировать и запускать как есть.