Преглед на типовете JPA / Hibernate Cascade

1. Въведение

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

2. Какво е каскадно?

Взаимоотношенията между субектите често зависят от съществуването на друг обект - например връзката Лице - Адрес . Без Личността , субектът Адрес няма собствено значение. Когато изтрием обекта Person , нашият обект Address също трябва да бъде изтрит.

Каскадирането е начинът да се постигне това. Когато изпълняваме някакво действие върху целевия обект, същото действие ще бъде приложено към асоциирания обект.

2.1. Каскаден тип JPA

Всички специфични за JPA каскадни операции са представени от преброяването javax.persistence.CascadeType, съдържащо записи:

  • ВСИЧКО
  • УПОРСТВАМ
  • СЛИВАНЕ
  • ПРЕМАХВАНЕ
  • ОСВЕЖЕТЕ
  • ДЕТАЙТ

2.2. Hibernate Cascade Type

Hibernate поддържа три допълнителни типа каскада, заедно с тези, посочени от JPA. Тези специфични за Hibernate типове каскада се предлагат в org.hibernate.annotations.CascadeType :

  • РЕПЛИКАЦИЯ
  • ЗАПАЗЕТЕ_АКТУАЛИЗИРАНЕ
  • КЛЮЧАЛКА

3. Разлика между видовете каскада

3.1. CascadeType . ВСИЧКО

Cascade.ALL разпространява всички операции - включително специфични за хибернация - от родителски до дъщерен обект.

Нека го видим в пример:

@Entity public class Person { @Id @GeneratedValue(strategy = GenerationType.AUTO) private int id; private String name; @OneToMany(mappedBy = "person", cascade = CascadeType.ALL) private List addresses; }

Имайте предвид, че в асоциациите OneToMany споменахме каскаден тип в анотацията.

Сега, нека да видим свързания обект Адрес :

@Entity public class Address { @Id @GeneratedValue(strategy = GenerationType.AUTO) private int id; private String street; private int houseNumber; private String city; private int zipCode; @ManyToOne(fetch = FetchType.LAZY) private Person person; }

3.2. CascadeType . УПОРСТВАМ

Операцията persist прави преходния екземпляр постоянен. CascadeType PERSIST пропагандира продължат да съществуват операция от родител на дете лице . Когато запишем субекта на лицето, субектът на адреса също ще се запази.

Нека да видим тестовия случай за персистираща операция:

@Test public void whenParentSavedThenChildSaved() { Person person = new Person(); Address address = new Address(); address.setPerson(person); person.setAddresses(Arrays.asList(address)); session.persist(person); session.flush(); session.clear(); }

Когато стартираме горния тестов случай, ще видим следния SQL:

Hibernate: insert into Person (name, id) values (?, ?) Hibernate: insert into Address ( city, houseNumber, person_id, street, zipCode, id) values (?, ?, ?, ?, ?, ?)

3.3. CascadeType . СЛИВАНЕ

Операцията за сливане копира състоянието на дадения обект върху постоянния обект със същия идентификатор. CascadeType.MERGE разпространява операцията за сливане от родител на дъщерна същност .

Нека тестваме операцията за сливане:

@Test public void whenParentSavedThenMerged() { int addressId; Person person = buildPerson("devender"); Address address = buildAddress(person); person.setAddresses(Arrays.asList(address)); session.persist(person); session.flush(); addressId = address.getId(); session.clear(); Address savedAddressEntity = session.find(Address.class, addressId); Person savedPersonEntity = savedAddressEntity.getPerson(); savedPersonEntity.setName("devender kumar"); savedAddressEntity.setHouseNumber(24); session.merge(savedPersonEntity); session.flush(); }

Когато изпълним горния тестов случай, операцията за сливане генерира следния SQL:

Hibernate: select address0_.id as id1_0_0_, address0_.city as city2_0_0_, address0_.houseNumber as houseNum3_0_0_, address0_.person_id as person_i6_0_0_, address0_.street as street4_0_0_, address0_.zipCode as zipCode5_0_0_ from Address address0_ where address0_.id=? Hibernate: select person0_.id as id1_1_0_, person0_.name as name2_1_0_ from Person person0_ where person0_.id=? Hibernate: update Address set city=?, houseNumber=?, person_id=?, street=?, zipCode=? where id=? Hibernate: update Person set name=? where id=?

Тук можем да видим, че операцията за обединяване първо зарежда обекти и адрес и лице и след това актуализира и двете в резултат на CascadeType MERGE .

3.4. CascadeType.REMOVE

Както подсказва името, операцията за премахване премахва реда, съответстващ на обекта, от базата данни, а също и от постоянния контекст.

CascadeType.REMOVE разпространява операцията за премахване от родителска към детска същност. Подобно на CascadeType.REMOVE на JPA, имаме CascadeType.DELETE , който е специфичен за Hibernate . Няма разлика между двете.

