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