jpa-cascade-types
JPA / Hibernateカスケードタイプの概要
1. 前書き
このチュートリアルでは、JPA / Hibernateのカスケードについて説明します。 次に、使用可能なさまざまなカスケードタイプとそのセマンティクスについて説明します。
2. カスケードとは何ですか?
エンティティの関係は、多くの場合、別のエンティティ(たとえば、_Person_†"_Address_関係)の存在に依存します。 _Person_がない場合、_Address_エンティティはそれ自体の意味を持ちません。 _Person_エンティティを削除すると、_Address_エンティティも削除されます。
カスケードはこれを達成する方法です。 *ターゲットエンティティに対して何らかのアクションを実行すると、同じアクションが関連するエンティティに適用されます。*
2.1. JPAカスケードタイプ
すべてのJPA固有のカスケード操作は、エントリを含む_javax.persistence.CascadeType_列挙によって表されます。
-
すべて
-
PERSIST
-
マージ
-
削除する
-
REFRESH
-
DETACH
2.2. 休止状態のカスケードタイプ
Hibernateは、JPAで指定されたものに加えて、3つの追加カスケードタイプをサポートします。 これらのHibernate固有のカスケードタイプは、_org.hibernate.annotations.CascadeType_で使用できます。
-
REPLICATE
-
SAVE_UPDATE
-
ロック
3. カスケードタイプの違い
3.1. CascadeType _. ALL_
_Cascade.ALL_ *すべての操作(Hibernate固有の操作を含む)を親から子エンティティに伝達します。*
例で見てみましょう:
@Entity
public class Person {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
@OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
private List<Address> addresses;
}
-
_OneToMany_アソシエーションでは、注釈でカスケードタイプに言及していることに注意してください。*
次に、関連するエンティティ_Address_を見てみましょう。
@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_は、永続操作を親から子エンティティに伝達します*。 _person_エンティティを保存すると、_address_エンティティも保存されます。
永続化操作のテストケースを見てみましょう。
@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 _. MERGE_
マージ操作は、指定されたオブジェクトの状態を同じ識別子を持つ永続オブジェクトにコピーします。 * _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=?
ここでは、マージ操作が最初に_address_エンティティと_person_エンティティの両方をロードし、_CascadeType MERGE_の結果として両方を更新することがわかります。
3.4. CascadeType.REMOVE
名前が示すように、削除操作はエンティティに対応する行をデータベースおよび永続コンテキストから削除します。
-
_CascadeType.REMOVE_は、親から子エンティティに削除操作を伝播します。* * JPAの_CascadeType.REMOVEと同様に、_Hibernateに固有の_CascadeType.DELETE_があります*。 両者に違いはありません。
ここで、_CascadeType.Remove_をテストします。
@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=?
_person_に関連付けられた_address_も、_CascadeType REMOVE_の結果として削除されました。
3.5. CascadeType.DETACH
切り離し操作は、永続コンテキストからエンティティを削除します。 * _CascaseType.DETACH、_を使用すると、子エンティティも永続コンテキストから削除されます*。
実際に見てみましょう:
@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();
}
ここでは、_person_を切り離した後、_person_も_address_も永続コンテキストに存在しないことがわかります。
3.6. CascadeType _. LOCK_
*直感的には、_CascadeType.LOCK_は、エンティティとそれに関連付けられた子エンティティを永続コンテキストに再アタッチします。*
_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();
}
ご覧のとおり、_CascadeType.LOCK_を使用する場合、エンティティ_person_とそれに関連する_address_を永続的なコンテキストにアタッチしました。
3.7. _ CascadeType ._更新
更新操作*データベースから特定のインスタンスの値を再読み取り* 場合によっては、データベースに永続化した後にインスタンスを変更できますが、後でそれらの変更を元に戻す必要があります。
そのようなシナリオでは、これが役立つ場合があります。 *この操作をCascadeType _REFRESH_で使用すると、親エンティティが更新されるたびに、子エンティティもデータベースから再読み込みされます。*
理解を深めるために、_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);
}
ここでは、保存されたエンティティ_person_と_address_にいくつかの変更を加えました。 _person_エンティティを更新すると、_address_も更新されます。
3.8. _ CascadeType.REPLICATE _
*レプリケート操作は、複数のデータソースがあり、データを同期する必要がある場合に使用されます。
それでは、_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);
}
_CascadeType_ _REPLICATE_により、_person_エンティティを複製すると、関連付けられた_address_も設定した識別子で複製されます。
3.9. _ CascadeType.SAVE_UPDATE _
_CascadeType.SAVE_UPDATE_は、同じ操作を関連する子エンティティに伝播します。 _save、update、_、_ saveOrUpdate _ ** __などの** Hibernate固有の操作を使用する場合に役立ちます。 __
_ CascadeType.SAVE_UPDATE _の動作を見てみましょう。
@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_により、上記のテストケースを実行すると、_person_と_address_の両方が保存されたことがわかります。 結果のSQLは次のとおりです。
Hibernate: insert into Person (name, id) values (?, ?)
Hibernate: insert into Address (city, houseNumber, person_id, street, zipCode, id) values (?, ?, ?, ?, ?, ?)
4. 結論
この記事では、カスケードと、JPAおよびHibernateで使用可能なさまざまなカスケードタイプオプションについて説明しました。
この記事のソースコードは、https://github.com/eugenp/tutorials/tree/master/persistence-modules/jpa-hibernate-cascade-type [GitHubで入手可能]です。