Сега е време да тествате CascadeType .

@Test public void whenParentRemovedThenChildRemoved() { int personId; Person person = buildPerson("devender"); Address address = buildAddress(person); person.setAddresses(Arrays.asList(address)); session.persist(person); session.flush(); personId = person.getId(); session.clear(); Person savedPersonEntity = session.find(Person.class, personId); session.remove(savedPersonEntity); session.flush(); }

Когато стартираме горния тестов случай, ще видим следния SQL:

Hibernate: delete from Address where id=? Hibernate: delete from Person where id=?

На адреса , свързани с лицето, също бе отстранен в резултат на CascadeType REMOVE .

3.5. CascadeType.DETACH

The detach operation removes the entity from the persistent context. When we use CascaseType.DETACH, the child entity will also get removed from the persistent context.

Let's see it in action:

@Test public void whenParentDetachedThenChildDetached() { Person person = buildPerson("devender"); Address address = buildAddress(person); person.setAddresses(Arrays.asList(address)); session.persist(person); session.flush(); assertThat(session.contains(person)).isTrue(); assertThat(session.contains(address)).isTrue(); session.detach(person); assertThat(session.contains(person)).isFalse(); assertThat(session.contains(address)).isFalse(); }

Here, we can see that after detaching person, neither person nor address exists in the persistent context.

3.6. CascadeType.LOCK

Unintuitively, CascadeType.LOCK re-attaches the entity and its associated child entity with the persistent context again.

Let's see the test case to understand CascadeType.LOCK:

@Test public void whenDetachedAndLockedThenBothReattached() { Person person = buildPerson("devender"); Address address = buildAddress(person); person.setAddresses(Arrays.asList(address)); session.persist(person); session.flush(); assertThat(session.contains(person)).isTrue(); assertThat(session.contains(address)).isTrue(); session.detach(person); assertThat(session.contains(person)).isFalse(); assertThat(session.contains(address)).isFalse(); session.unwrap(Session.class) .buildLockRequest(new LockOptions(LockMode.NONE)) .lock(person); assertThat(session.contains(person)).isTrue(); assertThat(session.contains(address)).isTrue(); }

As we can see, when using CascadeType.LOCK, we attached the entity person and its associated address back to the persistent context.

3.7. CascadeType.REFRESH

Refresh operations re-read the value of a given instance from the database. In some cases, we may change an instance after persisting in the database, but later we need to undo those changes.

In that kind of scenario, this may be useful. When we use this operation with CascadeType REFRESH, the child entity also gets reloaded from the database whenever the parent entity is refreshed.

For better understanding, let's see a test case for CascadeType.REFRESH:

@Test public void whenParentRefreshedThenChildRefreshed() { Person person = buildPerson("devender"); Address address = buildAddress(person); person.setAddresses(Arrays.asList(address)); session.persist(person); session.flush(); person.setName("Devender Kumar"); address.setHouseNumber(24); session.refresh(person); assertThat(person.getName()).isEqualTo("devender"); assertThat(address.getHouseNumber()).isEqualTo(23); }

Here, we made some changes in the saved entities person and address. When we refresh the person entity, the address also gets refreshed.

3.8. CascadeType.REPLICATE

The replicate operation is used when we have more than one data source, and we want the data in sync. With CascadeType.REPLICATE, a sync operation also propagates to child entities whenever performed on the parent entity.

Now, let's test CascadeType.REPLICATE:

@Test public void whenParentReplicatedThenChildReplicated() { Person person = buildPerson("devender"); person.setId(2); Address address = buildAddress(person); address.setId(2); person.setAddresses(Arrays.asList(address)); session.unwrap(Session.class).replicate(person, ReplicationMode.OVERWRITE); session.flush(); assertThat(person.getId()).isEqualTo(2); assertThat(address.getId()).isEqualTo(2); }

Because of CascadeTypeREPLICATE, when we replicate the person entity, then its associated address also gets replicated with the identifier we set.

3.9. CascadeType.SAVE_UPDATE

CascadeType.SAVE_UPDATE propagates the same operation to the associated child entity. It's useful when we use Hibernate-specific operations like save, update, and saveOrUpdate.

Let's see CascadeType.SAVE_UPDATE in action:

@Test public void whenParentSavedThenChildSaved() { Person person = buildPerson("devender"); Address address = buildAddress(person); person.setAddresses(Arrays.asList(address)); session.saveOrUpdate(person); session.flush(); }

Поради CascadeType.SAVE_UPDATE , когато стартираме горния тестов случай, тогава можем да видим, че и лицето, и адресът са запазени. Ето полученият SQL:

Hibernate: insert into Person (name, id) values (?, ?) Hibernate: insert into Address ( city, houseNumber, person_id, street, zipCode, id) values (?, ?, ?, ?, ?, ?)

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

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

Изходният код на статията е достъпен на GitHub.