Изтриване на обекти с хибернация

1. Общ преглед

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

В тази статия изследваме различни начини, по които обектите могат да бъдат изтрити от база данни с помощта на Hibernate, и обясняваме често срещани проблеми и клопки, които могат да възникнат.

Използваме JPA и само отстъпваме назад и използваме Hibernate native API за тези функции, които не са стандартизирани в JPA.

2. Различни начини за изтриване на обекти

Обектите могат да бъдат изтрити в следните сценарии:

  • Като използвате EntityManager.remove
  • Когато изтриването е каскадно от други екземпляри на обекти
  • Когато се прилага осиротяване
  • Чрез изпълнение на оператор за изтриване на 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());

Ако премахнатата лента се позовава на 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());

Тук лентата е премахната, тъй като премахването е каскадно от foo , тъй като асоциацията е декларирана да каскадира всички операции от жизнения цикъл от Foo до Bar .

Имайте предвид, че почти винаги е грешка при каскадно ОТСТРАНЯВАНЕ на операция в асоциация @ManyToMany , защото това би задействало премахването на дъщерни екземпляри, които могат да бъдат свързани с други родителски екземпляри. Това се отнася и за CascadeType.ALL , тъй като означава, че всички операции трябва да бъдат каскадни, включително операцията REMOVE .

5. Премахване на сираци

В orphanRemoval директивата декларира, че асоциираното предприятие случаи следва да се отстранят, когато те са откъснати от родителя, или еквивалентно, когато родителят се отстранява.

Това показваме, като дефинираме такава асоциация от Bar до Baz:

@Entity public class Bar { @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) private List 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. Меко изтриване

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

In order to avoid lots of redundant conditions in where clauses in all the queries that read soft-deletable entities, Hibernate provides the @Where annotation which can be placed on an entity and which contains an SQL fragment that is automatically added to SQL queries generated for that entity.

To demonstrate this, we add the @Where annotation and a column named DELETED to the Foo entity:

@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; } }

The following test confirms that everything works as expected:

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. Conclusion

In this article, we looked at different ways in which data can be deleted with Hibernate. We explained basic concepts and some best practices. We also demonstrated how soft-deletes can be easily implemented with Hibernate.

Внедряването на това Изтриване на обекти с урок за хибернация е достъпно в Github. Това е проект, базиран на Maven, така че трябва да е лесно да се импортира и да се изпълнява както е.