10.11. 連鎖的な永続化

個々のオブジェクトを保存、または削除、再追加することはかなり面倒です。特に、関連するオブジェクトを扱うような場合には際立ちます。よくあるのは、親子関係を扱うケースです。以下の例を考えてみましょう:
親子関係の子が値型なら(例えば、住所や文字列のコレクション)、それらのライフサイクルは親に依存しており、便利な状態変化の「カスケード」には追加の作業は必要はありません。親が保存されたとき、値型の子オブジェクトも同じように保存されますし、親が削除されたときは、子も削除されます。その他の操作も同じです。コレクションから1つの子を削除するような操作でもうまくいきます。Hibernate は値型のオブジェクトは共有参照ができないので、この削除操作を検出すると、データベースからその子を削除します。
ここで、親と子が値型でなくエンティティであるとして同じシナリオを考えてみましょう(例えば、カテゴリーと品目の関係や親と子のcatの関係です)。エンティティは、それ自身がライフサイクルを持ち、共有参照に対応しています。そのため、コレクションからエンティティを削除しても、エンティティ自身を削除できるわけではありません。また、エンティティは、デフォルトでは、関連する他のエンティティへ状態をカスケードすることはありません。Hibernate は 到達可能性による永続化 をデフォルトでは実装していません。
Hibernate の Session の基本操作( persist(), merge(), saveOrUpdate(), delete(), lock(), refresh(), evict(), replicate() が含まれます)に対して、それぞれに対応するカスケードスタイルがあります。それぞれのカスケードスタイルには、 create, merge, save-update, delete, lock, refresh, evict, replicate という名前がついています。関連に沿ってカスケードさせたい操作があるなら、マッピングファイルにそう指定しなければなりません。例えば、以下のようにします:
<one-to-one name="person" cascade="persist"/>
カスケードスタイルは、組み合わせることができます:
<one-to-one name="person" cascade="persist,delete,lock"/>
すべての 操作を関連に沿ってカスケードするよう指定するときは、cascade="all" を使います。デフォルトの cascade="none" は、どの操作もカスケードしないことを意味します。
特殊なカスケードスタイル delete-orphan は、一対多関連にだけ適用できます。これは、関連から削除された子のオブジェクトに対して、 delete() 操作が適用されることを意味します。
推奨事項:
  • 通常、<many-to-one><many-to-many> 関連に対しては、カスケードを有効にする意味はありません。 <one-to-one><one-to-many> 関連に対しては、カスケードが役に立つことがあります。
  • 子オブジェクトの寿命が親オブジェクトの寿命に制限を受けるならば、 cascade="all,delete-orphan" を指定し、子オブジェクトを ライフサイクルオブジェクト にします。
  • それ以外の場合は、カスケードはほとんど必要ないでしょう。しかし、同じトランザクションのなかで親と子が一緒に動作することが多いと思い、いくらかのコードを書く手間を省きたいのであれば、 cascade="persist,merge,save-update" を使うことを考えましょう。
cascade="all" でマッピングした関連(単値関連やコレクション)は、 親子 スタイルの関連とマークされます。それは、親の保存/更新/削除が、子の保存/更新/削除を引き起こす関係のことです。
さらに、永続化された親が子を単に参照している場合、その子を保存/更新することになります。しかし、このメタファーは不完全です。親から参照されなくなった子は、cascade="delete-orphan" でマッピングされた <one-to-many> 関連を除き、自動的に削除されません。親子関係のカスケード操作の正確な意味は以下のようになります:
  • 親が persist() に渡されたならば、すべての子は persist() に渡されます。
  • merge() に渡されたならば、すべての子は merge() に渡されます。
  • 親が save()update()saveOrUpdate() に渡されたならば、すべての子は saveOrUpdate() に渡されます。
  • transient または detached の子が、永続化された親に参照されたならば、 saveOrUpdate() に渡されます。
  • 親が削除されたならば、すべての子は、 delete() に渡されます。
  • 子が永続化された親から参照されなくなったときは、 特に何も起こりません 。よって、アプリケーションが必要であれば、明示的に削除する必要があります。ただし、 cascade="delete-orphan" の場合を除きます。この場合、「親のない」子は削除されます。
最後に、操作のカスケードがオブジェクトグラフに適用されるのは、コールした時 あるいは、フラッシュした時 であることに注意してください。有効な場合、この操作が実行されるときに、全操作は到達可能な関連エンティティへカスケード化されます。しかし、 save-upatedelete-orphan は、 Session のフラッシュ時、到達可能な関連エンティティすべてに対しては推移的となります。