Comments
Description
Transcript
SQL - Red Hat Customer Portal
JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide for Use with JBoss Enterprise Application Platform 4.2 エディッション 1.0 Red Hat Documentation Group JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide for Use with JBoss Enterprise Application Platform 4.2 エディッション 1.0 Red Hat Do cumentatio n Gro up 法律上の通知 Copyright © 2010 Red Hat, Inc.. T his document is licensed by Red Hat under the Creative Commons Attribution-ShareAlike 3.0 Unported License. If you distribute this document, or a modified version of it, you must provide attribution to Red Hat, Inc. and provide a link to the original. If the document is modified, all Red Hat trademarks must be removed. Red Hat, as the licensor of this document, waives the right to enforce, and agrees not to assert, Section 4d of CC-BY-SA to the fullest extent permitted by applicable law. Red Hat, Red Hat Enterprise Linux, the Shadowman logo, JBoss, MetaMatrix, Fedora, the Infinity Logo, and RHCE are trademarks of Red Hat, Inc., registered in the United States and other countries. Linux ® is the registered trademark of Linus T orvalds in the United States and other countries. Java ® is a registered trademark of Oracle and/or its affiliates. XFS ® is a trademark of Silicon Graphics International Corp. or its subsidiaries in the United States and/or other countries. MySQL ® is a registered trademark of MySQL AB in the United States, the European Union and other countries. Node.js ® is an official trademark of Joyent. Red Hat Software Collections is not formally related to or endorsed by the official Joyent Node.js open source or commercial project. T he OpenStack ® Word Mark and OpenStack Logo are either registered trademarks/service marks or trademarks/service marks of the OpenStack Foundation, in the United States and other countries and are used with the OpenStack Foundation's permission. We are not affiliated with, endorsed or sponsored by the OpenStack Foundation, or the OpenStack community. All other trademarks are the property of their respective owners. 概要 T he Hibernate Reference Guide for use with JBoss Enterprise Application Platform 4.2 and its patch releases. 目次 目次 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .9. . . . . . . . . . 前書き . . .1章 第 . . . .Hibernate . . . . . . . . . . の導入 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 10 ............ 1.1. 前書き 10 1.2. パート1 - 初めての Hibernate アプリケーション 10 1.2.1. 最初のクラス 10 1.2.2. マッピングファイル 12 1.2.3. Hibernate の設定 14 1.2.4. Ant によるビルド 15 1.2.5. スタートアップとヘルパ 17 1.2.6. オブジェクトのロードと格納 18 1.3. パート2 - 関連のマッピング 21 1.3.1. Person クラスのマッピング 21 1.3.2. 単方向 Set ベース関連 22 1.3.3. 関連を働かせる 24 1.3.4. 値のコレクション 26 1.3.5. 双方向関連 27 1.3.6. 双方向リンクの動作 27 1.4. パート3 - EventManager Web アプリケーション 28 1.4.1. 基本的な Servlet の記述 28 1.4.2. 処理と描画 29 1.4.3. デプロイとテスト 31 1.5. 要約 32 . . .2章 第 . . . .アーキテクチャ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .33 ........... 2.1. 概観 33 2.2. インスタンスの状態 35 2.3. JMX との統合 36 2.4. JCA サポート 36 2.5. コンテキスト上のセッション 36 . . .3章 第 . . . 設定 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .38 ........... 3.1. プログラム上の設定 38 3.2. SessionFactory を取得する 39 3.3. JDBC コネクション 39 3.4. オプション設定プロパティ 40 3.4.1. SQL 方言(Dialect) 47 3.4.2. 外部結合フェッチ 48 3.4.3. バイナリストリーム 49 3.4.4. ニ次キャッシュとクエリキャッシュ 49 3.4.5. クエリ言語の置き換え 49 3.4.6. Hibernate 統計 49 3.5. ロギング 49 3.6. NamingStrategy の実装 50 3.7. XML 設定ファイル 50 3.8. J2EE アプリケーションサーバーとの統合 51 3.8.1. トランザクション戦略設定 52 3.8.2. SessionFactory の JNDI への登録 53 3.8.3. JT A による現在のセッションコンテキストマネージメント 54 3.8.4. JMX デプロイメント 54 . . .4.章 第 . . .永続クラス . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 56 ............ 4.1. 単純な POJO の例 56 1 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 4.1.1. 引数のないコンストラクタを実装する 4.1.2. 識別子プロパティを用意する(オプション) 4.1.3. final クラスにしない(オプション) 4.1.4. 永続フィールドに対するアクセサとミューテータを定義する(オプション) 4.2. 継承の実装 4.3. equals() と hashCode() の実装 4.4. 動的モデル 4.5. T uplizer 58 58 58 59 59 59 60 62 . . .5章 第 . . . .基本的な . . . . . . . . .O/R . . . .マッピング . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 64 ............ 5.1. マッピング定義 64 5.1.1. Doctype 66 5.1.1.1. エンティティリゾルバ 66 5.1.2. hibernate-mapping 66 5.1.3. class 67 5.1.4. id 70 5.1.4.1. ジェネレータ 71 5.1.4.2. Hi/lo アルゴリズム 73 5.1.4.3. UUID アルゴリズム 73 5.1.4.4. 識別子カラムとシーケンス 73 5.1.4.5. 識別子の割り当て 73 5.1.4.6. トリガにより割り当てられた主キー 74 5.1.5. composite-id 74 5.1.6. discriminator 75 5.1.7. version(オプション) 76 5.1.8. timestamp(オプション) 77 5.1.9. property 77 5.1.10. many-to-one 79 5.1.11. one-to-one 81 5.1.12. natural-id 83 5.1.13. component, dynamic-component 84 5.1.14. properties 85 5.1.15. subclass 86 5.1.16. joined-subclass 86 5.1.17. union-subclass 88 5.1.18. join 89 5.1.19. key 90 5.1.20. column と formula 要素 91 5.1.21. import 91 5.1.22. any 92 5.2. Hibernate の型 93 5.2.1. エンティティと値 93 5.2.2. 基本的な型 93 5.2.3. カスタム型 95 5.3. 1つのクラスに1つ以上のマッピング 96 5.4. バッククォートで囲んだ SQL 識別子 96 5.5. メタデータの代替手段 97 5.5.1. XDoclet マークアップの使用 97 5.5.2. JDK 5.0 アノテーションの使用 99 5.6. 生成プロパティ 100 5.7. 補助的なデータベースオブジェクト 100 . . .6章 第 . . . .コレクションのマッピング . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102 ............. 6.1. コレクションの永続化 102 6.2. コレクションのマッピング 102 6.2.1. コレクションの外部キー 104 2 目次 6.2.2. コレクションの要素 6.2.3. インデックス付きのコレクション 6.2.4. 値のコレクションと多対多関連 6.2.5. 一対多関連 6.3. 高度なコレクションマッピング 6.3.1. ソートされたコレクション 6.3.2. 双方向関連 6.3.3. インデックス付きコレクションと双方向関連 6.3.4. 3項関連 6.3.5. Using an <idbag> 6.4. コレクションの例 104 104 105 107 108 108 109 111 112 112 113 . . .7章 第 . . . .関連マッピング . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 117 ............. 7.1. イントロダクション 117 7.2. 単方向関連 117 7.2.1. 多対一 117 7.2.2. 一対一 117 7.2.3. 一対多 118 7.3. 結合テーブルを使った単方向関連 119 7.3.1. 一対多 119 7.3.2. 多対一 120 7.3.3. 一対一 120 7.3.4. 多対多 121 7.4. 双方向関連 121 7.4.1. 一対多/多対一 121 7.4.2. 一対一 122 7.5. 結合テーブルを使った双方向関連 123 7.5.1. 一対多/多対一 123 7.5.2. 一対一 124 7.5.3. 多対多 125 7.6. より複雑な関連マッピング 126 . . .8章 第 . . . .コンポーネントのマッピング . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 128 ............. 8.1. 依存オブジェクト 128 8.2. 従属するオブジェクトのコレクション 130 8.3. Map のインデックスとしてのコンポーネント 131 8.4. 複合識別子としてのコンポーネント 131 8.5. 動的コンポーネント 133 . . .9章 第 . . . .継承マッピング . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 134 ............. 9.1. 3つの戦略 134 9.1.1. クラス階層ごとのテーブル(table-per-class-hierarchy) 134 9.1.2. サブクラスごとのテーブル (table-per-subclass) 135 9.1.3. discriminator を用いた table-per-subclass 135 9.1.4. table-per-subclass と table-per-class-hierarchy の混合 136 9.1.5. 具象クラスごとのテーブル(table-per-concrete-class) 137 9.1.6. 暗黙的ポリモーフィズムを用いた table-per-concrete-class 137 9.1.7. 他の継承マッピングと暗黙的ポリモーフィズムの組み合わせ 138 9.2. 制限 139 . . .10章 第 . . . . .オブジェクトを扱う . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .14 . . .1. . . . . . . . . . 10.1. Hibernate におけるオブジェクトの状態 141 10.2. オブジェクトを永続状態にする 141 10.3. オブジェクトのロード 142 10.4. クエリ 143 10.4.1. クエリの実行 143 3 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 10.4.1.1. 結果をイテレートする 10.4.1.2. オブジェクトの組(tuple)を返すクエリ 10.4.1.3. スカラーの結果 10.4.1.4. パラメータのバインド 10.4.1.5. ページ分け 10.4.1.6. スクロール可能なイテレーション 10.4.1.7. 名前付きクエリの外出し 10.4.2. フィルタリングコレクション 10.4.3. クライテリアのクエリ 10.4.4. ネイティブ SQL のクエリ 10.5. 永続オブジェクトの修正 10.6. detached オブジェクトの修正 10.7. 自動的な状態検出 10.8. 永続オブジェクトの削除 10.9. 異なる二つのデータストア間でのオブジェクトのレプリケーション 10.10. セッションのフラッシュ 10.11. 連鎖的な永続化 10.12. メタデータの使用 144 144 145 145 146 146 146 147 147 148 148 149 150 151 151 151 152 154 . . .11章 第 . . . . .トランザクションと並行性 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155 ............. 11.1. session スコープと transaction スコープ 155 11.1.1. 作業単位(Unit of work) 155 11.1.2. 長い対話 156 11.1.3. オブジェクト識別子を考える 157 11.1.4. 一般的な問題 158 11.2. データベーストランザクション境界 158 11.2.1. 管理されていない環境 159 11.2.2. JT A を使用する 160 11.2.3. 例外ハンドリング 161 11.2.4. トランザクションのタイムアウト 162 11.3. 楽観的同時実行制御 163 11.3.1. アプリケーションによるバージョンチェック 163 11.3.2. 拡張セッションと自動バージョニング 164 11.3.3. デタッチされたオブジェクトと自動バージョニング 165 11.3.4. 自動バージョニングのカスタマイズ 165 11.4. 悲観的ロック 166 11.5. コネクション開放モード 166 . . .12章 第 . . . . .インターセプタとイベント . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168 ............. 12.1. インターセプタ 168 12.2. イベントシステム 170 12.3. Hibernate の宣言的なセキュリティ 171 . . .13章 第 . . . . .バッチ処理 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173 ............. 13.1. バッチ挿入 173 13.2. バッチ更新 173 13.3. StatelessSession インターフェース 174 13.4. DML スタイルの操作 175 . . .14 第 . .章 . . .HQL: . . . . . .Hibernate . . . . . . . . . . クエリ言語 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 178 ............. 14.1. 大文字と小文字の区別 178 14.2. from 節 178 14.3. 関連と結合 178 14.4. 結合構文の形式 180 14.5. 識別子プロパティの参照 180 14.6. Select 節 180 4 目次 14.7. 集約関数 14.8. ポリモーフィックなクエリ 14.9. where 節 14.10. Expressions 式 14.11. order by 節 14.12. group by 節 14.13. 副問い合わせ 14.14. HQL の例 14.15. 大量の UPDAT E と DELET E 14.16. T ips & T ricks 14.17. コンポーネント 14.18. 行値コンストラクタ構文 181 182 182 184 187 187 188 188 190 191 192 192 . . .15章 第 . . . . .Criteria . . . . . . . . クエリ . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .194 ............. 15.1. Criteria インスタンスの作成 194 15.2. リザルトセットの絞込み 194 15.3. 結果の整列 195 15.4. 関連 195 15.5. 関連の動的フェッチ 196 15.6. クエリの例 196 15.7. 射影、集約、グループ化 197 15.8. クエリおよびサブクエリの分離 198 15.9. 自然識別子によるクエリ 199 . . .16章 第 . . . . .ネイティブ . . . . . . . . . . . SQL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201 ............. 16.1. SQLQuery の使用 201 16.1.1. スカラーのクエリ 201 16.1.2. エンティティのクエリ 202 16.1.3. 関連とコレクションの操作 202 16.1.4. 複数エンティティの取得 203 16.1.4.1. 別名とプロパティのリファレンス 203 16.1.5. 管理されていないエンティティの取得 204 16.1.6. 継承の制御 204 16.1.7. パラメータ 204 16.2. 名前付き SQL クエリ 205 16.2.1. 列と列の別名を明示的に指定するために return-property を使う 206 16.2.2. 問い合わせするためにストアドプロシージャを使う 207 16.2.2.1. ストアドプロシージャを使う上でのルールと制限 208 16.3. 作成、更新、削除のためのカスタム SQL 208 16.4. ロードのためのカスタム SQL 209 . . .17章 第 . . . . .データのフィルタリング . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 211 ............. 17.1. Hibernate のフィルタ 211 . . .18章 第 . . . . .XML . . . . .マッピング . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 213 ............. 18.1. XML データでの作業 213 18.1.1. XML とクラスのマッピングを同時に指定する 213 18.1.2. XML マッピングだけを指定する 213 18.2. XML マッピングのメタデータ 214 18.3. XML データを扱う 216 . . .19章 第 . . . . .パフォーマンスの改善 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 218 ............. 19.1. フェッチ戦略 218 19.1.1. 遅延関連の働き 218 19.1.2. フェッチ戦略のチューニング 219 19.1.3. 単一端関連プロキシ 220 19.1.4. コレクションとプロキシの初期化 221 5 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 19.1.5. バッチフェッチの使用 19.1.6. サブセレクトフェッチの使用 19.1.7. 遅延プロパティフェッチの使用 19.2. 第2レベルキャッシュ 19.2.1. キャッシュのマッピング 19.2.2. read only 戦略 19.2.3. read/write 戦略 19.2.4. 厳密ではない read/write 戦略 19.2.5. transactional 戦略 19.3. キャッシュの管理 19.4. クエリキャッシュ 19.5. コレクションのパフォーマンスの理解 19.5.1. 分類 19.5.2. 更新にもっとも効率的なコレクション list、map、idbag、set 19.5.3. inverse コレクションにもっとも最適な bag と list 19.5.4. 一括削除 19.6. パフォーマンスのモニタリング 19.6.1. SessionFactory のモニタリング 19.6.2. メトリクス 222 223 223 224 225 225 226 226 226 227 228 229 229 229 230 230 230 231 231 . . .20章 第 . . . . .ツールセットガイド . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 233 ............. 20.1. スキーマの自動生成 233 20.1.1. スキーマのカスタマイズ 233 20.1.2. ツールの実行 236 20.1.3. プロパティ 236 20.1.4. Ant を使用する 237 20.1.5. インクリメンタルなスキーマ更新 237 20.1.6. インクリメンタルなスキーマ更新に対する Ant の使用 238 20.1.7. Schema validation 238 20.1.8. スキーマのバリデーションに Ant を使用します 239 . . .21章 第 . . . . .例: . . . . .親 . ./子供 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .24 . . .0. . . . . . . . . . 21.1. コレクションに関する注意 240 21.2. 双方向一対多 240 21.3. ライフサイクルのカスケード 242 21.4. カスケードと unsaved-value 242 21.5. 結論 243 . . .22章 第 . . . . .例 . .:. Weblog . . . . . . . . アプリケーション . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .24 . . .4. . . . . . . . . . 22.1. 永続クラス 244 22.2. Hibernate のマッピング 245 22.3. Hibernate のコード 247 . . .23章 第 . . . . .例: . . . . .いろいろなマッピング . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 252 ............. 23.1. 雇用者/従業員 252 23.2. 作者/作品 254 23.3. 顧客/注文/製品 256 23.4. 種々雑多なマッピング例 258 23.4.1. "T yped" one-to-one association 258 23.4.2. 複合キーの例 259 23.4.3. 複合キー属性を共有する多対多 261 23.4.4. discrimination に基づく内容 262 23.4.5. 代替キーの関連 263 . . .24 第 . .章 . . .ベストプラクティス . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265 ............. . . . . . . . . . .History Revision . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 268 ............. 6 目次 7 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 8 前書き 前書き Working with object-oriented software and a relational database can be cumbersome and time consuming in today's enterprise environments. Hibernate is an object/relational mapping tool for Java environments. T he term object/relational mapping (ORM) refers to the technique of mapping a data representation from an object model to a relational data model with a SQL-based schema. Hibernate は Java クラスからデータベーステーブルへのマッピング(及び Java データタイプから SQL データタイプへのマッピング)を行うだけでなくデータのクエリや検索機能も提供するため、 SQL や JDBC での手作業によるデータ処理を除き開発に要する時間を大幅に削減することが可能になります。 Hibernate の目標は、 開発者にとってのプログラミングにおける一般的なデータ永続性の作業の 95 % を 軽減することです。 Hibernate データベース内でビジネスロジックを実現するストアドプロシージャのみ を使用するデータ処理中心のアプリケーションに対しては最適ではないかもしれませんが、 Java ベース の中間層でのビジネスロジック及びオブジェクト指向のドメインモデルを使用する場合に最も役に立ちま す。 Hibernate は開発者がベンダー固有の SQL コードの除去あるいはカプセル化を行う際に便利なた め、 表形式の表現からオブジェクトのグラフへの結果セットの変換に関する一般的な作業に役立ちます。 Hibernate 及びオブジェクト/リレーショナルマッピング、 あるいは Java が不慣れな方は、 次の手順を 行ってください。 1. Read 1章Hibernate の導入 for a tutorial with step-by-step instructions. T he source code for the tutorial is included in the distribution in the doc/reference/tutorial/ directory. 2. Read 2章アーキテクチャ to understand the environments where Hibernate can be used. 3. Hibernate ディストリビューション内の eg/ ディレクトリ内を見てください。 シンプルなスタンド アローンのアプリケーションが含まれています。 ご使用の JDBC ドライバを lib/ ディレクトリに コピーしてから使用するデータベースに対して正しい値を指定するよう etc/hibernate.properties を編集します。 ディストリビューションディレクトリ内のコマン ドプロンプトから、 ant eg (Ant を使用)と入力するか、 Windows 環境の場合は build eg と入 力します。 4. おもな情報源として本リファレンスドキュメントをご利用ください。 アプリケーションのデザイン に関する詳細、 ステップバイステップによる解説が必要な場合は、 Hibernate in Action (http://www.manning.com/bauer) をお読みになってもいいでしょう。 また、 http://caveatemptor.hibernate.org から Hibernate in Action のサンプルアプリケーションをダウン ロードすることもできます。 5. よくある質問とその答え (FAQ) は Hibernate ウェブサイトでご覧ください。 6. サードパーティのデモ、 サンプル、 チュートリアルなどは Hibernate のウェブサイト上にリンクさ れています。 7. Hibernate ウェブサイト上の Community Area はデザインのパターンやさまざまな統合ソリュー ション (T omcat、 JBoss AS、 Struts、 EJB など)を検索する上で興味深いリソースになります。 質問がある場合は、 Hibernate ウェブサイト上にリンクされたユーザーフォーラムをご利用ください。 ま た、 バグ報告及び今後のリクエストに関しては JIRA (問題追跡システム) を提供しています。 Hibernate, の開発に興味がある方は、 開発者用メーリングリストにご参加ください。 本ドキュメントの翻訳に興味 がある方は、 開発者用メーリングリストよりご連絡ください。 Hibernate に関する商業用開発サポート、 実稼働サポート、 トレーニングについては JBoss Inc よりご利 用頂けます (http://www.hibernate.org/SupportT raining/ を参照)。 Hibernate はプロフェッショナルなオー プンソースプロジェクトであり、 JBoss Enterprise Middleware System (JEMS) スィート製品の重要なコ ンポーネントになります。 9 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 第 1章 Hibernate の導入 1.1. 前 書 き この章は Hibernate を初めて使うユーザー向けの入門的なチュートリアルです。インメモリデータベース を使う簡単なコマンドラインアプリケーションから始め、一歩一歩わかりやすいやり方で開発を進めま す。 このチュートリアルは Hibernate を初めて使うユーザーを想定していますが、理解するには Java と SQL についての知識が必要です。これは Michael Gloegl の手によるチュートリアルを下敷きにしていますが、 ここでサードパーティライブラリと述べているのは、 JDK 1.4 と 5.0 用のものです。 JDK 1.3 を利用する のであれば他のライブラリが必要かもしれません。 チュートリアルのソースコードは Hibernate ディストリビューションの doc/reference/tutorial/ にあります。 1.2. パ ー ト 1 - 初 め て の Hibernate ア プ リ ケ ー シ ョ ン First, we'll create a simple console-based Hibernate application. We use an Java database (HSQL DB), so we do not have to install any database server. Let's assume we need a small database application that can store events we want to attend, and information about the hosts of these events. まず最初にすることは開発用のディレクトリをセットアップして、必要となるすべての Java ライブラリ を配置することです。 Hibernate ウェブサイトから Hibernate ディストリビューションをダウンロードし てください。ファイルを解凍して /lib にある必要なライブラリのすべてを、新しい開発用ディレクトリ の /lib ディレクトリに配置してください。このようになっているはずです: . +lib antlr.jar cglib.jar asm.jar asm-attrs.jars commons-collections.jar commons-logging.jar hibernate3.jar jta.jar dom4j.jar log4j.jar これが 本ドキュメント執筆時点での Hibernate の必要最低限のライブラリです (メインアーカイブの hibernate3.jar もコピーしていることに注意してください)。 Hibernate のバージョンによってはさらに 必要なライブラリや、不要なライブラリがあるかもしれません。 Hibernate ディストリビューションの lib/ ディレクトリにある README.txt ファイルを見てください。必須またはオプションのサードパー ティライブラリについての情報を載せています (実際 Log4j は必須ではありませんが、多くの開発者が好 んでいます)。 次にデータベースに格納するイベントを表すクラスを作成します。 1.2.1. 最初のクラス 最初の永続クラスは、プロパティをいくつか持つシンプルな JavaBean です: 10 第1章 Hibernate の導入 package events; import java.util.Date; public class Event { private Long id; private String title; private Date date; public Event() {} public Long getId() { return id; } private void setId(Long id) { this.id = id; } public Date getDate() { return date; } public void setDate(Date date) { this.date = date; } public String getTitle() { return title; } public void setTitle(String title) { this.title = title; } } ご覧のとおり、このクラスはフィールドが private の可視性を持っているのと同時に、 JavaBean 標準の ゲッター、セッターメソッドの命名規約に従っています。このような設計は推奨されていますが必須では ありません。アクセサメソッドを設けるのはリファクタリングを考えた頑健性のためで、 Hibernate は フィールドに直接アクセスすることも可能です。引数のないコンストラクタは、リフレクションでこのク ラスのインスタンスを作成するために必要です。 T he id property holds a unique identifier value for a particular event. All persistent entity classes (there are less important dependent classes as well) will need such an identifier property if we want to use the full feature set of Hibernate. In fact, most applications (esp. web applications) need to distinguish objects by identifier, so you should consider this a feature rather than a limitation. However, we usually don't manipulate the identity of an object, hence the setter method should be private. Only Hibernate will assign identifiers when an object is saved. You can see that Hibernate can access public, private, and protected accessor methods, as well as (public, private, protected) fields directly. T he choice is up to you and you can match it to fit your application design. 引数のないコンストラクタはすべての永続クラスに必須です。これは Hibernate が Java のリフレクショ ンを使って、オブジェクトを作成しなければならないためです。コンストラクタを private にすることは 可能ですが、実行時のプロキシ生成と、バイトコード操作なしの効率的なデータの抽出には、 package 可 視性が必要です。 11 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 開発フォルダの src というディレクトリの適切なパッケージに、この Java ソースファイルを配置してく ださい。この時点でディレクトリは以下のようになっているはずです: . +lib <Hibernate and third-party libraries> +src +events Event.java 次のステップでは、 Hibernate にこの永続クラスの情報を教えます。 1.2.2. マッピングファイル Hibernate は、どのように永続クラスのオブジェクトをロードし格納すればよいかを知る必要がありま す。ここで Hibernate マッピングファイルが登場します。マッピングファイルは、データベース内のどの テーブルにアクセスしなければならないか、そのテーブルのどのカラムを使うべきかを、 Hibernate に教 えます。 マッピングファイルの基本的な構造はこのようになります: <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping> [...] </hibernate-mapping> Note that the Hibernate DT D is very sophisticated. You can use it for auto-completion of XML mapping elements and attributes in your editor or IDE. You also should open up the DT D file in your text editor it's the easiest way to get an overview of all elements and attributes and to see the defaults, as well as some comments. Note that Hibernate will not load the DT D file from the web, but first look it up from the classpath of the application. T he DT D file is included in hibernate3.jar as well as in the src/ directory of the Hibernate distribution. 以降の例ではコードを短くするために DT D 宣言を省略します。当然ですがこれはオプションではありま せん。 2つの hibernate-m apping タグの間に class 要素を含めてください。すべての永続エンティティク ラス(念を押しますが、ファーストクラスのエンティティではない依存クラスというものが後ほど登場し ます)は SQL データベース内のテーブルへのこのようなマッピングを必要とします。 <hibernate-mapping> <class name="events.Event" table="EVENTS"> </class> </hibernate-mapping> So far we told Hibernate how to persist and load object of class Event to the table EVENT S, each instance represented by a row in that table. Now we continue with a mapping of the unique identifier property to the tables primary key. In addition, as we don't want to care about handling this identifier, we configure Hibernate's identifier generation strategy for a surrogate primary key column: 12 第1章 Hibernate の導入 <hibernate-mapping> <class name="events.Event" table="EVENTS"> <id name="id" column="EVENT_ID"> <generator class="native"/> </id> </class> </hibernate-mapping> T he id element is the declaration of the identifer property, nam e="id" declares the name of the Java property - Hibernate will use the getter and setter methods to access the property. T he column attribute tells Hibernate which column of the EVENT S table we use for this primary key. T he nested generator element specifies the identifier generation strategy, in this case we used native, which picks the best strategy depending on the configured database (dialect). Hibernate supports database generated, globally unique, as well as application assigned identifiers (or any strategy you have written an extension for). 最後にクラスの永続プロパティの宣言をマッピングファイルに含めます。デフォルトでは、クラスのプロ パティは永続と見なされません: <hibernate-mapping> <class name="events.Event" table="EVENTS"> <id name="id" column="EVENT_ID"> <generator class="native"/> </id> <property name="date" type="timestamp" column="EVENT_DATE"/> <property name="title"/> </class> </hibernate-mapping> id 要素の場合と同様に、 property 要素の nam e 属性で、どのゲッターとセッターメソッドを使うべき かを Hibernate に教えます。この例では、 Hibernate は getDate()/setDate() と getT itle()/setT itle() を探します。 Why does the date property mapping include the colum n attribute, but the title doesn't? Without the colum n attribute Hibernate by default uses the property name as the column name. T his works fine for title. However, date is a reserved keyword in most database, so we better map it to a different name. T he next interesting thing is that the title mapping also lacks a type attribute. T he types we declare and use in the mapping files are not, as you might expect, Java data types. T hey are also not SQL database types. T hese types are so called Hibernate mapping types, converters which can translate from Java to SQL data types and vice versa. Again, Hibernate will try to determine the correct conversion and mapping type itself if the type attribute is not present in the mapping. In some cases this automatic detection (using Reflection on the Java class) might not have the default you expect or need. T his is the case with the date property. Hibernate can't know if the property (which is of java.util.Date) should map to a SQL date, tim estam p, or tim e column. We preserve full date and time information by mapping the property with a tim estam p converter. このマッピングファイルは、 Event.hbm .xm l として Event Java クラスソースファイルのすぐ隣に セーブするべきです。マッピングファイルの命名方法は任意ですが、 hbm .xm l サフィックスが Hibernate の開発者のコミュニティ内での習慣となっています。現在ディレクトリ構造は以下のように なっているはずです: 13 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide . +lib <Hibernate and third-party libraries> +src +events Event.java Event.hbm.xml Hibernate の主要な設定を続けます。 1.2.3. Hibernate の設定 ここまでで永続クラスとマッピングファイルが揃いました。これから Hibernate の設定を行いますが、そ の前にデータベースが必要です。 HSQL DB は Java ベースのインメモリ SQL DBMS であり、 HSQL DB ウェブサイトからダウンロードできます。実際にはダウンロードした中の hsqldb.jar だけが必要で す。このファイルを開発フォルダの lib/ ディレクトリに配置してください。 data というディレクトリを開発ディレクトリのルートに作成してください。 HSQL DB はここにデータ ファイルを格納します。このデータディレクトリにおいて java -classpath ../lib/hsqldb.jar org.hsqldb.Server を実行し、データベースを起動させてください。動作の開始と、 T CP/IP ソケッ トのバインドが確認できます。後ほど作成するアプリケーションはここに接続します。もしこのチュート リアル中にデータベースを初期化したければ、 HSQL DB をシャットダウンして(作業ウィンドウで CT RL + C を押します) data/ ディレクトリ内のファイルを全て消去した後、 HSQL DB を再起動しま す。 Hibernate はアプリケーションのデータベースに接続する層なので、コネクションの情報が必要になりま す。コネクションは JDBC コネクションプールを通じて行われますが、これも設定する必要があります。 Hibernate ディストリビューションにはいくつかのオープンソースの JDBC コネクションプールツールが 含まれていますが、このチュートリアルでは Hibernate に組み込まれたコネクションプールを使います。 もし製品レベルの品質のサードパーティ JDBC コネクションプールソフトウェアを使いたければ、クラス パスに必要なライブラリをコピーして、異なるコネクションプールを設定しなければならないことに注意 してください。 For Hibernate's configuration, we can use a simple hibernate.properties file, a slightly more sophisticated hibernate.cfg.xm l file, or even complete programmatic setup. Most users prefer the XML configuration file: 14 第1章 Hibernate の導入 <?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <session-factory> <!-- Database connection settings --> <property name="connection.driver_class">org.hsqldb.jdbcDriver</property> <property name="connection.url">jdbc:hsqldb:hsql://localhost</property> <property name="connection.username">sa</property> <property name="connection.password"></property> <!-- JDBC connection pool (use the built-in) --> <property name="connection.pool_size">1</property> <!-- SQL dialect --> <property name="dialect">org.hibernate.dialect.HSQLDialect</property> <!-- Enable Hibernate's automatic session context management --> <property name="current_session_context_class">thread</property> <!-- Disable the second-level cache --> <property name="cache.provider_class">org.hibernate.cache.NoCacheProvider</property> <!-- Echo all executed SQL to stdout --> <property name="show_sql">true</property> <!-- Drop and re-create the database schema on startup --> <property name="hbm2ddl.auto">create</property> <mapping resource="events/Event.hbm.xml"/> </session-factory> </hibernate-configuration> Note that this XML configuration uses a different DT D. We configure Hibernate's SessionFactory - a global factory responsible for a particular database. If you have several databases, use several <session-factory> configurations, usually in several configuration files (for easier startup). T he first four property elements contain the necessary configuration for the JDBC connection. T he dialect property element specifies the particular SQL variant Hibernate generates. Hibernate's automatic session management for persistence contexts will come in handy as you will soon see. T he hbm 2ddl.auto option turns on automatic generation of database schemas - directly into the database. T his can of course also be turned off (by removing the config option) or redirected to a file with the help of the Schem aExport Ant task. Finally, we add the mapping file(s) for persistent classes to the configuration. このファイルをソースディレクトリにコピーしてください。するとこれはクラスパスのルートにあること になります。 Hibernate は、スタートアップ時にクラスパスのルートで hibernate.cfg.xm l という ファイルを自動的に探します。 1.2.4. Ant によるビルド 15 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide We'll now build the tutorial with Ant. You will need to have Ant installed - get it from the Ant download page. How to install Ant will not be covered here. Please refer to the Ant manual. After you have installed Ant, we can start to create the buildfile. It will be called build.xm l and placed directly in the development directory. 基本的なビルドファイルはこのようになります: <project name="hibernate-tutorial" default="compile"> <property name="sourcedir" value="${basedir}/src"/> <property name="targetdir" value="${basedir}/bin"/> <property name="librarydir" value="${basedir}/lib"/> <path id="libraries"> <fileset dir="${librarydir}"> <include name="*.jar"/> </fileset> </path> <target name="clean"> <delete dir="${targetdir}"/> <mkdir dir="${targetdir}"/> </target> <target name="compile" depends="clean, copy-resources"> <javac srcdir="${sourcedir}" destdir="${targetdir}" classpathref="libraries"/> </target> <target name="copy-resources"> <copy todir="${targetdir}"> <fileset dir="${sourcedir}"> <exclude name="**/*.java"/> </fileset> </copy> </target> </project> これは .jar で終わる lib ディレクトリのすべてのファイルを、コンパイルに使用するクラスパスに追加 することを Ant に教えます。また、 Java ソースファイルでないすべてのファイルをターゲットディレク トリにコピーするということでもあります。例えば設定ファイルや Hibernate マッピングファイルなどで す。今 Ant を実行すると、このような出力があるはずです: C:\hibernateTutorial\>ant Buildfile: build.xml copy-resources: [copy] Copying 2 files to C:\hibernateTutorial\bin compile: [javac] Compiling 1 source file to C:\hibernateTutorial\bin BUILD SUCCESSFUL Total time: 1 second 16 第1章 Hibernate の導入 1.2.5. スタートアップとヘルパ It's time to load and store some Event objects, but first we have to complete the setup with some infrastructure code. We have to startup Hibernate. T his startup includes building a global SessionFactory object and to store it somewhere for easy access in application code. A SessionFactory can open up new Session's. A Session represents a single-threaded unit of work, the SessionFactory is a thread-safe global object, instantiated once. We'll create a HibernateUtil helper class which takes care of startup and makes accessing a SessionFactory convenient. Let's have a look at the implementation: package util; import org.hibernate.*; import org.hibernate.cfg.*; public class HibernateUtil { private static final SessionFactory sessionFactory; static { try { // Create the SessionFactory from hibernate.cfg.xml sessionFactory = new Configuration().configure().buildSessionFactory(); } catch (Throwable ex) { // Make sure you log the exception, as it might be swallowed System.err.println("Initial SessionFactory creation failed." + ex); throw new ExceptionInInitializerError(ex); } } public static SessionFactory getSessionFactory() { return sessionFactory; } } このクラスは静的初期化ブロック(クラスがロードされるときに JVM によって一度だけ呼ばれる) でグ ローバルの SessionFactory を生成するだけではなく、静的シングルトンの使用を隠蔽します。アプリ ケーションサーバーの JNDI から SessionFactory をルックアップするのと同様です。 設定ファイル内で SessionFactory に名前を与えると、 Hibernate は SessionFactory 構築後に JNDI に対しバインドを行おうとします。このコードを完全に排除するためには、 JMX デプロイメントを 利用して JMX を利用できるコンテナをインスタンス化し、 HibernateService を JNDI へバインドす ることもできます。これらの高度なオプションは、 Hibernate のリファレンスドキュメントで説明されて います。 HibernateUtil.java を開発ソースディレクトリにある events パッケージの隣に配置してくださ い。 17 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide . +lib <Hibernate and third-party libraries> +src +events Event.java Event.hbm.xml +util HibernateUtil.java hibernate.cfg.xml +data build.xml T his should again compile without problems. We finally need to configure a logging system - Hibernate uses commons logging and leaves you the choice between Log4j and JDK 1.4 logging. Most developers prefer Log4j: copy log4 j.properties from the Hibernate distribution (it's in the etc/ directory) to your src directory, next to hibernate.cfg.xm l. Have a look at the example configuration and change the settings if you like to have more verbose output. By default, only Hibernate startup message are shown on stdout. チュートリアルのインフラは完全です。 Hibernate を使って実際の作業をする準備が整いました。 1.2.6. オブジェクトのロードと格納 ついにオブジェクトのロードと格納に Hibernate を使うことができます。 m ain() メソッドを持つ EventManager クラスを書きます: 18 第1章 Hibernate の導入 package events; import org.hibernate.Session; import java.util.Date; import util.HibernateUtil; public class EventManager { public static void main(String[] args) { EventManager mgr = new EventManager(); if (args[0].equals("store")) { mgr.createAndStoreEvent("My Event", new Date()); } HibernateUtil.getSessionFactory().close(); } private void createAndStoreEvent(String title, Date theDate) { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); Event theEvent = new Event(); theEvent.setTitle(title); theEvent.setDate(theDate); session.save(theEvent); session.getTransaction().commit(); } } We create a new Event object, and hand it over to Hibernate. Hibernate now takes care of the SQL and executes INSERT s on the database. Let's have a look at the Session and T ransaction-handling code before we run this. A Session is a single unit of work. For now we'll keep things simple and assume a one-to-one granularity between a Hibernate Session and a database transaction. T o shield our code from the actual underlying transaction system (in this case plain JDBC, but it could also run with JT A) we use the T ransaction API that is available on the Hibernate Session. What does sessionFactory.getCurrentSession() do? First, you can call it as many times and anywhere you like, once you get hold of your SessionFactory (easy thanks to HibernateUtil). T he getCurrentSession() method always returns the "current" unit of work. Remember that we switched the configuration option for this mechanism to "thread" in hibernate.cfg.xm l? Hence, the current unit of work is bound to the current Java thread that executes our application. However, this is not the full picture, you also have to consider scope, when a unit of work begins and when it ends. A Session begins when it is first needed, when the first call to getCurrentSession() is made. It is then bound by Hibernate to the current thread. When the transaction ends, either through commit or rollback, Hibernate automatically unbinds the Session from the thread and closes it for you. If you call getCurrentSession() again, you get a new Session and can start a new unit of work. T his threadbound programming model is the most popular way of using Hibernate, as it allows flexible layering of 19 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide your code (transaction demarcation code can be separated from data access code, we'll do this later in this tutorial). 作業単位 (Unit of Work) の範囲に関して、 Hibernate の Session は1つまたはいくつかのデータベースオ ペレーションを実行するために使用されるべきでしょうか?上記の例は、1つのオペレーションで1つの Session を使用します。これは純粋な偶然で、例はその他のアプローチを示すほど込み入っていませ ん。 Hibernate の Session の範囲は柔軟ですが、 全ての データベースオペレーションのために新しい Hibernate Session を使用するようにアプリケーションをデザインするべきではありません。従って、も しそれを以下の (普通の) 例で何度か見たとしても、アンチパターンである オペレーション毎の Session を考慮してください。実際の (ウェブ) アプリケーションは、このチュートリアルで後に見ることができま す。 Have a look at 11章トランザクションと並行性 for more information about transaction handling and demarcation. We also skipped any error handling and rollback in the previous example. この最初のルーチンを実行するには、 Ant のビルドファイルに呼び出し可能なターゲットを追加しなけれ ばなりません: <target name="run" depends="compile"> <java fork="true" classname="events.EventManager" classpathref="libraries"> <classpath path="${targetdir}"/> <arg value="${action}"/> </java> </target> action 引数の値は、ターゲットを呼ぶときにコマンドラインで設定します: C:\hibernateTutorial\>ant run -Daction=store コンパイルすると、 Hibernate がスタートし、設定によりますが、多くのログ出力があるはずです。その 最後には以下の行があるでしょう: [java] Hibernate: insert into EVENTS (EVENT_DATE, title, EVENT_ID) values (?, ?, ?) これは Hibernate が実行する INSERT で、クエスチョンマークは JDBC バインドパラメータを表してい ます。引数としてバインドされる値を見るため、あるいはログの冗長性を減らすためには、 log4 j.properties をチェックしてください。 Now we'd like to list stored events as well, so we add an option to the main method: if (args[0].equals("store")) { mgr.createAndStoreEvent("My Event", new Date()); } else if (args[0].equals("list")) { List events = mgr.listEvents(); for (int i = 0; i < events.size(); i++) { Event theEvent = (Event) events.get(i); System.out.println("Event: " + theEvent.getTitle() + " Time: " + theEvent.getDate()); } } 新しい listEvents()メソッド も追加します。 20 第1章 Hibernate の導入 private List listEvents() { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); List result = session.createQuery("from Event").list(); session.getTransaction().commit(); return result; } ここですることは、データベースから存在するすべての Event オブジェクトをロードする HQL (Hibernate Query Language) クエリを使うことです。 Hibernate は適切な SQL を生成し、それをデータ ベースに送り、そのデータを使って Event オブジェクトを生成します。当然 HQL でさらに複雑なクエリ を作成できます。 以下のステップで、すべての実行とテストを行います: hbm2ddl を通す前にデータベースのデータを作成し、データベーススキーマを生成するために、 ant run -Daction=store を実行してください。 Now disable hbm2ddl by commenting out the property in your hibernate.cfg.xm l file. Usually you only leave it turned on in continous unit testing, but another run of hbm2ddl would drop everything you have stored - the create configuration setting actually translates into "drop all tables from the schema, then re-create all tables, when the SessionFactory is build". 今 -Daction=list と指定して Ant を呼ぶと、これまで格納したイベントが見えるはずです。 store ア クションを数回以上呼ぶことも可能です。 注記:初めて Hibernate に触れる人々の多くがここで失敗するため、 Table not found エラーメッセージ に関する質問を定期的に見かけます。しかし上記のステップに従えば、 hbm2ddl が最初に実行されたとき にデータベーススキーマを作成し、その後の実行においてもこのスキーマを使用するので、問題は起こら ないでしょう。マッピングやデータベーススキーマを変更したときは、もう一度 hbm2ddl を有効にしてく ださい。 1.3. パ ー ト 2 - 関 連 の マ ッ ピ ン グ We mapped a persistent entity class to a table. Let's build on this and add some class associations. First we'll add people to our application, and store a list of events they participate in. 1.3.1. Person クラスのマッピング 最初の Person クラスは単純です: 21 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide package events; public class Person { private private private private Long id; int age; String firstname; String lastname; public Person() {} // Accessor methods for all properties, private setter for 'id' } Create a new mapping file called Person.hbm .xm l (don't forget the DT D reference at the top): <hibernate-mapping> <class name="events.Person" table="PERSON"> <id name="id" column="PERSON_ID"> <generator class="native"/> </id> <property name="age"/> <property name="firstname"/> <property name="lastname"/> </class> </hibernate-mapping> Finally, add the new mapping to Hibernate's configuration: <mapping resource="events/Event.hbm.xml"/> <mapping resource="events/Person.hbm.xml"/> We'll now create an association between these two entities. Obviously, persons can participate in events, and events have participants. T he design questions we have to deal with are: directionality, multiplicity, and collection behavior. 1.3.2. 単方向 Set ベース関連 We'll add a collection of events to the Person class. T hat way we can easily navigate to the events for a particular person, without executing an explicit query - by calling aPerson.getEvents(). We use a Java collection, a Set, because the collection will not contain duplicate elements and the ordering is not relevant for us. We need a unidirectional, many-valued associations, implemented with a Set. Let's write the code for this in the Java classes and then map it: 22 第1章 Hibernate の導入 public class Person { private Set events = new HashSet(); public Set getEvents() { return events; } public void setEvents(Set events) { this.events = events; } } Before we map this association, think about the other side. Clearly, we could just keep this unidirectional. Or, we could create another collection on the Event, if we want to be able to navigate it bi-directional, i.e. anEvent.getParticipants(). T his is not necessary, from a functional perspective. You could always execute an explicit query to retrieve the participants for a particular event. T his is a design choice left to you, but what is clear from this discussion is the multiplicity of the association: "many" valued on both sides, we call this a many-to-many association. Hence, we use Hibernate's many-tomany mapping: <class name="events.Person" table="PERSON"> <id name="id" column="PERSON_ID"> <generator class="native"/> </id> <property name="age"/> <property name="firstname"/> <property name="lastname"/> <set name="events" table="PERSON_EVENT"> <key column="PERSON_ID"/> <many-to-many column="EVENT_ID" class="events.Event"/> </set> </class> Hibernate supports all kinds of collection mappings, a <set> being most common. For a many-to-many association (or n:m entity relationship), an association table is needed. Each row in this table represents a link between a person and an event. T he table name is configured with the table attribute of the set element. T he identifier column name in the association, for the person's side, is defined with the <key> element, the column name for the event's side with the colum n attribute of the <m any-to-m any>. You also have to tell Hibernate the class of the objects in your collection (correct: the class on the other side of the collection of references). そのためこのマッピングのデータベーススキーマは以下のようになります: 23 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide _____________ __________________ | | | | _____________ | EVENTS | | PERSON_EVENT | | | |_____________| |__________________| | PERSON | | | | | |_____________| | *EVENT_ID | <--> | *EVENT_ID | | | | EVENT_DATE | | *PERSON_ID | <--> | *PERSON_ID | | TITLE | |__________________| | AGE | |_____________| | FIRSTNAME | | LASTNAME | |_____________| 1.3.3. 関連を働かせる Let's bring some people and events together in a new method in EventManager: private void addPersonToEvent(Long personId, Long eventId) { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); Person aPerson = (Person) session.load(Person.class, personId); Event anEvent = (Event) session.load(Event.class, eventId); aPerson.getEvents().add(anEvent); session.getTransaction().commit(); } Person と Event をロードした後、普通のコレクションメソッドを使って単純にそのコレクションを修 正してください。ご覧のとおり update() や save() の明示的な呼び出しはありません。 Hibernate は、修正されたことにより更新する必要のあるコレクションを自動的に検知します。これは 自動ダーティ チェック と呼ばれ、オブジェクトの名前や date プロパティを修正することで試すことも可能です。それ らが 永続 状態にある限り、つまり特定の Hibernate Session にバインドされている限り (例えば作業 単位 (Unit of Work) の中で単にロードまたはセーブされた)、 Hibernate はどんな変更もモニターし、遅 延書き込み (write-behind) で SQL を実行します。通常、作業単位 (Unit of Work) の最後にだけ行われる データベースとメモリの状態を同期させる処理は、 フラッシュ と呼ばれます。このコードでは、作業単 位 (Unit of Work) はデータベーストランザクションのコミット(もしくはロールバック)で終了します。 これは、 CurrentSessionContext クラスに対して thread を設定したためです。 異なる作業単位 (Unit of Work) で人々とイベントをロードすることも当然できます。そうでなければ、永 続状態にないとき(以前に永続であったなら、この状態を 分離(detached) と呼びます)、 Session の外部でオブジェクトを修正します。分離されるときにはコレクションを変更することも可能です: 24 第1章 Hibernate の導入 private void addPersonToEvent(Long personId, Long eventId) { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); Person aPerson = (Person) session .createQuery("select p from Person p left join fetch p.events where p.id = :pid") .setParameter("pid", personId) .uniqueResult(); // Eager fetch the collection so we can use it detached Event anEvent = (Event) session.load(Event.class, eventId); session.getTransaction().commit(); // End of first unit of work aPerson.getEvents().add(anEvent); // aPerson (and its collection) is detached // Begin second unit of work Session session2 = HibernateUtil.getSessionFactory().getCurrentSession(); session2.beginTransaction(); session2.update(aPerson); // Reattachment of aPerson session2.getTransaction().commit(); } update の呼び出しは分離オブジェクトを再び永続化します。これは、新しい作業単位 (Unit of Work) に バインドすると言えるでしょう。そのため分離の間に加えられたどのような修正もデータベースにセーブ できます。エンティティオブジェクトのコレクションへの修正(追加·削除)も同様にセーブできます。 Well, this is not much use in our current situation, but it's an important concept you can design into your own application. For now, complete this exercise by adding a new action to the EventManager's main method and call it from the command line. If you need the identifiers of a person and an event - the save() method returns it (you might have to modify some of the previous methods to return that identifier): else if (args[0].equals("addpersontoevent")) { Long eventId = mgr.createAndStoreEvent("My Event", new Date()); Long personId = mgr.createAndStorePerson("Foo", "Bar"); mgr.addPersonToEvent(personId, eventId); System.out.println("Added person " + personId + " to event " + eventId); } T his was an example of an association between two equally important classes, two entities. As mentioned earlier, there are other classes and types in a typical model, usually "less important". Some you have already seen, like an int or a String. We call these classes value types, and their instances depend on a particular entity. Instances of these types don't have their own identity, nor are they shared between entities (two persons don't reference the same firstnam e object, even if they have the same first name). Of course, value types can not only be found in the JDK (in fact, in a Hibernate application all JDK classes are considered value types), but you can also write dependent classes yourself, Address or MonetaryAm ount, for example. 値型のコレクションを設計することもできます。これは他のエンティティへの参照のコレクションとは概 25 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 念的に非常に異なりますが、 Java ではほとんど同じように見えます。 1.3.4. 値のコレクション 値型オブジェクトのコレクションを Person エンティティへ追加します。 E メールアドレスを格納した いのですが、 String 型を使っているので、コレクションは再び Set です: private Set emailAddresses = new HashSet(); public Set getEmailAddresses() { return emailAddresses; } public void setEmailAddresses(Set emailAddresses) { this.emailAddresses = emailAddresses; } この Set のマッピングです: <set name="emailAddresses" table="PERSON_EMAIL_ADDR"> <key column="PERSON_ID"/> <element type="string" column="EMAIL_ADDR"/> </set> T he difference compared with the earlier mapping is the elem ent part, which tells Hibernate that the collection does not contain references to another entity, but a collection of elements of type String (the lowercase name tells you it's a Hibernate mapping type/converter). Once again, the table attribute of the set element determines the table name for the collection. T he key element defines the foreign-key column name in the collection table. T he colum n attribute in the elem ent element defines the column name where the String values will actually be stored. 更新したスキーマを見てください: _____________ __________________ | | | | _____________ | EVENTS | | PERSON_EVENT | | | ___________________ |_____________| |__________________| | PERSON | | | | | | |_____________| PERSON_EMAIL_ADDR | | *EVENT_ID | <--> | *EVENT_ID | | | |___________________| | EVENT_DATE | | *PERSON_ID | <--> | *PERSON_ID | <--> *PERSON_ID | | TITLE | |__________________| | AGE | *EMAIL_ADDR | |_____________| | FIRSTNAME | |___________________| | LASTNAME | |_____________| | | | | You can see that the primary key of the collection table is in fact a composite key, using both columns. T his also implies that there can't be duplicate email addresses per person, which is exactly the semantics we need for a set in Java. 26 第1章 Hibernate の導入 You can now try and add elements to this collection, just like we did before by linking persons and events. It's the same code in Java: private void addEmailToPerson(Long personId, String emailAddress) { Session session = HibernateUtil.getSessionFactory().getCurrentSession(); session.beginTransaction(); Person aPerson = (Person) session.load(Person.class, personId); // The getEmailAddresses() might trigger a lazy load of the collection aPerson.getEmailAddresses().add(emailAddress); session.getTransaction().commit(); } T his time we didnt' use a fetch query to initialize the collection. Hence, the call to its getter method will trigger an additional select to initialize it, so we can add an element to it. Monitor the SQL log and try to optimize this with an eager fetch. 1.3.5. 双方向関連 Next we are going to map a bi-directional association - making the association between person and event work from both sides in Java. Of course, the database schema doesn't change, we still have many-to-many multiplicity. A relational database is more flexible than a network programming language, so it doesn't need anything like a navigation direction - data can be viewed and retrieved in any possible way. まず Event イベントクラスに参加者のコレクションを追加します: private Set participants = new HashSet(); public Set getParticipants() { return participants; } public void setParticipants(Set participants) { this.participants = participants; } それでは Event.hbm .xm l で関連のこちら側をマッピングしてください。 <set name="participants" table="PERSON_EVENT" inverse="true"> <key column="EVENT_ID"/> <many-to-many column="PERSON_ID" class="events.Person"/> </set> As you see, these are normal set mappings in both mapping documents. Notice that the column names in key and m any-to-m any are swapped in both mapping documents. T he most important addition here is the inverse="true" attribute in the set element of the Event's collection mapping. この指定の意味は、2つの間のエンティティ間のリンクについての情報を探す必要があるとき、 Hibernate は反対側のエンティティ、つまり Person クラスから探すということです。一度2つのエンティティ間の 双方向リンクがどのように作成されるかがわかれば、これを理解することはとても簡単です。 1.3.6. 双方向リンクの動作 27 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide First, keep in mind that Hibernate does not affect normal Java semantics. How did we create a link between a Person and an Event in the unidirectional example? We added an instance of Event to the collection of event references, of an instance of Person. So, obviously, if we want to make this link working bi-directional, we have to do the same on the other side - adding a Person reference to the collection in an Event. T his "setting the link on both sides" is absolutely necessary and you should never forget doing it. 多くの開発者は慎重にプログラムするので、エンティティの両側に正しく関連を設定するリンク管理メ ソッドを作成します。例えば Person では以下のようになります。: protected Set getEvents() { return events; } protected void setEvents(Set events) { this.events = events; } public void addToEvent(Event event) { this.getEvents().add(event); event.getParticipants().add(this); } public void removeFromEvent(Event event) { this.getEvents().remove(event); event.getParticipants().remove(this); } コレクションのゲットとセットメソッドが現在 protected になっていることに注意してください。これは 同じパッケージのクラスやサブクラスのメソッドは依然アクセスが可能ですが、 (ほとんど) そのパッ ケージ外のどのクラスでも直接そのコレクションを台無しにすることを防ぎます。おそらく反対側のコレ クションにも同じことをした方がいいでしょう。 What about the inverse mapping attribute? For you, and for Java, a bi-directional link is simply a matter of setting the references on both sides correctly. Hibernate however doesn't have enough information to correctly arrange SQL INSERT and UPDAT E statements (to avoid constraint violations), and needs some help to handle bi-directional associations properly. Making one side of the association inverse tells Hibernate to basically ignore it, to consider it a mirror of the other side. T hat's all that is necessary for Hibernate to work out all of the issues when transformation a directional navigation model to a SQL database schema. T he rules you have to remember are straightforward: All bi-directional associations need one side as inverse. In a one-to-many association it has to be the many-side, in many-to-many association you can pick either side, there is no difference. Let's turn this into a small web application. 1.4. パ ー ト 3 - EventManager Web ア プ リ ケ ー シ ョ ン Hibernate の Web アプリケーションは、スタンドアローンのアプリケーションのように Session と T ransaction を使用します。しかしいくつかの一般的なパターンが役立ちます。ここで EventManagerServlet を作成します。このサーブレットは、データベースに格納した全てのイベント をリストにでき、さらに HT ML フォームから新しいイベントを入力できるものです。 1.4.1. 基本的な Servlet の記述 新しいクラスを、ソースディレクトリの events パッケージに作成してください。 28 第1章 Hibernate の導入 package events; // Imports public class EventManagerServlet extends HttpServlet { // Servlet code } Servlet は HT T P の GET リクエストのみを処理するので、 doGet() を実装します。 protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { SimpleDateFormat dateFormatter = new SimpleDateFormat("dd.MM.yyyy"); try { // Begin unit of work HibernateUtil.getSessionFactory() .getCurrentSession().beginTransaction(); // Process request and render page... // End unit of work HibernateUtil.getSessionFactory() .getCurrentSession().getTransaction().commit(); } catch (Exception ex) { HibernateUtil.getSessionFactory() .getCurrentSession().getTransaction().rollback(); throw new ServletException(ex); } } T he pattern we are applying here is called session-per-request. When a request hits the servlet, a new Hibernate Session is opened through the first call to getCurrentSession() on the SessionFactory. T hen a database transaction is started - all data access as to occur inside a transaction, no matter if data is read or written (we don't use the auto-commit mode in applications). 全てのデータベースオペレーションで新しい Hibernate Session を使用 しないでください 。全てのリク エストで機能する、1つの Hibernate Session を使用してください。自動的に現在の Java スレッドにバ インドされるので、 getCurrentSession() を使用してください。 Next, the possible actions of the request are processed and the response HT ML is rendered. We'll get to that part soon. Finally, the unit of work ends when processing and rendering is complete. If any problem occured during processing or rendering, an exception will be thrown and the database transaction rolled back. T his completes the session-per-request pattern. Instead of the transaction demarcation code in every servlet you could also write a servlet filter. See the Hibernate website and Wiki for more information about this pattern, called Open Session in View - you'll need it as soon as you consider rendering your view in JSP, not in a servlet. 1.4.2. 処理と描画 29 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide Let's implement the processing of the request and rendering of the page. // Write HTML header PrintWriter out = response.getWriter(); out.println("<html><head><title>Event Manager</title></head><body>"); // Handle actions if ( "store".equals(request.getParameter("action")) ) { String eventTitle = request.getParameter("eventTitle"); String eventDate = request.getParameter("eventDate"); if ( "".equals(eventTitle) || "".equals(eventDate) ) { out.println("<b><i>Please enter event title and date.</i></b>"); } else { createAndStoreEvent(eventTitle, dateFormatter.parse(eventDate)); out.println("<b><i>Added event.</i></b>"); } } // Print page printEventForm(out); listEvents(out, dateFormatter); // Write HTML footer out.println("</body></html>"); out.flush(); out.close(); Java と HT ML が混在するコーディングスタイルは、より複雑なアプリケーションには適していないで しょう (このチュートリアルでは、基本的な Hibernate のコンセプトを示しているだけであることを覚え ておいてください)。このコードは HT ML のヘッダーとフッターの記述です。このページには、イベント を入力する HT ML フォームと、データベースにある全てのイベントのリストが表示されます。最初のメ ソッドはごく単純な HT ML 出力です。 private void printEventForm(PrintWriter out) { out.println("<h2>Add new event:</h2>"); out.println("<form>"); out.println("Title: <input name='eventTitle' length='50'/><br/>"); out.println("Date (e.g. 24.12.2009): <input name='eventDate' length='10'/><br/>"); out.println("<input type='submit' name='action' value='store'/>"); out.println("</form>"); } listEvents() メソッドは、現在のスレッドに結びつく Hibernate の Session を使用して、クエリを実 行します。 30 第1章 Hibernate の導入 private void listEvents(PrintWriter out, SimpleDateFormat dateFormatter) { List result = HibernateUtil.getSessionFactory() .getCurrentSession().createCriteria(Event.class).list(); if (result.size() > 0) { out.println("<h2>Events in database:</h2>"); out.println("<table border='1'>"); out.println("<tr>"); out.println("<th>Event title</th>"); out.println("<th>Event date</th>"); out.println("</tr>"); for (Iterator it = result.iterator(); it.hasNext();) { Event event = (Event) it.next(); out.println("<tr>"); out.println("<td>" + event.getTitle() + "</td>"); out.println("<td>" + dateFormatter.format(event.getDate()) + "</td>"); out.println("</tr>"); } out.println("</table>"); } } 最後に、 store アクションが createAndStoreEvent() メソッドを呼び出します。このメソッドでも 現在のスレッドの Session を利用します。 protected void createAndStoreEvent(String title, Date theDate) { Event theEvent = new Event(); theEvent.setTitle(title); theEvent.setDate(theDate); HibernateUtil.getSessionFactory() .getCurrentSession().save(theEvent); } T hat's it, the servlet is complete. A request to the servlet will be processed in a single Session and T ransaction. As earlier in the standalone application, Hibernate can automatically bind these ojects to the current thread of execution. T his gives you the freedom to layer your code and access the SessionFactory in any way you like. Usually you'd use a more sophisticated design and move the data access code into data access objects (the DAO pattern). See the Hibernate Wiki for more examples. 1.4.3. デプロイとテスト このアプリケーションのデプロイのために、 Web アーカイブ(WAR)を作成してください。以下の Ant ターゲットを build.xm l に加えてください。 <target name="war" depends="compile"> <war destfile="hibernate-tutorial.war" webxml="web.xml"> <lib dir="${librarydir}"> <exclude name="jsdk*.jar"/> </lib> <classes dir="${targetdir}"/> </war> </target> このターゲットは hibernate-tutorial.war というファイルをプロジェクトディレクトリに作成しま 31 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide す。このファイルはすべてのライブラリと web.xm l 記述子を含んでおり、プロジェクトのベースディレ クトリに置かれることを期待されます: <?xml version="1.0" encoding="UTF-8"?> <web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd"> <servlet> <servlet-name>Event Manager</servlet-name> <servlet-class>events.EventManagerServlet</servlet-class> </servlet> <servlet-mapping> <servlet-name>Event Manager</servlet-name> <url-pattern>/eventmanager</url-pattern> </servlet-mapping> </web-app> Before you compile and deploy the web application, note that an additional library is required: jsdk.jar. T his is the Java servlet development kit, if you don't have this library already, get it from the Sun website and copy it to your library directory. However, it will be only used for compliation and excluded from the WAR package. T o build and deploy call ant war in your project directory and copy the hibernate-tutorial.war file into your T omcat webapp directory. If you don't have T omcat installed, download it and follow the installation instructions. You don't have to change any T omcat configuration to deploy this application though. 一度デプロイして T omcat を起動すれば、 http://localhost:8080/hibernatetutorial/eventm anager でアプリケーションへのアクセスが可能です。最初のリクエストが作成した サーブレットに渡ったときに、 T omcat のログで Hibernate の初期化処理を確認してください ( HibernateUtil 内の静的初期化ブロックが呼ばれています)。また、例外が発生したなら詳細を確認 してください。 1.5. 要 約 このチュートリアルでは、簡単なスタンドアローンの Hibernate アプリケーションと小規模の Web アプ リケーションを書くための基本を紹介しました。 If you already feel confident with Hibernate, continue browsing through the reference documentation table of contents for topics you find interesting - most asked are transactional processing (11章トランザ クションと並行性), fetch performance (19章パフォーマンスの改善), or the usage of the API (10章オブ ジェクトを扱う) and the query features (「クエリ」). Don't forget to check the Hibernate website for more (specialized) tutorials. 32 第2章 アーキテクチャ 第 2章 アーキテクチャ 2.1. 概 観 Hibernate アーキテクチャの(非常に)高いレベルからのビュー: 図 2.1 High Level view of the Hibernate Architecture この図は Hibernate が、アプリケーションに対して永続化サービス (と永続オブジェクト)を提供するた めに、データベースと設定データを使うことを示しています。 We would like to show a more detailed view of the runtime architecture. Unfortunately, Hibernate is flexible and supports several approaches. We will show the two extremes. T he "lite" architecture has the application provide its own JDBC connections and manage its own transactions. T his approach uses a minimal subset of Hibernate's APIs: 33 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 図 2.2 T he Lite Architecture T he "full cream" architecture abstracts the application away from the underlying JDBC/JT A APIs and lets Hibernate take care of the details. 以下は、図に含まれるオブジェクトの定義です: SessionFactory (org.hibernate.SessionFactory) 1つのデータベースに対するコンパイルされたマッピングのスレッドセーフな(更新不能の) キャッシュ。 Session のファクトリであり、 ConnectionProvider のクライアント。オプ ションとして、プロセスまたはクラスタレベルにおいて、トランザクション間で再利用可能な データの(二次)キャッシュを持ちます。 Session (org.hibernate.Session) アプリケーションと永続ストアとの対話を表す、シングルスレッドで短命のオブジェクト。 JDBC コネクションをラップします。 T ransaction のファクトリです。永続オブジェクトの必 須の(一次)キャッシュを保持します。このキャッシュはオブジェクトグラフをナビゲーション する時や、識別子でオブジェクトを検索する時に使われます。 Persistent objects と Collections 永続化状態とビジネス機能を持つ、短命でシングルスレッドのオブジェクト。これは通常の JavaBeans/POJO のこともありますが、特徴的なことは、その時点での(ただ1つの) Session と関連していることです。 Session がクローズされるとすぐに、それらは切り離さ れて他のアプリケーション層から自由に使うことができます(例えばデータトランスファオブ ジェクトとして、プレゼンテーション層から、またはプレゼンテーション層へ直接使用できま す)。 T ransient と detached な objects と Collections 現時点では Session と関連していない、永続クラスのインスタンス。すでにアプリケーション 側でインスタンス化されていて、まだ永続化されていないか、クローズされた Session でイン スタンス化されたかのどちらかです。 34 第2章 アーキテクチャ T ransaction (org.hibernate.T ransaction) (オプション) 原子性を持つ作業単位 (Unit of Work) を指定するために、アプリケーションが使用 する、シングルスレッドで短命なオブジェクト。下に位置する JDBC 、 JT A 、 CORBA トラン ザクションからアプリケーションを抽象化します。 Session は、時にはいくつかの T ransaction をまたがるかもしれません。しかし、下の層の API を使うにせよ、 T ransaction を使うにせよ、トランザクション境界を設定することは、決してオプションでは ありません。 ConnectionProvider (org.hibernate.connection.ConnectionProvider) (オプション) JDBC コネクション(とそのプール)のファクトリ。下の層に位置する Datasource や DriverManager からアプリケーションを抽象化します。アプリケーションに は公開されませんが、開発者が継承または実装することは可能です。 T ransactionFactory (org.hibernate.T ransactionFactory) (オプション) T ransaction インスタンスのファクトリ。アプリケーションには公開されません が、開発者が継承または実装することは可能です。 Extension Interfaces Hibernate は、永続層の振る舞いをカスタマイズするために、多くのオプション拡張インタ フェースを用意しています。詳細は API ドキュメントを参照してください。 Given a "lite" architecture, the application bypasses the T ransaction/T ransactionFactory and/or ConnectionProvider APIs to talk to JT A or JDBC directly. 2.2. イ ン ス タ ン ス の 状 態 永続クラスのインスタンスは、次の3つの異なる状態のどれかになります。それは、 永続コンテキスト によって決まります。 Hibernate の Session オブジェクトが、永続コンテキストになります: transient この状態のインスタンスは、現在もそして過去においても、永続コンテキストに関連づいていま せん。また、永続 ID (主キーの値)を 持っていません。 persistent この状態のインスタンスは、その時点で永続コンテキストに関連づいています。また、永続 ID (主キーの値)を持ち、たいていはデータベースに対応する行を持っているでしょう。特定の永 続コンテキストのなかでは、永続 ID が Java の ID (オブジェクトのメモリ上の位置)と同じで あることを Hibernate が 保証 します。 detached この状態のインスタンスは、かつて永続コンテキストに関連づけられたが、そのコンテキストが クローズされたか、あるいは、他のプロセスにそのインスタンスがシリアライズされたかです。 このインスタンスは、永続 ID を持ち、たいていはデータベースに対応する行を持っているで しょう。分離インスタンスに対しては、永続 ID と Java の ID との関連は、 Hibernate が保証し ません。 35 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 2.3. JMX と の 統 合 JMX は Java コンポーネント管理の J2EE 標準です。 JMX 標準サービスを通して、 Hibernate は管理され ます。ディストリビューションの中に org.hibernate.jm x.HibernateService という MBean 実 装を用意しています。 JBoss アプリケーションサーバー上に Hibernate を JMX サービスとしてデプロイする方法の例として は、 JBoss ユーザーガイドを参照してください。 JBoss アプリケーションサーバーにおいて、 JMX を 使ってデプロイすると、次のメリットが得られます: Session Management: T he Hibernate Session's lifecycle can be automatically bound to the scope of a JT A transaction. T his means you no longer have to manually open and close the Session, this becomes the job of a JBoss EJB interceptor. You also don't have to worry about transaction demarcation in your code anymore (unless you'd like to write a portable persistence layer of course, use the optional Hibernate T ransaction API for this). You call the HibernateContext to access a Session. HAR デプロイ: 通常、( EAR または SAR ファイルにある) JBoss サービスデプロイメントディスクリ プタを使って、 Hibernate JMX サービスをデプロイします。それは、 Hibernate の SessionFactory の全ての一般的な設定オプションをサポートします。しかし依然としてデプロイ メントディスクリプタのなかにすべてのマッピングファイルの名前を挙げる必要があります。もし、 オプションの HAR デプロイメントを使うことを決めたなら、 JBoss は自動的に HAR ファイルのなか の全てのマッピングファイルを検出します。 これらのオプションについての詳細な情報は、 JBoss アプリケーションサーバーユーザーガイドを参考に してください。 Another feature available as a JMX service are runtime Hibernate statistics. See 「Hibernate 統計」. 2.4. JCA サ ポ ー ト Hibernate は JCA コネクタとしても設定できます。詳細については、 Web サイトを見てください。 Hibernate JCA サポートは、今のところ実験段階として考えられていることに注意してください。 2.5. コ ン テ キ ス ト 上 の セ ッ シ ョ ン Most applications using Hibernate need some form of "contextual" sessions, where a given session is in effect throughout the scope of a given context. However, across applications the definition of what constitutes a context is typically different; and different contexts define different scopes to the notion of current. Applications using Hibernate prior to version 3.0 tended to utilize either home-grown T hreadLocal-based contextual sessions, helper classes such as HibernateUtil, or utilized thirdparty frameworks (such as Spring or Pico) which provided proxy/interception-based contextual sessions. バージョン 3.0.1 から、 Hibernate には SessionFactory.getCurrentSession() メソッドが加わり ました。これは、 JT A トランザクションの使用を前提にしています。 JT A トランザクションは、現在の セッションのスコープとコンテキストの両方を定義します。 Hibernate チームは、次のことを主張しま す。巨大なスタンドアロンの JT A T ransactionManager 実装が成熟したら、 J2EE コンテナ上にデ プロイされるかどうかにかかわらず、ほとんどの(すべてとは言わないが)アプリケーションが、 JT A ト ランザクション管理を使用すべきであると。この考えに基づくと、 JT A ベースの「コンテキスト上のセッ ション」を使うしかないでしょう。 しかし、バージョン 3.1 からは、 SessionFactory.getCurrentSession() の後の処理が、プラガ ブルになりました。これを受けて、現在のセッションを定義するスコープとコンテキストのプラガビリ 36 第2章 アーキテクチャ ティを可能にするために、新しい拡張インタフェース ( org.hibernate.context.CurrentSessionContext ) と新しい構成パラメータ ( hibernate.current_session_context_class ) が追加されました。 org.hibernate.context.CurrentSessionContext インタフェースの規約についての詳細な内容 は Javadoc を参照してください。それには、 currentSession() という1つのメソッドが定義されてお り、その実装は、現在の「コンテキスト上のセッション」を追跡することに責任を持ちます。そのまま使 えるように、 Hibernate はこのインタフェースの実装を2つ提供しています。 org.hibernate.context.JT ASessionContext - JT A トランザクションによって、現在のセッ ションが追跡され、スコープを決められます。この処理は、古い JT A だけのアプローチとまったく同 じです。詳細は Javadoc を参照してください。 org.hibernate.context.T hreadLocalSessionContext - スレッドの実行によって、現在の セッションが追跡されます。詳細は Javadoc を参照してください。 org.hibernate.context.ManagedSessionContext - スレッドの実行によって、現在のセッ ションが追跡されます。しかし、このクラスの static メソッドで Session インスタンスをバインド/ アンバインドする責任はあなたにあります。これは決して Session をオープン、フラッシュ、ク ローズしません。 T he first two implementations provide a "one session - one database transaction" programming model, also known and used as session-per-request. T he beginning and end of a Hibernate session is defined by the duration of a database transaction. If you use programatic transaction demarcation in plain JSE without JT A, you are adviced to use the Hibernate T ransaction API to hide the underlying transaction system from your code. If you use JT A, use the JT A interfaces to demarcate transactions. If you execute in an EJB container that supports CMT , transaction boundaries are defined declaratively and you don't need any transaction or session demarcation operations in your code. Refer to 11章トランザクションと 並行性 for more information and code examples. T he hibernate.current_session_context_class configuration parameter defines which org.hibernate.context.CurrentSessionContext implementation should be used. Note that for backwards compatibility, if this config param is not set but a org.hibernate.transaction.T ransactionManagerLookup is configured, Hibernate will use the org.hibernate.context.JT ASessionContext. T ypically, the value of this parameter would just name the implementation class to use; for the three out-of-the-box implementations, however, there are two corresponding short names, "jta", "thread", and "managed". 37 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 第 3章 設定 Hibernate は様々な環境で動作するようにデザインされているため、非常に多くの設定要素があります。 幸いなことに、 Hibernate は、公開されているパッケージの etc/ フォルダの hibernate.properties に、ほとんどの設定要素の適切なデフォルト値が記述されています。この hibernate.properties をクラスパスに設定し、設定要素をカスタマイズするだけです。 3.1. プ ロ グ ラ ム 上 の 設 定 An instance of org.hibernate.cfg.Configuration represents an entire set of mappings of an application's Java types to an SQL database. T he Configuration is used to build an (immutable) SessionFactory. T he mappings are compiled from various XML mapping files. 通常、 Configuration インスタンスは、特定の XML マッピングファイルによって直接初期化されま す。もし、マッピングファイルがクラスパスに設定されている場合、次のメソッドを使ってください。 addResource() : Configuration cfg = new Configuration() .addResource("Item.hbm.xml") .addResource("Bid.hbm.xml"); 代替案 (こちらのほうが良いときもあります) としてマッピングクラスを指定する方法もあります。 Hibernate に、マッピングファイルを 見つけさせてください: Configuration cfg = new Configuration() .addClass(org.hibernate.auction.Item.class) .addClass(org.hibernate.auction.Bid.class); Hibernate は、クラスパスにある以下のような名前のマッピングファイルを見つけます。 /org/hibernate/auction/Item .hbm .xm l 、 /org/hibernate/auction/Bid.hbm .xm l 。こ の方法だと、ハードコーディングされたファイル名を排除できます。 Configuration は、設定プロパティを指定することもできます: Configuration cfg = new Configuration() .addClass(org.hibernate.auction.Item.class) .addClass(org.hibernate.auction.Bid.class) .setProperty("hibernate.dialect", "org.hibernate.dialect.MySQLInnoDBDialect") .setProperty("hibernate.connection.datasource", "java:comp/env/jdbc/test") .setProperty("hibernate.order_updates", "true"); Hibernate に設定プロパティを渡す方法は1つではありません。さまざまなオプションを用意しています: 1. java.util.Properties インスタンスを Configuration.setProperties() に渡します。 2. hibernate.properties をクラスパスのルートディレクトリに置きます。 3. System プロパティが java -Dproperty=value を使うように設定します。 4. Include <property> elements in hibernate.cfg.xm l (discussed later). 今すぐ始めたいのなら、 hibernate.properties を使うのが一番の近道です。 Configuration は、起動時にだけあるオブジェクトであり、一度 SessionFactory を生成した後 は、破棄されることを意図しています。 38 第3章 設定 3.2. SessionFactory を 取 得 す る Configuration がすべてのマッピング情報を解析したら、アプリケーションは、 Session インスタン スのためにファクトリを取得しなければなりません。この SessionFactory は、 Hibernate を使用するす べてのスレッドで共有されるべきです: SessionFactory sessions = cfg.buildSessionFactory(); Hibernate は、アプリケーションが SessionFactory を複数生成することを可能にします。これは、複 数のデータベースを使用する場合に便利です。 3.3. JDBC コ ネ ク シ ョ ン 通常、開発者は SessionFactory を生成し、 SessionFactory で JDBC コネクションをプーリングした いと考えます。そのアプローチを採用する場合、単純に Session をオープンしてください: Session session = sessions.openSession(); // open a new Session これだけで、プーリングした JDBC コネクションを使って目的のデータベースにアクセスすることができ ます。 そのためには、 JDBC コネクションのプロパティを Hibernate に設定する必要があります。すべての Hibernate プロパティ名とセマンティクスは org.hibernate.cfg.Environm ent クラスに定義され ています。この設定は JDBC コネクション設定の中で一番重要なものです。 もし、以下のプロパティを設定すると、 Hibernate はコネクションを取得するために(プールも) java.sql.DriverManager を使います: 表 3.1 Hibernate JDBC プロパティ プロパティ名 意味 hibernate.connection.driver_class jdbc driver class hibernate.connection.url jdbc URL hibernate.connection.usernam e database user hibernate.connection.password database user password hibernate.connection.pool_size maximum number of pooled connections Hibernate's own connection pooling algorithm is however quite rudimentary. It is intended to help you get started and is not intended for use in a production system or even for performance testing. You should use a third party pool for best performance and stability. Just replace the hibernate.connection.pool_size property with connection pool specific settings. T his will turn off Hibernate's internal pool. For example, you might like to use C3P0. C3P0 is an open source JDBC connection pool distributed along with Hibernate in the lib directory. Hibernate will use its C3P0ConnectionProvider for connection pooling if you set hibernate.c3p0.* properties. If you'd like to use Proxool refer to the packaged hibernate.properties and the Hibernate web site for more information. C3P0 用の hibernate.properties ファイルを例として示します: 39 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide hibernate.connection.driver_class = org.postgresql.Driver hibernate.connection.url = jdbc:postgresql://localhost/mydatabase hibernate.connection.username = myuser hibernate.connection.password = secret hibernate.c3p0.min_size=5 hibernate.c3p0.max_size=20 hibernate.c3p0.timeout=1800 hibernate.c3p0.max_statements=50 hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect For use inside an application server, you should almost always configure Hibernate to obtain connections from an application server Datasource registered in JNDI. You'll need to set at least one of the following properties: 表 3.2 Hibernate データソースプロパティ プロパティ名 意味 hibernate.connection.datasource datasource JNDI name hibernate.jndi.url JNDI プロバイダの URL (オプション) hibernate.jndi.class JNDI のクラス InitialContextFactory (オプ ション) hibernate.connection.usernam e データベースユーザ (オプション) hibernate.connection.password データベースユーザのパスワード (オプション) Here's an example hibernate.properties file for an application server provided JNDI datasource: hibernate.connection.datasource = java:/comp/env/jdbc/test hibernate.transaction.factory_class = \ org.hibernate.transaction.JTATransactionFactory hibernate.transaction.manager_lookup_class = \ org.hibernate.transaction.JBossTransactionManagerLookup hibernate.dialect = org.hibernate.dialect.PostgreSQLDialect JNDI データソースから取得した JDBC コネクションは、アプリケーションサーバーのコンテナ管理トラ ンザクションに自動的に参加します。 Arbitrary connection properties may be given by prepending "hibernate.connnection" to the property name. For example, you may specify a charSet using hibernate.connection.charSet. JDBC コネクションを取得する戦略を持つ独自のプラグインを定義する場合は、 org.hibernate.connection.ConnectionProvider インターフェースを実装してください。そし て、実装クラスを hibernate.connection.provider_class に設定してください。 3.4. オ プ シ ョ ン 設 定 プ ロ パ テ ィ これらのプロパティはランタイムに Hibernate の挙動を制御するものです。これらのプロパティはすべて 妥当なデフォルト値があり、任意で設定します。 Warning: some of these properties are "system-level" only. System-level properties can be set only via java -Dproperty=value or hibernate.properties. T hey may not be set by the other techniques described above. 40 第3章 設定 表 3.3 Hibernate 設定プロパティ プロパティ名 意味 hibernate.dialect Hibernate のクラス名 Dialect が入ります。こ れはリレーショナルデータベースごとに最適化さ れた SQL を生成します。 例 full.classnam e.of.Dialect hibernate.show_sql 発行されたすべての SQL をコンソールに出力しま す。これはログカテゴリの org.hibernate.SQL に debug を設定する方法 の代替手段です。 true | false hibernate.form at_sql ログとコンソールの SQL を整形して表示します。 true | false hibernate.default_schem a 生成される SQL 文のテーブルに設定するスキー マ/テーブルスペースです。 例 .SCHEMA_NAME hibernate.default_catalog 生成される SQL 文のテーブルに設定するカタログ です。 例 CAT ALOG_NAME hibernate.session_factory_nam e SessionFactory は生成後、この名前で JNDI に自動的に登録されます。 例 jndi/com posite/nam e hibernate.m ax_fetch_depth Set a maximum "depth" for the outer join fetch tree for single-ended associations (one-to-one, many-to-one). A 0 disables default outer join fetching. 例: 推奨する値は 0 から 3 の間です。 hibernate.default_batch_fetch_size 関連フェッチのデフォルト Hibernate バッチサイ ズを指定します。 例: 推奨する値は 4 , 8 , 16 で す。 hibernate.default_entity_m ode SessionFactory からセッションをオープンし たときに使用するエンティティのデフォルトモー ドを設定します。 dynam ic-m ap, dom 4 j, pojo hibernate.order_updates 項目が更新されたときに、別の SQL で主キーを更 新することを強制します。この場合、同時実行可 能なシステムでは、まれにデッドロックが発生す る可能性があります。 true | false hibernate.generate_statistics 有効の場合、 Hibernate はパフォーマンスチュー ニングに有効な統計情報を収集します。 true | false hibernate.use_identifer_rollback 有効の場合、オブジェクトが削除されたときに識 別子プロパティをリセットし、デフォルト値にし たものを生成します。 true | false hibernate.use_sql_com m ents 有効の場合、 SQL 内にコメントを生成します。こ 41 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide れはデバックを容易にします。デフォルトの値は false です。 true | false 42 第3章 設定 表 3.4 Hibernate JDBC とコネクションプロパティ プロパティ名 意味 hibernate.jdbc.fetch_size 値が0でない場合、 JDBC フェッチサイズを決定 します ( Statem ent.setFetchSize() を呼び ます)。 hibernate.jdbc.batch_size 値が0でない場合、 Hibernate が JDBC2 バッチ更 新を使用します。 例: 推奨する値は 5 から 30 の間です。 hibernate.jdbc.batch_versioned_data もし JDBC ドライバが executeBatch() によっ て正確な行数を返す場合、このプロパティを true にしてください (通常はこのオプションを ON するのが安全です)。 Hibernate は、自動 バージョンデータのためバッチ DML を使います。 デフォルトの値は false です。 true | false hibernate.jdbc.factory_class カスタム Batcher を選びます。ほとんどのアプ リケーションに、この設定プロパティは必要あり ません。 例 classnam e.of.Batcher hibernate.jdbc.use_scrollable_result set Hibernate による JDBC2 のスクロール可能なリザ ルトセットの使用を有効にします。このプロパ ティは、ユーザーによって提供された JDBC コネ クションを使用している場合のみ必要で、そうで なければ Hibernate はコネクションメタデータを 使用します。 true | false hibernate.jdbc.use_stream s_for_binar y JDBC へ/から binary や serializable の書き 込み/読み込みストリームを使います (システムレ ベルのプロパティ)。 true | false hibernate.jdbc.use_get_generated_key s 挿入の後に自動生成された主キーを取得するため の JDBC3 PreparedStatem ent.getGeneratedKeys() の使用を有効にします。これは JDBC3+ ドライバ と JRE1.4+ を必要とし、もし Hibernate の識別子 ジェネレータに問題が発生するようなら false に 設定してください。デフォルトではコネクション メタデータを使いドライバの能力を決定します。 例 true|false hibernate.connection.provider_class JDBC コネクションを Hibernate に提供する独自 の ConnectionProvider のクラス名。 例 classnam e.of.ConnectionProvider hibernate.connection.isolation JDBC トランザクション分離レベルを設定しま す。妥当な値を調べるためには java.sql.Connection をチェックしてくださ 43 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide い。しかし使用するデータベースが、すべての分 離レベルをサポートしているとは限りません。 例 1, 2, 4 , 8 hibernate.connection.autocom m it プールされている JDBC コネクションの自動コ ミットを有効にする(非推奨)。 true | false hibernate.connection.release_m ode Hibernate がいつ JDBC コネクションをリリース するかを指定します。デフォルトではセッション が明示的にクローズまたは切断されてもコネク ションは保持します。アプリケーションサーバー の JT A データソースの場合、すべての JDBC コー ルの後、強制的にコネクションをリリースするた めに after_statem ent を使ってください。非 JT A コネクションの場合、各トランザクションが 終了したときに after_transaction を使い、 コネクションをリリースしてください。 auto に すると、 JT A や CMT トランザクションの場合、 after_statem ent でクローズし、 JDBC トラ ンザクションの場合、 after_transaction で クローズします。 例 auto (デフォルト) | on_close | after_transaction | after_statem ent Note that this setting only affects Session s returned from SessionFactory.openSession. For Session s obtained through SessionFactory.getCurrentSession, the CurrentSessionContext implementation configured for use controls the connection release mode for those Session s. See 「コンテ キスト上のセッション」. hibernate.connection.<propertyNam e> JDBC プロパティ propertyNam e を DriverManager.getConnection() に渡しま す。 hibernate.jndi.<propertyNam e> プロパティ propertyNam e を JNDI InitialContextFactory に渡します。 44 第3章 設定 表 3.5 Hibernate キャッシュプロパティ プロパティ名 意味 hibernate.cache.provider_class カスタム CacheProvider のクラス名です。 例 classnam e.of.CacheProvider hibernate.cache.use_m inim al_puts 書き込みを最小限にするために、二次キャッシュ の操作を最適化します。その代わりに、読み込み がより頻繁に発生するようになります。このセッ ティングはクラスタキャッシュで役に立ちます。 Hibernate3 ではクラスタキャッシュ実装用にデ フォルトでは有効になっています。 例 true|false hibernate.cache.use_query_cache 特定のクエリがキャッシュ可能な場合に、クエリ キャッシュを有効にします。 例 true|false hibernate.cache.use_second_level_cac he May be used to completely disable the second level cache, which is enabled by default for classes which specify a <cache> mapping. 例 true|false hibernate.cache.query_cache_factory カスタム QueryCache インターフェースのクラ ス名を指定します。デフォルトでは StandardQueryCache になります。 例 classnam e.of.QueryCache hibernate.cache.region_prefix 二次キャッシュの領域名の接頭辞です。 prefix hibernate.cache.use_structured_entri es 二次キャッシュに格納するデータを、人が理解し やすいフォーマットにします。 例 true|false 45 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 表 3.6 Hibernate トランザクションプロパティ プロパティ名 意味 hibernate.transaction.factory_class Hibernate T ransaction API と一緒に使われる T ransactionFactory のクラス名です。 (デ フォルトでは JDBCT ransactionFactory で す)。 例 classnam e.of.T ransactionFactory> jta.UserT ransaction アプリケーションサーバーから JT A UserT ransaction を取得するために JT AT ransactionFactory に使われる JNDI 名 です。 例 jndi/com posite/nam e hibernate.transaction.m anager_lookup _class T ransactionManagerLookup のクラス名で す。 JT A 環境において、 JVM レベルのキャッ シュを有効にするときか、 hilo ジェネレータが使 用されるときに必要です。 例 classnam e.of.T ransactionManagerLoo kup hibernate.transaction.flush_before_c om pletion If enabled, the session will be automatically flushed during the before completion phase of the transaction. Built-in and automatic session context management is preferred, see 「コンテキ スト上のセッション」. true | false hibernate.transaction.auto_close_ses sion If enabled, the session will be automatically closed during the after completion phase of the transaction. Built-in and utomatic session context management is preferred, see 「コンテキスト上 のセッション」. true | false 46 第3章 設定 表 3.7 その他のプロパティ プロパティ名 意味 hibernate.current_session_context_cl ass Supply a (custom) strategy for the scoping of the "current" Session. See 「コンテキスト上のセッ ション」 for more information about the built-in strategies. 例 jta | thread | m anaged | custom .Class hibernate.query.factory_class HQL パーサーの実装を選択します。 例 org.hibernate.hql.ast.AST QueryT ran slatorFactory or org.hibernate.hql.classic.ClassicQue ryT ranslatorFactory hibernate.query.substitutions HQL と SQL のトークンをマッピングします。 (例えば、トークンは関数やリテラル名です)。 例 hqlLiteral=SQL_LIT ERAL, hqlFunction=SQLFUNC hibernate.hbm 2ddl.auto SessionFactory を生成したときに、自動的に スキーマ DDL を有効にしデータベースに出力しま す。 create-drop の場合、 SessionFactory をクローズしたときに、データベーススキーマを ドロップします。 例 validate | update | create | createdrop hibernate.cglib.use_reflection_optim izer 実行時リフレクションの代わりの CGLIB の使用を 有効にします (システムレベルのプロパティ) 。 リフレクションはトラブルシューティングのとき に役立つことがあります。オプティマイザをオフ にしているときでさえ、 Hibernate には必ず CGLIB が必要なことに注意してください。このプ ロパティは hibernate.cfg.xm l で設定できま せん。 true | false 3.4.1. SQL 方言( Dialect) hibernate.dialect プロパティには、使用するデータベースの正しい org.hibernate.dialect.Dialect のサブクラスを、必ず指定すべきです。しかし方言を指定すれ ば、 Hibernate は上述したプロパティのいくつかについて、より適切なデフォルト値を使います。そうす れば、それらを手作業で設定する手間が省けます。 47 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 表 3.8 Hibernate SQL Dialects (hibernate.dialect) RDBMS Dialect DB2 org.hibernate.dialect.DB2Dialect DB2 AS/400 org.hibernate.dialect.DB24 00Dialect DB2 OS390 org.hibernate.dialect.DB2390Dialect PostgreSQL org.hibernate.dialect.PostgreSQLDial ect MySQL org.hibernate.dialect.MySQLDialect MySQL with InnoDB org.hibernate.dialect.MySQLInnoDBDia lect MySQL with MyISAM org.hibernate.dialect.MySQLMyISAMDia lect Oracle (any version) org.hibernate.dialect.OracleDialect Oracle 9i org.hibernate.dialect.Oracle9iDiale ct Oracle 10g org.hibernate.dialect.Oracle10gDial ect Sybase org.hibernate.dialect.SybaseDialect Sybase Anywhere org.hibernate.dialect.SybaseAnywhere Dialect Microsoft SQL Server org.hibernate.dialect.SQLServerDiale ct SAP DB org.hibernate.dialect.SAPDBDialect Informix org.hibernate.dialect.Inform ixDiale ct HypersonicSQL org.hibernate.dialect.HSQLDialect Ingres org.hibernate.dialect.IngresDialect Progress org.hibernate.dialect.ProgressDialec t Mckoi SQL org.hibernate.dialect.MckoiDialect Interbase org.hibernate.dialect.InterbaseDiale ct Pointbase org.hibernate.dialect.PointbaseDiale ct FrontBase org.hibernate.dialect.FrontbaseDiale ct Firebird org.hibernate.dialect.FirebirdDiale ct 3.4.2. 外部結合フェッチ もしデータベースが ANSI か、 Oracle か Sybase スタイルの外部結合をサポートしている場合、 outer join fetching は、データベースの SQL 発行回数を節約しパフォーマンスを良くします(データベース内で より多くの処理コストが発生します)。外部結合フェッチは、多対一、一対多、多対多、一対一のオブ 48 第3章 設定 ジェクト関連でグループオブジェクトを1つの SQL で SELECT します。 Outer join fetching may be disabled globally by setting the property hibernate.m ax_fetch_depth to 0. A setting of 1 or higher enables outer join fetching for one-to-one and many-to-one associations which have been mapped with fetch="join". See 「フェッチ戦略」 for more information. 3.4.3. バイナリストリーム Oracle は JDBC ドライバとの間でやりとりされる byte 配列のサイズを制限します。 binary や serializable 型の大きなインスタンスを使いたければ、 hibernate.jdbc.use_stream s_for_binary を有効にしてください。 ただし これはシステムレベ ルの設定だけです 。 3.4.4. ニ次キャッシュとクエリキャッシュ T he properties prefixed by hibernate.cache allow you to use a process or cluster scoped secondlevel cache system with Hibernate. See the 「第2レベルキャッシュ」 for more details. 3.4.5. クエリ言語の置き換え hibernate.query.substitutions を使うことで、新しい Hibernate クエリトークンを定義できま す。例: hibernate.query.substitutions true=1, false=0 これはトークン true と false を、生成される SQL において整数リテラルに翻訳します。 hibernate.query.substitutions toLowercase=LOWER これは SQL の LOWER 関数の名前の付け替えを可能にします。 3.4.6. Hibernate 統計 hibernate.generate_statistics を有効にした場合、動作しているシステムをチューニングすると きに、 SessionFactory.getStatistics() を経由して、 Hibernate は便利な統計情報を出力しま す。 JMX を経由して統計情報を出力することも可能です。 Javadoc の org.hibernate.stats パッ ケージ内のインターフェースにはより多くの情報があります。 3.5. ロ ギ ン グ Hibernate は Apache commons-logging を使って、さまざまなイベントをログとして出力します。 commons-logging サービスは(クラスパスに log4 j.jar を含めれば) Apache Log4j に、または (JDK1.4 かそれ以上で実行させれば) JDK1.4 logging に直接出力します。 Log4j は http://jakarta.apache.org からダウンロードできます。 Log4j を使うためには、クラスパスに log4 j.properties ファイルを配置する必要があります。例のプロパティファイルは Hibernate と一緒 に配布され、それは src/ ディレクトリにあります。 We strongly recommend that you familiarize yourself with Hibernate's log messages. A lot of work has been put into making the Hibernate log as detailed as possible, without making it unreadable. It is an essential troubleshooting device. T he most interesting log categories are the following: 49 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 表 3.9 Hibernate ログカテゴリ カテゴリ 機能 org.hibernate.SQL 実行したすべての SQL(DDL)ステートメントを ロギングします。 org.hibernate.type すべての JDBC パラメータをロギングします。 org.hibernate.tool.hbm 2ddl 実行したすべての SQL(DDL)ステートメントを ロギングします。 org.hibernate.pretty session に関連するすべてのエンティティ(最大 20)のフラッシュ時間をロギングします。 org.hibernate.cache すべてのニ次キャッシュの動作をロギングしま す。 org.hibernate.transaction トランザクションに関連する動作をロギングしま す。 org.hibernate.jdbc JDBC リソース取得をロギングします。 org.hibernate.hql.ast.AST HQL と SQL の AST のクエリパースをロギングし ます。 org.hibernate.secure すべての JAAS 分析をロギングします。 org.hibernate すべてをロギングします。(情報が大量になりま すが、トラブルシューティングには便利です) Hibernate でアプリケーションを作成するときは、 org.hibernate.SQL カテゴリの debug を常に有 効にしておいたほうが良いでしょう。代替方法として、 hibernate.show_sql プロパティを有効にす る方法があります。 3.6. NamingStrategy の 実 装 T he interface org.hibernate.cfg.Nam ingStrategy allows you to specify a "naming standard" for database objects and schema elements. You may provide rules for automatically generating database identifiers from Java identifiers or for processing "logical" column and table names given in the mapping file into "physical" table and column names. T his feature helps reduce the verbosity of the mapping document, eliminating repetitive noise (T BL_ prefixes, for example). T he default strategy used by Hibernate is quite minimal. マッピングを追加する前に Configuration.setNam ingStrategy() を呼ぶことで以下のように異な る戦略を指定することができます: SessionFactory sf = new Configuration() .setNamingStrategy(ImprovedNamingStrategy.INSTANCE) .addFile("Item.hbm.xml") .addFile("Bid.hbm.xml") .buildSessionFactory(); org.hibernate.cfg.Im provedNam ingStrategy は組み込みの戦略です。これはいくつかのアプ リケーションにとって有用な開始点となるかもしれません。 3.7. XML 設 定 フ ァ イ ル もう1つの方法は hibernate.cfg.xm l という名前のファイルで十分な設定を指定する方法です。この 50 第3章 設定 ファイルは hibernate.properties ファイルの代わりとなります。もし両方のファイルがあれば、プ ロパティが置き換えられます。 XML 設定ファイルは初期設定で CLASSPAT H の root に配置してください。これが例です: <?xml version='1.0' encoding='utf-8'?> <!DOCTYPE hibernate-configuration PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN" "http://hibernate.sourceforge.net/hibernate-configuration-3.0.dtd"> <hibernate-configuration> <!-- a SessionFactory instance listed as /jndi/name --> <session-factory name="java:hibernate/SessionFactory"> <!-- properties --> <property name="connection.datasource">java:/comp/env/jdbc/MyDB</property> <property name="dialect">org.hibernate.dialect.MySQLDialect</property> <property name="show_sql">false</property> <property name="transaction.factory_class"> org.hibernate.transaction.JTATransactionFactory </property> <property name="jta.UserTransaction">java:comp/UserTransaction</property> <!-- mapping files --> <mapping resource="org/hibernate/auction/Item.hbm.xml"/> <mapping resource="org/hibernate/auction/Bid.hbm.xml"/> <!-- cache settings --> <class-cache class="org.hibernate.auction.Item" usage="read-write"/> <class-cache class="org.hibernate.auction.Bid" usage="read-only"/> <collection-cache collection="org.hibernate.auction.Item.bids" usage="read-write"/> </session-factory> </hibernate-configuration> 見てのとおり、この方法の優位性は設定のためのマッピングファイル名を外出しにできることです。 Hibernate キャッシュをチューニングしなければならないのであれば、 hibernate.cfg.xm l はより便 利です。 hibernate.properties と hibernate.cfg.xm l の どちらかを使えることを覚えておい てください。2つは同じもので、違うところといえば XML 構文を使うことの利点だけです。 XML 設定を使うことで、 Hibernate は以下のようにシンプルになります。 SessionFactory sf = new Configuration().configure().buildSessionFactory(); 違う XML 設定ファイルを使うこともできます。 SessionFactory sf = new Configuration() .configure("catdb.cfg.xml") .buildSessionFactory(); 3.8. J2EE ア プ リ ケ ー シ ョ ン サ ー バ ー と の 統 合 51 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide Hibernate は J2EE 構造と統合するポイントをサポートしています: コンテナ管理データソース: Hibernate は JNDI が提供し、コンテナが管理する JDBC コネクションを 使用できます。通常、 JT A 準拠の T ransactionManager と ResourceManager がトランザク ション管理 (CMT )、特に様々なデータソースにまたがる分散トランザクションを扱います。当然プロ グラムでトランザクション境界を指定できます (BMT )。あるいは、記述したコードのポータビリティ を保つために、オプションの Hibernate の T ransaction API を使いたくなるかもしれません。 自動 JNDI バインディング: Hibernate は JNDI が立ち上がった後に SessionFactory を生成しま す。 JTA セッションバインディング: Hibernate Session は自動的に JT A トランザクションのスコープ にバインドされます。単純に SessionFactory を JNDI から lookup して、現在の Session を取得 します。 JT A トランザクションが完了したときに、 Hibernateが Session をフラッシュし、クロー ズします。トランザクション境界は、宣言 (CMT ) することも、プログラム (BMT /UserT ransaction) することも可能です。 JMX デプロイメント: もし JMX が使用可能なアプリケーションサーバー(例えば JBoss AS) がある 場合、 Hibernate を MBean としてデプロイすることを選べます。これは Configuration から SessionFactory を生成するコードを無くすことができます。コンテナは HibernateService を 起動し、サービスの依存を理想的に管理します(データソースは Hibernate やその他が起動する前に 使用できるようにしなければなりません)。 Depending on your environment, you might have to set the configuration option hibernate.connection.aggressive_release to true if your application server shows "connection containment" exceptions. 3.8.1. トランザクション戦略設定 Hibernate Session API は、アーキテクチャ内のシステムの管轄であるあらゆるトランザクションに依存 しません。もしコネクションプールの JDBC を直接使いたい場合、 JDBC API から トランザクションを呼 ぶことができます。もし、 J2EE アプリケーションサーバーで動作させるなら、 Bean 管理トランザク ションを使い、必要に応じて UserT ransaction を JT A API から呼ぶことになるでしょう。 2つ(それ以上)の環境で互換性のあるコードを維持するために、オプションとして根本的なシステムを ラッピングする Hibernate T ransaction API を推奨します。 Hibernate 設定プロパティの hibernate.transaction.factory_class を設定することで、ある特定の T ransaction クラス のインスタンスを持つことができます。 3つの基本的な(既にある)選択を挙げます: org.hibernate.transaction.JDBCT ransactionFactory データベース (JDBC) トランザクションに委譲します(デフォルト) org.hibernate.transaction.JT AT ransactionFactory もし、このコンテキスト(例えば、 EJB セッション Bean メソッド)で進行中のトランザクショ ンが存在する場合、コンテナ管理トランザクションに委譲します。そうでない場合は、新しいト ランザクションが開始されており、 Bean 管理トランザクションが使われます。 org.hibernate.transaction.CMT T ransactionFactory コンテナ管理 JT A トランザクションに委譲します 52 第3章 設定 自分自身のトランザクション戦略(例えば、 CORBA トランザクションサービス)を定義することもでき ます。 Hibernate のいくつかの機能(例えば、二次キャッシュ、 JT A によるコンテキストセッション等)は管理 された環境の中の JT A T ransactionManager へのアクセスを要求します。 J2EE がひとつのメカニズ ムに規格化されていないので、アプリケーションサーバーにおいて、 Hibernateが T ransactionManager のリファレンスを取得する方法を明確にする必要があります。 表 3.10 JT A トランザクションマネージャ T ransaction Factory Application Server org.hibernate.transaction.JBossT rans actionManagerLookup JBoss org.hibernate.transaction.WeblogicT r ansactionManagerLookup Weblogic org.hibernate.transaction.WebSphereT ransactionManagerLookup WebSphere org.hibernate.transaction.WebSphereE xtendedJT AT ransactionLookup WebSphere 6 org.hibernate.transaction.OrionT rans actionManagerLookup Orion org.hibernate.transaction.ResinT rans actionManagerLookup Resin org.hibernate.transaction.JOT MT ransa ctionManagerLookup JOT M org.hibernate.transaction.JOnAST rans actionManagerLookup JOnAS org.hibernate.transaction.JRun4 T rans actionManagerLookup JRun4 org.hibernate.transaction.BEST ransac tionManagerLookup Borland ES 3.8.2. SessionFactory の JNDI への登録 JNDI に登録した Hibernate SessionFactory はファクトリのルックアップと新しい Session の作成を 簡易化します。これは JNDI に登録された Datasource には関連せず、両方とも単に同じ登録を使うこ とに注意してください。 もし SessionFactory を JNDI ネームスペースに登録したい場合、特別な名前(例えば、 java:hibernate/SessionFactory )を hibernate.session_factory_nam e プロパティに 使ってください。もしこのプロパティを省略した場合、 SessionFactory は JNDI に登録されません。 (これは T omcat のようなデフォルト実装で JNDI が読み取り専用の環境の場合は特に便利です。) SessionFactory を JNDI に登録するとき、 Hibernate は hibernate.jndi.url の値を使用 し、hibernate.jndi.class をイニシャルコンテキストとして具体化します。もし何も設定しない場 合は、デフォルトの InitialContext を使用します。 cfg.buildSessionFactory() をコール後 Hibernate は自動的に SessionFactory を JNDI に配置 します。 HibernateService と一緒に JMX デプロイメントを使わない限り、これはこの呼び出しをア プリケーション内の何らかのスタートアップコード(もしくはユーティリティクラス) に配置しなければ 53 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide ならないことを意味します(後で議論します)。 もし JNDI SessionFactory を使う場合、 EJB や他のクラスは JNDI ルックアップを使って SessionFactory を取得します。 管理された環境では SessionFactory を JNDI にバインドし、そうでなければ static シングルトンを 使うことを推奨します。こういった詳細からアプリケーションコードを保護するために、 HibernateUtil.getSessionFactory() のようなヘルパークラスの中に、 SessionFactory を ルックアップするコードを隠すことを推奨します。このようなヘルパークラスは Hibernate を開始する便 利な手段でもあります。 - 1章を参照してください。 3.8.3. JTA による現在のセッションコンテキストマネージメント T he easiest way to handle Session s and transactions is Hibernates automatic "current" Session management. See the discussion of 「コンテキスト上のセッション」 current sessions. Using the "jta" session context, if there is no Hibernate Session associated with the current JT A transaction, one will be started and associated with that JT A transaction the first time you call sessionFactory.getCurrentSession(). T he Session s retrieved via getCurrentSession() in "jta" context will be set to automatically flush before the transaction completes, close after the transaction completes, and aggressively release JDBC connections after each statement. T his allows the Session s to be managed by the lifecycle of the JT A transaction to which it is associated, keeping user code clean of such management concerns. Your code can either use JT A programmatically through UserT ransaction, or (recommended for portable code) use the Hibernate T ransaction API to set transaction boundaries. If you run in an EJB container, declarative transaction demarcation with CMT is preferred. 3.8.4. JMX デプロイメント SessionFactory を JNDI から取得するためには cfg.buildSessionFactory() 行をどこかで実行 していなければなりません。あなたはこれを、 static 初期化ブロック内( HibernateUtil のよう な)か managed service として Hibernate をデプロイするか、どちらかで実行できます。 JBoss AS のような JMX の機能でアプリケーションサーバーにデプロイするために org.hibernate.jm x.HibernateService を使って、配置します。実際のデプロイメントと設定は ベンダー特有です。ここで例として JBoss 4.0.x 用の jboss-service.xm l を示します。 54 第3章 設定 <?xml version="1.0"?> <server> <mbean code="org.hibernate.jmx.HibernateService" name="jboss.jca:service=HibernateFactory,name=HibernateFactory"> <!-- Required services --> <depends>jboss.jca:service=RARDeployer</depends> <depends>jboss.jca:service=LocalTxCM,name=HsqlDS</depends> <!-- Bind the Hibernate service to JNDI --> <attribute name="JndiName">java:/hibernate/SessionFactory</attribute> <!-- Datasource settings --> <attribute name="Datasource">java:HsqlDS</attribute> <attribute name="Dialect">org.hibernate.dialect.HSQLDialect</attribute> <!-- Transaction integration --> <attribute name="TransactionStrategy"> org.hibernate.transaction.JTATransactionFactory</attribute> <attribute name="TransactionManagerLookupStrategy"> org.hibernate.transaction.JBossTransactionManagerLookup</attribute> <attribute name="FlushBeforeCompletionEnabled">true</attribute> <attribute name="AutoCloseSessionEnabled">true</attribute> <!-- Fetching options --> <attribute name="MaximumFetchDepth">5</attribute> <!-- Second-level caching --> <attribute name="SecondLevelCacheEnabled">true</attribute> <attribute name="CacheProviderClass">org.hibernate.cache.EhCacheProvider</attribute> <attribute name="QueryCacheEnabled">true</attribute> <!-- Logging --> <attribute name="ShowSqlEnabled">true</attribute> <!-- Mapping files --> <attribute name="MapResources">auction/Item.hbm.xml,auction/Category.hbm.xml</attribute> </mbean> </server> このファイルは MET A-INF ディレクトリに配置され、 JAR ファイルを拡張した .sar (service archive) でパッケージ化されます。同様に Hibernate パッケージも必要です。また、 Hibernate はサードパーティ のライブラリも要求します。コンパイルした永続化クラスとそのマッピングファイルも同様にアーカイブ (.sarファイル)に入れます。エンタープライズ Bean (通常はセッション Bean )は自身の JAR ファイ ルを保持しますが、1回で(ホット)デプロイ可能なユニットのためにメインサービスアーカイブとして この EJB JAR ファイルを含めることができます。 JBoss AS のドキュメントに JXM サービスと EJB デプ ロイメントのより多くの情報があります。 55 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 第 4章 永続クラス 永続クラスはビジネス上の問題のエンティティ(例えば、 E コマースアプリケーションの顧客や注文) を実装するアプリケーションのクラスです。永続クラスのすべてのインスタンスが永続状態であると見な されるわけではありません。インスタンスは逆に一時的(transient)であったり、分離状態(detached) であったりするかもしれません。 Plain Old Java Object (POJO)プログラミングモデルとしても知られるいくつかの単純なルールに従うな ら、 Hibernate は最もよく働きます。しかしこれらのルールは難しいものではありません。実際 Hibernate3 は永続オブジェクトの性質にほとんど何の前提も置いていません。ドメインモデルは他の方法 で表現することもできます。例えば Map インスタンスのツリーを使う方法があります。 4.1. 単 純 な POJO の 例 以下はネコ科の動物を表現する永続クラスです。 56 第4章 永続クラス package eg; import java.util.Set; import java.util.Date; public class Cat { private Long id; // identifier private private private private private Date birthdate; Color color; char sex; float weight; int litterId; private Cat mother; private Set kittens = new HashSet(); private void setId(Long id) { this.id=id; } public Long getId() { return id; } void setBirthdate(Date date) { birthdate = date; } public Date getBirthdate() { return birthdate; } void setWeight(float weight) { this.weight = weight; } public float getWeight() { return weight; } public Color getColor() { return color; } void setColor(Color color) { this.color = color; } void setSex(char sex) { this.sex=sex; } public char getSex() { return sex; } void setLitterId(int id) { this.litterId = id; } public int getLitterId() { return litterId; } void setMother(Cat mother) { this.mother = mother; 57 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide } public Cat getMother() { return mother; } void setKittens(Set kittens) { this.kittens = kittens; } public Set getKittens() { return kittens; } // addKitten not needed by Hibernate public void addKitten(Cat kitten) { kitten.setMother(this); kitten.setLitterId( kittens.size() ); kittens.add(kitten); } } 従うべき4つのルールがあります: 4.1.1. 引数のないコンストラクタを実装する Cat には引数のないコンストラクタがあります。 Hibernate が Constructor.newInstance() を使っ て永続クラスのインスタンス化を行えるように、すべての永続クラスにはデフォルトコンストラクタ (public でなくても構いません) がなければなりません。 Hibernate の実行時プロキシ生成のために、少 なくとも package の可視性を持つデフォルトコンストラクタを強くお勧めします。 4.1.2. 識別子プロパティを用意する(オプション) Cat has a property called id. T his property maps to the primary key column of a database table. T he property might have been called anything, and its type might have been any primitive type, any primitive "wrapper" type, java.lang.String or java.util.Date. (If your legacy database table has composite keys, you can even use a user-defined class with properties of these types - see the section on composite identifiers later.) 識別子プロパティは厳密にはオプションです。これを省略して、 Hibernate に内部的にオブジェクトの識 別子を追跡させることは可能です。しかしお勧めはしません。 実際に、識別子プロパティを宣言するクラスだけが利用可能な機能がいくつかあります: T ransitive reattachment for detached objects (cascade update or cascade merge) - see 「連鎖的な 永続化」 Session.saveOrUpdate() Session.m erge() 永続クラスには、一貫した名前の識別子プロパティを定義することをお勧めします。さらに null 値を取れ る(つまりプリミティブではない)型を使った方がよいでしょう。 4.1.3. final クラスにしない(オプション) Hibernate の中心的な特徴である プロキシ は、永続クラスが final でないこと、またはメソッドを全部 public で宣言しているインターフェースが実装されているかに依存しています。 You can persist final classes that do not implement an interface with Hibernate, but you won't be able to use proxies for lazy association fetching - which will limit your options for performance tuning. 58 第4章 永続クラス You should also avoid declaring public final methods on the non-final classes. If you want to use a class with a public final method, you must explicitly disable proying by setting lazy="false". 4.1.4. 永続フィールドに対するアクセサとミューテータを定義する(オプション) Cat ではすべての永続フィールドに対してアクセサメソッドを定義しています。他の多くの ORM ツール は、永続インスタンス変数を直接永続化します。私たちはリレーショナルスキーマとクラスの内部構造を 分離する方が良いと信じています。デフォルトでは、 Hibernate は JavaBean スタイルのプロパティを永 続化し、 getFoo, isFoo, setFoo 形式のメソッド名を認識します。しかし必要なら、特定のプロパティ に対して、直接のフィールドアクセスに切り替えることは可能です。 プロパティは public で宣言する必要は ありません 。 Hibernate はデフォルトで、 protected もしくは private の get / set のペアを持つプロパティを永続化することができます。 4.2. 継 承 の 実 装 サブクラスも1番目と2番目のルールを守らなければなりません。サブクラスはスーパークラス Cat から 識別子プロパティを継承します。 package eg; public class DomesticCat extends Cat { private String name; public String getName() { return name; } protected void setName(String name) { this.name=name; } } 4.3. equals() と hashCode() の 実 装 以下の条件の場合、 equals() と hashCode() メソッドをオーバーライドしなければなりません、 永続クラスのインスタンスを Set に置く場合。 (これは多値の関連を表現するおすすめの方法です) そして同時に 分離インスタンスをセッションへ再追加する場合。 Hibernate は、永続 ID (データベースの行)と、特定のセッションスコープ内に限定ですが Java ID とが 等価であることを保証します。ですから異なるセッションで検索したインスタンスを組み合わせる場合、 Set に意味のあるセマンティクスを持たせようと思っているならすぐに equals() と hashCode() を実 装しなければなりません。 T he most obvious way is to implement equals()/hashCode() by comparing the identifier value of both objects. If the value is the same, both must be the same database row, they are therefore equal (if both are added to a Set, we will only have one element in the Set). Unfortunately, we can't use that approach with generated identifiers! Hibernate will only assign identifier values to objects that are persistent, a newly created instance will not have any identifier value! Furthermore, if an instance is unsaved and currently in a Set, saving it will assign an identifier value to the object. If equals() and hashCode() are based on the identifier value, the hash code would change, breaking the contract of the Set. See the Hibernate website for a full discussion of this problem. Note that this is not a Hibernate issue, but normal Java semantics of object identity and equality. 59 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide ビジネスキーの等価性 を使って、 equals() と hashCode() を実装することをお勧めします。ビジネ スキーの等価性とは、 equals() メソッドが、ビジネスキー、つまり現実の世界においてインスタンスを 特定するキー(自然 候補キー) を形成するプロパティだけを比較することを意味します。 public class Cat { ... public boolean equals(Object other) { if (this == other) return true; if ( !(other instanceof Cat) ) return false; final Cat cat = (Cat) other; if ( !cat.getLitterId().equals( getLitterId() ) ) return false; if ( !cat.getMother().equals( getMother() ) ) return false; return true; } public int hashCode() { int result; result = getMother().hashCode(); result = 29 * result + getLitterId(); return result; } } Note that a business key does not have to be as solid as a database primary key candidate (see 「オブ ジェクト識別子を考える」). Immutable or unique properties are usually good candidates for a business key. 4.4. 動 的 モ デ ル Note that the following features are currently considered experimental and may change in the near future. Persistent entities don't necessarily have to be represented as POJO classes or as JavaBean objects at runtime. Hibernate also supports dynamic models (using Map s of Map s at runtime) and the representation of entities as DOM4J trees. With this approach, you don't write persistent classes, only mapping files. By default, Hibernate works in normal POJO mode. You may set a default entity representation mode for a particular SessionFactory using the default_entity_m ode configuration option (see 表 3.3「Hibernate 設定プロパティ」. 以下の例では Map を使った表現を紹介します。まずマッピングファイルで、クラス名の代わりに(また はそれに加えて) entity-nam e を定義しなければなりません: 60 第4章 永続クラス <hibernate-mapping> <class entity-name="Customer"> <id name="id" type="long" column="ID"> <generator class="sequence"/> </id> <property name="name" column="NAME" type="string"/> <property name="address" column="ADDRESS" type="string"/> <many-to-one name="organization" column="ORGANIZATION_ID" class="Organization"/> <bag name="orders" inverse="true" lazy="false" cascade="all"> <key column="CUSTOMER_ID"/> <one-to-many class="Order"/> </bag> </class> </hibernate-mapping> 関連がターゲットのクラス名を使って定義していたとしても、関連のターゲット型も POJO ではなく動的 なエンティティでも構わないことに注意してください。 SessionFactory に対してデフォルトのエンティティモードを dynam ic-m ap に設定した後、実行時 に Map の Map を使うことができます: 61 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide Session s = openSession(); Transaction tx = s.beginTransaction(); Session s = openSession(); // Create a customer Map david = new HashMap(); david.put("name", "David"); // Create an organization Map foobar = new HashMap(); foobar.put("name", "Foobar Inc."); // Link both david.put("organization", foobar); // Save both s.save("Customer", david); s.save("Organization", foobar); tx.commit(); s.close(); 動的なマッピングの利点は、エンティティクラスの実装を必要としないため、プロトタイピングに要する ターンアラウンドタイムが早いということです。しかしコンパイル時の型チェックがないので、実行時に 非常に多くの例外処理を扱わなければならないでしょう。 Hibernate マッピングのおかげで、データベー ススキーマは容易に正規化でき、健全になり、後で適切なドメインモデルの実装を追加することが可能に なります。 エンティティ表現モードは Session ごとに設定することも可能です。 Session dynamicSession = pojoSession.getSession(EntityMode.MAP); // Create a customer Map david = new HashMap(); david.put("name", "David"); dynamicSession.save("Customer", david); ... dynamicSession.flush(); dynamicSession.close() ... // Continue on pojoSession Please note that the call to getSession() using an EntityMode is on the Session API, not the SessionFactory. T hat way, the new Session shares the underlying JDBC connection, transaction, and other context information. T his means you don't have tocall flush() and close() on the secondary Session, and also leave the transaction and connection handling to the primary unit of work. More information about the XML representation capabilities can be found in 18章XML マッピング. 4.5. Tuplizer org.hibernate.tuple.T uplizer, and its sub-interfaces, are responsible for managing a particular representation of a piece of data, given that representation's org.hibernate.EntityMode. If a given piece of data is thought of as a data structure, then a tuplizer is the thing which knows how to create such a data structure and how to extract values from and inject values into such a data structure. For example, for the POJO entity mode, the correpsonding tuplizer knows how create the POJO through its 62 第4章 永続クラス constructor and how to access the POJO properties using the defined property accessors. T here are two high-level types of T uplizers, represented by the org.hibernate.tuple.entity.EntityT uplizer and org.hibernate.tuple.com ponent.Com ponentT uplizer interfaces. EntityT uplizer s are responsible for managing the above mentioned contracts in regards to entities, while Com ponentT uplizer s do the same for components. ユーザーは独自の T uplizer に差し替えることも可能です。おそらく dynamic-map entity-mode の際に java.util.HashMap を使うのではなく、 java.util.Map の実装が必要でしょう。もしくは、おそら くデフォルトのものではなく、別のプロキシ生成戦略の定義が必要でしょう。両者とも、カスタムの T uplizer 実装を定義することで達成されます。 T uplizer の定義は、管理しようとするエンティティやコン ポーネントのマッピングに結び付けられます。顧客エンティティの例は以下になります: <hibernate-mapping> <class entity-name="Customer"> <!-Override the dynamic-map entity-mode tuplizer for the customer entity --> <tuplizer entity-mode="dynamic-map" class="CustomMapTuplizerImpl"/> <id name="id" type="long" column="ID"> <generator class="sequence"/> </id> <!-- other properties --> ... </class> </hibernate-mapping> public class CustomMapTuplizerImpl extends org.hibernate.tuple.entity.DynamicMapEntityTuplizer { // override the buildInstantiator() method to plug in our custom map... protected final Instantiator buildInstantiator( org.hibernate.mapping.PersistentClass mappingInfo) { return new CustomMapInstantiator( mappingInfo ); } private static final class CustomMapInstantiator extends org.hibernate.tuple.DynamicMapInstantitor { // override the generateMap() method to return our custom map... protected final Map generateMap() { return new CustomMap(); } } } 63 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 第 5章 基本的な O/R マッピング 5.1. マ ッ ピ ン グ 定 義 オブジェクト/リレーショナルマッピングは通常 XML ドキュメントで定義します。マッピングドキュメン トは、読みやすく手作業で編集しやすいようにデザインされています。マッピング言語は Java 中心、つ まりテーブル定義ではなく永続クラスの定義に基づいて構築されています。 多くの Hibernate ユーザーは XML マッピングの記述を手作業で行いますが、 XDoclet, Middlegen, AndroMDA というようなマッピングドキュメントを生成するツールがいくつか存在することを覚えておい てください。 サンプルのマッピングから始めましょう: 64 第5章 基本的な O/R マッピング <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="eg"> <class name="Cat" table="cats" discriminator-value="C"> <id name="id"> <generator class="native"/> </id> <discriminator column="subclass" type="character"/> <property name="weight"/> <property name="birthdate" type="date" not-null="true" update="false"/> <property name="color" type="eg.types.ColorUserType" not-null="true" update="false"/> <property name="sex" not-null="true" update="false"/> <property name="litterId" column="litterId" update="false"/> <many-to-one name="mother" column="mother_id" update="false"/> <set name="kittens" inverse="true" order-by="litter_id"> <key column="mother_id"/> <one-to-many class="Cat"/> </set> <subclass name="DomesticCat" discriminator-value="D"> <property name="name" type="string"/> </subclass> </class> <class name="Dog"> 65 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide <!-- mapping for Dog could go here --> </class> </hibernate-mapping> マッピングドキュメントの内容を説明します。ただし、ここでは Hibernate が実行時に使うドキュメント 要素と属性についてのみ説明します。マッピングドキュメントは、いくつかのオプション属性と要素を含 んでいます(例えば not-null 属性)。それらはスキーマエクスポートツールが出力するデータベース スキーマに影響を与えるものです。 5.1.1. Doctype XML マッピングでは、お見せしたようなドキュメント型を必ず定義すべきです。実際の DT D は、上記の URL の hibernate-x.x.x/src/org/hibernate ディレクトリ、または hibernate.jar 内にあり ます。 Hibernate は常に、そのクラスパス内で DT D を探し始めます。インターネットにある DT D ファイ ルを探そうとしたなら、クラスパスの内容を見て、 DT D 宣言を確認してください。 5.1.1.1. エンティティリゾルバ 前述したように、 Hibernate はまずクラスパス内で DT D を解決しようとします。 org.xm l.sax.EntityResolver のカスタム実装を XML ファイルを読み込むための SAXReader に登 録することによって、 DT D を解決します。このカスタムの EntityResolver は2つの異なるシステム ID 名前空間を認識します。 hibernate nam espace は、リゾルバが http://hibernate.sourceforge.net/ で始まるシ ステム ID に到達したときに認識されます。そしてリゾルバは、 Hibernate のクラスをロードしたクラ スローダを用いて、これらのエンティティを解決しようとします。 user nam espace は、リゾルバが URL プロトコルの classpath:// を使ったシステム ID に到達 したときに、認識されます。そしてリゾルバは、 (1) カレントスレッドのコンテキストクラスロー ダー、または (2) Hibernate のクラスをロードしたクラスローダを使って、これらのエンティティを解 決しようとします。 下記は、ユーザー名前空間を使った例です: <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd" [ <!ENTITY types SYSTEM "classpath://your/domain/types.xml"> ]> <hibernate-mapping package="your.domain"> <class name="MyEntity"> <id name="id" type="my-custom-id-type"> ... </id> </class> & types; </hibernate-mapping> Where types.xm l is a resource in the your.dom ain package and contains a custom 「カスタム型」 typedef. 5.1.2. hibernate-mapping この要素にはいくつかオプション属性があります。 schem a 属性と catalog 属性は、このマッピングが 参照するテーブルが、この属性によって指定されたスキーマと(または)カタログに属することを指定しま 66 第5章 基本的な O/R マッピング す。この属性が指定されると、テーブル名は与えられたスキーマ名とカタログ名で修飾されます。これら の属性が指定されていなければ、テーブル名は修飾されません。 default-cascade 属性は、 cascade 属性を指定していないプロパティやコレクションに、どのカスケードスタイルを割り当てるか を指定します。 auto-im port 属性は、クエリ言語内で修飾されていないクラス名を、デフォルトで使え るようにします。 <hibernate-mapping schema="schemaName" catalog="catalogName" default-cascade="cascade_style" default-access="field|property|ClassName" default-lazy="true|false" auto-import="true|false" package="package.name" /> schem a(オプション):データベーススキーマの名前。 catalog (オプション):データベースカタログの名前。 default-cascade (オプション - デフォルトは none): デフォルトのカスケードスタイル。 default-access (オプション - デフォルトは property ): Hibernate がプロパティにアクセスする 際に取るべき戦略。 PropertyAccessor を実装することでカスタマイズ可能。 default-lazy (オプション - デフォルトは true ): lazy 属性が指定されていないクラスやコレクショ ンマッピングに対するデフォルト値。 auto-im port (オプション - デフォルトは true):クエリ言語内で、(このマッピング内のクラス の)修飾されていないクラス名を使えるかどうかを指定します。 package (オプション): マッピングドキュメント内で修飾されていないクラス名に対して割り当てる、 パッケージの接頭辞 (prefix) を指定します。 If you have two persistent classes with the same (unqualified) name, you should set autoim port="false". Hibernate will throw an exception if you attempt to assign two classes to the same "imported" name. Note that the hibernate-m apping element allows you to nest several persistent <class> mappings, as shown above. It is however good practice (and expected by some tools) to map only a single persistent class (or a single class hierarchy) in one mapping file and name it after the persistent superclass, e.g. Cat.hbm .xm l, Dog.hbm .xm l, or if using inheritance, Anim al.hbm .xm l. 5.1.3. class class 要素を使って、永続クラスを宣言できます: 67 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide <class name="ClassName" table="tableName" discriminator-value="discriminator_value" mutable="true|false" schema="owner" catalog="catalog" proxy="ProxyInterface" dynamic-update="true|false" dynamic-insert="true|false" select-before-update="true|false" polymorphism="implicit|explicit" where="arbitrary sql where condition" persister="PersisterClass" batch-size="N" optimistic-lock="none|version|dirty|all" lazy="true|false" entity-name="EntityName" check="arbitrary sql check condition" rowid="rowid" subselect="SQL expression" abstract="true|false" node="element-name" /> nam e (オプション):永続クラス(またはインターフェース)の完全修飾 Java クラス名。もしこの属性が 欠落している場合、 POJO ではないエンティティに対するマッピングとして扱われます。 table (オプション - デフォルトは修飾されていないクラス名):データベーステーブルの名前 discrim inator-value (オプション - デフォルトはクラス名): ポリモーフィックな振る舞いに使われ る個々のサブクラスを識別するための値。値は null か not null のいずれかを取ります。 m utable (オプション、デフォルトは true ): そのクラスのインスタンスが更新可能(または不可能) であることを指定します。 schem a (optional): Override the schema name specified by the root <hibernate-m apping> element. catalog (optional): Override the catalog name specified by the root <hibernate-m apping> element. proxy (オプション):遅延初期化プロキシに使うインターフェースを指定します。永続化するクラス名 そのものを指定することも可能です。 dynam ic-update (オプション、 デフォルトは false ):値が変更されたカラムだけを含む SQL の UPDAT E 文を、実行時に生成することを指定します。 dynam ic-insert (オプション, デフォルトは false ):値が null ではないカラムだけを含む SQL の INSERT 文を、実行時に生成することを指定します。 select-before-update (オプション、デフォルトは false): オブジェクトが変更されたのが確実でな いならば、 Hibernate が SQL の UPDAT E を 決して実行しない ことを指定します。ある特定の場合(実際 的には、一時オブジェクトが update() を使い、新しいセッションと関連付けられた時だけ)、 UPDAT E が実際に必要かどうかを決定するために、 Hibernate が余分な SQL の SELECT 文を実行することを意味 します。 polym orphism (オプション、デフォルトでは im plicit ): implicit(暗黙)かexplicit(明示)の、どち 68 第5章 基本的な O/R マッピング らのクエリポリモーフィズムを使うか決定します。 where (オプション): このクラスのオブジェクトを検索するときに使用する、任意の SQL の WHERE 条件を指定します。 persister (オプション):カスタム ClassPersister を指定します。 batch-size (optional, defaults to 1) specify a "batch size" for fetching instances of this class by identifier. optim istic-lock (オプション、デフォルトは version ): 楽観ロック戦略を決定します。 lazy (optional): Lazy fetching may be completely disabled by setting lazy="false". entity-nam e (optional, defaults to the class name): Hibernate3 allows a class to be mapped multiple times (to different tables, potentially), and allows entity mappings that are represented by Maps or XML at the Java level. In these cases, you should provide an explicit arbitrary name for the entity. See 「動的 モデル」 and 18章XML マッピング for more information. check (オプション):自動的にスキーマを生成するために、複数行の check 制約を生成する SQL 式。 rowid (オプション): Hibernate は、それをサポートしているデータベースで ROWID と 呼ばれるも のを使うことができます。例えば Oracle を使っているとき、このオプションに rowid を設定すれば、 Hiberante は update を高速化するために rowid という特別なカラムを使うことができます。 ROWID は 詳細な実装であり、保存されたタプルの物理的な位置を表しています。 subselect (optional): Maps an immutable and read-only entity to a database subselect. Useful if you want to have a view instead of a base table, but don't. See below for more information. abstract (optional): Used to mark abstract superclasses in <union-subclass> hierarchies. It is perfectly acceptable for the named persistent class to be an interface. You would then declare implementing classes of that interface using the <subclass> element. You may persist any static inner class. You should specify the class name using the standard form ie. eg.Foo$Bar. Immutable classes, m utable="false", may not be updated or deleted by the application. T his allows Hibernate to make some minor performance optimizations. T he optional proxy attribute enables lazy initialization of persistent instances of the class. Hibernate will initially return CGLIB proxies which implement the named interface. T he actual persistent object will be loaded when a method of the proxy is invoked. See "Proxies for Lazy Initialization" below. Implicit polymorphism means that instances of the class will be returned by a query that names any superclass or implemented interface or the class and that instances of any subclass of the class will be returned by a query that names the class itself. Explicit polymorphism means that class instances will be returned only by queries that explicitly name that class and that queries that name the class will return only instances of subclasses mapped inside this <class> declaration as a <subclass> or <joinedsubclass>. For most purposes the default, polym orphism ="im plicit", is appropriate. Explicit polymorphism is useful when two different classes are mapped to the same table (this allows a "lightweight" class that contains a subset of the table columns). T he persister attribute lets you customize the persistence strategy used for the class. You may, for example, specify your own subclass of org.hibernate.persister.EntityPersister or you might even provide a completely new implementation of the interface org.hibernate.persister.ClassPersister that implements persistence via, for example, stored procedure calls, serialization to flat files or LDAP. See org.hibernate.test.Custom Persister for 69 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide procedure calls, serialization to flat files or LDAP. See org.hibernate.test.Custom Persister for a simple example (of "persistence" to a Hashtable). Note that the dynam ic-update and dynam ic-insert settings are not inherited by subclasses and so may also be specified on the <subclass> or <joined-subclass> elements. T hese settings may increase performance in some cases, but might actually decrease performance in others. Use judiciously. select-before-update の使用は通常パフォーマンスを落とします。もし Session へ分離インスタ ンスのグラフを再追加するなら、データベース更新のトリガを不必要に呼び出すのを避けるという点で、 非常に有用です。 dynam ic-update を有効にすれば、楽観ロック戦略を選ぶことになります: version バージョン/タイムスタンプカラムをチェックします。 all すべてのカラムをチェックします。 dirty 変更したカラムをチェックし、同時更新できるようにします。 none 楽観ロックを使用しません。 Hibernate で楽観的ロック戦略を使うなら、バージョン/タイムスタンプカラムを使うことを 非常に 強く お勧めします。楽観的ロックはパフォーマンスの観点からも最適であり、さらに分離インスタンスへの修 正 (つまり Session.m arge() が使われるとき) を正確に扱うことのできる唯一の戦略でもありま す。 T here is no difference between a view and a base table for a Hibernate mapping, as expected this is transparent at the database level (note that some DBMS don't support views properly, especially with updates). Sometimes you want to use a view, but can't create one in the database (ie. with a legacy schema). In this case, you can map an immutable and read-only entity to a given SQL subselect expression: <class name="Summary"> <subselect> select item.name, max(bid.amount), count(*) from item join bid on bid.item_id = item.id group by item.name </subselect> <synchronize table="item"/> <synchronize table="bid"/> <id name="name"/> ... </class> Declare the tables to synchronize this entity with, ensuring that auto-flush happens correctly, and that queries against the derived entity do not return stale data. T he <subselect> is available as both as an attribute and a nested mapping element. 5.1.4. id Mapped classes must declare the primary key column of the database table. Most classes will also have a JavaBeans-style property holding the unique identifier of an instance. T he <id> element defines the mapping from that property to the primary key column. 70 第5章 基本的な O/R マッピング <id name="propertyName" type="typename" column="column_name" unsaved-value="null|any|none|undefined|id_value" access="field|property|ClassName"> node="element-name|@attribute-name|element/@attribute|." <generator class="generatorClass"/> </id> nam e(オプション):識別子プロパティの名前。 type(オプション): Hibernate の型を示す名前。 colum n(オプション - デフォルトはプロパティ名): 主キーカラムの名前。 unsaved-value (optional - defaults to a "sensible" value): An identifier property value that indicates that an instance is newly instantiated (unsaved), distinguishing it from detached instances that were saved or loaded in a previous session. access (オプション - デフォルトは property ): Hibernate がプロパティの値にアクセスするために使用 すべき戦略。 nam e 属性がなければ、クラスには識別子プロパティがないものとみなされます。 unsaved-value 属性は Hibernate3 ではほとんどの場合、必要ではありません。 T here is an alternative <com posite-id> declaration to allow access to legacy data with composite keys. We strongly discourage its use for anything else. 5.1.4 .1. ジェネレータ T he optional <generator> child element names a Java class used to generate unique identifiers for instances of the persistent class. If any parameters are required to configure or initialize the generator instance, they are passed using the <param > element. <id name="id" type="long" column="cat_id"> <generator class="org.hibernate.id.TableHiLoGenerator"> <param name="table">uid_table</param> <param name="column">next_hi_value_column</param> </generator> </id> すべてのジェネレータは、 org.hibernate.id.IdentifierGenerator インターフェースを実装し ます。これはとても単純なインターフェースなので、特別な実装を独自に用意するアプリケーションもあ るかもしれません。しかし Hibernate は組み込みの実装をいくつも用意しています。組み込みのジェネ レータには以下のショートカット名があります: increm ent long , short , int 型の識別子を生成します。これらは他のプロセスが同じテーブルにデータを 挿入しないときだけユニークです。 クラスタ内では使わないでください 。 identity DB2, MySQL, MS SQL Server, Sybase, HypersonicSQL の識別子カラムをサポートします。返さ れる識別子の型は long , short , int のいずれかです。 71 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide sequence DB2, PostgreSQL, Oracle, SAP DB, McKoi のシーケンスや、 Interbase のジェネレータを使用し ます。返される識別子の型は long , short , int のいずれかです。 hilo uses a hi/lo algorithm to efficiently generate identifiers of type long, short or int, given a table and column (by default hibernate_unique_key and next_hi respectively) as a source of hi values. T he hi/lo algorithm generates identifiers that are unique only for a particular database. seqhilo long , short , int 型の識別子を効率的に生成する hi/lo アルゴリズムを使います。指定された データベースシーケンスを与えます。 uuid ( IP アドレスが使用される)ネットワーク内でユニークな文字列型の識別子を生成するために、 128 ビットの UUID アルゴリズムを使用します。 UUID は長さ 32 の 16 進数字の文字列としてエ ンコードされます。 guid MS SQL サーバーと MySQL でデータベースが生成する GUID 文字列を使用します。 native 使用するデータベースの性能により identity 、 sequence 、 hilo のいずれかが選ばれま す。 assigned lets the application to assign an identifier to the object before save() is called. T his is the default strategy if no <generator> element is specified. select あるユニークキーによる行の選択と主キーの値の復元により、データベーストリガが割り当てた 主キーを取得します。 foreign uses the identifier of another associated object. Usually used in conjunction with a <one-toone> primary key association. sequence-identity 実際の値の生成のためにデータベースシーケンスを使用する特別なシーケンス生成戦略ですが、 JDBC3 getGeneratedKeys と結びついて、 INSERT 文の実行の一部として生成された識別子の 値を実際に返します。この戦略は JDK 1.4 を対象とする Oracle 10g のドライバでサポートされ ていることが知られています。これらの INSERT 文でのコメントは Oracle のドライバのバグに より無効にされていることに注意してください。 72 第5章 基本的な O/R マッピング 5.1.4 .2. Hi/lo アルゴリズム T he hilo and seqhilo generators provide two alternate implementations of the hi/lo algorithm, a favorite approach to identifier generation. T he first implementation requires a "special" database table to hold the next available "hi" value. T he second uses an Oracle-style sequence (where supported). <id name="id" type="long" column="cat_id"> <generator class="hilo"> <param name="table">hi_value</param> <param name="column">next_value</param> <param name="max_lo">100</param> </generator> </id> <id name="id" type="long" column="cat_id"> <generator class="seqhilo"> <param name="sequence">hi_value</param> <param name="max_lo">100</param> </generator> </id> Unfortunately, you can't use hilo when supplying your own Connection to Hibernate. When Hibernate is using an application server datasource to obtain connections enlisted with JT A, you must properly configure the hibernate.transaction.m anager_lookup_class. 5.1.4 .3. UUID アルゴリズム T he UUID contains: IP address, startup time of the JVM (accurate to a quarter second), system time and a counter value (unique within the JVM). It's not possible to obtain a MAC address or memory address from Java code, so this is the best we can do without using JNI. 5.1.4 .4 . 識別子カラムとシーケンス 識別子カラムをサポートしているデータベース(DB2, MySQL, Sybase, MS SQL)では、 identity キー 生成が使えます。シーケンスをサポートするデータベース(DB2, Oracle, PostgreSQL, Interbase, McKoi, SAP DB)では、 sequence スタイルのキー生成が使えます。どちらの戦略も、新しいオブジェクトを挿 入するために、 SQL クエリを2つ必要とします。 <id name="id" type="long" column="person_id"> <generator class="sequence"> <param name="sequence">person_id_sequence</param> </generator> </id> <id name="id" type="long" column="person_id" unsaved-value="0"> <generator class="identity"/> </id> クロスプラットフォームの開発では、native 戦略は identity 、 sequence 、 hilo 戦略の中から1 つを選択しますが、これは使用しているデータベースの能力に依存します。 5.1.4 .5. 識別子の割り当て If you want the application to assign identifiers (as opposed to having Hibernate generate them), you 73 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide may use the assigned generator. T his special generator will use the identifier value already assigned to the object's identifier property. T his generator is used when the primary key is a natural key instead of a surrogate key. T his is the default behavior if you do no specify a <generator> element. Choosing the assigned generator makes Hibernate use unsaved-value="undefined", forcing Hibernate to go to the database to determine if an instance is transient or detached, unless there is a version or timestamp property, or you define Interceptor.isUnsaved(). 5.1.4 .6. トリガにより割り当てられた主キー レガシースキーマのためにのみ指定します( Hibernate はトリガを使って DDL を生成しません)。 <id name="id" type="long" column="person_id"> <generator class="select"> <param name="key">socialSecurityNumber</param> </generator> </id> 上記の例の中で、クラスで自然キーとして定義された socialSecurityNum ber という名前のユニーク な値のプロパティと、値がトリガにより生成される person_id という名前の代理キーがあります。 5.1.5. composite-id <composite-id name="propertyName" class="ClassName" mapped="true|false" access="field|property|ClassName"> node="element-name|." <key-property name="propertyName" type="typename" column="column_name"/> <key-many-to-one name="propertyName class="ClassName" column="column_name"/> ...... </composite-id> For a table with a composite key, you may map multiple properties of the class as identifier properties. T he <com posite-id> element accepts <key-property> property mappings and <key-m any-toone> mappings as child elements. <composite-id> <key-property name="medicareNumber"/> <key-property name="dependent"/> </composite-id> 複合識別子の等価性を実装するためには、永続クラスが equals() と hashCode() をオーバーライド しなければなりません 。 また Serializable も実装しなければいけません。 Unfortunately, this approach to composite identifiers means that a persistent object is its own identifier. T here is no convenient "handle" other than the object itself. You must instantiate an instance of the persistent class itself and populate its identifier properties before you can load() the persistent state associated with a composite key. We call this approach an embedded composite identifier, and discourage it for serious applications. A second approach is what we call a mapped composite identifier, where the identifier properties named inside the <com posite-id> element are duplicated on both the persistent class and a separate 74 第5章 基本的な O/R マッピング identifier class. <composite-id class="MedicareId" mapped="true"> <key-property name="medicareNumber"/> <key-property name="dependent"/> </composite-id> この例では、複合識別子クラス( MedicareId )とエンティティクラス自身の両方が、 m edicareNum ber と dependent という名前のプロパティを持ちます。識別子クラスは、 equals() と hashCode() をオーバライドし、 Serializable を実装しなくてはなりません。この方法には、明 らかにコードが重複するという不都合があります。 次の属性はマッピングした複合識別子を指定するために使用します: m apped (オプション、デフォルトは false ): マッピングした複合識別子が使用されることと、包含 されたプロパティのマッピングが、エンティティクラスと複合識別子クラスの両方を参照することを 示します。 class (オプション、ただしマッピングした複合識別子には必須): 複合識別子として使用するクラス。 We will describe a third, even more convenient approach where the composite identifier is implemented as a component class in 「複合識別子としてのコンポーネント」. T he attributes described below apply only to this alternative approach: nam e (オプション、このアプローチでは必須): 複合識別子を保持するコンポーネントタイプのプロパ ティ(9章を参照してください)。 access (オプション - デフォルトは property ): Hibernate がプロパティの値にアクセスするために 使用すべき戦略。 class (オプション - デフォルトはリフレクションにより決定されるプロパティの型): 複合識別子 として使われるコンポーネントのクラス(次の節を見てください)。 この3つ目の方法は 識別子コンポーネント と呼び、ほとんどすべてのアプリケーションに対して推奨する 方法です。 5.1.6. discriminator T he <discrim inator> element is required for polymorphic persistence using the table-per-classhierarchy mapping strategy and declares a discriminator column of the table. T he discriminator column contains marker values that tell the persistence layer what subclass to instantiate for a particular row. A restricted set of types may be used: string, character, integer, byte, short, boolean, yes_no, true_false. <discriminator column="discriminator_column" type="discriminator_type" force="true|false" insert="true|false" formula="arbitrary sql expression" /> colum n(オプション - デフォルトは class ): 識別カラムの名前。 type (オプション - デフォルトは string ): Hibernate の型を示す名前。 force (optional - defaults to false) "force" Hibernate to specify allowed discriminator values even when retrieving all instances of the root class. 75 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide insert (オプション - デフォルトは true ): もし識別カラムがマッピングする複合識別子の一部なら ば、 false と設定してください。 (Hibernate に SQL の INSERT 内のカラムを含ませないよう伝えま す。) form ula (オプション) 型が評価されるときに実行される任意の SQL 式。コンテンツベースの識別を可能 にします。 Actual values of the discriminator column are specified by the discrim inator-value attribute of the <class> and <subclass> elements. T he force attribute is (only) useful if the table contains rows with "extra" discriminator values that are not mapped to a persistent class. T his will not usually be the case. form ula 属性を使うと、行の型を評価するために任意の SQL 式を宣言できます: <discriminator formula="case when CLASS_TYPE in ('a', 'b', 'c') then 0 else 1 end" type="integer"/> 5.1.7. version(オプション) T he <version> element is optional and indicates that the table contains versioned data. T his is particularly useful if you plan to use long transactions (see below). <version column="version_column" name="propertyName" type="typename" access="field|property|ClassName" unsaved-value="null|negative|undefined" generated="never|always" insert="true|false" node="element-name|@attribute-name|element/@attribute|." /> colum n (オプション - デフォルトはプロパティ名): バージョン番号を保持するカラムの名前。 nam e :永続クラスのプロパティの名前。 type (オプション - デフォルトは integer ):バージョン番号の型。 access (オプション - デフォルトは property ): Hibernate がプロパティの値にアクセスするために使用 すべき戦略。 unsaved-value (オプション - デフォルトは undefined ): インスタンスが新しくインスタンス化 されたことを示す (セーブされていないことを示す) バージョンプロパティの値。以前の Session で セーブまたはロードされた一時的なインスタンスと区別するために使います。 ( undefined は識別子 プロパティの値が使われることを指定します。) generated (optional - defaults to never): Specifies that this version property value is actually generated by the database. See the discussion of 「生成プロパティ」 generated properties. insert (オプション - デフォルトは true ): SQLの insert 文にバージョンカラムを含めるべきかどうかを 指定します。もしデータベースカラムのデフォルト値が 0 と定義されるときには、 false に設定すると 良いでしょう。 76 第5章 基本的な O/R マッピング バージョン番号は Hibernate の long 、 integer 、 short 、 tim estam p 、 calendar 型のいずれ かです。 バージョンやタイムスタンプのプロパティは、分離されたインスタンスに対して null であってはなりませ ん。そのためどのような unsaved-value 戦略が指定されても、 Hibernate は null のバージョンやタイ ムスタンプを持ったすべてのインスタンスを、一時的なものであると判断します。 null を許容するバー ジョンやタイムスタンプのプロパティを定義することは、 Hibernate において過渡的に一時オブジェクト とすることを防ぐ簡単な方法です。特に識別子の割り当てや複合キーを使用しているときには特に有用で す。 5.1.8. timestamp(オプション) T he optional <tim estam p> element indicates that the table contains timestamped data. T his is intended as an alternative to versioning. T imestamps are by nature a less safe implementation of optimistic locking. However, sometimes the application might use the timestamps in other ways. <timestamp column="timestamp_column" name="propertyName" access="field|property|ClassName" unsaved-value="null|undefined" source="vm|db" generated="never|always" node="element-name|@attribute-name|element/@attribute|." /> colum n(オプション - デフォルトはプロパティ名): タイムスタンプを保持するカラムの名前。 nam e : 永続クラスである Java の Date型または T im estam p 型 の、 JavaBeans スタイルプロパティ の名前。 access (オプション - デフォルトは property ): Hibernate がプロパティの値にアクセスするために使用 すべき戦略。 unsaved-value (オプション - デフォルトは null ): インスタンスが新しくインスタンス化された (セーブされていない)ことを示すバージョンプロパティの値。以前の Session でセーブまたはロードさ れた一時的なインスタンスと区別するために使われます。 ( undefined と指定すると、識別子プロパ ティの値が使われます。) source (optional - defaults to vm ): From where should Hibernate retrieve the timestamp value? From the database, or from the current JVM? Database-based timestamps incur an overhead because Hibernate must hit the database in order to determine the "next value", but will be safer for use in clustered environments. Note also, that not all Dialect s are known to support retrieving of the database's current timestamp, while others might be unsafe for usage in locking due to lack of precision (Oracle 8 for example). generated (optional - defaults to never): Specifies that this timestamp property value is actually generated by the database. See the discussion of 「生成プロパティ」 generated properties. Note that <tim estam p> is equivalent to <version type="tim estam p">. And <tim estam p source="db"> is equivalent to <version type="dbtim estam p"> 5.1.9. property T he <property> element declares a persistent, JavaBean style property of the class. 77 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide <property name="propertyName" column="column_name" type="typename" update="true|false" insert="true|false" formula="arbitrary SQL expression" access="field|property|ClassName" lazy="true|false" unique="true|false" not-null="true|false" optimistic-lock="true|false" generated="never|insert|always" node="element-name|@attribute-name|element/@attribute|." index="index_name" unique_key="unique_key_id" length="L" precision="P" scale="S" /> nam e: 小文字で始まるプロパティ名。 colum n (optional - defaults to the property name): the name of the mapped database table column. T his may also be specified by nested <colum n> element(s). type(オプション): Hibernate の型を示す名前。 update, insert (optional - defaults to true) : specifies that the mapped columns should be included in SQL UPDAT E and/or INSERT statements. Setting both to false allows a pure "derived" property whose value is initialized from some other property that maps to the same colum(s) or by a trigger or other application. form ula(オプション): 計算 プロパティのための値を定義する SQL 式。計算されたプロパティは自 身のカラムへのマッピングがありません。 access (オプション - デフォルトは property ): Hibernate がプロパティの値にアクセスするために使用 すべき戦略。 lazy (オプション - デフォルトは false ): インスタンス変数に最初にアクセスしたときに、プロパティ を遅延して取得するよう指定します。 (バイトコード実装を作成する時間が必要になります)。 unique (オプション):カラムにユニーク制約をつける DDL の生成を可能にします。また、 propertyref のターゲットとすることもできます。 not-null (オプション):カラムに null 値を許可する DDL の生成を可能にします。 optim istic-lock (オプション - デフォルトは true ): このプロパティの更新に楽観ロックの取得を要 求するかどうかを指定します。言い換えれば、このプロパティがダーティであるときにバージョンを増や すべきかを決定します。 generated (optional - defaults to never): Specifies that this property value is actually generated by the database. See the discussion of 「生成プロパティ」 generated properties. typename には以下の値が可能です: 1. Hibernate の基本型の名前(例 integer, string, character, date, tim estam p, 78 第5章 基本的な O/R マッピング float, binary, serializable, object, blob )。 2. デフォルトの基本型の Java クラス名 (例 int, float, char, java.lang.String, java.util.Date, java.lang.Integer, java.sql.Clob )。 3. シリアライズ可能な Java クラスの名前。 4. カスタム型のクラス名(例 com .illflow.type.MyCustom T ype )。 型を指定しなければ、 Hibernate は正しい Hibernate の型を推測するために、指定されたプロパティに対 してリフレクションを使います。 Hibernate はルール2, 3, 4をその順序に使い、 getter プロパティの返り 値のクラスの名前を解釈しようとします。しかしこれで常に十分であるとは限りません。場合によって は、 type 属性が必要な場合があります。 (例えば Hibernate.DAT E と Hibernate.T IMEST AMP を 区別するため、またはカスタム型を指定するためなどです。) T he access attribute lets you control how Hibernate will access the property at runtime. By default, Hibernate will call the property get/set pair. If you specify access="field", Hibernate will bypass the get/set pair and access the field directly, using reflection. You may specify your own strategy for property access by naming a class that implements the interface org.hibernate.property.PropertyAccessor. 特に強力な特徴は生成プロパティです。これらのプロパティは当然読み取り専用であり、プロパティの値 はロード時に計算されます。計算を SQL 式として宣言すると、このプロパティはインスタンスをロード する SQL クエリの SELECT 句のサブクエリに変換されます: <property name="totalPrice" formula="( SELECT SUM (li.quantity*p.price) FROM LineItem li, Product p WHERE li.productId = p.productId AND li.customerId = customerId AND li.orderNumber = orderNumber )"/> Note that you can reference the entities own table by not declaring an alias on a particular column (custom erId in the given example). Also note that you can use the nested <form ula> mapping element if you don't like to use the attribute. 5.1.10. many-to-one 他の永続クラスへの通常の関連は m any-to-one 要素を使って定義します。リレーショナルモデルは多対 一関連です。つまりあるテーブルの外部キーは、ターゲットとなるテーブルの主キーカラムを参照してい ます。 79 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide <many-to-one name="propertyName" column="column_name" class="ClassName" cascade="cascade_style" fetch="join|select" update="true|false" insert="true|false" property-ref="propertyNameFromAssociatedClass" access="field|property|ClassName" unique="true|false" not-null="true|false" optimistic-lock="true|false" lazy="proxy|no-proxy|false" not-found="ignore|exception" entity-name="EntityName" formula="arbitrary SQL expression" node="element-name|@attribute-name|element/@attribute|." embed-xml="true|false" index="index_name" unique_key="unique_key_id" foreign-key="foreign_key_name" /> nam e:プロパティ名。 colum n (optional): T he name of the foreign key column. T his may also be specified by nested <colum n> element(s). class(オプション - デフォルトはリフレクションにより決定されるプロパティの型): 関連クラスの名 前。 cascade(オプション): どの操作を、親オブジェクトから関連オブジェクトへとカスケードさせるか を指定します。 fetch(オプション - デフォルトは select ): 外部結合フェッチと順次選択フェッチ(sequential select fetch)のどちらかを選択します。 update, insert (optional - defaults to true) specifies that the mapped columns should be included in SQL UPDAT E and/or INSERT statements. Setting both to false allows a pure "derived" association whose value is initialized from some other property that maps to the same colum(s) or by a trigger or other application. property-ref(オプション): この外部キーに結合された関連クラスのプロパティ名。何も指定しな ければ、関連クラスの主キーが使われます。 access (オプション - デフォルトは property ): Hibernate がプロパティの値にアクセスするために使用 すべき戦略。 unique(オプション): 外部キーカラムに対してユニーク制約をつけた DDL の生成を可能にします。 また、 property-ref のターゲットにすることもできます。これにより関連の多重度を効果的に一対一 にします。 not-null (オプション): 外部キーカラムに対して、 null 値を許可する DDL の生成を可能にします。 optim istic-lock (オプション - デフォルトは true ): このプロパティの更新に楽観的ロックの取得を 要求するかどうかを指定します。言い換えれば、このプロパティがダーティであるときにバージョンを増 80 第5章 基本的な O/R マッピング やすべきかを決定します。 lazy (optional - defaults to proxy): By default, single point associations are proxied. lazy="noproxy" specifies that the property should be fetched lazily when the instance variable is first accessed (requires build-time bytecode instrumentation). lazy="false" specifies that the association will always be eagerly fetched. not-found (オプション - デフォルトは exception ): 欠落した行を参照する外部キーをどのように扱う かを指定します。 ignore は欠落した行を null 関連として扱います。 entity-nam e (オプション):関連したクラスのエンティティ名。 form ula (オプション): 計算された 外部キーに対して値を定義する SQL 式 Setting a value of the cascade attribute to any meaningful value other than none will propagate certain operations to the associated object. T he meaningful values are the names of Hibernate's basic operations, persist, m erge, delete, save-update, evict, replicate, lock, refresh, as well as the special values delete-orphan and all and comma-separated combinations of operation names, for example, cascade="persist,m erge,evict" or cascade="all,deleteorphan". See 「連鎖的な永続化」 for a full explanation. Note that single valued associations (many-toone and one-to-one associations) do not support orphan delete. 典型的な m any-to-one 宣言は次のようにシンプルです。: <many-to-one name="product" class="Product" column="PRODUCT_ID"/> T he property-ref attribute should only be used for mapping legacy data where a foreign key refers to a unique key of the associated table other than the primary key. T his is an ugly relational model. For example, suppose the Product class had a unique serial number, that is not the primary key. (T he unique attribute controls Hibernate's DDL generation with the SchemaExport tool.) <property name="serialNumber" unique="true" type="string" column="SERIAL_NUMBER"/> 以下のように OrderItem に対してマッピングを使えます: <many-to-one name="product" property-ref="serialNumber" column="PRODUCT_SERIAL_NUMBER"/> しかし、これは決して推奨できません。 If the referenced unique key comprises multiple properties of the associated entity, you should map the referenced properties inside a named <properties> element. もし参照したユニークキーがコンポーネントのプロパティである場合は、プロパティのパスを指定できま す: <many-to-one name="owner" property-ref="identity.ssn" column="OWNER_SSN"/> 5.1.11. one-to-one 他の永続クラスへの一対一関連は、one-to-one 要素で定義します。 81 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide <one-to-one name="propertyName" class="ClassName" cascade="cascade_style" constrained="true|false" fetch="join|select" property-ref="propertyNameFromAssociatedClass" access="field|property|ClassName" formula="any SQL expression" lazy="proxy|no-proxy|false" entity-name="EntityName" node="element-name|@attribute-name|element/@attribute|." embed-xml="true|false" foreign-key="foreign_key_name" /> nam e:プロパティ名。 class(オプション - デフォルトはリフレクションにより決定されるプロパティの型): 関連クラスの名 前。 cascade(オプション): 親オブジェクトから関連オブジェクトへ、どの操作をカスケードするかを指 定します。 constrained(オプション): マッピングされたテーブルの主キーに対する外部キー制約が、関連クラ スのテーブルを参照することを指定します。このオプションは save() と delete() がカスケードされ る順序に影響し、そして関連がプロキシされるかどうかにも影響します (そしてスキーマエクスポート ツールにも使われます)。 fetch(オプション - デフォルトは select ): 外部結合フェッチと順次選択フェッチ(sequential select fetch)のどちらかを選択します。 property-ref(オプション): このクラスの主キーに結合された関連クラスのプロパティ名。指定さ れなければ、関連クラスの主キーが使われます。 access (オプション - デフォルトは property ): Hibernate がプロパティの値にアクセスするために使用 すべき戦略。 form ula (オプション): ほとんどすべての一対一関連はオーナーのエンティティの主キーへとマッピング されます。これ以外の稀な場合は、他のカラムや、複数のカラム、 SQL 構文を使った結合するための式 を指定できます。(例は org.hibernate.test.onetooneform ula を参照してください。) lazy (optional - defaults to proxy): By default, single point associations are proxied. lazy="noproxy" specifies that the property should be fetched lazily when the instance variable is first accessed (requires build-time bytecode instrumentation). lazy="false" specifies that the association will always be eagerly fetched. Note that if constrained="false", proxying is impossible and Hibernate will eager fetch the association! entity-nam e (オプション):関連したクラスのエンティティ名。 一対一関連には2種類あります: 主キー関連 ユニーク外部キー関連 Primary key associations don't need an extra table column; if two rows are related by the association 82 第5章 基本的な O/R マッピング then the two table rows share the same primary key value. So if you want two objects to be related by a primary key association, you must make sure that they are assigned the same identifier value! 主キー関連を行うためには、以下のマッピングを Em ployee と Person のそれぞれに追加してくださ い。 <one-to-one name="person" class="Person"/> <one-to-one name="employee" class="Employee" constrained="true"/> ここで、 PERSON と EMPLOYEE テーブルの関係する行の主キーが同じであることを確実にしなければ いけません。ここでは、 foreign という特殊な Hibernate 識別子生成戦略を使います: <class name="person" table="PERSON"> <id name="id" column="PERSON_ID"> <generator class="foreign"> <param name="property">employee</param> </generator> </id> ... <one-to-one name="employee" class="Employee" constrained="true"/> </class> Em ployee インスタンスが、 Person の em ployee プロパティで参照されるように、新しくセーブさ れた Person のインスタンスには同じ主キーの値が代入されます。新しくセーブする Person インスタ ンスは、その Person の em ployee プロパティが参照する Em ployee インスタンスとして同じ主キー が割り当てられます。 もう1つの方法として、 Em ployee から Person へのユニーク制約を使った外部キー関連は以下のよう に表現されます: <many-to-one name="person" class="Person" column="PERSON_ID" unique="true"/> そしてこの関連は、以下の記述を Person のマッピングに追加することで双方向にすることができます: <one-to-one name"employee" class="Employee" property-ref="person"/> 5.1.12. natural-id <natural-id mutable="true|false"/> <property ... /> <many-to-one ... /> ...... </natural-id> Even though we recommend the use of surrogate keys as primary keys, you should still try to identify natural keys for all entities. A natural key is a property or combination of properties that is unique and non-null. If it is also immutable, even better. Map the properties of the natural key inside the <naturalid> element. Hibernate will generate the necessary unique key and nullability constraints, and your mapping will be more self-documenting. エンティティの自然キープロパティの比較には、 equals() と hashCode() の実装を強くお勧めしま 83 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide す。 このマッピングは自然主キーを使ったエンティティでの使用を意図していません。 m utable (オプション、 デフォルトは false ): デフォルトでは、自然識別子プロパティは不変(定数) と想定されています。 5.1.13. component, dynamic-component T he <com ponent> element maps properties of a child object to columns of the table of a parent class. Components may, in turn, declare their own properties, components or collections. See "Components" below. <component name="propertyName" class="className" insert="true|false" update="true|false" access="field|property|ClassName" lazy="true|false" optimistic-lock="true|false" unique="true|false" node="element-name|." > <property ...../> <many-to-one .... /> ........ </component> nam e:プロパティ名。 class (オプション - デフォルトはリフレクションにより決定されるプロパティの型): コンポーネン ト(子)クラスの名前。 insert:マッピングされたカラムが SQL の INSERT に現れるようにするかどうかを指定します。 update:マッピングされたカラムが SQL の UPDAT E に現れるようにするかどうかを指定します。 access (オプション - デフォルトは property ): Hibernate がプロパティの値にアクセスするために使用 すべき戦略。 lazy (オプション - デフォルトは false ): インスタンス変数に最初にアクセスしたときに、コンポーネ ントを遅延してフェッチするよう指定します。 (バイトコード実装を作成する時間が必要になります) optim istic-lock (オプション - デフォルトは true ): このプロパティの更新に、楽観ロックの取得を 要求するかどうかを指定します。言い換えれば、このプロパティがダーティであるときにバージョンを増 やすべきかを決定します。 unique (オプション - デフォルトは false ): コンポーネントのすべてのマッピングするカラムに、ユ ニーク制約が存在するかを指定します。 T he child <property> tags map properties of the child class to table columns. T he <com ponent> element allows a <parent> subelement that maps a property of the component class as a reference back to the containing entity. 84 第5章 基本的な O/R マッピング T he <dynam ic-com ponent> element allows a Map to be mapped as a component, where the property names refer to keys of the map, see 「動的コンポーネント」. 5.1.14. properties T he <properties> element allows the definition of a named, logical grouping of properties of a class. T he most important use of the construct is that it allows a combination of properties to be the target of a property-ref. It is also a convenient way to define a multi-column unique constraint. <properties name="logicalName" insert="true|false" update="true|false" optimistic-lock="true|false" unique="true|false" > <property ...../> <many-to-one .... /> ........ </properties> nam e : グルーピングの論理名。実際のプロパティ名では ありません 。 insert:マッピングされたカラムが SQL の INSERT に現れるようにするかどうかを指定します。 update:マッピングされたカラムが SQL の UPDAT E に現れるようにするかどうかを指定します。 optim istic-lock (オプション - デフォルトは true ): これらのプロパティの更新に楽観的ロックの取 得を要求するかどうかを指定します。言い換えれば、このプロパティがダーティであるときにバージョン を増やすべきかを決定します。 unique (オプション - デフォルトは false ): コンポーネントのすべてのマッピングするカラムに、ユ ニーク制約が存在するかを指定します。 For example, if we have the following <properties> mapping: <class name="Person"> <id name="personNumber"/> ... <properties name="name" unique="true" update="false"> <property name="firstName"/> <property name="initial"/> <property name="lastName"/> </properties> </class> 主キーの代わりに Person テーブルのユニークキーへの参照を持つ、レガシーデータの関連を持つかもし れません。: 85 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide We don't recommend the use of this kind of thing outside the context of mapping legacy data. 5.1.15. subclass Finally, polymorphic persistence requires the declaration of each subclass of the root persistent class. For the table-per-class-hierarchy mapping strategy, the <subclass> declaration is used. <subclass name="ClassName" discriminator-value="discriminator_value" proxy="ProxyInterface" lazy="true|false" dynamic-update="true|false" dynamic-insert="true|false" entity-name="EntityName" node="element-name" extends="SuperclassName"> <property .... /> ..... </subclass> nam e:サブクラスの完全修飾されたクラス名。 discrim inator-value(オプション - デフォルトはクラス名): 個々のサブクラスを区別するための 値。 proxy (オプション): 遅延初期化プロキシに使用するクラスやインターフェースを指定します。 lazy (optional, defaults to true): Setting lazy="false" disables the use of lazy fetching. Each subclass should declare its own persistent properties and subclasses. <version> and <id> properties are assumed to be inherited from the root class. Each subclass in a heirarchy must define a unique discrim inator-value. If none is specified, the fully qualified Java class name is used. For information about inheritance mappings, see 9章継承マッピング. 5.1.16. joined-subclass Alternatively, each subclass may be mapped to its own table (table-per-subclass mapping strategy). Inherited state is retrieved by joining with the table of the superclass. We use the <joinedsubclass> element. 86 第5章 基本的な O/R マッピング <joined-subclass name="ClassName" table="tablename" proxy="ProxyInterface" lazy="true|false" dynamic-update="true|false" dynamic-insert="true|false" schema="schema" catalog="catalog" extends="SuperclassName" persister="ClassName" subselect="SQL expression" entity-name="EntityName" node="element-name"> <key .... > <property .... /> ..... </joined-subclass> nam e:サブクラスの完全修飾されたクラス名。 table :サブクラステーブルの名前。 proxy (オプション): 遅延初期化プロキシに使用するクラスやインターフェースを指定します。 lazy (optional, defaults to true): Setting lazy="false" disables the use of lazy fetching. No discriminator column is required for this mapping strategy. Each subclass must, however, declare a table column holding the object identifier using the <key> element. T he mapping at the start of the chapter would be re-written as: 87 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="eg"> <class name="Cat" table="CATS"> <id name="id" column="uid" type="long"> <generator class="hilo"/> </id> <property name="birthdate" type="date"/> <property name="color" not-null="true"/> <property name="sex" not-null="true"/> <property name="weight"/> <many-to-one name="mate"/> <set name="kittens"> <key column="MOTHER"/> <one-to-many class="Cat"/> </set> <joined-subclass name="DomesticCat" table="DOMESTIC_CATS"> <key column="CAT"/> <property name="name" type="string"/> </joined-subclass> </class> <class name="eg.Dog"> <!-- mapping for Dog could go here --> </class> </hibernate-mapping> For information about inheritance mappings, see 9章継承マッピング. 5.1.17. union-subclass A third option is to map only the concrete classes of an inheritance hierarchy to tables, (the table-perconcrete-class strategy) where each table defines all persistent state of the class, including inherited state. In Hibernate, it is not absolutely necessary to explicitly map such inheritance hierarchies. You can simply map each class with a separate <class> declaration. However, if you wish use polymorphic associations (e.g. an association to the superclass of your hierarchy), you need to use the <unionsubclass> mapping. 88 第5章 基本的な O/R マッピング <union-subclass name="ClassName" table="tablename" proxy="ProxyInterface" lazy="true|false" dynamic-update="true|false" dynamic-insert="true|false" schema="schema" catalog="catalog" extends="SuperclassName" abstract="true|false" persister="ClassName" subselect="SQL expression" entity-name="EntityName" node="element-name"> <property .... /> ..... </union-subclass> nam e:サブクラスの完全修飾されたクラス名。 table :サブクラステーブルの名前。 proxy (オプション): 遅延初期化プロキシに使用するクラスやインターフェースを指定します。 lazy (optional, defaults to true): Setting lazy="false" disables the use of lazy fetching. このマッピング戦略では識別カラムやキーカラムは必要ありません。 For information about inheritance mappings, see 9章継承マッピング. 5.1.18. join Using the <join> element, it is possible to map properties of one class to several tables, when there's a 1-to-1 relationship between the tables. <join table="tablename" schema="owner" catalog="catalog" fetch="join|select" inverse="true|false" optional="true|false"> <key ... /> <property ... /> ... </join> table :結合したテーブルの名前 schem a (optional): Override the schema name specified by the root <hibernate-m apping> element. catalog (optional): Override the catalog name specified by the root <hibernate-m apping> element. 89 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide fetch (optional - defaults to join): If set to join, the default, Hibernate will use an inner join to retrieve a <join> defined by a class or its superclasses and an outer join for a <join> defined by a subclass. If set to select then Hibernate will use a sequential select for a <join> defined on a subclass, which will be issued only if a row turns out to represent an instance of the subclass. Inner joins will still be used to retrieve a <join> defined by the class and its superclasses. inverse (オプション - デフォルトは false ): もし可能であれば、 Hibernate はこの結合で定義されて いるプロパティに対し挿入や更新を行いません。 optional (オプション - デフォルトは false ): もし可能であれば、 Hibernate はこの結合で定義された プロパティが null でない場合にのみ行を挿入し、そのプロパティの検索には常に外部結合を使用します。 例えば人のアドレスの情報を分離したテーブルにマッピングすることが可能です (すべてのプロパティに 対して値型のセマンティクスを保持します): <class name="Person" table="PERSON"> <id name="id" column="PERSON_ID">...</id> <join table="ADDRESS"> <key column="ADDRESS_ID"/> <property name="address"/> <property name="zip"/> <property name="country"/> </join> ... この特徴はしばしばレガシーデータモデルに対してのみ有用ですが、クラスよりも少ないテーブルと、き めの細かいドメインモデルを推奨します。しかし後で説明するように、1つのクラス階層で継承のマッピ ング戦略を切り替える時には有用です。 5.1.19. key We've seen the <key> element crop up a few times now. It appears anywhere the parent mapping element defines a join to a new table, and defines the foreign key in the joined table, that references the primary key of the original table. <key column="columnname" on-delete="noaction|cascade" property-ref="propertyName" not-null="true|false" update="true|false" unique="true|false" /> colum n (optional): T he name of the foreign key column. T his may also be specified by nested <colum n> element(s). on-delete (オプション, デフォルトは noaction): 外部キー制約がデータベースレベルでカスケード削 除が可能かどうかを指定します。 property-ref (オプション): オリジナルテーブルの主キーではないカラムを参照する外部キーを指定し ます (レガシーデータに対して提供されます)。 90 第5章 基本的な O/R マッピング not-null (オプション): 外部キーカラムが null 値を許容しないことを指定します (このことは外部キーが 主キーの一部であることを暗黙的に示します)。 update (オプション): 外部キーを決して更新してはならないことを指定します (このことは外部キーが主 キーの一部であることを暗黙的に示します)。 unique (オプション): 外部キーがユニーク制約を持つべきであることを指定します (このことは外部キー が主キーの一部であることを暗黙的に示します)。 We recommend that for systems where delete performance is important, all keys should be defined ondelete="cascade", and Hibernate will use a database-level ON CASCADE DELET E constraint, instead of many individual DELET E statements. Be aware that this feature bypasses Hibernate's usual optimistic locking strategy for versioned data. T he not-null and update attributes are useful when mapping a unidirectional one to many association. If you map a unidirectional one to many to a non-nullable foreign key, you must declare the key column using <key not-null="true">. 5.1.20. column と formula 要素 Any mapping element which accepts a colum n attribute will alternatively accept a <colum n> subelement. Likewise, <form ula> is an alternative to the form ula attribute. <column name="column_name" length="N" precision="N" scale="N" not-null="true|false" unique="true|false" unique-key="multicolumn_unique_key_name" index="index_name" sql-type="sql_type_name" check="SQL expression" default="SQL expression"/> <formula>SQL expression</formula> 同じプロパティや関連のマッピングの中で、 colum n と form ula 属性を組み合わせることができま す。例えば、特殊な結合条件などです。 <many-to-one name="homeAddress" class="Address" insert="false" update="false"> <column name="person_id" not-null="true" length="10"/> <formula>'MAILING'</formula> </many-to-one> 5.1.21. import Suppose your application has two persistent classes with the same name, and you don't want to specify the fully qualified (package) name in Hibernate queries. Classes may be "imported" explicitly, rather than relying upon auto-im port="true". You may even import classes and interfaces that are not explicitly mapped. <import class="java.lang.Object" rename="Universe"/> 91 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide <import class="ClassName" rename="ShortName" /> class: Java クラスの完全修飾されたクラス名。 renam e (オプション - デフォルトは修飾されていないクラス名): クエリ言語で使われる名前。 5.1.22. any T here is one further type of property mapping. T he <any> mapping element defines a polymorphic association to classes from multiple tables. T his type of mapping always requires more than one column. T he first column holds the type of the associated entity. T he remaining columns hold the identifier. It is impossible to specify a foreign key constraint for this kind of association, so this is most certainly not meant as the usual way of mapping (polymorphic) associations. You should use this only in very special cases (eg. audit logs, user session data, etc). m eta-type により、アプリケーションはカスタム型を指定できます。このカスタム型はデータベースカ ラムの値を、 id-type で指定した型の識別子プロパティを持った永続クラスへマッピングします。 meta-type の値からクラス名へのマッピングを指定しなければなりません。 <any name="being" id-type="long" meta-type="string"> <meta-value value="TBL_ANIMAL" class="Animal"/> <meta-value value="TBL_HUMAN" class="Human"/> <meta-value value="TBL_ALIEN" class="Alien"/> <column name="table_name"/> <column name="id"/> </any> <any name="propertyName" id-type="idtypename" meta-type="metatypename" cascade="cascade_style" access="field|property|ClassName" optimistic-lock="true|false" > <meta-value ... /> <meta-value ... /> ..... <column .... /> <column .... /> ..... </any> nam e: プロパティ名。 id-type: 識別子の型。 m eta-type(オプション - デフォルトは string ): ディスクリミネータマッピングで許された型。 cascade(オプション - デフォルトは none ): カスケードのスタイル。 access (オプション - デフォルトは property ): Hibernate がプロパティの値にアクセスするために使用 すべき戦略。 92 第5章 基本的な O/R マッピング optim istic-lock (オプション - デフォルトは true ): このプロパティの更新に楽観ロックの取得を要 求するかどうかを指定します。言い換えれば、このプロパティがダーティであるときにバージョンを増や すべきかを定義します。 5.2. Hibernate の 型 5.2.1. エンティティと値 永続サービスに関わる様々な Java 言語レベルのオブジェクトの振る舞いを理解するためには、オブジェ クトを2つのグループに分ける必要があります: エンティティ はエンティティへの参照を保持する、他のすべてのオブジェクトから独立して存在します。 参照されないオブジェクトがガベージコレクトされてしまう性質を持つ通常の Java モデルと、これを比 べてみてください。(親エンティティから子へ、セーブと削除が カスケード されうることを除いて)エ ンティティは明示的にセーブまたは削除されなければなりません。これは到達可能性によるオブジェクト 永続化の ODMG モデルとは異なっています。大規模なシステムでアプリケーションオブジェクトが普通 どのように使われるかにより密接に対応します。エンティティは循環と参照の共有をサポートします。ま たそれらはバージョン付けすることもできます。 An entity's persistent state consists of references to other entities and instances of value types. Values are primitives, collections (not what's inside a collection), components and certain immutable objects. Unlike entities, values (in particular collections and components) are persisted and deleted by reachability. Since value objects (and primitives) are persisted and deleted along with their containing entity they may not be independently versioned. Values have no independent identity, so they cannot be shared by two entities or collections. Up until now, we've been using the term "persistent class" to refer to entities. We will continue to do that. Strictly speaking, however, not all user-defined classes with persistent state are entities. A component is a user defined class with value semantics. A Java property of type java.lang.String also has value semantics. Given this definition, we can say that all types (classes) provided by the JDK have value type semantics in Java, while user-defined types may be mapped with entity or value type semantics. T his decision is up to the application developer. A good hint for an entity class in a domain model are shared references to a single instance of that class, while composition or aggregation usually translates to a value type. We'll revisit both concepts throughout the documentation. T he challenge is to map the Java type system (and the developers' definition of entities and value types) to the SQL/database type system. T he bridge between both systems is provided by Hibernate: for entities we use <class>, <subclass> and so on. For value types we use <property>, <com ponent>, etc, usually with a type attribute. T he value of this attribute is the name of a Hibernate mapping type. Hibernate provides many mappings (for standard JDK value types) out of the box. You can write your own mapping types and implement your custom conversion strategies as well, as you'll see later. コレクションを除く組み込みの Hibernate の型はすべて、 null セマンティクスをサポートします。 5.2.2. 基本的な型 組み込みの 基本的なマッピング型 は大まかに以下のように分けられます。 integer, long, short, float, double, character, byte, boolean, yes_no, true_false Java のプリミティブやラッパークラスから適切な(ベンダー固有の) SQL カラム型への型マッ ピング。 boolean, yes_no と true_false は、すべて Java の boolean または 93 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide java.lang.Boolean の代替エンコードです。 string java.lang.String から VARCHAR (または Oracle の VARCHAR2 )への型マッピング。 date, tim e, tim estam p java.util.Date とそのサブクラスから SQL 型の DAT E 、 T IME 、 T IMEST AMP (またはそ れらと等価なもの) への型マッピング。 calendar, calendar_date java.util.Calendar から SQL 型 の「 T IMEST AMP 、 DAT E (またはそれらと等価なもの) への型マッピング。 big_decim al, big_integer java.m ath.BigDecim al と java.m ath.BigInteger から NUMERIC(または Oracle の NUMBER )への型マッピング。 locale, tim ezone, currency java.util.Locale 、 java.util.T im eZone 、 java.util.Currency から VARCHAR (または Oracle の VARCHAR2 )への型マッピング。 Locale と Currency のインスタンス は、それらの ISO コードにマッピングされます。 T im eZone のインスタンスは、それらの ID にマッピングされます。 class java.lang.Class から VARCHAR (または Oracle の VARCHAR2 )への型マッピング。 Class はその完全修飾された名前にマッピングされます。 binary バイト配列は、適切な SQL のバイナリ型にマッピングされます。 text 長い Java 文字列は、 SQL の CLOB または T EXT 型にマッピングされます。 serializable シリアライズ可能な Java 型は、適切な SQL のバイナリ型にマッピングされます。デフォルトで 基本型ではないシリアライズ可能な Java クラスやインターフェースの名前を指定することで、 Hibernate の型を serializable とすることもできます。 clob, blob JDBC クラス java.sql.Clob と java.sql.Blob に対する型マッピング。 blob や clob オブ ジェクトはトランザクションの外では再利用できないため、アプリケーションによっては不便か もしれません。(さらにはドライバサポートが一貫していません。) im m _date, im m _tim e, im m _tim estam p, im m _calendar, im m _calendar_date, 94 第5章 基本的な O/R マッピング im m _serializable, im m _binary ほとんどの場合に可変である Java の型に対する型マッピング。 Hibernate は不変な Java の型に 対しては最適化を行い、アプリケーションはそれを不変オブジェクトとして扱います。例えば im m _tim estam p としてマップしたインスタンスに対して、 Date.setT im e() を呼び出して はなりません。プロパティの値を変更しその変更を永続化するためには、アプリケーションはプ ロパティに対して新しい (同一でない) オブジェクトを割り当てなければなりません。 エンティティとコレクションのユニークな識別子は、 binary 、 blob 、 clob を除く、どんな基本型 でも構いません。(複合識別子でも構いません。以下を見てください。) 基本的な値型には、 org.hibernate.Hibernate で定義された T ype 定数がそれぞれあります。例え ば、 Hibernate.ST RING は string 型を表現しています。 5.2.3. カスタム型 開発者が独自の値型を作成することは、比較的簡単です。例えば、 java.lang.BigInteger 型のプロ パティを VARCHAR カラムに永続化したいかもしれません。 Hibernate はこのための組み込み型を用意し ていません。しかしカスタム型は、プロパティ(またはコレクションの要素)を1つのテーブルカラムに マッピングするのに制限はありません。そのため例えば、 java.lang.String 型の getNam e() / setNam e() Java プロパティを FIRST _NAME 、 INIT IAL 、 SURNAME カラムに永続化できます。 カスタム型を実装するには、 org.hibernate.UserT ype または org.hibernate.Com positeUserT ype を実装し、型の完全修飾された名前を使ってプロパティを定 義します。どのような種類のものが可能かを調べるには、 org.hibernate.test.DoubleStringT ype を確認してください。 <property name="twoStrings" type="org.hibernate.test.DoubleStringType"> <column name="first_string"/> <column name="second_string"/> </property> Notice the use of <colum n> tags to map a property to multiple columns. Com positeUserT ype 、 EnhancedUserT ype 、 UserCollectionT ype 、 UserVersionT ype インターフェースは、より特殊な使用法に対してのサポートを提供します。 You may even supply parameters to a UserT ype in the mapping file. T o do this, your UserT ype must implement the org.hibernate.usertype.Param eterizedT ype interface. T o supply parameters to your custom type, you can use the <type> element in your mapping files. <property name="priority"> <type name="com.mycompany.usertypes.DefaultValueIntegerType"> <param name="default">0</param> </type> </property> UserT ype は、引数として渡された Properties オブジェクトから、 default で指定したパラメータ に対する値を検索することができます。 If you use a certain UserT ype very often, it may be useful to define a shorter name for it. You can do this using the <typedef> element. T ypedefs assign a name to a custom type, and may also contain a list of default parameter values if the type is parameterized. 95 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide <typedef class="com.mycompany.usertypes.DefaultValueIntegerType" name="default_zero"> <param name="default">0</param> </typedef> <property name="priority" type="default_zero"/> プロパティのマッピングで型パラメータを使うことで、 typedef で提供されたパラメータをその都度オー バーライドすることが可能です。 Even though Hibernate's rich range of built-in types and support for components means you will very rarely need to use a custom type, it is nevertheless considered good form to use custom types for (nonentity) classes that occur frequently in your application. For example, a MonetaryAm ount class is a good candidate for a Com positeUserT ype, even though it could easily be mapped as a component. One motivation for this is abstraction. With a custom type, your mapping documents would be futureproofed against possible changes in your way of representing monetary values. 5.3. 1 つ の ク ラ ス に 1 つ 以 上 の マ ッ ピ ン グ ある永続クラスに、一つ以上のマッピングを提供することが出来ます。この場合、マッピングする2つの エンティティのインスタンスを明確にするために、 エンティティ名 を指定しなければなりません (デフォ ルトではエンティティ名はクラス名と同じです。)。 Hibernate では、永続オブジェクトを扱うとき、クエ リを書き込むとき、指定されたエンティティへの関連をマッピングするときに、エンティティ名を指定し なければなりません。 <class name="Contract" table="Contracts" entity-name="CurrentContract"> ... <set name="history" inverse="true" order-by="effectiveEndDate desc"> <key column="currentContractId"/> <one-to-many entity-name="HistoricalContract"/> </set> </class> <class name="Contract" table="ContractHistory" entity-name="HistoricalContract"> ... <many-to-one name="currentContract" column="currentContractId" entity-name="CurrentContract"/> </class> 関連が class の代わりに entity-nam e を使って、どのように指定されるのかに注目してください。 5.4. バ ッ ク ク ォ ー ト で 囲 ん だ SQL 識 別 子 マッピングドキュメントでテーブルやカラムの名前をバッククォートで囲むことで、 Hibernate で生成さ れた SQL 中の識別子を引用させることができます。 Hibernate は SQL の Dialect に対応する、正しい 引用スタイルを使います(普通はダブルクォートですが、 SQL Server ではかぎ括弧、 MySQL ではバッ ククォートです)。 96 第5章 基本的な O/R マッピング <class name="LineItem" table="`Line Item`"> <id name="id" column="`Item Id`"/><generator class="assigned"/></id> <property name="itemNumber" column="`Item #`"/> ... </class> 5.5. メ タ デ ー タ の 代 替 手 段 XML isn't for everyone, and so there are some alternative ways to define O/R mapping metadata in Hibernate. 5.5.1. XDoclet マークアップの使用 多くの Hibernate ユーザーは XDoclet の @ hibernate.tags を使って、ソースコード内に直接マッピン グ情報を埋め込むことを好みます。これは厳密に言えば XDoclet の分野なので、本ドキュメントではこの 方法を対象とはしません。しかし XDoclet を使った以下の Cat マッピングの例を示します。 97 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide package eg; import java.util.Set; import java.util.Date; /** * @hibernate.class * table="CATS" */ public class Cat { private Long id; // identifier private Date birthdate; private Cat mother; private Set kittens private Color color; private char sex; private float weight; /* * @hibernate.id * generator-class="native" * column="CAT_ID" */ public Long getId() { return id; } private void setId(Long id) { this.id=id; } /** * @hibernate.many-to-one * column="PARENT_ID" */ public Cat getMother() { return mother; } void setMother(Cat mother) { this.mother = mother; } /** * @hibernate.property * column="BIRTH_DATE" */ public Date getBirthdate() { return birthdate; } void setBirthdate(Date date) { birthdate = date; } /** * @hibernate.property * column="WEIGHT" */ public float getWeight() { return weight; } void setWeight(float weight) { this.weight = weight; } 98 第5章 基本的な O/R マッピング /** * @hibernate.property * column="COLOR" * not-null="true" */ public Color getColor() { return color; } void setColor(Color color) { this.color = color; } /** * @hibernate.set * inverse="true" * order-by="BIRTH_DATE" * @hibernate.collection-key * column="PARENT_ID" * @hibernate.collection-one-to-many */ public Set getKittens() { return kittens; } void setKittens(Set kittens) { this.kittens = kittens; } // addKitten not needed by Hibernate public void addKitten(Cat kitten) { kittens.add(kitten); } /** * @hibernate.property * column="SEX" * not-null="true" * update="false" */ public char getSex() { return sex; } void setSex(char sex) { this.sex=sex; } } Hibernate のウェブサイトには、 XDoclet と Hibernate に関するサンプルが多数あります。 5.5.2. JDK 5.0 アノテーションの使用 JDK5.0 ではタイプセーフかつコンパイル時にチェックできる、言語レベルの XDoclet スタイルのアノ テーションを導入しました。このメカニズムは XDoclet のアノテーションよりも強力で、ツールや IDE も 多くがサポートしています。例えば IntelliJ IDEA は、 JDK5.0 にアノテーションの自動補完と構文の強調 表示をサポートしています。 EJB 仕様 (JSR-220) の新しいバージョンでは、エンティティ Bean に対す る主要なメタデータメカニズムとして JDK5.0 のアノテーションを使用しています。 Hibernate3 では JSR-220 (永続化 API) の EntityManager を実装し、メタデータマッピングに対するサポートは、別ダ ウンロードの Hibernate Annotations パッケージにより利用可能です。これは EJB3 (JSR-220) と Hibernate3 のメタデータをどちらもサポートしています。 以下は EJB のエンティティ Bean として注釈された POJO クラスの例です: 99 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide @Entity(access = AccessType.FIELD) public class Customer implements Serializable { @Id; Long id; String firstName; String lastName; Date birthday; @Transient Integer age; @Embedded private Address homeAddress; @OneToMany(cascade=CascadeType.ALL) @JoinColumn(name="CUSTOMER_ID") Set<Order> orders; // Getter/setter and business methods } JDK5.0 のアノテーション (と JSR-220) のサポートは進行中の作業であり、完全ではないことに注意して ください。さらに詳しい情報は Hibernate のアノテーションモジュールを参照してください。 5.6. 生 成 プ ロ パ テ ィ 生成プロパティとは、データベースによって生成された値を持つプロパティです。通常、 Hibernate アプ リケーションは、データベースが値を生成したプロパティを含むオブジェクトを リフレッシュ する必要 がありました。しかし、プロパティが生成されたということをマークすることで、アプリケーションはリ フレッシュの責任を Hibernate に委譲します。基本的に、生成プロパティを持つと定義したエンティティ に対して Hibernate が INSERT や UPDAT E の SQL を発行した後すぐに、生成された値を読み込むための SELECT SQL が発行されます。 Properties marked as generated must additionally be non-insertable and non-updateable. Only 「version(オプション)」 versions, 「timestamp(オプション)」 timestamps, and 「property」 simple properties can be marked as generated. never (デフォルト) - 与えられたプロパティの値は、データベースから生成されないことを意味します。 insert - states that the given property value is generated on insert, but is not regenerated on subsequent updates. T hings like created-date would fall into this category. Note that even thought 「version(オプション)」 version and 「timestamp(オプション)」 timestamp properties can be marked as generated, this option is not available there... always - 挿入時も更新時もプロパティの値が生成されることを示します。 5.7. 補 助 的 な デ ー タ ベ ー ス オ ブ ジ ェ ク ト Allows CREAT E and DROP of arbitrary database objects, in conjunction with Hibernate's schema evolution tools, to provide the ability to fully define a user schema within the Hibernate mapping files. Although designed specifically for creating and dropping things like triggers or stored procedures, really any SQL command that can be run via a java.sql.Statem ent.execute() method is valid here (ALT ERs, INSERT S, etc). T here are essentially two modes for defining auxiliary database objects... 100 第5章 基本的な O/R マッピング 1つ目の方法は、 CREAT E と DROP コマンドをマッピングファイルの外に、明示的に記載することです: <hibernate-mapping> ... <database-object> <create>CREATE TRIGGER my_trigger ...</create> <drop>DROP TRIGGER my_trigger</drop> </database-object> </hibernate-mapping> 2つ目の方法は、 CREAT E と DROP コマンドの組み立て方を知っているカスタムクラスを提供すること です。このカスタムクラスは org.hibernate.m apping.AuxiliaryDatabaseObject インタ フェースを実装しなければなりません。 <hibernate-mapping> ... <database-object> <definition class="MyTriggerDefinition"/> </database-object> </hibernate-mapping> さらに、あるデータベース方言が使用される時にだけ適用するといったように、データベースオブジェク トが使われるケースを限定できます。 <hibernate-mapping> ... <database-object> <definition class="MyTriggerDefinition"/> <dialect-scope name="org.hibernate.dialect.Oracle9Dialect"/> <dialect-scope name="org.hibernate.dialect.OracleDialect"/> </database-object> </hibernate-mapping> 101 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 第 6章 コレクションのマッピング 6.1. コ レ ク シ ョ ン の 永 続 化 コレクション型のフィールドを永続化するには、そのコレクション型がインターフェース型である必要が あります。例えば、 public class Product { private String serialNumber; private Set parts = new HashSet(); public Set getParts() { return parts; } void setParts(Set parts) { this.parts = parts; } public String getSerialNumber() { return serialNumber; } void setSerialNumber(String sn) { serialNumber = sn; } } T he actual interface might be java.util.Set, java.util.Collection, java.util.List, java.util.Map, java.util.SortedSet, java.util.SortedMap or ... anything you like! (Where "anything you like" means you will have to write an implementation of org.hibernate.usertype.UserCollectionT ype.) Notice how we initialized the instance variable with an instance of HashSet. T his is the best way to initialize collection valued properties of newly instantiated (non-persistent) instances. When you make the instance persistent - by calling persist(), for example - Hibernate will actually replace the HashSet with an instance of Hibernate's own implementation of Set. Watch out for errors like this: Cat cat = new DomesticCat(); Cat kitten = new DomesticCat(); .... Set kittens = new HashSet(); kittens.add(kitten); cat.setKittens(kittens); session.persist(cat); kittens = cat.getKittens(); // Okay, kittens collection is a Set (HashSet) cat.getKittens(); // Error! Hibernate により注入された永続性コレクションは、インターフェース型に応じて、 HashMap や HashSet、 T reeMap、 T reeSet、 ArrayList のように振舞います。 コレクションインスタンスは、値型として普通に振舞います。永続化オブジェクトに参照されたときに自 動的に永続化され、参照がなくなったときに自動的に削除されます。もしある永続化オブジェクトから別 の永続化オブジェクトに渡されたら、その要素は現在のテーブルから別のテーブルに移動するかもしれま せん。2つのエンティティが同じコレクションインスタンスを共有してはいけません。リレーショナルモ デルをベースにしているため、コレクション型のプロパティに null 値を代入しても意味がありません。つ まり Hibernate は参照先のないコレクションと空のコレクションを区別しません。 You shouldn't have to worry much about any of this. Use persistent collections the same way you use ordinary Java collections. Just make sure you understand the semantics of bidirectional associations (discussed later). 6.2. コ レ ク シ ョ ン の マ ッ ピ ン グ T he Hibernate mapping element used for mapping a collection depends upon the type of the interface. 102 第6章 コレクションのマッピング For example, a <set> element is used for mapping properties of type Set. <class name="Product"> <id name="serialNumber" column="productSerialNumber"/> <set name="parts"> <key column="productSerialNumber" not-null="true"/> <one-to-many class="Part"/> </set> </class> Apart from <set>, there is also <list>, <m ap>, <bag>, <array> and <prim itive-array> mapping elements. T he <m ap> element is representative: <map name="propertyName" table="table_name" schema="schema_name" lazy="true|extra|false" inverse="true|false" cascade="all|none|save-update|delete|all-delete-orphan|delete-orphan" sort="unsorted|natural|comparatorClass" order-by="column_name asc|desc" where="arbitrary sql where condition" fetch="join|select|subselect" batch-size="N" access="field|property|ClassName" optimistic-lock="true|false" mutable="true|false" node="element-name|." embed-xml="true|false" > <key .... /> <map-key .... /> <element .... /> </map> nam e コレクション型であるプロパティの名前 table (オプション - デフォルトはプロパティ名)コレクションテーブルの名前(一対多関連では使用し ません)。 schem a (オプション)テーブルスキーマの名前。ルート要素で宣言されているスキーマより優先されま す。 lazy (optional - defaults to true) may be used to disable lazy fetching and specify that the association is always eagerly fetched, or to enable "extra-lazy" fetching where most operations do not initialize the collection (suitable for very large collections) inverse (optional - defaults to false) mark this collection as the "inverse" end of a bidirectional association cascade (オプション - デフォルトは none) 子エンティティへのカスケード操作を有効にします。 sort (オプション)コレクションを自然な順序でソートする場合は natural を指定します。あるいは Comparator クラスを指定します。 103 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide order-by (オプション、 JDK1.4 のみ) Map、 Set、 bag のイテレーション順序を定義するテーブル カラムを指定すると共に、オプションとして asc、 desc を指定します。 where (オプション)コレクションの検索や削除の際に使う任意の SQL のWHERE 条件を指定します (利用可能なデータの一部分だけをコレクションが含むべきときに、これは有用です)。 fetch (オプション - デフォルトは select) 外部結合によるフェッチ、順次選択フェッチ (sequential select fetch) 、順次サブセレクトフェッチ (sequential subselect fetch) のどれかを選択 してください。 batch-size (optional, defaults to 1) specify a "batch size" for lazily fetching instances of this collection. access (オプション - デフォルトは property) コレクション型プロパティの値にアクセスするため に使用する戦略です。 optim istic-lock (optional - defaults to true): Species that changes to the state of the collection results in increment of the owning entity's version. (For one to many associations, it is often reasonable to disable this setting.) m utable(オプション - デフォルトは true) false 値は、コレクションの要素が変更されないことを 表します (ある場合には、少しパフォーマンスを高めます)。 6.2.1. コレクションの外部キー Collection instances are distinguished in the database by the foreign key of the entity that owns the collection. T his foreign key is referred to as the collection key column (or columns) of the collection table. T he collection key column is mapped by the <key> element. T here may be a nullability constraint on the foreign key column. For most collections, this is implied. For unidirectional one to many associations, the foreign key column is nullable by default, so you might need to specify not-null="true". <key column="productSerialNumber" not-null="true"/> 外部キーの制約が ON DELET E CASCADE を使うかもしれません。 <key column="productSerialNumber" on-delete="cascade"/> See the previous chapter for a full definition of the <key> element. 6.2.2. コレクションの要素 Collections may contain almost any other Hibernate type, including all basic types, custom types, components, and of course, references to other entities. T his is an important distinction: an object in a collection might be handled with "value" semantics (its lifecycle fully depends on the collection owner) or it might be a reference to another entity, with its own lifecycle. In the latter case, only the "link" between the two objects is considered to be state held by the collection. T he contained type is referred to as the collection element type. Collection elements are mapped by <elem ent> or <com posite-elem ent>, or in the case of entity references, with <one-to-m any> or <m any-to-m any>. T he first two map elements with value semantics, the next two are used to map entity associations. 6.2.3. インデックス付きのコレクション 104 第6章 コレクションのマッピング All collection mappings, except those with set and bag semantics, need an index column in the collection table - a column that maps to an array index, or List index, or Map key. T he index of a Map may be of any basic type, mapped with <m ap-key>, it may be an entity reference mapped with <m ap-key-m anyto-m any>, or it may be a composite type, mapped with <com posite-m ap-key>. T he index of an array or list is always of type integer and is mapped using the <list-index> element. T he mapped column contains sequential integers (numbered from zero, by default). <list-index column="column_name" base="0|1|..."/> colum n_nam e (必須): コレクションインデックス値を持っているカラムの名前。 base (オプション - デフォルトは 0): リスト又は配列の1つめの要素に対応するインデックスカラムの 値。 <map-key column="column_name" formula="any SQL expression" type="type_name" node="@attribute-name" length="N"/> colum n (オプション): コレクションインデックス値を持っているカラムの名前。 form ula (オプション): マップのキーを評価するために使用される SQL 式。 class (必須): マップキーのタイプ <map-key-many-to-many column="column_name" formula="any SQL expression" class="ClassName" /> colum n (オプション): コレクションインデックス値のための外部キーカラムの名前。 form ula (オプション): マップキーの外部キーを評価するために使用される SQL 式。 class (必須): マップキーとして使用されるエンティティクラス。 If your table doesn't have an index column, and you still wish to use List as the property type, you should map the property as a Hibernate <bag>. A bag does not retain its order when it is retrieved from the database, but it may be optionally sorted or ordered. 多くの一般的なリレーショナルモデルをカバーしたために、コレクションのために利用できるマッピング にはかなりの幅があります。様々なマッピング宣言がどのようにデータベーステーブルに変換されるかを 知るために、スキーマ生成ツールを使ってみると良いでしょう。 6.2.4. 値のコレクションと多対多関連 値のコレクションや多対多関連は、専用の コレクションテーブル が必要です。このテーブルは、外部 キーカラムと、 コレクション要素のカラム と、場合によってはインデックスカラムを持ちます。 For a collection of values, we use the <elem ent> tag. 105 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide <element column="column_name" formula="any SQL expression" type="typename" length="L" precision="P" scale="S" not-null="true|false" unique="true|false" node="element-name" /> colum n (オプション): コレクション要素の値を持っているカラムの名前。 form ula (オプション): 要素を評価するために使用される SQL 式。 type (必須): コレクション要素のタイプ。 A many-to-many association is specified using the <m any-to-m any> element. <many-to-many column="column_name" formula="any SQL expression" class="ClassName" fetch="select|join" unique="true|false" not-found="ignore|exception" entity-name="EntityName" property-ref="propertyNameFromAssociatedClass" node="element-name" embed-xml="true|false" /> colum n (オプション): 要素の外部キーカラムの名前。 form ula (オプション): 要素の外部キーを評価するために使用される SQL 式。 class (必須): 関連クラスの名前。 fetch (optional - defaults to join): enables outer-join or sequential select fetching for this association. T his is a special case; for full eager fetching (in a single SELECT ) of an entity and its many-to-many relationships to other entities, you would enable join fetching not only of the collection itself, but also with this attribute on the <m any-to-m any> nested element. unique (オプション): 外部キーカラムのためのユニークな制約の DDL 生成を有効にします。これは関連 の多様性を効率的に一対多にします。 not-found (オプション - デフォルトは exception): 参照先の行がない外部キーをどのように扱うか を指定します: ignore を指定すると、行がないことを関連がないものとして扱います。 entity-nam e (オプション): class の代替である関連クラスのエンティティ名。 class の代わりに 指定する、関連クラスのエンティティ名。 property-ref: (オプション) この外部キーに加わる、関連クラスのプロパティの名前。指定されていな い場合は、関連クラスの主キーが使用されます。 以下にいくつか例を示します。まずは string の set に関しての例です。 106 第6章 コレクションのマッピング <set name="names" table="person_names"> <key column="person_id"/> <element column="person_name" type="string"/> </set> 整数値を含む bag (bagは order-by 属性によって反復順序が定義されています): <bag name="sizes" table="item_sizes" order-by="size asc"> <key column="item_id"/> <element column="size" type="integer"/> </bag> エンティティの配列 - この場合、多対多の関連です。 <array name="addresses" table="PersonAddress" cascade="persist"> <key column="personId"/> <list-index column="sortOrder"/> <many-to-many column="addressId" class="Address"/> </array> 文字列と日付の map <map name="holidays" table="holidays" schema="dbo" order-by="hol_name asc"> <key column="id"/> <map-key column="hol_name" type="string"/> <element column="hol_date" type="date"/> </map> コンポーネントの list (次の章で詳しく説明します) <list name="carComponents" table="CarComponents"> <key column="carId"/> <list-index column="sortOrder"/> <composite-element class="CarComponent"> <property name="price"/> <property name="type"/> <property name="serialNumber" column="serialNum"/> </composite-element> </list> 6.2.5. 一対多関連 一対多関連 は、コレクションテーブルを介さず、外部キーにより2つのクラスのテーブルを関連付けま す。このマッピングは標準的な Java のコレクションのセマンティクスをいくつか失います: エンティティクラスのインスタンスは、2つ以上のコレクションのインスタンスに属してはいけませ ん。 107 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide コレクションに含まれるエンティティクラスのインスタンスは、コレクションインデックスの値とし て2度以上現れてはいけません。 An association from Product to Part requires existence of a foreign key column and possibly an index column to the Part table. A <one-to-m any> tag indicates that this is a one to many association. <one-to-many class="ClassName" not-found="ignore|exception" entity-name="EntityName" node="element-name" embed-xml="true|false" /> class (必須): 関連クラスの名前。 not-found (オプション - デフォルトは exception): 参照先の行がないキャッシュされた識別子をど のように扱うかを指定します: ignore を指定すると、行がないことを関連がないものとして扱います。 entity-nam e (オプション): class の代替である関連クラスのエンティティ名。 class の代わりに 指定する、関連クラスのエンティティ名。 Notice that the <one-to-m any> element does not need to declare any columns. Nor is it necessary to specify the table name anywhere. Very important note: If the foreign key column of a <one-to-m any> association is declared NOT NULL, you must declare the <key> mapping not-null="true" or use a bidirectional association with the collection mapping marked inverse="true". See the discussion of bidirectional associations later in this chapter. 次の例は、名称(Part の永続的なプロパティである partNam e) による Part エンティティの map を 表しています。 formula によるインデックスを使っていることに注意してください。 <map name="parts" cascade="all"> <key column="productId" not-null="true"/> <map-key formula="partName"/> <one-to-many class="Part"/> </map> 6.3. 高 度 な コ レ ク シ ョ ン マ ッ ピ ン グ 6.3.1. ソートされたコレクション Hibernate は java.util.SortedMap と java.util.SortedSet を実装したコレクションをサポート しています。開発者はマッピング定義ファイルにコンパレータを指定しなければなりません: 108 第6章 コレクションのマッピング <set name="aliases" table="person_aliases" sort="natural"> <key column="person"/> <element column="name" type="string"/> </set> <map name="holidays" sort="my.custom.HolidayComparator"> <key column="year_id"/> <map-key column="hol_name" type="string"/> <element column="hol_date" type="date"/> </map> sort 属性に設定できる値は unsorted と natural および、 java.util.Com parator を実装したク ラスの名前です。 ソートされたコレクションは実質的には java.util.T reeSet や java.util.T reeMap のように振舞 います。 もしデータベース自身にコレクションの要素を並べさせたいなら、 set や bag、m ap の order-by 属性 を使います。この解決法は JDK1.4 、もしくはそれ以上のバージョンで利用可能です (LinkedHashSet または LinkedHashMapを使って実装されています)。整列はメモリ上ではなく、 SQL クエリ内で実行 されます。 <set name="aliases" table="person_aliases" order-by="lower(name) asc"> <key column="person"/> <element column="name" type="string"/> </set> <map name="holidays" order-by="hol_date, hol_name"> <key column="year_id"/> <map-key column="hol_name" type="string"/> <element column="hol_date type="date"/> </map> order-by 属性の値が SQL 命令であって、 HQL 命令ではないことに注意してください。 関連は、コレクションの filter() を使うことで、実行時に任意の criteria によってソートすることも可 能です。 sortedUsers = s.createFilter( group.getUsers(), "order by this.name" ).list(); 6.3.2. 双方向関連 A bidirectional association allows navigation from both "ends" of the association. T wo kinds of bidirectional association are supported: one-to-many 片側が set か bag 、もう片方が単一値です。 many-to-many 両側が set か bag です。 2つの多対多関連で同じデータベーステーブルをマッピングし、片方を inverse として宣言することで、双 109 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 方向の多対多関連を指定することが出来ます (どちらを inverse に選んだとしても、そちら側にはイン デックス付きのコレクションは使えません)。 Here's an example of a bidirectional many-to-many association; each category can have many items and each item can be in many categories: <class name="Category"> <id name="id" column="CATEGORY_ID"/> ... <bag name="items" table="CATEGORY_ITEM"> <key column="CATEGORY_ID"/> <many-to-many class="Item" column="ITEM_ID"/> </bag> </class> <class name="Item"> <id name="id" column="CATEGORY_ID"/> ... <!-- inverse end --> <bag name="categories" table="CATEGORY_ITEM" inverse="true"> <key column="ITEM_ID"/> <many-to-many class="Category" column="CATEGORY_ID"/> </bag> </class> 関連の inverse 側にのみ行われた変更は永続化 されません。これは、 Hibernate は全ての双方向関連につ いて、メモリ上に2つの表現を持っているという意味です。つまり一つは A から B へのリンクで、もう一 つは B から A へのリンクということです。 Java のオブジェクトモデルについて考え、 Java で双方向関 係をどうやって作るかを考えれば、これは理解しやすいです。下記に、 Java での双方向関連を示しま す。 category.getItems().add(item); relationship item.getCategories().add(category); relationship session.persist(item); session.persist(category); // The category now "knows" about the // The item now "knows" about the // The relationship won't be saved! // The relationship will be saved 関連の inverse ではない側は、メモリ上の表現をデータベースに保存するのに使われます。 You may define a bidirectional one-to-many association by mapping a one-to-many association to the same table column(s) as a many-to-one association and declaring the many-valued end inverse="true". 110 第6章 コレクションのマッピング <class name="Parent"> <id name="id" column="parent_id"/> .... <set name="children" inverse="true"> <key column="parent_id"/> <one-to-many class="Child"/> </set> </class> <class name="Child"> <id name="id" column="child_id"/> .... <many-to-one name="parent" class="Parent" column="parent_id" not-null="true"/> </class> Mapping one end of an association with inverse="true" doesn't affect the operation of cascades, these are orthogonal concepts! 6.3.3. インデックス付きコレクションと双方向関連 A bidirectional association where one end is represented as a <list> or <m ap> requires special consideration. If there is a property of the child class which maps to the index column, no problem, we can continue using inverse="true" on the collection mapping: <class name="Parent"> <id name="id" column="parent_id"/> .... <map name="children" inverse="true"> <key column="parent_id"/> <map-key column="name" type="string"/> <one-to-many class="Child"/> </map> </class> <class name="Child"> <id name="id" column="child_id"/> .... <property name="name" not-null="true"/> <many-to-one name="parent" class="Parent" column="parent_id" not-null="true"/> </class> But, if there is no such property on the child class, we can't think of the association as truly bidirectional (there is information available at one end of the association that is not available at the other end). In this case, we can't map the collection inverse="true". Instead, we could use the following mapping: 111 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide <class name="Parent"> <id name="id" column="parent_id"/> .... <map name="children"> <key column="parent_id" not-null="true"/> <map-key column="name" type="string"/> <one-to-many class="Child"/> </map> </class> <class name="Child"> <id name="id" column="child_id"/> .... <many-to-one name="parent" class="Parent" column="parent_id" insert="false" update="false" not-null="true"/> </class> 注意: このマッピングでは、関連のコレクション値の側は、外部キーをアップデートする責任がありま す。 T ODO: これは本当にいくつかの不必要なアップデートステートメントをもたらすのでしょうか? 6.3.4. 3項関連 3項関連のマッピングには3つのアプローチがあります。1つ目は関連をインデックスとして Map を使用す るアプローチです: <map name="contracts"> <key column="employer_id" not-null="true"/> <map-key-many-to-many column="employee_id" class="Employee"/> <one-to-many class="Contract"/> </map> <map name="connections"> <key column="incoming_node_id"/> <map-key-many-to-many column="outgoing_node_id" class="Node"/> <many-to-many column="connection_id" class="Connection"/> </map> 2つ目は単純に関連をエンティティクラスとしてモデルを作り直すアプローチで、頻繁に使われます。 最後は composite 要素を使うアプローチです。これに関する議論は後ほど行います。 6.3.5. Using an <idbag> If you've fully embraced our view that composite keys are a bad thing and that entities should have synthetic identifiers (surrogate keys), then you might find it a bit odd that the many to many associations and collections of values that we've shown so far all map to tables with composite keys! Now, this point is quite arguable; a pure association table doesn't seem to benefit much from a surrogate key (though a collection of composite values might). Nevertheless, Hibernate provides a feature that allows you to map many to many associations and collections of values to a table with a surrogate key. T he <idbag> element lets you map a List (or Collection) with bag semantics. 112 第6章 コレクションのマッピング <idbag name="lovers" table="LOVERS"> <collection-id column="ID" type="long"> <generator class="sequence"/> </collection-id> <key column="PERSON1"/> <many-to-many column="PERSON2" class="Person" fetch="join"/> </idbag> As you can see, an <idbag> has a synthetic id generator, just like an entity class! A different surrogate key is assigned to each collection row. Hibernate does not provide any mechanism to discover the surrogate key value of a particular row, however. Note that the update performance of an <idbag> is much better than a regular <bag>! Hibernate can locate individual rows efficiently and update or delete them individually, just like a list, map or set. In the current implementation, the native identifier generation strategy is not supported for <idbag> collection identifiers. 6.4. コ レ ク シ ョ ン の 例 これまでのセクションの説明では理解しにくいので、以下の例を見てください。 package eg; import java.util.Set; public class Parent { private long id; private Set children; public long getId() { return id; } private void setId(long id) { this.id=id; } private Set getChildren() { return children; } private void setChildren(Set children) { this.children=children; } .... .... } このクラスは Child インスタンスのコレクションを持っています。もし各々の child が最大でも一つの parent を持っているならば、最も自然なマッピングは一対多関連です。 113 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide <hibernate-mapping> <class name="Parent"> <id name="id"> <generator class="sequence"/> </id> <set name="children"> <key column="parent_id"/> <one-to-many class="Child"/> </set> </class> <class name="Child"> <id name="id"> <generator class="sequence"/> </id> <property name="name"/> </class> </hibernate-mapping> これは以下のテーブル定義にマッピングします。 create table parent ( id bigint not null primary key ) create table child ( id bigint not null primary key, name varchar(255), parent_id bigint ) alter table child add constraint childfk0 (parent_id) references parent もし parent が 要求 されるなら、双方向の一対多関連を使用してください: <hibernate-mapping> <class name="Parent"> <id name="id"> <generator class="sequence"/> </id> <set name="children" inverse="true"> <key column="parent_id"/> <one-to-many class="Child"/> </set> </class> <class name="Child"> <id name="id"> <generator class="sequence"/> </id> <property name="name"/> <many-to-one name="parent" class="Parent" column="parent_id" notnull="true"/> </class> </hibernate-mapping> NOT NULL 制約に注意してください。 114 第6章 コレクションのマッピング create table parent ( id bigint not null primary key ) create table child ( id bigint not null primary key, name varchar(255), parent_id bigint not null ) alter table child add constraint childfk0 (parent_id) references parent Alternatively, if you absolutely insist that this association should be unidirectional, you can declare the NOT NULL constraint on the <key> mapping: <hibernate-mapping> <class name="Parent"> <id name="id"> <generator class="sequence"/> </id> <set name="children"> <key column="parent_id" not-null="true"/> <one-to-many class="Child"/> </set> </class> <class name="Child"> <id name="id"> <generator class="sequence"/> </id> <property name="name"/> </class> </hibernate-mapping> 一方で、もし child が複数の parent を持てるならば、多対多関連が妥当です: <hibernate-mapping> <class name="Parent"> <id name="id"> <generator class="sequence"/> </id> <set name="children" table="childset"> <key column="parent_id"/> <many-to-many class="Child" column="child_id"/> </set> </class> <class name="Child"> <id name="id"> <generator class="sequence"/> </id> <property name="name"/> </class> </hibernate-mapping> テーブル定義は以下のようになります: 115 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide create table parent ( id bigint not null primary key ) create table child ( id bigint not null primary key, name varchar(255) ) create table childset ( parent_id bigint not null, child_id bigint not null, primary key ( parent_id, child_id ) ) alter table childset add constraint childsetfk0 (parent_id) references parent alter table childset add constraint childsetfk1 (child_id) references child For more examples and a complete walk-through a parent/child relationship mapping, see 21章例: 親/子 供. また、さらに特殊な関連マッピングも可能です。次の章で詳しく述べます。 116 第7章 関連マッピング 第 7章 関連マッピング 7.1. イ ン ト ロ ダ ク シ ョ ン Association mappings are the often most difficult thing to get right. In this section we'll go through the canonical cases one by one, starting with unidirectional mappings, and then considering the bidirectional cases. We'll use Person and Address in all the examples. We'll classify associations by whether or not they map to an intervening join table, and by multiplicity. null 可能な外部キーは従来型データモデリングの中では良い習慣と見なされていないため、すべての例で not null の外部キーを使用します。これは Hibernate の要件ではありません。 not null 制約を外したとして も、マッピングは問題なく動作します。 7.2. 単 方 向 関 連 7.2.1. 多対一 単方向多対一関連 は単方向関連の中で最も一般的なものです。 <class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> <many-to-one name="address" column="addressId" not-null="true"/> </class> <class name="Address"> <id name="id" column="addressId"> <generator class="native"/> </id> </class> create table Person ( personId bigint not null primary key, addressId bigint not null ) create table Address ( addressId bigint not null primary key ) 7.2.2. 一対一 外部キーの単方向一対一関連 はほとんど同じものです。唯一違うのは、カラムのユニークな制約です。 117 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide <class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> <many-to-one name="address" column="addressId" unique="true" not-null="true"/> </class> <class name="Address"> <id name="id" column="addressId"> <generator class="native"/> </id> </class> create table Person ( personId bigint not null primary key, addressId bigint not null unique ) create table Address ( addressId bigint not null primary key ) A unidirectional one-to-one association on a primary key usually uses a special id generator. (Notice that we've reversed the direction of the association in this example.) <class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> </class> <class name="Address"> <id name="id" column="personId"> <generator class="foreign"> <param name="property">person</param> </generator> </id> <one-to-one name="person" constrained="true"/> </class> create table Person ( personId bigint not null primary key ) create table Address ( personId bigint not null primary key ) 7.2.3. 一対多 外部キーの単方向一対多関連 はとても特殊なケースで、あまり推奨されていません。 118 第7章 関連マッピング <class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> <set name="addresses"> <key column="personId" not-null="true"/> <one-to-many class="Address"/> </set> </class> <class name="Address"> <id name="id" column="addressId"> <generator class="native"/> </id> </class> create table Person ( personId bigint not null primary key ) create table Address ( addressId bigint not null primary key, personId bigint not null ) We think it's better to use a join table for this kind of association. 7.3. 結 合 テ ー ブ ル を 使 っ た 単 方 向 関 連 7.3.1. 一対多 A unidirectional one-to-many association on a join table is much preferred. Notice that by specifying unique="true", we have changed the multiplicity from many-to-many to one-to-many. <class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> <set name="addresses" table="PersonAddress"> <key column="personId"/> <many-to-many column="addressId" unique="true" class="Address"/> </set> </class> <class name="Address"> <id name="id" column="addressId"> <generator class="native"/> </id> </class> create table create table not null create table Person ( personId bigint not null primary key ) PersonAddress ( personId not null, addressId bigint primary key ) Address ( addressId bigint not null primary key ) 119 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 7.3.2. 多対一 結合テーブルの単方向多対一関連 は関連が任意であるときに非常に一般的なものです。 <class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> <join table="PersonAddress" optional="true"> <key column="personId" unique="true"/> <many-to-one name="address" column="addressId" not-null="true"/> </join> </class> <class name="Address"> <id name="id" column="addressId"> <generator class="native"/> </id> </class> create create bigint create table Person ( personId bigint not null primary key ) table PersonAddress ( personId bigint not null primary key, addressId not null ) table Address ( addressId bigint not null primary key ) 7.3.3. 一対一 結合テーブルの単方向一対一関連 は、非常に特殊ですが不可能ではありません。 <class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> <join table="PersonAddress" optional="true"> <key column="personId" unique="true"/> <many-to-one name="address" column="addressId" not-null="true" unique="true"/> </join> </class> <class name="Address"> <id name="id" column="addressId"> <generator class="native"/> </id> </class> 120 第7章 関連マッピング create table create table bigint not null create table Person ( personId bigint not null primary key ) PersonAddress ( personId bigint not null primary key, addressId unique ) Address ( addressId bigint not null primary key ) 7.3.4. 多対多 最後に、 単方向多対多関連 を示します。 <class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> <set name="addresses" table="PersonAddress"> <key column="personId"/> <many-to-many column="addressId" class="Address"/> </set> </class> <class name="Address"> <id name="id" column="addressId"> <generator class="native"/> </id> </class> create table create table primary create table Person ( personId bigint not null primary key ) PersonAddress ( personId bigint not null, addressId bigint not null, key (personId, addressId) ) Address ( addressId bigint not null primary key ) 7.4. 双 方 向 関 連 7.4.1. 一対多 /多対一 双方向多対一関連 は最も一般的な関連です。 (標準的な親子関係です) 121 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide <class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> <many-to-one name="address" column="addressId" not-null="true"/> </class> <class name="Address"> <id name="id" column="addressId"> <generator class="native"/> </id> <set name="people" inverse="true"> <key column="addressId"/> <one-to-many class="Person"/> </set> </class> create table Person ( personId bigint not null primary key, addressId bigint not null ) create table Address ( addressId bigint not null primary key ) If you use a List (or other indexed collection) you need to set the key column of the foreign key to not null, and let Hibernate manage the association from the collections side to maintain the index of each element (making the other side virtually inverse by setting update="false" and insert="false"): <class name="Person"> <id name="id"/> ... <many-to-one name="address" column="addressId" not-null="true" insert="false" update="false"/> </class> <class name="Address"> <id name="id"/> ... <list name="people"> <key column="addressId" not-null="true"/> <list-index column="peopleIdx"/> <one-to-many class="Person"/> </list> </class> It is important that you define not-null="true" on the <key> element of the collection mapping if the underlying foreign key column is NOT NULL. Don't only declare not-null="true" on a possible nested <colum n> element, but on the <key> element. 7.4.2. 一対一 外部キーの双方向一対一関連 は非常に一般的です。 122 第7章 関連マッピング <class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> <many-to-one name="address" column="addressId" unique="true" not-null="true"/> </class> <class name="Address"> <id name="id" column="addressId"> <generator class="native"/> </id> <one-to-one name="person" property-ref="address"/> </class> create table Person ( personId bigint not null primary key, addressId bigint not null unique ) create table Address ( addressId bigint not null primary key ) 主キーの双方向一対一関連 は特殊な ID ジェネレータを使います。 <class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> <one-to-one name="address"/> </class> <class name="Address"> <id name="id" column="personId"> <generator class="foreign"> <param name="property">person</param> </generator> </id> <one-to-one name="person" constrained="true"/> </class> create table Person ( personId bigint not null primary key ) create table Address ( personId bigint not null primary key ) 7.5. 結 合 テ ー ブ ル を 使 っ た 双 方 向 関 連 7.5.1. 一対多 /多対一 A bidirectional one-to-many association on a join table. Note that the inverse="true" can go on either end of the association, on the collection, or on the join. 123 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide <class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> <set name="addresses" table="PersonAddress"> <key column="personId"/> <many-to-many column="addressId" unique="true" class="Address"/> </set> </class> <class name="Address"> <id name="id" column="addressId"> <generator class="native"/> </id> <join table="PersonAddress" inverse="true" optional="true"> <key column="addressId"/> <many-to-one name="person" column="personId" not-null="true"/> </join> </class> create table Person ( personId bigint not null primary key ) create table PersonAddress ( personId bigint not null, addressId bigint not null primary key ) create table Address ( addressId bigint not null primary key ) 7.5.2. 一対一 結合テーブルの双方向一対一関連 は非常に特殊ですが、可能です。 124 第7章 関連マッピング <class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> <join table="PersonAddress" optional="true"> <key column="personId" unique="true"/> <many-to-one name="address" column="addressId" not-null="true" unique="true"/> </join> </class> <class name="Address"> <id name="id" column="addressId"> <generator class="native"/> </id> <join table="PersonAddress" optional="true" inverse="true"> <key column="addressId" unique="true"/> <many-to-one name="person" column="personId" not-null="true" unique="true"/> </join> </class> create table create table bigint not null create table Person ( personId bigint not null primary key ) PersonAddress ( personId bigint not null primary key, addressId unique ) Address ( addressId bigint not null primary key ) 7.5.3. 多対多 最後に、 双方向多対多関連 を示します。 125 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide <class name="Person"> <id name="id" column="personId"> <generator class="native"/> </id> <set name="addresses" table="PersonAddress"> <key column="personId"/> <many-to-many column="addressId" class="Address"/> </set> </class> <class name="Address"> <id name="id" column="addressId"> <generator class="native"/> </id> <set name="people" inverse="true" table="PersonAddress"> <key column="addressId"/> <many-to-many column="personId" class="Person"/> </set> </class> create table Person ( personId bigint not null primary key ) create table PersonAddress ( personId bigint not null, addressId bigint not null, primary key (personId, addressId) ) create table Address ( addressId bigint not null primary key ) 7.6. よ り 複 雑 な 関 連 マ ッ ピ ン グ より複雑な関連結合は 極めて 稀です。マッピングドキュメントに SQL 文を埋め込むことで、さらに複雑 な状況を扱うことができます。例えば、 accountNum ber 、 effectiveEndDate 、 effectiveStartDate カラムを持つ account (口座)情報の履歴を扱うテーブルは、以下のように マッピングします。 <properties name="currentAccountKey"> <property name="accountNumber" type="string" not-null="true"/> <property name="currentAccount" type="boolean"> <formula>case when effectiveEndDate is null then 1 else 0 end</formula> </property> </properties> <property name="effectiveEndDate" type="date"/> <property name="effectiveStateDate" type="date" not-null="true"/> そして、関連を 現時点の インスタンス (effectiveEndDate が null であるもの)にマッピングしま す。以下のようになります: <many-to-one name="currentAccountInfo" property-ref="currentAccountKey" class="AccountInfo"> <column name="accountNumber"/> <formula>'1'</formula> </many-to-one> In a more complex example, imagine that the association between Em ployee and Organization is 126 第7章 関連マッピング maintained in an Em ploym ent table full of historical employment data. T hen an association to the employee's most recent employer (the one with the most recent startDate) might be mapped this way: <join> <key column="employeeId"/> <subselect> select employeeId, orgId from Employments group by orgId having startDate = max(startDate) </subselect> <many-to-one name="mostRecentEmployer" class="Organization" column="orgId"/> </join> この機能は非常に強力です。しかしこのような場合、普通は HQL や criteria クエリを使う方がより実践的 です。 127 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 第 8章 コンポーネントのマッピング コンポーネント の概念は、 Hibernate を通して様々な状況の中で異なる目的のために再利用されます。 8.1. 依 存 オ ブ ジ ェ ク ト A component is a contained object that is persisted as a value type, not an entity reference. T he term "component" refers to the object-oriented notion of composition (not to architecture-level components). For example, you might model a person like this: public class Person { private java.util.Date birthday; private Name name; private String key; public String getKey() { return key; } private void setKey(String key) { this.key=key; } public java.util.Date getBirthday() { return birthday; } public void setBirthday(java.util.Date birthday) { this.birthday = birthday; } public Name getName() { return name; } public void setName(Name name) { this.name = name; } ...... ...... } 128 第8章 コンポーネントのマッピング public class Name { char initial; String first; String last; public String getFirst() { return first; } void setFirst(String first) { this.first = first; } public String getLast() { return last; } void setLast(String last) { this.last = last; } public char getInitial() { return initial; } void setInitial(char initial) { this.initial = initial; } } Now Nam e may be persisted as a component of Person. Notice that Nam e defines getter and setter methods for its persistent properties, but doesn't need to declare any interfaces or identifier properties. マッピング定義は以下のようになります。 <class name="eg.Person" table="person"> <id name="Key" column="pid" type="string"> <generator class="uuid"/> </id> <property name="birthday" type="date"/> <component name="Name" class="eg.Name"> <!-- class attribute optional --> <property name="initial"/> <property name="first"/> <property name="last"/> </component> </class> Person テーブルは pid、 birthday、 initial、 first、 last カラムを持ちます。 Like all value types, components do not support shared references. In other words, two persons could have the same name, but the two person objects would contain two independent name ojects, only "the same" by value. T he null value semantics of a component are ad hoc. When reloading the containing object, Hibernate will assume that if all component columns are null, then the entire component is null. T his should be okay for most purposes. コンポーネントの属性はどんな Hibernate の型でも構いません(コレクション、 many-to-one 関連、他の コンポーネントなど)。ネストされたコンポーネントは滅多に使わないと考えるべきでは ありません 。 Hibernate は非常にきめの細かいオブジェクトモデルをサポートするように意図されています。 T he <com ponent> element allows a <parent> subelement that maps a property of the component class as a reference back to the containing entity. 129 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide <class name="eg.Person" table="person"> <id name="Key" column="pid" type="string"> <generator class="uuid"/> </id> <property name="birthday" type="date"/> <component name="Name" class="eg.Name" unique="true"> <parent name="namedPerson"/> <!-- reference back to the Person --> <property name="initial"/> <property name="first"/> <property name="last"/> </component> </class> 8.2. 従 属 す る オ ブ ジ ェ ク ト の コ レ ク シ ョ ン Collections of components are supported (eg. an array of type Nam e). Declare your component collection by replacing the <elem ent> tag with a <com posite-elem ent> tag. <set name="someNames" table="some_names" lazy="true"> <key column="id"/> <composite-element class="eg.Name"> <!-- class attribute required --> <property name="initial"/> <property name="first"/> <property name="last"/> </composite-element> </set> 注記: コンポジットエレメントの Set を定義したなら、 equals() と hashCode() を正しく実装するこ とが重要です。 Composite elements may contain components but not collections. If your composite element itself contains components, use the <nested-com posite-elem ent> tag. T his is a pretty exotic case - a collection of components which themselves have components. By this stage you should be asking yourself if a one-to-many association is more appropriate. T ry remodelling the composite element as an entity - but note that even though the Java model is the same, the relational model and persistence semantics are still slightly different. Please note that a composite element mapping doesn't support null-able properties if you're using a <set>. Hibernate has to use each columns value to identify a record when deleting objects (there is no separate primary key column in the composite element table), which is not possible with null values. You have to either use only not-null properties in a composite-element or choose a <list>, <m ap>, <bag> or <idbag>. A special case of a composite element is a composite element with a nested <m any-to-one> element. A mapping like this allows you to map extra columns of a many-to-many association table to the composite element class. T he following is a many-to-many association from Order to Item where purchaseDate, price and quantity are properties of the association: 130 第8章 コンポーネントのマッピング <class name="eg.Order" .... > .... <set name="purchasedItems" table="purchase_items" lazy="true"> <key column="order_id"> <composite-element class="eg.Purchase"> <property name="purchaseDate"/> <property name="price"/> <property name="quantity"/> <many-to-one name="item" class="eg.Item"/> <!-- class attribute is optional --> </composite-element> </set> </class> Of course, there can't be a reference to the purchae on the other side, for bidirectional association navigation. Remember that components are value types and don't allow shared references. A single Purchase can be in the set of an Order, but it can't be referenced by the Item at the same time. 3項関連(あるいは4項など)も可能です。 <class name="eg.Order" .... > .... <set name="purchasedItems" table="purchase_items" lazy="true"> <key column="order_id"> <composite-element class="eg.OrderLine"> <many-to-one name="purchaseDetails class="eg.Purchase"/> <many-to-one name="item" class="eg.Item"/> </composite-element> </set> </class> コンポジットエレメントは他のエンティティへの関連として、同じシンタックスを使っているクエリ内で 使用できます。 8.3. Map の イ ン デ ッ ク ス と し て の コ ン ポ ー ネ ン ト T he <com posite-m ap-key> element lets you map a component class as the key of a Map. Make sure you override hashCode() and equals() correctly on the component class. 8.4. 複 合 識 別 子 と し て の コ ン ポ ー ネ ン ト コンポーネントをエンティティクラスの識別子として使うことができます。コンポーネントクラスは以下 の条件を満たす必要があります。 java.io.Serializable を実装しなければなりません。 It must re-implement equals() and hashCode(), consistently with the database's notion of composite key equality. Note: in Hibernate3, the second requirement is not an absolutely hard requirement of Hibernate. But do it anyway. You can't use an IdentifierGenerator to generate composite keys. Instead the application must assign its own identifiers. Use the <com posite-id> tag (with nested <key-property> elements) in place of the usual <id> 131 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide declaration. For example, the OrderLine class has a primary key that depends upon the (composite) primary key of Order. <class name="OrderLine"> <composite-id name="id" class="OrderLineId"> <key-property name="lineId"/> <key-property name="orderId"/> <key-property name="customerId"/> </composite-id> <property name="name"/> <many-to-one name="order" class="Order" insert="false" update="false"> <column name="orderId"/> <column name="customerId"/> </many-to-one> .... </class> このとき、 OrderLine テーブルへ関連する外部キーもまた複合です。他のクラスのマッピングでこれを 宣言しなければなりません。 OrderLine への関連は次のようにマッピングされます。 <many-to-one name="orderLine" class="OrderLine"> <!-- the "class" attribute is optional, as usual --> <column name="lineId"/> <column name="orderId"/> <column name="customerId"/> </many-to-one> (Note that the <colum n> tag is an alternative to the colum n attribute everywhere.) OrderLine への m any-to-m any 関連も複合外部キーを使います。 <set name="undeliveredOrderLines"> <key column name="warehouseId"/> <many-to-many class="OrderLine"> <column name="lineId"/> <column name="orderId"/> <column name="customerId"/> </many-to-many> </set> Order にある OrderLine のコレクションは次のものを使用します。 <set name="orderLines" inverse="true"> <key> <column name="orderId"/> <column name="customerId"/> </key> <one-to-many class="OrderLine"/> </set> (T he <one-to-m any> element, as usual, declares no columns.) 132 第8章 コンポーネントのマッピング OrderLine 自身がコレクションを持っている場合、同時に複合外部キーも持っています。 <class name="OrderLine"> .... .... <list name="deliveryAttempts"> <key> <!-- a collection inherits the composite key type --> <column name="lineId"/> <column name="orderId"/> <column name="customerId"/> </key> <list-index column="attemptId" base="1"/> <composite-element class="DeliveryAttempt"> ... </composite-element> </set> </class> 8.5. 動 的 コ ン ポ ー ネ ン ト Map 型のプロパティのマッピングも可能です。 <dynamic-component name="userAttributes"> <property name="foo" column="FOO" type="string"/> <property name="bar" column="BAR" type="integer"/> <many-to-one name="baz" class="Baz" column="BAZ_ID"/> </dynamic-component> T he semantics of a <dynam ic-com ponent> mapping are identical to <com ponent>. T he advantage of this kind of mapping is the ability to determine the actual properties of the bean at deployment time, just by editing the mapping document. Runtime manipulation of the mapping document is also possible, using a DOM parser. Even better, you can access (and change) Hibernate's configuration-time metamodel via the Configuration object. 133 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 第 9章 継承マッピング 9.1. 3つ の 戦 略 Hibernate は3つの基本的な継承のマッピング戦略をサポートします。 クラス階層ごとのテーブル (table-per-class-hierarchy) table per subclass 具象クラスごとのテーブル (table-per-concrete-class) 加えて4つ目に、 Hibernate はわずかに異なる性質を持ったポリモーフィズムをサポートします。 暗黙的ポリモーフィズム It is possible to use different mapping strategies for different branches of the same inheritance hierarchy, and then make use of implicit polymorphism to achieve polymorphism across the whole hierarchy. However, Hibernate does not support mixing <subclass>, and <joined-subclass> and <union-subclass> mappings under the same root <class> element. It is possible to mix together the table per hierarchy and table per subclass strategies, under the the same <class> element, by combining the <subclass> and <join> elements (see below). subclass、 union-subclass と joined-subclass マッピングを複数のマッピングドキュメントに 定義することが出来、 hibernate-m apping の直下に配置します。これは新しいマッピングファイルを 追加するだけで、クラス階層を拡張できるということです。あらかじめマップしたスーパークラスを指定 して、サブクラスマッピングに extends 属性を記述しなければなりません。注記:この特徴により、以 前はマッピングドキュメントの順番が重要でした。 Hibernate3 からは、 extends キーワードを使う場 合、マッピングドキュメントの順番は問題になりません。1つのマッピングファイル内で順番付けを行う ときは、依然として、サブクラスを定義する前にスーパークラスを定義する必要があります。) <hibernate-mapping> <subclass name="DomesticCat" extends="Cat" discriminator-value="D"> <property name="name" type="string"/> </subclass> </hibernate-mapping> 9.1.1. クラス階層ごとのテーブル( table-per-class-hierarchy) 例えば、インターフェース Paym ent と、それを実装した CreditCardPaym ent、 CashPaym ent、 ChequePaym ent があるとします。階層ごとのテーブルマッピングは以下のようになります: 134 第9章 継承マッピング <class name="Payment" table="PAYMENT"> <id name="id" type="long" column="PAYMENT_ID"> <generator class="native"/> </id> <discriminator column="PAYMENT_TYPE" type="string"/> <property name="amount" column="AMOUNT"/> ... <subclass name="CreditCardPayment" discriminator-value="CREDIT"> <property name="creditCardType" column="CCTYPE"/> ... </subclass> <subclass name="CashPayment" discriminator-value="CASH"> ... </subclass> <subclass name="ChequePayment" discriminator-value="CHEQUE"> ... </subclass> </class> ちょうど一つのテーブルが必要です。このマッピング戦略には一つ大きな制限があります。 CCT YPE のよ うな、サブクラスで宣言されたカラムは NOT NULL 制約を持てません。 9.1.2. サブクラスごとのテーブル ( table-per-subclass) table-per-subclass マッピングは以下のようになります: <class name="Payment" table="PAYMENT"> <id name="id" type="long" column="PAYMENT_ID"> <generator class="native"/> </id> <property name="amount" column="AMOUNT"/> ... <joined-subclass name="CreditCardPayment" table="CREDIT_PAYMENT"> <key column="PAYMENT_ID"/> <property name="creditCardType" column="CCTYPE"/> ... </joined-subclass> <joined-subclass name="CashPayment" table="CASH_PAYMENT"> <key column="PAYMENT_ID"/> ... </joined-subclass> <joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT"> <key column="PAYMENT_ID"/> ... </joined-subclass> </class> このマッピングには4つのテーブルが必要です。3つのサブクラステーブルはスーパークラステーブルとの 関連を示す主キーを持っています (実際、関係モデル上は一対一関連です)。 9.1.3. discriminator を用いた table-per-subclass Note that Hibernate's implementation of table per subclass requires no discriminator column. Other object/relational mappers use a different implementation of table per subclass which requires a type discriminator column in the superclass table. T he approach taken by Hibernate is much more difficult to implement but arguably more correct from a relational point of view. If you would like to use a discriminator column with the table per subclass strategy, you may combine the use of <subclass> and <join>, as follow: 135 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide <class name="Payment" table="PAYMENT"> <id name="id" type="long" column="PAYMENT_ID"> <generator class="native"/> </id> <discriminator column="PAYMENT_TYPE" type="string"/> <property name="amount" column="AMOUNT"/> ... <subclass name="CreditCardPayment" discriminator-value="CREDIT"> <join table="CREDIT_PAYMENT"> <key column="PAYMENT_ID"/> <property name="creditCardType" column="CCTYPE"/> ... </join> </subclass> <subclass name="CashPayment" discriminator-value="CASH"> <join table="CASH_PAYMENT"> <key column="PAYMENT_ID"/> ... </join> </subclass> <subclass name="ChequePayment" discriminator-value="CHEQUE"> <join table="CHEQUE_PAYMENT" fetch="select"> <key column="PAYMENT_ID"/> ... </join> </subclass> </class> T he optional fetch="select" declaration tells Hibernate not to fetch the ChequePaym ent subclass data using an outer join when querying the superclass. 9.1.4. table-per-subclass と table-per-class-hierarchy の混合 このアプローチを使用すると、 table-per-hierarchy と table-per-subclass 戦略を組み合わせる事も可能で す。 <class name="Payment" table="PAYMENT"> <id name="id" type="long" column="PAYMENT_ID"> <generator class="native"/> </id> <discriminator column="PAYMENT_TYPE" type="string"/> <property name="amount" column="AMOUNT"/> ... <subclass name="CreditCardPayment" discriminator-value="CREDIT"> <join table="CREDIT_PAYMENT"> <property name="creditCardType" column="CCTYPE"/> ... </join> </subclass> <subclass name="CashPayment" discriminator-value="CASH"> ... </subclass> <subclass name="ChequePayment" discriminator-value="CHEQUE"> ... </subclass> </class> For any of these mapping strategies, a polymorphic association to the root Paym ent class is mapped 136 第9章 継承マッピング using <m any-to-one>. <many-to-one name="payment" column="PAYMENT_ID" class="Payment"/> 9.1.5. 具象クラスごとのテーブル( table-per-concrete-class) T here are two ways we could go about mapping the table per concrete class strategy. T he first is to use <union-subclass>. <class name="Payment"> <id name="id" type="long" column="PAYMENT_ID"> <generator class="sequence"/> </id> <property name="amount" column="AMOUNT"/> ... <union-subclass name="CreditCardPayment" table="CREDIT_PAYMENT"> <property name="creditCardType" column="CCTYPE"/> ... </union-subclass> <union-subclass name="CashPayment" table="CASH_PAYMENT"> ... </union-subclass> <union-subclass name="ChequePayment" table="CHEQUE_PAYMENT"> ... </union-subclass> </class> サブクラスごとに3つのテーブルが必要です。それぞれのテーブルは、継承プロパティを含んだ、クラス の全てのプロパティに対するカラムを定義します。 このアプローチには制限があります。それは、プロパティがスーパークラスにマッピングされていた場 合、全てのサブクラスにおいてカラム名が同じでなければならないというものです。(Hibernate の今後 のリリースで緩和されるかもしれません)。 union subclass 継承では識別子生成戦略を使用できませ ん。主キーを生成するためのシードは、全ての union subclass の階層内で共有する必要があるからで す。 If your superclass is abstract, map it with abstract="true". Of course, if it is not abstract, an additional table (defaults to PAYMENT in the example above) is needed to hold instances of the superclass. 9.1.6. 暗黙的ポリモーフィズムを用いた table-per-concrete-class もう一つのアプローチは暗黙的ポリモーフィズムの使用です: 137 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide <class name="CreditCardPayment" table="CREDIT_PAYMENT"> <id name="id" type="long" column="CREDIT_PAYMENT_ID"> <generator class="native"/> </id> <property name="amount" column="CREDIT_AMOUNT"/> ... </class> <class name="CashPayment" table="CASH_PAYMENT"> <id name="id" type="long" column="CASH_PAYMENT_ID"> <generator class="native"/> </id> <property name="amount" column="CASH_AMOUNT"/> ... </class> <class name="ChequePayment" table="CHEQUE_PAYMENT"> <id name="id" type="long" column="CHEQUE_PAYMENT_ID"> <generator class="native"/> </id> <property name="amount" column="CHEQUE_AMOUNT"/> ... </class> Notice that nowhere do we mention the Paym ent interface explicitly. Also notice that properties of Paym ent are mapped in each of the subclasses. If you want to avoid duplication, consider using XML entities (e.g. [ <!ENT IT Y allproperties SYST EM "allproperties.xm l"> ] in the DOCT YPE declartion and & allproperties; in the mapping). このアプローチの欠点は、 Hibernate がポリモーフィックなクエリの実行時に SQL UNION を生成しない 点です。 For this mapping strategy, a polymorphic association to Paym ent is usually mapped using <any>. <any name="payment" meta-type="string" id-type="long"> <meta-value value="CREDIT" class="CreditCardPayment"/> <meta-value value="CASH" class="CashPayment"/> <meta-value value="CHEQUE" class="ChequePayment"/> <column name="PAYMENT_CLASS"/> <column name="PAYMENT_ID"/> </any> 9.1.7. 他の継承マッピングと暗黙的ポリモーフィズムの組み合わせ T here is one further thing to notice about this mapping. Since the subclasses are each mapped in their own <class> element (and since Paym ent is just an interface), each of the subclasses could easily be part of another inheritance hierarchy! (And you can still use polymorphic queries against the Paym ent interface.) 138 第9章 継承マッピング <class name="CreditCardPayment" table="CREDIT_PAYMENT"> <id name="id" type="long" column="CREDIT_PAYMENT_ID"> <generator class="native"/> </id> <discriminator column="CREDIT_CARD" type="string"/> <property name="amount" column="CREDIT_AMOUNT"/> ... <subclass name="MasterCardPayment" discriminator-value="MDC"/> <subclass name="VisaPayment" discriminator-value="VISA"/> </class> <class name="NonelectronicTransaction" table="NONELECTRONIC_TXN"> <id name="id" type="long" column="TXN_ID"> <generator class="native"/> </id> ... <joined-subclass name="CashPayment" table="CASH_PAYMENT"> <key column="PAYMENT_ID"/> <property name="amount" column="CASH_AMOUNT"/> ... </joined-subclass> <joined-subclass name="ChequePayment" table="CHEQUE_PAYMENT"> <key column="PAYMENT_ID"/> <property name="amount" column="CHEQUE_AMOUNT"/> ... </joined-subclass> </class> Once again, we don't mention Paym ent explicitly. If we execute a query against the Paym ent interface for example, from Paym ent - Hibernate automatically returns instances of CreditCardPaym ent (and its subclasses, since they also implement Paym ent), CashPaym ent and ChequePaym ent but not instances of NonelectronicT ransaction. 9.2. 制 限 T here are certain limitations to the "implicit polymorphism" approach to the table per concrete-class mapping strategy. T here are somewhat less restrictive limitations to <union-subclass> mappings. 次のテーブルに、 Hibernate における table-per-concrete-class マッピングの制限や暗黙的ポリモーフィ ズムの制限を示します。 139 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 表 9.1 継承マッピングの機能 継承戦 略 多対一の ポリモー フィズム 一対一の ポリモー フィズム 一対多の ポリモー フィズム 多対多の ポリモー フィズム ポリモー フィック な load()/ get() ポリ モー フィズ ムを 使った クエリ ポリ モー フィズ ムを 使った 結合 外部結 合によ る フェッ チ table per classhierarchy <m anytoone> <onetoone> <onetom any> <m anytom any> s.get(P aym ent .class, id) from Paym en t p from Order o join o.paym ent p supporte d table per subclass <m anytoone> <onetoone> <onetom any> <m anytom any> s.get(P aym ent .class, id) from Paym en t p from Order o join o.paym ent p supporte d table per concrete -class (unionsubclass ) <m anytoone> <onetoone> <onetom any> (for invers e="tru e" only) <m anytom any> s.get(P aym ent .class, id) from Paym en t p from Order o join o.paym ent p supporte d table per concrete class (implicit polymorp hism) <any> not supporte d not supporte d <m anytoany> s.crea teCrit eria(P aym ent .class) .add( Restri ctions .idEq( id) ).uniqu eResul t() from Paym en t p not supporte d not supporte d 14 0 第10章 オブジェクトを扱う 第 10章 オブジェクトを扱う Hibernate は完全なオブジェクト/リレーショナルマッピングソリューションであり、データベース管理シ ステムの詳細を開発者から隠蔽するだけでなく、オブジェクトの 状態管理 も行います。これは、 JDBC/SQL 永続層と同じような SQL statem ents の管理とは異なり、 Java アプリケーションにおける 永続化に対する、とても自然なオブジェクト指向の考え方を提供します。 言いかえれば、 Hibernate を用いるアプリケーション開発者は、オブジェクトの 状態 については常に意 識すべきであり、 SQL 文の実行については必ずしもそうではありません。この部分は、通常、 Hibernate が処理し、システムのパフォーマンスをチューニングするときにだけ、問題になってきます。 10.1. Hibernate に お け る オ ブ ジ ェ ク ト の 状 態 Hibernate は次のようなオブジェクトの状態を定義し、サポートしています: Transient - an object is transient if it has just been instantiated using the new operator, and it is not associated with a Hibernate Session. It has no persistent representation in the database and no identifier value has been assigned. T ransient instances will be destroyed by the garbage collector if the application doesn't hold a reference anymore. Use the Hibernate Session to make an object persistent (and let Hibernate take care of the SQL statements that need to be executed for this transition). Persistent - a persistent instance has a representation in the database and an identifier value. It might just have been saved or loaded, however, it is by definition in the scope of a Session. Hibernate will detect any changes made to an object in persistent state and synchronize the state with the database when the unit of work completes. Developers don't execute manual UPDAT E statements, or DELET E statements when an object should be made transient. Detached - detached インスタンスとは、永続化されているが、それと関連付いていた Session がク ローズされているオブジェクトのことです。そのオブジェクトへの参照は、依然として有効です。そ して、もちろん、detached インスタンスはこの状態に修正することさえできます。 detacched インス タンスは、もう一度永続化したい(そして、すべての変更を永続化したい)ときに、新しい Session に再追加できます。この機能は、ユーザーが考える時間を必要とするような、長期間に及ぶ作業単位 に対するプログラミングモデルを可能にします。我々は、これを アプリケーションのトランザクショ ン(application transactions) と呼んでいます。すなわち、ユーザーから見た作業単位だということ です。 We'll now discuss the states and state transitions (and the Hibernate methods that trigger a transition) in more detail. 10.2. オ ブ ジ ェ ク ト を 永 続 状 態 に す る 新しくインスタンス化された永続クラスのインスタンスは、 Hibernate では transient と見なされます。 以下のように、セッションと関連づけることで、 transient インスタンスを 永続状態 (persistent) にでき ます。 DomesticCat fritz = new DomesticCat(); fritz.setColor(Color.GINGER); fritz.setSex('M'); fritz.setName("Fritz"); Long generatedId = (Long) sess.save(fritz); Cat クラスの識別子が自動生成されるのであれば、 save() が呼ばれるときに、識別子が生成され、 cat インスタンスに割り当てられます。 Cat の識別子が assigned 識別子を持つか、複合キーであるなら、 save() を呼び出す前に、識別子を cat インスタンスを割り当てなければなりません。 save() の代わり 14 1 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide に、 EJB3 の初期ドラフトで定義された persist() を使うことも可能です。 代わりに、識別子を引数にとる save() メソッドを使って、識別子を割り当てることもできます。 DomesticCat pk = new DomesticCat(); pk.setColor(Color.TABBY); pk.setSex('F'); pk.setName("PK"); pk.setKittens( new HashSet() ); pk.addKitten(fritz); sess.save( pk, new Long(1234) ); 永続化するオブジェクトが関連オブジェクトを持っている場合 (例えば、前の例における kittens コレ クションのように)、外部キーカラムに、 NOT NULL 制約をつけない限りは、これらの一連のオブジェ クトをどんな順番で永続化してもかまいません。外部キー制約を違反する恐れはありません。しかし、 NOT NULL 制約がある場合、間違った順番でオブジェクトを save() してしまうと、制約に違反するかも しれません。 Usually you don't bother with this detail, as you'll very likely use Hibernate's transitive persistence feature to save the associated objects automatically. T hen, even NOT NULL constraint violations don't occur - Hibernate will take care of everything. T ransitive persistence is discussed later in this chapter. 10.3. オ ブ ジ ェ ク ト の ロ ー ド 永続化されたインスタンスの識別子があらかじめ分かっているなら、 Session の load() メソッドを 使って、復元できます。 load() は、 Class オブジェクトを引数にとり、そのクラスのインスタンスを 新たに生成し、状態をロードします。そのインスタンスの状態は、永続 (persistent) 状態です。 Cat fritz = (Cat) sess.load(Cat.class, generatedId); // you need to wrap primitive identifiers long id = 1234; DomesticCat pk = (DomesticCat) sess.load( DomesticCat.class, new Long(id) ); あるいは、以下のように、既存のインスタンスに状態をロードすることもできます: Cat cat = new DomesticCat(); // load pk's state into cat sess.load( cat, new Long(pkId) ); Set kittens = cat.getKittens(); DB に該当する行が無い場合、 load() は回復不可能な例外を投げることに注意しましょう。そのクラス がプロキシを使ってマッピングされている場合、 load() は初期化されていないプロキシを返し、プロキ シのメソッドが呼ばれるまで実際にはデータベースにアクセスしません。もし、実際にデータベースから ロードせずに、オブジェクトに対する関連を作りたい場合、この振る舞いはとても役立ちます。 batchsize がクラスマッピングに定義されているならば、複数のインスタンスを一括でロードすることが可能 です。 該当する行が存在することを確信できない場合は、 get() メソッドを使うべきです。それは、データ ベースにすぐにアクセスし、該当する行が無い場合は null を返します。 14 2 第10章 オブジェクトを扱う Cat cat = (Cat) sess.get(Cat.class, id); if (cat==null) { cat = new Cat(); sess.save(cat, id); } return cat; LockMode を使えば、 SELECT ... FOR UPDAT E という SQL を使ってオブジェクトをロードすること ができます。詳細な情報は、 API ドキュメントを参照してください。 Cat cat = (Cat) sess.get(Cat.class, id, LockMode.UPGRADE); 関連に対するカスケード方法として lock や all を指定しない限り、関連するインスタンスや含まれる コレクションは FOR UPDAT E で復元 されない ことに注意しましょう。 refresh() メソッドを使うことで、どんなときでも、オブジェクトやそのコレクションをリロードする ことができます。データベースのトリガがテーブルを更新した際に、そのテーブルに対応するオブジェク トのプロパティを同期する場合、このメソッドが役に立ちます。 sess.save(cat); sess.flush(); //force the SQL INSERT sess.refresh(cat); //re-read the state (after the trigger executes) An important question usually appears at this point: How much does Hibernate load from the database and how many SQL SELECT s will it use? T his depends on the fetching strategy and is explained in 「フェッチ戦略」. 10.4. ク エ リ If you don't know the identifiers of the objects you are looking for, you need a query. Hibernate supports an easy-to-use but powerful object oriented query language (HQL). For programmatic query creation, Hibernate supports a sophisticated Criteria and Example query feature (QBC and QBE). You may also express your query in the native SQL of your database, with optional support from Hibernate for result set conversion into objects. 10.4.1. クエリの実行 HQL やネイティブな SQL クエリは、 org.hibernate.Query のインスタンスとして表現されます。こ のインタフェースは、パラメータバインディングや ResultSet のハンドリングやクエリの実行を行うメ ソッドを用意しています。通常、 Query は、以下に示すように、その時点の Session を使って取得し ます。 14 3 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide List cats = session.createQuery( "from Cat as cat where cat.birthdate < ?") .setDate(0, date) .list(); List mothers = session.createQuery( "select mother from Cat as cat join cat.mother as mother where cat.name = ?") .setString(0, name) .list(); List kittens = session.createQuery( "from Cat as cat where cat.mother = ?") .setEntity(0, pk) .list(); Cat mother = (Cat) session.createQuery( "select cat.mother from Cat as cat where cat = ?") .setEntity(0, izi) .uniqueResult();]] Query mothersWithKittens = (Cat) session.createQuery( "select mother from Cat as mother left join fetch mother.kittens"); Set uniqueMothers = new HashSet(mothersWithKittens.list()); クエリは、普通、 list() を呼び出すことによって実行されます。クエリの結果は、メモリ上にあるコレ クションにすべてロードされます。クエリによって復元されたエンティティのインスタンスは、永続状態 です。もし、クエリがたった1個のインスタンスを返すと分かっているなら、 uniqueResult() メソッ ドが手っ取り早い方法です。即時フェッチを利用したクエリの場合、ふつう、得られたコレクションに は、ルートのオブジェクトが重複して含まれています(しかし、ルートが持つコレクションは初期化 (ロード)されています)。この重複は Set を使って取り除くことができます。 10.4 .1.1. 結果をイテレートする 時々、 iterate() メソッドを使ってクエリを実行することで、より良いパフォーマンスを得ることがで きます。これは、通常、クエリによって得られた実際のエンティティのインスタンスが、すでにセッショ ンまたは二次キャッシュに存在することが期待できる場合だけです。それらが、まだキャッシュされてい ないなら、 iterate() は、 list() よりも遅く、簡単なクエリに対しても多くのデータベースアクセス を必要とします。そのアクセスとは、識別子だけを取得するための最初の select 1回 と、実際のインス タンスを初期化するために後から行う n回 の select のことです。 // fetch ids Iterator iter = sess.createQuery("from eg.Qux q order by q.likeliness").iterate(); while ( iter.hasNext() ) { Qux qux = (Qux) iter.next(); // fetch the object // something we couldnt express in the query if ( qux.calculateComplicatedAlgorithm() ) { // delete the current instance iter.remove(); // dont need to process the rest break; } } 10.4 .1.2. オブジェクトの組( tuple)を返すクエリ Hibernate のクエリでは、時々、オブジェクトの組を返すことがあります。その場合は、各タプルは配列 として返されます: 14 4 第10章 オブジェクトを扱う Iterator kittensAndMothers = sess.createQuery( "select kitten, mother from Cat kitten join kitten.mother mother") .list() .iterator(); while ( kittensAndMothers.hasNext() ) { Object[] tuple = (Object[]) kittensAndMothers.next(); Cat kitten = tuple[0]; Cat mother = tuple[1]; .... } 10.4 .1.3. スカラーの結果 Queries may specify a property of a class in the select clause. T hey may even call SQL aggregate functions. Properties or aggregates are considered "scalar" results (and not entities in persistent state). Iterator results = sess.createQuery( "select cat.color, min(cat.birthdate), count(cat) from Cat cat " + "group by cat.color") .list() .iterator(); while ( results.hasNext() ) { Object[] row = (Object[]) results.next(); Color type = (Color) row[0]; Date oldest = (Date) row[1]; Integer count = (Integer) row[2]; ..... } 10.4 .1.4 . パラメータのバインド Query は、名前付きのパラメータや JDBC スタイルの ? パラメータに値をバインドするためのメソッド を持っています。 JDBC とは違い、 Hibernate はパラメータにゼロから番号を振っていきます。名前付き のパラメータとは、クエリ文字列のなかにある :nam e 形式の識別子です。名前付きパラメータの利点は 次の通りです。 名前付きパラメータは、クエリ文字列に登場する順番と無関係です 同じクエリ内に複数回登場することができます 自分自身を説明します //named parameter (preferred) Query q = sess.createQuery("from DomesticCat cat where cat.name = :name"); q.setString("name", "Fritz"); Iterator cats = q.iterate(); //positional parameter Query q = sess.createQuery("from DomesticCat cat where cat.name = ?"); q.setString(0, "Izi"); Iterator cats = q.iterate(); 14 5 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide //named parameter list List names = new ArrayList(); names.add("Izi"); names.add("Fritz"); Query q = sess.createQuery("from DomesticCat cat where cat.name in (:namesList)"); q.setParameterList("namesList", names); List cats = q.list(); 10.4 .1.5. ページ分け ResultSet に制限(復元したい最大行数や復元したい最初の行)を加える必要があれば、以下のように、 Query インターフェースのメソッドを使います。 Query q = sess.createQuery("from DomesticCat cat"); q.setFirstResult(20); q.setMaxResults(10); List cats = q.list(); 制限付きのクエリを DBMS のネイティブな SQL に変換する方法を、 Hibernate は知っています。 10.4 .1.6. スクロール可能なイテレーション JDBC ドライバがスクロール可能な ResultSet をサポートしていれば、 Query インターフェースを 使って、 ScrollableResults オブジェクトを取得できます。それを使うと、クエリの結果に対して柔 軟にナビゲーションできます。 Query q = sess.createQuery("select cat.name, cat from DomesticCat cat " + "order by cat.name"); ScrollableResults cats = q.scroll(); if ( cats.first() ) { // find the first name on each page of an alphabetical list of cats by name firstNamesOfPages = new ArrayList(); do { String name = cats.getString(0); firstNamesOfPages.add(name); } while ( cats.scroll(PAGE_SIZE) ); // Now get the first page of cats pageOfCats = new ArrayList(); cats.beforeFirst(); int i=0; while( ( PAGE_SIZE > i++ ) && cats.next() ) pageOfCats.add( cats.get(1) ); } cats.close() この機能にはオープン状態のデータベースコネクションが必要であることに注意してください。もし、オ フラインのページ分け機能が必要であれば、 setMaxResult() / setFirstResult() を使いましょ う。 10.4 .1.7. 名前付きクエリの外出し マッピングドキュメントに名前付きのクエリを定義することができます。(マークアップと解釈される文 字がクエリに含まれるなら、 CDAT A セクションを使うことを忘れないようにしましょう。) 14 6 第10章 オブジェクトを扱う <query name="ByNameAndMaximumWeight"><![CDATA[ from eg.DomesticCat as cat where cat.name = ? and cat.weight > ? ] ]></query> パラメータのバインディングと実行は、以下のようなプログラムで行われます: Query q = sess.getNamedQuery("ByNameAndMaximumWeight"); q.setString(0, name); q.setInt(1, minWeight); List cats = q.list(); 実際のプログラムコードは、使われるクエリ言語に依存していないことに注意しましょう。メタデータに は、ネイティブ SQL クエリを定義することもできます。また、既存のクエリをマッピングファイルに移 すことで、 Hibernate に移行することもできます。 Also note that a query declaration inside a <hibernate-m apping> element requires a global unique name for the query, while a query declaration inside a <class> element is made unique automatically by prepending the fully qualified name of the class, for example eg.Cat.ByNam eAndMaxim um Weight. 10.4.2. フィルタリングコレクション コレクション フィルタ は、永続化されているコレクションや配列に適用される特殊なタイプのクエリで す。そのクエリ文字列では、コレクションのその時点での要素を意味する this を使います。 Collection blackKittens = session.createFilter( pk.getKittens(), "where this.color = ?") .setParameter( Color.BLACK, Hibernate.custom(ColorUserType.class) ) .list() ); T he returned collection is considered a bag, and it's a copy of the given collection. T he original collection is not modified (this is contrary to the implication of the name "filter", but consistent with expected behavior). フィルタには from 節が不要であることに気づくでしょう(必要なら、持つことも可能ですが)。フィル タは、コレクションの要素自体を返して構いません。 Collection blackKittenMates = session.createFilter( pk.getKittens(), "select this.mate where this.color = eg.Color.BLACK.intValue") .list(); クエリを含まないフィルタも役に立ちます。例えば、非常に大きなコレクションの部分集合をロードする ために使えます。 Collection tenKittens = session.createFilter( mother.getKittens(), "") .setFirstResult(0).setMaxResults(10) .list(); 10.4.3. クライテリアのクエリ 14 7 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide HQL は非常に強力ですが、クエリ文字列を作るよりも、オブジェクト指向の API を使って動的にクエリを 作る方を好む開発者もいます。こういった場合のために、 Hibernate は直感的な Criteria クエリ API を提供しています。 Criteria crit = session.createCriteria(Cat.class); crit.add( Expression.eq( "color", eg.Color.BLACK ) ); crit.setMaxResults(10); List cats = crit.list(); T he Criteria and the associated Exam ple API are discussed in more detail in 15章Criteria クエリ. 10.4.4. ネイティブ SQL のクエリ createSQLQuery() を使って、 SQL でクエリを表現することもできます。そして、 Hibernate に、 ResultSet からオブジェクトへのマッピングをまかせます。 session.connection() を呼べばどんな ときでも、直接、 JDBC Connection を使用できることを覚えておきましょう。もし、 Hibernate API を使うのであれば、下記のように SQL の別名を括弧でくくらなければなりません。 List cats = session.createSQLQuery( "SELECT {cat.*} FROM CAT {cat} WHERE ROWNUM<10", "cat", Cat.class ).list(); List cats = session.createSQLQuery( "SELECT {cat}.ID AS {cat.id}, {cat}.SEX AS {cat.sex}, " + "{cat}.MATE AS {cat.mate}, {cat}.SUBCLASS AS {cat.class}, ... " + "FROM CAT {cat} WHERE ROWNUM<10", "cat", Cat.class ).list() SQL queries may contain named and positional parameters, just like Hibernate queries. More information about native SQL queries in Hibernate can be found in 16章ネイティブ SQL. 10.5. 永 続 オ ブ ジ ェ ク ト の 修 正 処理中の永続インスタンス (例: Session によって、ロード、セーブ、作成、クエリされたオブジェク ト)は、アプリケーションに操作されます。その際に変更された永続状態は、 Session が フラッシュ されるときに、永続化されます(これは、この章の後半で述べています)。変更を永続化するために、特 殊なメソッド( update() のようなもの。これは、別の目的で使用します)を呼ぶ必要はありません。オ ブジェクトの状態を更新する一番簡単な方法は、オブジェクトを load() し、 Session をオープンにし ている間に、直接操作することです。 DomesticCat cat = (DomesticCat) sess.load( Cat.class, new Long(69) ); cat.setName("PK"); sess.flush(); // changes to cat are automatically detected and persisted (オブジェクトをロードするための) SQL の SELECT と(更新された状態を永続化するための) SQL の UPDAT E が同じセッションで必要となるので、このプログラミングモデルは、効率が悪くなる場合があり ます。そのため、 Hibernate は別の方法を用意しています。それは、 detached インスタンスを使用する 方法です。 Note that Hibernate does not offer its own API for direct execution of UPDATE or DELETE statements. 14 8 第10章 オブジェクトを扱う Hibernate is a state management service, you don't have to think in statements to use it. JDBC is a perfect API for executing SQL statements, you can get a JDBC Connection at any time by calling session.connection(). Furthermore, the notion of mass operations conflicts with object/relational mapping for online transaction processing-oriented applications. Future versions of Hibernate may however provide special mass operation functions. See 13章バッチ処理 for some possible batch operation tricks. 10.6. detached オ ブ ジ ェ ク ト の 修 正 Many applications need to retrieve an object in one transaction, send it to the UI layer for manipulation, then save the changes in a new transaction. Applications that use this kind of approach in a highconcurrency environment usually use versioned data to ensure isolation for the "long" unit of work. Hibernate は、 Session.update() や Session.m erge() メソッドを使って、 detached インスタン スを再追加することで、このモデルに対応します。 // in the first session Cat cat = (Cat) firstSession.load(Cat.class, catId); Cat potentialMate = new Cat(); firstSession.save(potentialMate); // in a higher layer of the application cat.setMate(potentialMate); // later, in a new session secondSession.update(cat); // update cat secondSession.update(mate); // update mate 識別子 catId を持つ Cat が、既に secondSession でロードされていた場合は、再追加しようとした ときに、例外が投げられます。 同じ識別子を持つ永続インスタンスをセッションが既に保持していないことを確信できるなら update() を使います。そして、セッションの状態を考えずに、どんな場合でも変更をマージしたい場合は、 m erge() を使います。すなわち、 detached インスタンスの再追加操作が、最初に実行されることを確 実にするために、通常は update() が新しいセッションのなかで最初に呼ばれるメソッドになります。 T he application should individually update() detached instances reachable from the given detached instance if and only if it wants their state also updated. T his can be automated of course, using transitive persistence, see 「連鎖的な永続化」. lock() メソッドでもまた、新しいセッションにオブジェクトを再関連付けできます。しかし、 detached インスタンスは無修正でなければなりません。 //just reassociate: sess.lock(fritz, LockMode.NONE); //do a version check, then reassociate: sess.lock(izi, LockMode.READ); //do a version check, using SELECT ... FOR UPDATE, then reassociate: sess.lock(pk, LockMode.UPGRADE); lock() は、さまざまな LockMode とともに使うことができます。詳細は、 API ドキュメントとトラン ザクション処理の章を参照してください。再追加のときにだけ、 lock() が使われるわけではありませ ん。 Other models for long units of work are discussed in 「楽観的同時実行制御」. 14 9 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 10.7. 自 動 的 な 状 態 検 出 Hibernate のユーザーは次の2つのケースのどちらにも使える汎用的なメソッドを要求していました。それ は、新しい識別子を生成して transient インスタンスをセーブすることと、その時点の識別子と関連づい ている detached インスタンスを更新/再追加することのできるメソッドです。 saveOrUpdate() はこの ような機能を実現したメソッドです。 // in the first session Cat cat = (Cat) firstSession.load(Cat.class, catID); // in a higher tier of the application Cat mate = new Cat(); cat.setMate(mate); // later, in a new session secondSession.saveOrUpdate(cat); secondSession.saveOrUpdate(mate); // update existing state (cat has a non-null id) // save the new instance (mate has a null id) saveOrUpdate() の使用方法と意味は、新しいユーザーにとって混乱を招くかもしれません。まず第一 に、あるセッションで使用したインスタンスを別の新しいセッションで使おうとしない限り、 update() や saveOrUpdate() や m erge() を使う必要はありません。アプリケーション全体を通じて、これらの メソッドを全く使わないこともあります。 通常、 update() や saveOrUpdate() は次のシナリオで使われます: アプリケーションが最初のセッションでオブジェクトをロードします。 オブジェクトが UI 層に送られます。 オブジェクトに対して変更が加えられます。 オブジェクトがビジネスロジック層に送られます。 アプリケーションは、2番目のセッションで update() を呼ぶことで、これらの変更を永続化しま す。 saveOrUpdate() は以下のことを行います: オブジェクトがこのセッションで、すでに永続化されていれば、何もしません。 そのセッションに関連づいている別のオブジェクトが同じ識別子を持っているなら、例外を投げま す。 オブジェクトの識別子が値を持たないならば、 save() します。 if the object's identifier has the value assigned to a newly instantiated object, save() it if the object is versioned (by a <version> or <tim estam p>), and the version property value is the same value assigned to a newly instantiated object, save() it そうでない場合は、そのオブジェクトを update() します。 そして、 m erge() は以下のように非常に異なります: 同じ識別子を持つ永続化インスタンスがその時点でセッションと関連付いているならば、引数で受け 取ったオブジェクトの状態を永続化インスタンスにコピーします。 永続化インスタンスがその時点でセッションに関連付いていないなら、データベースからそれをロー ドするか、あるいは、新しい永続化インスタンスを作成します。 永続化インスタンスが返されます。 引数として与えたインスタンスはセッションと関連を持ちません。それは、分離状態のままです。 150 第10章 オブジェクトを扱う 10.8. 永 続 オ ブ ジ ェ ク ト の 削 除 Session.delete() will remove an object's state from the database. Of course, your application might still hold a reference to a deleted object. It's best to think of delete() as making a persistent instance transient. sess.delete(cat); 外部キー制約に違反するリスクもなく、好きな順番でオブジェクトを削除することができます。ただし、 間違った順番でオブジェクトを削除すると、外部キーカラムの NOT NULL 制約に違反する可能性があり ます。例えば、親オブジェクトを削除したときに、子供オブジェクトを削除し忘れた場合です。 10.9. 異 な る 二 つ の デ ー タ ス ト ア 間 で の オ ブ ジ ェ ク ト の レ プ リ ケーション 永続インスタンスのグラフを別のデータストアに永続化する場合に、識別子の値を再生成せずにすむと便 利な場合があります。 //retrieve a cat from one database Session session1 = factory1.openSession(); Transaction tx1 = session1.beginTransaction(); Cat cat = session1.get(Cat.class, catId); tx1.commit(); session1.close(); //reconcile with a second database Session session2 = factory2.openSession(); Transaction tx2 = session2.beginTransaction(); session2.replicate(cat, ReplicationMode.LATEST_VERSION); tx2.commit(); session2.close(); レプリケーション先のデータベースに行が既にある場合、 replicate() が衝突をどのように扱うかを ReplicationMode で指定します。 ReplicationMode.IGNORE - 同じ識別子を持つ行がデータベースに存在するなら、そのオブジェク トを無視します。 ReplicationMode.OVERWRIT E - 同じ識別子を持つ既存の行をすべて上書きします。 ReplicationMode.EXCEPT ION - 同じ識別子を持つ行がデータベースに存在するなら、例外を投げ ます。 ReplicationMode.LAT EST _VERSION - 行に保存されているバージョン番号が、引数のオブジェク トのバージョン番号より古いならば、その行を上書きします。 次のようなケースで、この機能を使用します。異なるデータベースインスタンスに入れられたデータの同 期、製品更新時におけるシステム設定情報の更新、非 ACID トランザクションのなかで加えられた変更の ロールバックなどです。 10.10. セ ッ シ ョ ン の フ ラ ッ シ ュ From time to time the Session will execute the SQL statements needed to synchronize the JDBC connection's state with the state of objects held in memory. T his process, flush, occurs by default at the following points 151 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide クエリを実行する前 org.hibernate.T ransaction.com m it() を実行したとき Session.flush() を実行したとき SQL 文は以下の順番で発行されます。 1. すべてのエンティティの挿入。これは、 Session.save() を使ってセーブしたオブジェクトの順 に実行していきます。 2. すべてのエンティティの更新 3. すべてのコレクションの削除 4. すべてのコレクションの要素に対する削除、更新、挿入 5. すべてのコレクションの挿入 6. すべてのエンティティの削除。これは、 Session.delete() を使って削除したオブジェクトの順 に実行していきます。 (1つ例外があります。 native ID 生成を使ったオブジェクトは、それらがセーブされたときに挿入され ます。) 明示的に flush() するときを除いて、 いつ Session が JDBC をコールするのかについて絶対的な保証 はありません。ただし、それらが実行される 順番 だけは保証されます。また、 Hibernate は、 Query.list(..) が古いデータや間違ったデータ返さないことを保証しています。 It is possible to change the default behavior so that flush occurs less frequently. T he FlushMode class defines three different modes: only flush at commit time (and only when the Hibernate T ransaction API is used), flush automatically using the explained routine, or never flush unless flush() is called explicitly. T he last mode is useful for long running units of work, where a Session is kept open and disconnected for a long time (see 「拡張セッションと自動バージョニング」). sess = sf.openSession(); Transaction tx = sess.beginTransaction(); sess.setFlushMode(FlushMode.COMMIT); // allow queries to return stale state Cat izi = (Cat) sess.load(Cat.class, id); izi.setName(iznizi); // might return stale data sess.find("from Cat as cat left outer join cat.kittens kitten"); // change to izi is not flushed! ... tx.commit(); // flush occurs sess.close(); During flush, an exception might occur (e.g. if a DML operation violates a constraint). Since handling exceptions involves some understanding of Hibernate's transactional behavior, we discuss it in 11章ト ランザクションと並行性. 10.11. 連 鎖 的 な 永 続 化 個々のオブジェクトをセーブしたり、削除したり、再追加したりすることはかなり面倒です。特に、関連 するオブジェクトを扱うような場合には際立ちます。よくあるのは、親子関係を扱うケースです。以下の 例を考えてみましょう: 152 第10章 オブジェクトを扱う If the children in a parent/child relationship would be value typed (e.g. a collection of addresses or strings), their lifecycle would depend on the parent and no further action would be required for convenient "cascading" of state changes. When the parent is saved, the value-typed child objects are saved as well, when the parent is deleted, the children will be deleted, etc. T his even works for operations such as the removal of a child from the collection; Hibernate will detect this and, since valuetyped objects can't have shared references, delete the child from the database. ここで、親と子が値型でなくエンティティであるとして同じシナリオを考えてみましょう。(例えば、カ テゴリーと品目の関係や親と子の猫の関係です。)エンティティは、それ自身がライフサイクルを持ち、 参照の共有をサポートします。(そのため、コレクションからエンティティを削除することは、エンティ ティ自身の削除を意味しません。)また、エンティティは、デフォルトでは、関連する他のエンティティ へ状態をカスケードすることはありません。 Hibernate は 到達可能性による永続化 をデフォルトでは実 行しません。 Hibernate の Session の基本操作( persist(), m erge(), saveOrUpdate(), delete(), lock(), refresh(), evict(), replicate() が含まれます)に対して、それぞれに対応するカス ケードスタイルがあります。それぞれのカスケードスタイルには、 create, m erge, save-update, delete, lock, refresh, evict, replicate という名前がついています。もし、関連に沿ってカ スケードさせたい操作があるなら、マッピングファイルにそう指定しなければなりません。例えば、以下 のようにします: <one-to-one name="person" cascade="persist"/> カスケードスタイルは、組み合わせることができます: <one-to-one name="person" cascade="persist,delete,lock"/> You may even use cascade="all" to specify that all operations should be cascaded along the association. T he default cascade="none" specifies that no operations are to be cascaded. 特殊なカスケードスタイル delete-orphan は、一対多関連にだけ適用できます。これは、関連から削 除された子供のオブジェクトに対して、 delete() 操作が適用されることを意味します。 おすすめ: It doesn't usually make sense to enable cascade on a <m any-to-one> or <m any-to-m any> association. Cascade is often useful for <one-to-one> and <one-to-m any> associations. If the child object's lifespan is bounded by the lifespan of the of the parent object make it a lifecycle object by specifying cascade="all,delete-orphan". Otherwise, you might not need cascade at all. But if you think that you will often be working with the parent and children together in the same transaction, and you want to save yourself some typing, consider using cascade="persist,m erge,save-update". Mapping an association (either a single valued association, or a collection) with cascade="all" marks the association as a parent/child style relationship where save/update/delete of the parent results in save/update/delete of the child or children. Futhermore, a mere reference to a child from a persistent parent will result in save/update of the child. T his metaphor is incomplete, however. A child which becomes unreferenced by its parent is not automatically deleted, except in the case of a <one-to-m any> association mapped with cascade="delete-orphan". T he precise semantics of cascading operations for a parent/child relationship are as follows: 親が persist() に渡されたならば、すべての子は persist() に渡されます。 153 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide m erge() に渡されたならば、すべての子は m erge() に渡されます。 親が save() 、 update() 、 saveOrUpdate() に渡されたならば、すべての子は saveOrUpdate() に渡されます。 transient または detached の子が、永続化された親に参照されたならば、 saveOrUpdate() に渡さ れます。 親が削除されたならば、すべての子は、 delete() に渡されます。 If a child is dereferenced by a persistent parent, nothing special happens - the application should explicitly delete the child if necessary - unless cascade="delete-orphan", in which case the "orphaned" child is deleted. 最後に、操作のカスケードがオブジェクトグラフに適用されるのは、 コールした時 あるいは、 flushした 時 であることに注意してください。すべての操作は、その操作が実行されたときに、到達可能な関連する エンティティに対してカスケードが可能ならカスケードします。しかし、 save-upate と deleteorphan は、 Session が flush している間に、すべての到達可能な関連するエンティティに伝播しま す。 10.12. メ タ デ ー タ の 使 用 Hibernate requires a very rich meta-level model of all entity and value types. From time to time, this model is very useful to the application itself. For example, the application might use Hibernate's metadata to implement a "smart" deep-copy algorithm that understands which objects should be copied (eg. mutable value types) and which should not (eg. immutable value types and, possibly, associated entities). Hibernate は ClassMetadata と CollectionMetadata インタフェースと T ype 階層を通してメタ データを公開します。メタデータインターフェースのインスタンスは、 SessionFactory から得られま す。 Cat fritz = ......; ClassMetadata catMeta = sessionfactory.getClassMetadata(Cat.class); Object[] propertyValues = catMeta.getPropertyValues(fritz); String[] propertyNames = catMeta.getPropertyNames(); Type[] propertyTypes = catMeta.getPropertyTypes(); // get a Map of all properties which are not collections or associations Map namedValues = new HashMap(); for ( int i=0; i<propertyNames.length; i++ ) { if ( !propertyTypes[i].isEntityType() && !propertyTypes[i].isCollectionType() ) { namedValues.put( propertyNames[i], propertyValues[i] ); } } 154 第11章 トランザクションと並行性 第 11章 トランザクションと並行性 Hibernate と同時実行制御について最も重要な点は、容易に理解できることです。 Hibernate は新たな ロックの振る舞いを追加しておらず、直接 JDBC コネクションと JT A リソースを使用します。 JDBC 、 ANSI 、およびデータベース管理システム(DBMS)のトランザクション分離の仕様を少し時間をかけて勉 強することを強く推奨します。 Hibernate はメモリ内のオブジェクトをロックしません。アプリケーションは、データベーストランザク ションの分離レベルで定義した振る舞いを期待できます。トランザクションスコープのキャッシュでもあ る Session のお陰で、識別子やクエリにより検索したエンティティはリピータブルリードになります (スカラー値を返すようなレポートクエリは違います)。 バージョニングによる自動的な楽観的同時実行制御に加えて、 SELECT FOR UPDAT E 文を使用して、行 を悲観的ロックするための(マイナーな) API も提供します。楽観的同時実行制御とこの API について は、この章の後のほうで議論します。 データベーストランザクションや長い対話(conversation、ロングトランザクション)だけでなく、 Configuration、SessionFactory、および Session という粒度で Hibernate が行う同時実行制御 の議論を始めます。 11.1. session ス コ ー プ と transaction ス コ ー プ SessionFactory は生成することが高価で、スレッドセーフなオブジェクトです。よって、アプリケー ションのすべてのスレッドで共有すべきです。通常、アプリケーションの起動時に、 Configuration インスタンスから1度だけ生成します。 Session は高価ではなく、スレッドセーフなオブジェクトでもありません。よって、1つの要求や1つ の対話、1つの作業単位(unit of work)に対して1度だけ使い、その後で捨てるべきです。 Session は 必要になるまで、 JDBC Connection(もしくは DataSource)を獲得しません。ゆえに、実際に使用 するときまでリソースを消費しません。 この状況を完了させるために、データベーストランザクションについても考えなければなりません。デー タベース内のロックの競合を少なくするために、データベーストランザクションは可能な限り短くするべ きです。長いデータベーストランザクションは、アプリケーションの高い並列実行性を阻害します。ゆえ に、ユーザーが考えている間(作業単位が完了するまで)データベーストランザクションを開いたままに するのは、たいていの場合よい設計とはいえません。 作業単位というスコープとは何でしょうか?1つの Hibernate Session は、いくつかのデータベースト ランザクションをまたがることができるでしょうか?または、スコープと一対一の関係でしょうか?いつ Session を開き、閉じるべきでしょうか?そして、データベーストランザクション境界をどのように分 けるのでしょうか? 11.1.1. 作業単位( Unit of work) First, don't use the session-per-operation antipattern, that is, don't open and close a Session for every simple database call in a single thread! Of course, the same is true for database transactions. Database calls in an application are made using a planned sequence, they are grouped into atomic units of work. (Note that this also means that auto-commit after every single SQL statement is useless in an application, this mode is intended for ad-hoc SQL console work. Hibernate disables, or expects the application server to do so, auto-commit mode immediately.) Database transactions are never optional, all communication with a database has to occur inside a transaction, no matter if you read or write data. As explained, auto-commit behavior for reading data should be avoided, as many small transactions are unlikely to perform better than one clearly defined unit of work. T he latter is also much more maintainable and extensible. 155 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide マルチユーザーのクライアント/サーバーアプリケーションの中で、最もよく使われるパターンは、 session-per-request です。このモデルの中では、クライアントから( Hibernate 永続化層が動作する) サーバーへリクエストが送られ、新しい Hibernate Session が開かれます。そして、この作業単位の中 ですべてのデータOベース処理が実行されます。作業が完了した(そして、クライアントへのレスポンス が準備できた)時点で、 session をフラッシュし、閉じます。クライアントの要求を処理するために、1 つのデータベーストランザクションを使用するでしょう。 Session を開き、閉じる際に、データベース トランザクションを開始し、コミットします。二つの関係は一対一です。このモデルは多くのアプリケー ションに完全に適合します。 T he challenge lies in the implementation. Hibernate provides built-in management of the "current session" to simplify this pattern. All you have to do is start a transaction when a server request has to be processed, and end the transaction before the response is send to the client. You can do this in any way you like, common solutions are ServletFilter, AOP interceptor with a pointcut on the service methods, or a proxy/interception container. An EJB container is a standardized way to implement crosscutting aspects such as transaction demarcation on EJB session beans, declaratively with CMT . If you decide to use programmatic transaction demarcation, prefer the Hibernate T ransaction API shown later in this chapter, for ease of use and code portability. Your application code can access a "current session" to process the request by simply calling sessionFactory.getCurrentSession() anywhere and as often as needed. You will always get a Session scoped to the current database transaction. T his has to be configured for either resourcelocal or JT A environments, see 「コンテキスト上のセッション」. Sometimes it is convenient to extend the scope of a Session and database transaction until the "view has been rendered". T his is especially useful in servlet applications that utilize a separate rendering phase after the request has been processed. Extending the database transaction until view rendering is complete is easy to do if you implement your own interceptor. However, it is not easily doable if you rely on EJBs with container-managed transactions, as a transaction will be completed when an EJB method returns, before rendering of any view can start. See the Hibernate website and forum for tips and examples around this Open Session in View pattern. 11.1.2. 長い対話 session-per-request パターンは、作業単位を設計する際に役立つ考えというだけではありません。多く のビジネスプロセスは、ユーザーとの一連の相互作用全体を要求します。その相互作用には、データベー スアクセスが含まれます。 Web とエンタープライズアプリケーションでは、データベーストランザク ションがユーザーとの相互作用にまで渡ることは許されません。次の例をよく考えてみてください: ダイアログの最初の画面が開き、個々の Session とデータベーストランザクションの中でロードさ れたデータをユーザーに見せます。ユーザーはオブジェクトを自由に修正できます。 T he user clicks "Save" after 5 minutes and expects his modifications to be made persistent; he also expects that he was the only person editing this information and that no conflicting modification can occur. この作業単位を(ユーザーの視点で)長期の 対話 (もしくは、アプリケーショントランザクション )と 呼びます。アプリケーションにこれを実装する方法はたくさんあります。 最初に思いつく実装は、ユーザーが考えている間、 Session とデータベーストランザクションを開いた ままにしておくことです。同時に修正されず、分離と原子性が保証されるように、データベース内のロッ クは保持したままにします。もちろん、これはアンチパターンです。なぜなら、ロックの競合が発生する と、アプリケーションが同時ユーザー数に応じてスケールアップできなくなるからです。 Clearly, we have to use several database transactions to implement the converastion. In this case, maintaining isolation of business processes becomes the partial responsibility of the application tier. A single conversation usually spans several database transactions. It will be atomic if only one of these 156 第11章 トランザクションと並行性 database transactions (the last one) stores the updated data, all others simply read data (e.g. in a wizard-style dialog spanning several request/response cycles). T his is easier to implement than it might sound, especially if you use Hibernate's features: 自動バージョニング - Hibernate は自動的に楽観的同時実行制御ができます。ユーザーが考えている間 に同時に修正がおきた場合、自動的に検出できます。通常、対話の終了時にチェックするだけです。 分離(Detached)オブジェクト - すでに議論した session-per-request パターンを使うと決定した場 合、ロードされたすべてのインスタンスは、ユーザーが考えている間は、セッションから分離された 状態になります。オブジェクトをセッションに再追加し、修正を永続化できます。これを sessionper-request-with-detached-objects パターンと呼びます。自動バージョニングを使うことで、同時に行 われる修正を分離できます。 拡張(もしくは、長い)セッション - Hibernate の Session は、データベーストランザクションをコ ミットした後、裏で結びついている JDBC コネクションを切断できます。そして、クライアントから の新しい要求が発生した際に、再接続できます。このパターンは、 session-per-conversation という 名で知られており、オブジェクトをセッションへ再追加することさえ不要にします。自動バージョニ ングを使うことで、同時に行われる修正を分離できます。通常 Session を自動的にフラッシュさせ ず、明示的にフラッシュします。 session-per-request-with-detached-objects と session-per-conversation の2つは、利点と欠点を持って います。これについては、この章の後のほうで、楽観的同時実行制御の文脈の中で議論します。 11.1.3. オブジェクト識別子を考える アプリケーションは、2つの異なる Session から同じ永続状態に同時にアクセスできます。しかし、2 つの Session インスタンスが永続性クラスの1つのインスタンスを共有することはできません。ゆえ に、識別子には2つの異なる概念があるということになります。 データベース識別子 foo.getId().equals( bar.getId() ) JVM 識別子 foo==bar T hen for objects attached to a particularSession (i.e. in the scope of a Session) the two notions are equivalent, and JVM identity for database identity is guaranteed by Hibernate. However, while the application might concurrently access the "same" (persistent identity) business object in two different sessions, the two instances will actually be "different" (JVM identity). Conflicts are resolved using (automatic versioning) at flush/commit time, using an optimistic approach. T his approach leaves Hibernate and the database to worry about concurrency; it also provides the best scalability, since guaranteeing identity in single-threaded units of work only doesn't need expensive locking or other means of synchronization. T he application never needs to synchronize on any business object, as long as it sticks to a single thread per Session. Within a Session the application may safely use == to compare objects. However, an application that uses == outside of a Session, might see unexpected results. T his might occur even in some unexpected places, for example, if you put two detached instances into the same Set. Both might have the same database identity (i.e. they represent the same row), but JVM identity is by definition not guaranteed for instances in detached state. T he developer has to override the equals() and hashCode() methods in persistent classes and implement his own notion of object equality. T here is one caveat: Never use the database identifier to implement equality, use a business key, a combination of unique, usually immutable, attributes. T he database identifier will change if a 157 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide transient object is made persistent. If the transient instance (usually together with detached instances) is held in a Set, changing the hashcode breaks the contract of the Set. Attributes for business keys don't have to be as stable as database primary keys, you only have to guarantee stability as long as the objects are in the same Set. See the Hibernate website for a more thorough discussion of this issue. Also note that this is not a Hibernate issue, but simply how Java object identity and equality has to be implemented. 11.1.4. 一般的な問題 session-per-user-session と session-per-application アンチパターンは使ってはいけません(もちろん、 まれに例外があります)。注記:下記の問題のいくつかは、推奨されるパターンとしても出現します。設 計を決定する前に、裏の意味を理解するようにしてください。 Session はスレッドセーフではありません。 HT T P リクエスト、セッション Bean 、 Swing ワー カーのように、同時実行が可能なものが Session インスタンスを共有すると、競合状態を引き起こ します。(後で議論する) HttpSession の中で Hibernate Session を保持する場合、 HttpSession へのアクセスを同期化することを考慮すべきです。さもなければ、ユーザーが十分早くリロードをク リックすると、同時に走る2つのスレッドの中で、同じ Session が使われます。 An exception thrown by Hibernate means you have to rollback your database transaction and close the Session immediately (discussed later in more detail). If your Session is bound to the application, you have to stop the application. Rolling back the database transaction doesn't put your business objects back into the state they were at the start of the transaction. T his means the database state and the business objects do get out of sync. Usually this is not a problem, because exceptions are not recoverable and you have to start over after rollback anyway. T he Session caches every object that is in persistent state (watched and checked for dirty state by Hibernate). T his means it grows endlessly until you get an OutOfMemoryException, if you keep it open for a long time or simply load too much data. One solution for this is to call clear() and evict() to manage the Session cache, but you most likely should consider a Stored Procedure if you need mass data operations. Some solutions are shown in 13章バッチ処理. Keeping a Session open for the duration of a user session also means a high probability of stale data. 11.2. デ ー タ ベ ー ス ト ラ ン ザ ク シ ョ ン 境 界 データベース (もしくはシステム) トランザクションの境界は、常に必要です。データベーストランザ クションの外で、データベースとの通信は起きません (これは自動コミットモードに慣れている多くの開 発者を混乱させるかもしれません) 。読み込むだけの操作にでも、いつも明確なトランザクション境界を 使用してください。分離レベルとデータベースの能力次第で、これは必要ないかもしれませんが、常にト ランザクション境界を明示的に指定しても、マイナス面は全くありません。確かに、1つのデータベース トランザクションは多数の小さなトランザクションより (データの読み込みであっても) パフォーマン スがすぐれています。 J2EE 環境に管理されていない状態 (すなわち、スタンドアロン、単純な Web や Swing アプリケーショ ン)でも、管理された状態でも、 Hibernate アプリケーションを実行できます。管理されていない環境で は、 Hiberante がデータベースのコネクションプールを提供します。アプリケーション開発者は、トラン ザクション境界を手動で設定しなければなりません。言い換えると、データベーストランザクションの開 始、コミット、ロールバックを開発者自身が設定する必要があるということです。通常、管理された環境 では、コンテナ管理によるトランザクション (CMT ) が提供されます。例えば、セッション Bean のデ プロイメントディスクリプタで宣言的に定義し、トランザクションを組み立てます。プログラムによるト ランザクション境界はもう必要ありません。 However, it is often desirable to keep your persistence layer portable between non-managed resourcelocal environments, and systems that can rely on JT A but use BMT instead of CMT . In both cases you'd use programmatic transaction demaracation. Hibernate offers a wrapper API called T ransaction that 158 第11章 トランザクションと並行性 translates into the native transaction system of your deployment environment. T his API is actually optional, but we strongly encourage its use unless you are in a CMT session bean. 通常、 Session 終了は、4つの異なるフェーズを含みます: セッションのフラッシュ トランザクションのコミット セッションのクローズ 例外のハンドリング Flushing the session has been discussed earlier, we'll now have a closer look at transaction demarcation and exception handling in both managed- and non-managed environments. 11.2.1. 管理されていない環境 Hibernate 永続化層を管理されていない環境で実装する場合は、通常単純なコネクションプール (すなわ ち DataSource ではない) によって、データベースコネクションを制御します。 Hibernate はそのコネク ションプールから必要なコネクションを取得します。セッション/トランザクション制御のイディオムは 次のようになります: // Non-managed environment idiom Session sess = factory.openSession(); Transaction tx = null; try { tx = sess.beginTransaction(); // do some work ... tx.commit(); } catch (RuntimeException e) { if (tx != null) tx.rollback(); throw e; // or display error message } finally { sess.close(); } You don't have to flush() the Session explicitly - the call to com m it() automatically triggers the synchronization (depending upon the 「セッションのフラッシュ」 FlushMode for the session. A call to close() marks the end of a session. T he main implication of close() is that the JDBC connection will be relinquished by the session. T his Java code is portable and runs in both non-managed and JT A environments. A much more flexible solution is Hibernate's built-in "current session" context management, as described earlier: 159 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide // Non-managed environment idiom with getCurrentSession() try { factory.getCurrentSession().beginTransaction(); // do some work ... factory.getCurrentSession().getTransaction().commit(); } catch (RuntimeException e) { factory.getCurrentSession().getTransaction().rollback(); throw e; // or display error message } You will very likely never see these code snippets in a regular application; fatal (system) exceptions should always be caught at the "top". In other words, the code that executes Hibernate calls (in the persistence layer) and the code that handles Runtim eException (and usually can only clean up and exit) are in different layers. T he current context management by Hibernate can significantly simplify this design, as all you need is access to a SessionFactory. Exception handling is discussed later in this chapter. Note that you should select org.hibernate.transaction.JDBCT ransactionFactory (which is the default), and for the second example "thread" as your hibernate.current_session_context_class. 11.2.2. JTA を使用する 永続化層をアプリケーションサーバー (例えば、 EJB セッション Bean の背後) で実行する場合、 Hibernate から取得するすべてのデータソースコネクションは、自動的にグローバル JT A トランザクショ ンの一部になります。 EJB を使わずに、スタンドアロンの JT A 実装を導入することもできます。 JT A 統 合のために、 Hibernate は2つの戦略を提供します。 Bean 管理トランザクション(BMT )を使い、 T ransaction API を使う場合、 Hibernate はアプリケー ションサーバーに BMT トランザクションの開始と終わりを告げます。すなわち、トランザクション管理 のコードは、管理されない環境と同じになります。 // BMT idiom Session sess = factory.openSession(); Transaction tx = null; try { tx = sess.beginTransaction(); // do some work ... tx.commit(); } catch (RuntimeException e) { if (tx != null) tx.rollback(); throw e; // or display error message } finally { sess.close(); } トランザクション境界として Session を使いたい場合、簡単にコンテキストを伝播する機能である getCurrentSession() があるので、 JT Aの UserT ransaction API を直接使用すべきでしょう。 160 第11章 トランザクションと並行性 // BMT idiom with getCurrentSession() try { UserTransaction tx = (UserTransaction)new InitialContext() .lookup("java:comp/UserTransaction"); tx.begin(); // Do some work on Session bound to transaction factory.getCurrentSession().load(...); factory.getCurrentSession().persist(...); tx.commit(); } catch (RuntimeException e) { tx.rollback(); throw e; // or display error message } CMT では、トランザクション境界をセッション Bean のデプロイメントディスクリプタで定義し、プログ ラムでは行いません。ゆえに、コードは次のように少なくなります: // CMT idiom Session sess = factory.getCurrentSession(); // do some work ... In a CMT /EJB even rollback happens automatically, since an unhandled Runtim eException thrown by a session bean method tells the container to set the global transaction to rollback. This means you do not need to use the Hibernate Transaction API at all with BMT or CMT, and you get automatic propagation of the "current" Session bound to the transaction. Note that you should choose org.hibernate.transaction.JT AT ransactionFactory if you use JT A directly (BMT ), and org.hibernate.transaction.CMT T ransactionFactory in a CMT session bean, when you configure Hibernate's transaction factory. Remember to also set hibernate.transaction.m anager_lookup_class. Furthermore, make sure that your hibernate.current_session_context_class is either unset (backwards compatiblity), or set to "jta". getCurrentSession() オペレーションは、 JT A 環境では1つの欠点を持ちます。デフォルトで使われ る after_statem ent コネクションリリースモードを使用する上で、警告が1つあります。 JT A 仕様の 愚かな制約のために、 scroll() または iterate() が返した、閉じられていない ScrollableResults または Iterator インスタンスを Hibernate が自動的にクリーンアップすること はできません。 finally ブロックの中で、 ScrollableResults.close() または Hibernate.close(Iterator) を明示的に呼び出して、裏に潜んだデータベースカーソルを解放 しな ければなりません。 (もちろん、多くのアプリケーションでは、 JT A か CMT コードで scroll() や iterate() の使用を避けるのは容易です。) 11.2.3. 例外ハンドリング Session が例外 (SQLExceptionを含む) を投げた場合、直ちに、データベーストランザクションを ロールバックし、 Session.close() を呼び、 Session インスタンスを破棄すべきです。 Session のいくつかのメソッドは、セッションの状態を 矛盾したまま にします。 Hibernate が投げた例外を、回 復できるものとして扱うことはできません。 finally ブロックの中で close() を呼んで、 Session 161 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide を確実に閉じてください。 T he HibernateException, which wraps most of the errors that can occur in a Hibernate persistence layer, is an unchecked exception (it wasn't in older versions of Hibernate). In our opinion, we shouldn't force the application developer to catch an unrecoverable exception at a low layer. In most systems, unchecked and fatal exceptions are handled in one of the first frames of the method call stack (i.e. in higher layers) and an error message is presented to the application user (or some other appropriate action is taken). Note that Hibernate might also throw other unchecked exceptions which are not a HibernateException. T hese are, again, not recoverable and appropriate action should be taken. Hibernate は、データベースとの対話中に投げられた SQLException を JDBCException でラップし ます。実は、例外をより意味のある JDBCException のサブクラスに変換しようと試みます。元の SQLException は、 JDBCException.getCause() によりいつでも得られます。 Hibernate は、 SessionFactory に追加されている SQLExceptionConverter を使い、 SQLException を適当な JDBCException サブクラスに変換します。デフォルトでは、 SQLExceptionConverter は設定され ている SQL 方言により定義されます。一方で、独自の実装に差し替えることもできます (詳細は、 SQLExceptionConverterFactory クラスの Javadoc を参照してください)。標準的な JDBCException のサブタイプを下記に示します。 JDBCConnectionException - 基礎となる JDBC 通信のエラーを表します。 SQLGram m arException - 発行する SQL の文法もしくは構文の問題を表します。 ConstraintViolationException - 何らかの形式の完全性制約違反を表します。 LockAcquisitionException - 要求された操作を実施するのに必要なロックレベルを得る際のエ ラーを表します。 GenericJDBCException - 他のカテゴリに一致しなかった一般的な例外です。 11.2.4. トランザクションのタイムアウト EJB のような管理された環境が提供するきわめて重要な特徴の1つは、トランザクションのタイムアウト です。これは管理されていないコードには提供できません。トランザクションタイムアウトは、不品行な トランザクションがユーザーにレスポンスを返さないまま、無期限にリソースを使い続けないことを保障 します。管理された環境 (JT A) の外では、 Hibernate はこの機能をフルに提供できません。しかしなが ら、 Hibernate は次のようなデータアクセス操作の制御くらいはできます。データベースレベルのデッド ロックや大きなリザルトセットを返すクエリを定義されたタイムアウトによって確実に制限します。管理 された環境では、 Hibernate はトランザクションタイムアウトを JT A に委譲します。この機能は、 Hibernate の T ransaction オブジェクトによって抽象化されています。 162 第11章 トランザクションと並行性 Session sess = factory.openSession(); try { //set transaction timeout to 3 seconds sess.getTransaction().setTimeout(3); sess.getTransaction().begin(); // do some work ... sess.getTransaction().commit() } catch (RuntimeException e) { sess.getTransaction().rollback(); throw e; // or display error message } finally { sess.close(); } CMT Bean の中では setT im eout() を呼び出せないことに注意してください。トランザクションタイム アウトは宣言的に定義されるべきです。 11.3. 楽 観 的 同 時 実 行 制 御 高い並列性と高いスケーラビリティの両方を実現するアプローチは、バージョニングを使った楽観的同時 実行制御のみです。更新の衝突を見つけるために(および、更新が失われるのを防ぐために)、バージョ ン番号もしくはタイムスタンプを使って、バージョンをチェックします。 Hibernate は、楽観的同時実行 を行うアプリケーションコードを書くためのアプローチを3つ提供します。私たちが見せるユースケース は、長い対話を持ちますが、バージョンチェックはまだ1つのデータベーストランザクションの中で更新 を失うことを防ぐ利点も持っています。 11.3.1. アプリケーションによるバージョンチェック Hibernate にほとんど助けてもらわずに実装するケースです。データベースとのやり取りは、それぞれ新 しい Session の中で起こります。開発者は、すべての永続性インスタンスを操作する前に、データベー スから再読み込みする責務があります。このアプローチでは、対話トランザクションの分離を守るため に、アプリケーション自身がバージョンチェックを行う必要があります。このアプローチは、データベー スアクセスの中では、最も非効率です。エンティティ EJB と最も似ているアプローチです。 // foo is an instance loaded by a previous Session session = factory.openSession(); Transaction t = session.beginTransaction(); int oldVersion = foo.getVersion(); session.load( foo, foo.getKey() ); // load the current state if ( oldVersion!=foo.getVersion ) throw new StaleObjectStateException(); foo.setProperty("bar"); t.commit(); session.close(); T he version property is mapped using <version>, and Hibernate will automatically increment it during flush if the entity is dirty. Of course, if you are operating in a low-data-concurrency environment and don't require version checking, you may use this approach and just skip the version check. In that case, last commit wins will 163 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide be the default strategy for your long conversations. Keep in mind that this might confuse the users of the application, as they might experience lost updates without error messages or a chance to merge conflicting changes. 確かに、マニュアルによるバージョンチェックは、些細な儀式だけで実行できますが、多くのアプリケー ションにとって実用的ではありません。しばしば、1つのインスタンスだけでなく、修正されたオブジェ クトの完全なグラフをチェックしなければなりません。 Hibernate は、設計パラダイムとして、拡張 Session か分離されたインスタンスを自動的にバージョンチェックします。 11.3.2. 拡張セッションと自動バージョニング A single Session instance and its persistent instances are used for the whole conversation, known as session-per-conversation. Hibernate checks instance versions at flush time, throwing an exception if concurrent modification is detected. It's up to the developer to catch and handle this exception (common options are the opportunity for the user to merge changes or to restart the business conversation with non-stale data). ユーザーの対話を待っているときは、 Session を基礎となる JDBC コネクションから切り離します。こ のアプローチは、データベースアクセスの中では、最も効率的です。アプリケーションは、バージョン チェックや分離されたインスタンスを再追加することに関心を持つ必要はありません。また、あらゆる データベーストランザクションの中でインスタンスを再読み込みする必要はありません。 // foo is an instance loaded earlier by the old session Transaction t = session.beginTransaction(); // Obtain a new JDBC connection, start transaction foo.setProperty("bar"); session.flush(); t.commit(); session.close(); // Only for last transaction in conversation // Also return JDBC connection // Only for last transaction in conversation T he foo object still knows which Session it was loaded in. Beginning a new database transaction on an old session obtains a new connection and resumes the session. Committing a database transaction disconnects a session from the JDBC connection and returns the connection to the pool. After reconnection, to force a version check on data you aren't updating, you may call Session.lock() with LockMode.READ on any objects that might have been updated by another transaction. You don't need to lock any data that you are updating. Usually you would set FlushMode.NEVER on an extended Session, so that only the last database transaction cycle is allowed to actually persist all modifications made in this conversation. Hence, only this last database transaction would include the flush() operation, and then also close() the session to end the conversation. ユーザーが考慮中に、格納することができないくらい Session が大きいのであれば、このパターンは問 題があります。例えば、 HttpSession は可能な限り小さく保つべきです。 Session は (強制的に) 1次キャッシュでもあり、ロードしたオブジェクトをすべて保持します。おそらく、リクエスト/レスポ ンスのサイクルが数回であれば、この戦略が使えます。1つの対話のためだけに Session を使うべきで す。なぜなら、すぐに新鮮でないデータを持つためです。 (Hibernate の以前のバージョンは、明示的な Session の切断と再接続が必要だったことに注意してく ださい。これらのメソッドは非推奨になりました。なぜなら、トランザクションの開始と終了は同じ効果 があるためです。) Also note that you should keep the disconnected Session close to the persistence layer. In other words, use an EJB stateful session bean to hold the Session in a three-tier environment, and don't transfer it to the web layer (or even serialize it to a separate tier) to store it in the HttpSession. 164 第11章 トランザクションと並行性 拡張セッションパターン (もしくは、 session-per-conversation ) は、自動的なカレントセッションコ ンテキスト管理を実施するより難しい。このために、あなたは CurrentSessionContext の実装を供 給する必要があります。 Hibernate Wiki にある例を参照してください。 11.3.3. デタッチされたオブジェクトと自動バージョニング 新しい Session により、永続化ストア (訳注:DB) との対話が発生します。また一方、同じ永続性 インスタンスが、データベースとの対話ごとに再利用されます。アプリケーションは、元々は他の Session でロードされ、デタッチされたインスタンスの状態を操作します。そして、 Session.update() もしくは、 Session.saveOrUpdate() 、 Session.m erge() を使って、それ らのインスタンスを再追加します。 // foo is an instance loaded by a previous Session foo.setProperty("bar"); session = factory.openSession(); Transaction t = session.beginTransaction(); session.saveOrUpdate(foo); // Use merge() if "foo" might have been loaded already t.commit(); session.close(); この場合もやはり、 Hibernate はフラッシュする際に、インスタンスのバージョンをチェックします。更 新の競合が発生した場合には、例外を投げます。 オブジェクトが修正されていないことを確信している場合は、 update() の代わりに、 LockMode.READ を使って、 lock() を呼び出すこともできます (すべてのキャッシュを迂回し、バー ジョンチェックを実施します)。 11.3.4. 自動バージョニングのカスタマイズ You may disable Hibernate's automatic version increment for particular properties and collections by setting the optim istic-lock mapping attribute to false. Hibernate will then no longer increment versions if the property is dirty. Legacy database schemas are often static and can't be modified. Or, other applications might also access the same database and don't know how to handle version numbers or even timestamps. In both cases, versioning can't rely on a particular column in a table. T o force a version check without a version or timestamp property mapping, with a comparison of the state of all fields in a row, turn on optim istic-lock="all" in the <class> mapping. Note that this concepetually only works if Hibernate can compare the old and new state, i.e. if you use a single long Session and not sessionper-request-with-detached-objects. Sometimes concurrent modification can be permitted as long as the changes that have been made don't overlap. If you set optim istic-lock="dirty" when mapping the <class>, Hibernate will only compare dirty fields during flush. In both cases, with dedicated version/timestamp columns or with full/dirty field comparison, Hibernate uses a single UPDAT E statement (with an appropriate WHERE clause) per entity to execute the version check and update the information. If you use transitive persistence to cascade reattachment to associated entities, Hibernate might execute uneccessary updates. T his is usually not a problem, but on update triggers in the database might be executed even when no changes have been made to detached instances. You can customize this behavior by setting select-before-update="true" in the <class> mapping, forcing Hibernate to SELECT the instance to ensure that changes did actually occur, before updating the row. 165 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 11.4. 悲 観 的 ロ ッ ク ユーザーがロック戦略に悩むのに多くの時間を費やすことを意図していません。通常は、 JDBC コネク ションに分離レベルを指定し、単にデータベースにすべての仕事をさせれば十分です。しかしながら、高 度なユーザーは、排他的な悲観的ロックを獲得することか、新しいトランザクションが開始される際に ロックを再獲得することをときどき望むかもしれません。 Hibernate はいつもデータベースのロックの仕組みを使います。メモリ内のオブジェクトを決してロック しません。 LockMode クラスは、 Hibernate が獲得できる異なるロックレベルを定義します。以下の仕組みにより、 ロックを獲得できます。 LockMode.WRIT E は、 Hibernate が行を更新もしくは挿入する際に自動的に得られます。 LockMode.UPGRADE は、データベースでサポートされている文法 SELECT ... FOR UPDAT E を 使った、明示的なユーザー要求により得られるかもしれません。 LockMode.UPGRADE_NOWAIT は、 Oracle で SELECT ... FOR UPDAT E NOWAIT を使った、明 示的なユーザー要求により得られるかもしれません。 LockMode.READ は、 Repeatable Read もしくは Serializable の分離レベルで、データを読んだ際に 自動的に得られます。おそらく、明示的なユーザー要求により、再取得されます。 LockMode.NONE は、ロックしないことを表します。 T ransaction の終わりに、すべてのオブ ジェクトはこのロックモードに切り替わります。 update() や saveOrUpdate() を呼び出すことに よって、セッションに関連付けられたオブジェクトも、このロックモードで出発します。 T he "explicit user request" is expressed in one of the following ways: LockMode を指定した Session.load() の呼び出し。 Session.lock() の呼び出し。 Query.setLockMode() の呼び出し。 UPGRADE もしくは UPGRADE_NOWAIT が指定された Session.load() が呼び出され、かつ要求された オブジェクトがセッションによってまだロードされていなかった場合は、 SELECT ... FOR UPDAT E を使って、オブジェクトがロードされます。 load() で呼び出されたオブジェクトが、要求されているよ り制限が少ないロックですでにロードされていた場合は、 Hibernate はそのオブジェクトのために、 lock() を呼び出します。 指定されたロックモードが READ もしくは、 UPGRADE 、 UPGRADE_NOWAIT だった場合、 Session.lock() は、バージョン番号のチェックを実施します。 (UPGRADE もしくは UPGRADE_NOWAIT の場合、 SELECT ... FOR UPDAT E が使われます。) データベースが要求されたロックモードをサポートしていない場合、 Hibernate は(例外を投げる代わり に、)適切な代わりのモードを使います。これは、アプリケーションがポータブルであることを保証しま す。 11.5. コ ネ ク シ ョ ン 開 放 モ ー ド Hibernate のレガシー(2.x)の JDBC コネクション管理に関する振る舞いは、最初に必要とした際に Session がコネクションを得るというものでした。そして、セッションが閉じられるまで、そのコネク ションを保持しました。 Hibernate 3.x は、セッションに JDBC コネクションをどのように制御するかを 伝えるコネクション開放モードという概念を導入しました。以降の議論は、構成された ConnectionProvider を通して提供されるコネクションに適切であることに注意してください。異な る開放モードは、 org.hibernate.ConnectionReleaseMode に列挙された値により確認されま す。 166 第11章 トランザクションと並行性 ON_CLOSE - 本質的に上記で述べたレガシーの振る舞いです。 Hibernate セッションは最初に JDBC アクセスを実行する必要がある際にコネクションを得ます。そして、セッションが閉じられるまで、 コネクションを保持します。 AFT ER_T RANSACT ION - org.hibernate.T ransaction が完了した後、コネクションを開放しま す。 AFT ER_ST AT EMENT (積極的な開放とも呼ばれる) - すべてのステートメントがそれぞれ実行され た後、コネクションが開放されます。ステートメントがセッションに関連するリソースを開いたまま にする場合は、この積極的な開放はスキップされます。今のところ、これが起こるのは org.hibernate.ScrollableResults が使われる場合のみです。 コンフィグレーションパラメータの hibernate.connection.release_m ode は、使用する開放モー ドを指定するために使います。指定できる値は次の通りです: auto (デフォルト) - これを選択すると org.hibernate.transaction.T ransactionFactory.getDefaultReleaseMode() メソッ ドによって返される開放モードに委譲されます。このメソッドは、 JT AT ransactionFactory には ConnectionReleaseMode.AFT ER_ST AT EMENT を返し、 JDBCT ransactionFactory には ConnectionReleaseMode.AFT ER_T RANSACT ION を返します。このデフォルトの振る舞いを変えて うまくいった試しがありません。それは、この設定値が原因で起こる障害は、ユーザーコードの中で バグや間違った条件になりやすいからです。 on_close - ConnectionReleaseMode.ON_CLOSE を使います。この設定は後方互換のために残され ていますが、使わないことを強く勧めます。 after_transaction - ConnectionReleaseMode.AFT ER_T RANSACT ION を使います。この設定は JT A 環境の中では使うべきではありません。 ConnectionReleaseMode.AFT ER_T RANSACT ION を指 定し、自動コミットモードの中では、開放モードが AFT ER_ST AT EMENT であるかのように、コネク ションは開放されることに注意してください。 after_statem ent - ConnectionReleaseMode.AFT ER_ST AT EMENT を使います。さらに、設定さ れた ConnectionProvider は、この設定 (supportsAggressiveRelease()) をサポートするか どうかを調べるために使用します。もしそうでない場合、開放モードは ConnectionReleaseMode.AFT ER_T RANSACT ION にリセットされます。この設定は次の環境でのみ 安全です。それは、 ConnectionProvider.getConnection() を呼び出すたびに基盤となる JDBC コネクションが同じものを取得できるか、同じコネクションが得られることが問題とならない自 動コミット環境の中です。 167 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 第 12章 インターセプタとイベント アプリケーションが Hibernate の内部で発生するイベントに対応できると役に立つことがあります。ある 種の一般的な機能を実装できるようになり、また Hibernate の機能を拡張することもできるようになりま す。 12.1. イ ン タ ー セ プ タ Interceptor インターフェースを使って、セッションからアプリケーションへコールバックをすること ができます。これにより永続オブジェクトの保存、更新、削除、読み込みの前に、アプリケーションがプ ロパティを検査したり操作したりできるようになります。これは監査情報の追跡に利用できます。下の例 で Interceptor は Auditable が作成されると自動的に createT im estam p を設定し、 Auditable が更新されると自動的に lastUpdateT im estam p プロパティを更新します。 Interceptor を直接実装したり、 (さらによいのは) Em ptyInterceptor を拡張したりできます。 168 第12章 インターセプタとイベント package org.hibernate.test; import java.io.Serializable; import java.util.Date; import java.util.Iterator; import org.hibernate.EmptyInterceptor; import org.hibernate.Transaction; import org.hibernate.type.Type; public class AuditInterceptor extends EmptyInterceptor { private int updates; private int creates; private int loads; public void onDelete(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) { // do nothing } public boolean onFlushDirty(Object entity, Serializable id, Object[] currentState, Object[] previousState, String[] propertyNames, Type[] types) { if ( entity instanceof Auditable ) { updates++; for ( int i=0; i < propertyNames.length; i++ ) { if ( "lastUpdateTimestamp".equals( propertyNames[i] ) ) { currentState[i] = new Date(); return true; } } } return false; } public boolean onLoad(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) { if ( entity instanceof Auditable ) { loads++; } return false; } public boolean onSave(Object entity, Serializable id, Object[] state, String[] propertyNames, Type[] types) { 169 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide if ( entity instanceof Auditable ) { creates++; for ( int i=0; i<propertyNames.length; i++ ) { if ( "createTimestamp".equals( propertyNames[i] ) ) { state[i] = new Date(); return true; } } } return false; } public void afterTransactionCompletion(Transaction tx) { if ( tx.wasCommitted() ) { System.out.println("Creations: " + creates + ", Updates: " + updates, "Loads: " + loads); } updates=0; creates=0; loads=0; } } インターセプタには二種類あります: Session スコープのものと SessionFactory スコープのもので す。 Session スコープのインターセプタは、セッションをオープンするときに指定します。 Interceptor を引数に取る SessionFactory.openSession() のオーバーロードメソッドの一つを使います。 Session session = sf.openSession( new AuditInterceptor() ); SessionFactory スコープのインターセプタは Configuration オブジェクトを使って登録します。 これは SessionFactory の構築よりも優先されます。この場合、提供されるインターセプタは SessionFactory からオープンされたすべてのセッションに適用されます。これは使用するインターセ プタを明示的に指定してセッションをオープンしない限り、そうなります。 SessionFactory スコープ のインターセプタはスレッドセーフでなければなりません。複数のセッションが (潜在的に) このイン ターセプタを同時並行で使用することになるため、セッション固有の状態を格納しないように気をつけて ください。 new Configuration().setInterceptor( new AuditInterceptor() ); 12.2. イ ベ ン ト シ ス テ ム 永続化層で特定のイベントに対応しなければならない場合、 Hibernate3 の イベント アーキテクチャを使 うこともできます。イベントシステムはインターセプタと一緒に使うか、またはインターセプタの代わり として使うことができます。 本質的に Session インターフェースのすべてのメソッドは、1個のイベントと相互に関連します。例え ば LoadEvent 、 FlushEvent などがあります (定義済みのイベント型の一覧については、 XML 設定 ファイルの DT D や org.hibernate.event パッケージを調べてください) 。リクエストがこれらのメ ソッドの1つから作られるとき、 Hibernate の Session は適切なイベントを生成し、そのイベント型に 設定されたイベントリスナに渡します。すばらしいことに、これらのリスナはそのメソッドと同じ処理を 実装します。とはいえ、リスナインターフェースの一つを自由にカスタム実装できます (つまり、 LoadEvent は登録された LoadEventListener インターフェースの実装により処理されます)。その 170 第12章 インターセプタとイベント 場合、その実装には Session から作られたどのような load() リクエストをも処理する責任がありま す。 リスナは事実上シングルトンであると見なせます。つまりリスナはリクエスト間で共有されるため、イン スタンス変数として状態を保持するべきではないということです。 A custom listener should implement the appropriate interface for the event it wants to process and/or extend one of the convenience base classes (or even the default event listeners used by Hibernate outof-the-box as these are declared non-final for this purpose). Custom listeners can either be registered programmatically through the Configuration object, or specified in the Hibernate configuration XML (declarative configuration through the properties file is not supported). Here's an example of a custom load event listener: public class MyLoadListener implements LoadEventListener { // this is the single method defined by the LoadEventListener interface public void onLoad(LoadEvent event, LoadEventListener.LoadType loadType) throws HibernateException { if ( !MySecurity.isAuthorized( event.getEntityClassName(), event.getEntityId() ) ) { throw MySecurityException("Unauthorized access"); } } } デフォルトリスナ以外のリスナを使うには、 Hibernate への設定も必要です: <hibernate-configuration> <session-factory> ... <event type="load"> <listener class="com.eg.MyLoadListener"/> <listener class="org.hibernate.event.def.DefaultLoadEventListener"/> </event> </session-factory> </hibernate-configuration> またその他に、プログラムで登録する方法もあります: Configuration cfg = new Configuration(); LoadEventListener[] stack = { new MyLoadListener(), new DefaultLoadEventListener() }; cfg.EventListeners().setLoadEventListeners(stack); Listeners registered declaratively cannot share instances. If the same class name is used in multiple <listener/> elements, each reference will result in a separate instance of that class. If you need the capability to share listener instances between listener types you must use the programmatic registration approach. なぜインターフェースを実装して、特化した型を設定時に指定するのでしょうか?リスナの実装クラス に、複数のイベントリスナインターフェースを実装できるからです。登録時に追加で型を指定すること で、カスタムリスナの on/off を設定時に簡単に切り替えられます。 12.3. Hibernate の 宣 言 的 な セ キ ュ リ テ ィ 一般的に Hibernate アプリケーションの宣言的なセキュリティは、セッションファサード層で管理しま 171 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide す。現在、 Hiberenate3 は JACC で許可し、さらに JAAS で認証したアクションを許しています。これは イベントアーキテクチャの最上位に組み込まれているオプションの機能です。 まず最初に、適切なイベントリスナを設定して JAAS 認証を使えるようにしなければなりません。 <listener type="pre-delete" class="org.hibernate.secure.JACCPreDeleteEventListener"/> <listener type="pre-update" class="org.hibernate.secure.JACCPreUpdateEventListener"/> <listener type="pre-insert" class="org.hibernate.secure.JACCPreInsertEventListener"/> <listener type="pre-load" class="org.hibernate.secure.JACCPreLoadEventListener"/> Note that <listener type="..." class="..."/> is just a shorthand for <event type="..."><listener class="..."/></event> when there is exactly one listener for a particular event type. 次に、同じく hibernate.cfg.xm l でロールにパーミッションを与えてください: <grant role="admin" entity-name="User" actions="insert,update,read"/> <grant role="su" entity-name="User" actions="*"/> このロール名は使用する JACC プロバイダに理解されるロールです。 172 第13章 バッチ処理 第 13章 バッチ処理 Hibernate を使ってデータベースに100,000行を挿入する愚直な方法は、このようなものです: Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); for ( int i=0; i<100000; i++ ) { Customer customer = new Customer(.....); session.save(customer); } tx.commit(); session.close(); T his would fall over with an OutOfMem oryException somewhere around the 50 000th row. T hat's because Hibernate caches all the newly inserted Custom er instances in the session-level cache. In this chapter we'll show you how to avoid this problem. First, however, if you are doing batch processing, it is absolutely critical that you enable the use of JDBC batching, if you intend to achieve reasonable performance. Set the JDBC batch size to a reasonable number (say, 10-50): hibernate.jdbc.batch_size 20 Note that Hibernate disables insert batching at the JDBC level transparently if you use an identiy identifier generator. また二次キャッシュが全く効かないプロセスで、このような作業をしたいと思うかもしれません: hibernate.cache.use_second_level_cache false しかし、これは絶対に必要というわけではありません。なぜなら明示的に CacheMode を設定して、二次 キャッシュとの相互作用を無効にすることができるからです。 13.1. バ ッ チ 挿 入 新しいオブジェクトを永続化するとき、一次キャッシュのサイズを制限するため、セッションを flush() して clear() しなければなりません。 Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); for ( int i=0; i<100000; i++ ) { Customer customer = new Customer(.....); session.save(customer); if ( i % 20 == 0 ) { //20, same as the JDBC batch size //flush a batch of inserts and release memory: session.flush(); session.clear(); } } tx.commit(); session.close(); 13.2. バ ッ チ 更 新 173 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide データを復元したり更新したりするには同じアイディアを適用します。それに加えて、データの行を多く 返すクエリに対して有効なサーバーサイドのカーソルの利点を生かしたければ scroll() を使う必要が あります。 Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); ScrollableResults customers = session.getNamedQuery("GetCustomers") .setCacheMode(CacheMode.IGNORE) .scroll(ScrollMode.FORWARD_ONLY); int count=0; while ( customers.next() ) { Customer customer = (Customer) customers.get(0); customer.updateStuff(...); if ( ++count % 20 == 0 ) { //flush a batch of updates and release memory: session.flush(); session.clear(); } } tx.commit(); session.close(); 13.3. StatelessSession イ ン タ ー フ ェ ー ス Alternatively, Hibernate provides a command-oriented API that may be used for streaming data to and from the database in the form of detached objects. A StatelessSession has no persistence context associated with it and does not provide many of the higher-level lifecycle semantics. In particular, a stateless session does not implement a first-level cache nor interact with any second-level or query cache. It does not implement transactional write-behind or automatic dirty checking. Operations performed using a stateless session do not ever cascade to associated instances. Collections are ignored by a stateless session. Operations performed via a stateless session bypass Hibernate's event model and interceptors. Stateless sessions are vulnerable to data aliasing effects, due to the lack of a first-level cache. A stateless session is a lower-level abstraction, much closer to the underlying JDBC. StatelessSession session = sessionFactory.openStatelessSession(); Transaction tx = session.beginTransaction(); ScrollableResults customers = session.getNamedQuery("GetCustomers") .scroll(ScrollMode.FORWARD_ONLY); while ( customers.next() ) { Customer customer = (Customer) customers.get(0); customer.updateStuff(...); session.update(customer); } tx.commit(); session.close(); このコード例では、クエリが返す Custom er インスタンスは即座に (セッションから) 分離されること に注意してください。これは、どのような永続コンテキストとも決して関連しません。 StatelessSession インターフェースで定義されている insert(), update() と delete() の操作 は、低レベルの直接的なデータベース操作と考えられます。結果として、 SQL の INSERT , UPDAT E ま たは DELET E がそれぞれ即座に実行されます。このように、これらは Session インターフェースで定義 174 第13章 バッチ処理 されている save(), saveOrUpdate() と delete() とは非常に異なる意味を持ちます。 13.4. DML ス タ イ ル の 操 作 As already discussed, automatic and transparent object/relational mapping is concerned with the management of object state. T his implies that the object state is available in memory, hence manipulating (using the SQL Data Manipulation Language (DML) statements: INSERT , UPDAT E, DELET E) data directly in the database will not affect in-memory state. However, Hibernate provides methods for bulk SQL-style DML statement execution which are performed through the Hibernate Query Language (14 章HQL: Hibernate クエリ言語 HQL). UPDAT E と DELET E 文の疑似構文は: ( UPDAT E | DELET E ) FROM? エンティティ名 (WHERE 条 件節 )? です。注意すべき点がいくつかあります: from 節において、 FROM キーワードはオプションです。 from 節では単一のエンティティ名だけが可能で、任意で別名を付けることができます。エンティティ 名に別名が与えられると、どのようなプロパティ参照も、その別名を使って修飾しなければなりませ ん。もしエンティティ名に別名が与えられなければ、どのようなプロパティ参照も修飾してはなりま せん。 No 「結合構文の形式」 joins (either implicit or explicit) can be specified in a bulk HQL query. Subqueries may be used in the where-clause; the subqueries, themselves, may contain joins. where 節はオプションです。 As an example, to execute an HQL UPDAT E, use the Query.executeUpdate() method (the method is named for those familiar with JDBC's PreparedStatem ent.executeUpdate()): Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); String hqlUpdate = "update Customer c set c.name = :newName where c.name = :oldName"; // or String hqlUpdate = "update Customer set name = :newName where name = :oldName"; int updatedEntities = s.createQuery( hqlUpdate ) .setString( "newName", newName ) .setString( "oldName", oldName ) .executeUpdate(); tx.commit(); session.close(); HQL UPDAT E statements, by default do not effect the 「version(オプション)」 version or the 「timestamp(オプション)」 timestamp property values for the affected entities; this is in keeping with the EJB3 specification. However, you can force Hibernate to properly reset the version or tim estam p property values through the use of a versioned update. T his is achieved by adding the VERSIONED keyword after the UPDAT E keyword. 175 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); String hqlVersionedUpdate = "update versioned Customer set name = :newName where name = :oldName"; int updatedEntities = s.createQuery( hqlUpdate ) .setString( "newName", newName ) .setString( "oldName", oldName ) .executeUpdate(); tx.commit(); session.close(); カスタムバージョン型(org.hibernate.usertype.UserVersionT ype)は update versioned 文と一緒に使えないことに注意してください。 HQL の DELET E を実行するには、同じ Query.executeUpdate() メソッドを使ってください: Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); String hqlDelete = "delete Customer c where c.name = :oldName"; // or String hqlDelete = "delete Customer where name = :oldName"; int deletedEntities = s.createQuery( hqlDelete ) .setString( "oldName", oldName ) .executeUpdate(); tx.commit(); session.close(); T he int value returned by the Query.executeUpdate() method indicate the number of entities effected by the operation. Consider this may or may not correlate to the number of rows effected in the database. An HQL bulk operation might result in multiple actual SQL statements being executed, for joined-subclass, for example. T he returned number indicates the number of actual entities affected by the statement. Going back to the example of joined-subclass, a delete against one of the subclasses may actually result in deletes against not just the table to which that subclass is mapped, but also the "root" table and potentially joined-subclass tables further down the inheritence hierarchy. INSERT 文の疑似構文は: INSERT INT O エンティティ名プロパティリスト select 文 です。注意 すべき点がいくつかあります: INSERT INT O ... SELECT ... の形式だけがサポートされています。 INSERT INT O ... VALUES ... の形式 はサポートされていません。 プロパティリストは、 SQL の INSERT 文における カラムの仕様 に類似しています。継承のマッピ ングに含まれるエンティティに対して、クラスレベルで直接定義されたプロパティだけが、プロパ ティリストに使えます。スーパークラスのプロパティは認められず、サブクラスのプロパティは効果 がありません。言い換えると INSERT 文は、本質的にポリモーフィックではありません。 select 文の返り値の型が insert 文が期待する型とマッチしていれば、その select 文は妥当な HQL select クエリとなりえます。現在このチェックをデータベースへ任せるのではなく、クエリのコンパ イル時にチェックします。このことは、 equal とは違い、 Hibernate の T ype 間の equivalent に関す る問題を引き起こすことに注意してください。これは org.hibernate.type.DateT ype として定 義されたプロパティと、 org.hibernate.type.T im estam pT ype として定義されたプロパティの 間のミスマッチの問題を引き起こします。データベースがそれらを区別できなくても、変換すること ができても、この問題は発生します。 For the id property, the insert statement gives you two options. You can either explicitly specify the id property in the properties_list (in which case its value is taken from the corresponding select expression) or omit it from the properties_list (in which case a generated value is used). T his later 176 第13章 バッチ処理 option is only available when using id generators that operate in the database; attempting to use this option with any "in memory" type generators will cause an exception during parsing. Note that for the purposes of this discussion, in-database generators are considered to be org.hibernate.id.SequenceGenerator (and its subclasses) and any implementors of org.hibernate.id.PostInsertIdentifierGenerator. T he most notable exception here is org.hibernate.id.T ableHiLoGenerator, which cannot be used because it does not expose a selectable way to get its values. version や tim estam p としてマッピングされるプロパティに対して、 insert 文には二つの選択肢 があります。プロパティリストで明示的にプロパティを指定するか(この場合、対応する select 式か ら値が取られます)、プロパティリストから除外するか(この場合、 org.hibernate.type.VersionT ype で定義された シード値 が使われます)のいずれかです。 HQL の INSERT 文の実行例です: Session session = sessionFactory.openSession(); Transaction tx = session.beginTransaction(); String hqlInsert = "insert into DelinquentAccount (id, name) select c.id, c.name from Customer c where ..."; int createdEntities = s.createQuery( hqlInsert ) .executeUpdate(); tx.commit(); session.close(); 177 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 第 14章 HQL: Hibernate クエリ言語 Hibernate is equipped with an extremely powerful query language that (quite intentionally) looks very much like SQL. But don't be fooled by the syntax; HQL is fully object-oriented, understanding notions like inheritence, polymorphism and association. 14.1. 大 文 字 と 小 文 字 の 区 別 クエリは Java のクラス名とプロパティ名を除いて大文字、小文字を区別しません。従って SeLeCT は sELEct と同じで、かつ SELECT とも同じですが org.hibernate.eg.FOO は org.hibernate.eg.Foo とは違い、かつ foo.barSet は foo.BARSET とも違います。 このマニュアルでは小文字の HQL キーワードを使用します。大文字のキーワードのクエリの方が読みや すいと感じるユーザーもいると思います。ですが、 Java コード内に埋め込まれたときには見づらいと思 います。 14.2. from 節 もっとも単純な Hibernate クエリは次の形式です: from eg.Cat which simply returns all instances of the class eg.Cat. We don't usually need to qualify the class name, since auto-im port is the default. So we almost always just write: from Cat ほとんどの場合クエリのほかの部分で Cat を参照するので、 別名 を割り当てる必要があるでしょ う。 from Cat as cat このクエリでは Cat インスタンスに cat という別名を付けています。そのため、後でこのクエリ内で、 この別名を使うことができます。 as キーワードはオプションです。つまりこのように書くこともできま す: from Cat cat Multiple classes may appear, resulting in a cartesian product or "cross" join. from Formula, Parameter from Formula as form, Parameter as param ローカル変数の Java のネーミング基準と一致した、頭文字に小文字を使ったクエリの別名を付けること はいい習慣です (例えば dom esticCat)。 14.3. 関 連 と 結 合 関連するエンティティあるいは値コレクションの要素にも、 結合 を使って別名を割り当てることが出来 ます。 178 第14章 HQL: Hibernate クエリ言語 from Cat as cat inner join cat.mate as mate left outer join cat.kittens as kitten from Cat as cat left join cat.mate.kittens as kittens from Formula form full join form.parameter param サポートしている結合のタイプは ANSI SQL と同じです。 inner join left outer join right outer join full join (たいていの場合使いづらい) inner join、 left outer join、 right outer join には省略形を使うこともできます。 from Cat as cat join cat.mate as mate left join cat.kittens as kitten HQL の with キーワードを使うと、結合条件を付け加えることができます。 from Cat as cat left join cat.kittens as kitten with kitten.bodyWeight > 10.0 In addition, a "fetch" join allows associations or collections of values to be initialized along with their parent objects, using a single select. T his is particularly useful in the case of a collection. It effectively overrides the outer join and lazy declarations of the mapping file for associations and collections. See 「フェッチ戦略」 for more information. from Cat as cat inner join fetch cat.mate left join fetch cat.kittens 結合によるフェッチは関連するオブジェクトが where 節 (または他のどんな節でも) で使われてはならな いので、通常別名を割り当てる必要がありません。また関連オブジェクトは問い合わせ結果として直接返 されません。代わりに親オブジェクトを通してアクセスできます。コレクションを再帰的に結合フェッチ する場合のみ、別名が必要になります: from Cat as cat inner join fetch cat.mate left join fetch cat.kittens child left join fetch child.kittens Note that the fetch construct may not be used in queries called using iterate() (though scroll() can be used). Nor should fetch be used together with setMaxResults() or setFirstResult() as these operations are based on the result rows, which usually contain duplicates for eager collection fetching, hence, the number of rows is not what you'd expect. Nor may fetch be used together with an ad hoc with condition. It is possible to create a cartesian product by join fetching more than one collection in a query, so take care in this case. Join fetching multiple collection roles also sometimes 179 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide gives unexpected results for bag mappings, so be careful about how you formulate your queries in this case. Finally, note that full join fetch and right join fetch are not meaningful. もしプロパティレベルの遅延フェッチを使う場合(内部的にバイトコード処理をする場合)、 fetch all properties を使うことで Hibernate に遅延プロパティを速やかに(最初のクエリで)フェッチさ せることができます。 from Document fetch all properties order by name from Document doc fetch all properties where lower(doc.name) like '%cats%' 14.4. 結 合 構 文 の 形 式 HQL は2つの関連結合形式をサポートします: 暗黙的 と 明示的 。 これまでのセクションでお見せした使い方はすべて 明示的な 形式で、 from 節で明示的に join キーワー ドを使っています。この形式をおすすめします。 T he im plicit form does not use the join keyword. Instead, the associations are "dereferenced" using dot-notation. im plicit joins can appear in any of the HQL clauses. im plicit join result in inner joins in the resulting SQL statement. from Cat as cat where cat.mate.name like '%s%' 14.5. 識 別 子 プ ロ パ テ ィ の 参 照 T here are, generally speaking, 2 ways to refer to an entity's identifier property: 特別なプロパティ (小文字) id は、 id と名付けられた非識別子プロパティを定義しないエンティティ を与えられた エンティティの識別子プロパティを参照するのに使用されます。 もしエンティティが名付けられた識別子プロパティを定義したら、そのプロパティ名を使用できま す。 複合識別子プロパティへの参照は同じ命名ルールに従います。もしエンティティが id と名付けられた非識 別子プロパティを持っていたら、複合識別子プロパティはその定義された名前で参照することができま す。そうでないと、特別な id プロパティは、識別子プロパティを参照するのに使用されます。 注記: これは、バージョン 3.2.2 から大幅に変更しました。前バージョンでは、 id は、その実際の名前に 関係なく 常に 識別子プロパティを参照していました。その結果、 id と名付けられた非識別子プロパティ は、 Hibernate で決して参照されませんでした。 14.6. Select 節 select 節は以下のようにどのオブジェクトと属性をクエリリザルトセットに返すかを選択します: select mate from Cat as cat inner join cat.mate as mate 上記のクエリは他の Cat の m ate を選択します。実際には次のように、より簡潔に表現できます: 180 第14章 HQL: Hibernate クエリ言語 select cat.mate from Cat cat クエリはコンポーネント型のプロパティを含む、あらゆる値型のプロパティも返せます: select cat.name from DomesticCat cat where cat.name like 'fri%' select cust.name.firstName from Customer as cust クエリは複数のオブジェクトと (または) プロパティを Object[] 型の配列として返せます。 select mother, offspr, mate.name from DomesticCat as mother inner join mother.mate as mate left outer join mother.kittens as offspr もしくは List として、 select new list(mother, offspr, mate.name) from DomesticCat as mother inner join mother.mate as mate left outer join mother.kittens as offspr または、タイプセーフな Java オブジェクトを返せます。 select new Family(mother, mate, offspr) from DomesticCat as mother join mother.mate as mate left join mother.kittens as offspr あるいは Fam ily クラスが適切なコンストラクタを持っているとするならば、 select 節に as を使って別名をつけることもできます。 select max(bodyWeight) as max, min(bodyWeight) as min, count(*) as n from Cat cat select new m ap と一緒に使うときに最も役立ちます: select new map( max(bodyWeight) as max, min(bodyWeight) as min, count(*) as n ) from Cat cat このクエリは別名から select した値へ Map を返します。 14.7. 集 約 関 数 HQL のクエリはプロパティの集約関数の結果も返せます: select avg(cat.weight), sum(cat.weight), max(cat.weight), count(cat) from Cat cat サポートしている集約関数は以下のものです。 181 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide avg(...), sum (...), m in(...), m ax(...) count(* ) count(...), count(distinct ...), count(all...) select 節において算術操作、連結と承認された SQL 関数を使うことができます: select cat.weight + sum(kitten.weight) from Cat cat join cat.kittens kitten group by cat.id, cat.weight select firstName||' '||initial||' '||upper(lastName) from Person SQL と同じ意味を持つ distinct と all キーワードを使うことができます。 select distinct cat.name from Cat cat select count(distinct cat.name), count(cat) from Cat cat 14.8. ポ リ モ ー フ ィ ッ ク な ク エ リ 次のようなクエリ: from Cat as cat Cat インスタンスだけではなく、 Dom esticCat のようなサブクラスも返されます。 Hibernate クエリ は どんな Java クラスやインターフェースも from 節に入れることができます。クエリはそのクラスを拡 張した、もしくはインターフェースを実装した全ての永続クラスを返します。次のクエリは永続オブジェ クトをすべて返します: from java.lang.Object o Nam ed インターフェースは様々な永続クラスによって実装されます。: from Named n, Named m where n.name = m.name Note that these last two queries will require more than one SQL SELECT . T his means that the order by clause does not correctly order the whole result set. (It also means you can't call these queries using Query.scroll().) 14.9. where 節 where 節は返されるインスタンスのリストを絞ることができます。もし別名がない場合、名前でプロパ ティを参照します。 from Cat where name='Fritz' もし別名がある場合、修飾名を使ってください: from Cat as cat where cat.name='Fritz' 182 第14章 HQL: Hibernate クエリ言語 returns instances of Cat named 'Fritz'. select foo from Foo foo, Bar bar where foo.startDate = bar.date 上の HQL は、 Foo の startDate プロパティと等しい date プロパティを持った bar インスタンスが 存在する、すべての Foo インスタンスを返します。コンパウンドパス式)は where 節を非常に強力にし ます。注目: from Cat cat where cat.mate.name is not null このクエリはテーブル結合(内部結合)を持つ SQL クエリに変換されます。その代わりに以下のように 書くと、 from Foo foo where foo.bar.baz.customer.address.city is not null もし上のクエリを記述したらクエリ内に4つのテーブル結合を必要とする SQL クエリに変換されます。 = 演算子は以下のように、プロパティだけでなくインスタンスを比較するためにも使われます。: from Cat cat, Cat rival where cat.mate = rival.mate select cat, mate from Cat cat, Cat mate where cat.mate = mate T he special property (lowercase) id may be used to reference the unique identifier of an object. See 「識別子プロパティの参照」 for more information. from Cat as cat where cat.id = 123 from Cat as cat where cat.mate.id = 69 2番目のクエリは効率的です。テーブル結合が必要ありません。 Properties of composite identifiers may also be used. Suppose Person has a composite identifier consisting of country and m edicareNum ber. Again, see 「識別子プロパティの参照」 for more information regarding referencing identifier properties. from bank.Person person where person.id.country = 'AU' and person.id.medicareNumber = 123456 from bank.Account account where account.owner.id.country = 'AU' and account.owner.id.medicareNumber = 123456 繰り返しますが、2番目のクエリにはテーブル結合が必要ありません。 同様に class は特別なプロパティであり、ポリモーフィックな永続化におけるインスタンスの discriminator 値にアクセスします。 where 節に埋め込まれた Java のクラス名はその discriminator 値に 変換されます。 183 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide from Cat cat where cat.class = DomesticCat You may also use components or composite user types, or properties of said component types. See 「コンポーネント」 for more details. An "any" type has the special properties id and class, allowing us to express a join in the following way (where AuditLog.item is a property mapped with <any>). from AuditLog log, Payment payment where log.item.class = 'Payment' and log.item.id = payment.id log.item .class と paym ent.class が上記のクエリ中で全く異なるデータベースカラムの値を参照 するということに注意してください。 14.10. Expressions 式 SQL の where 節で記述することが出来る式のほとんどを HQL でも記述できます: 算術演算子:+, -, * , / binary comparison operators =, >=, <=, <>, !=, like 論理演算子:and, or, not グループ分けを表す括弧:( ) in, not in, between, is null, is not null, is em pty, is not em pty, m em ber of and not m em ber of "Simple" case, case ... when ... then ... else ... end, and "searched" case, case when ... then ... else ... end ストリングの連結 ...||... または concat(...,...) current_date(), current_tim e(), current_tim estam p() second(...), m inute(...), hour(...), day(...), m onth(...), year(...), EJB-QL 3.0 で定義されている関数や演算子: substring(), trim (), lower(), upper(), length(), locate(), abs(), sqrt(), bit_length(), m od() coalesce() と nullif() 数字や時間の値を String にコンバートする str() 2番目の引数が Hibernate 型の名前である cast(... as ...) と extract(... from ...)。ただ し使用するデータベースが ANSI cast() と extract() をサポートする場合に限ります。 結合したインデックス付きのコレクションの別名に適用される HQL の index() 関数。 コレクション値のパス式を取る HQL 関数: size(), m inelem ent(), m axelem ent(), m inindex(), m axindex() 。 som e, all, exists, any, in を使って修飾することができる 特別な elem ents() と indices 関数と一緒に使います。 sign()、 trunc()、 rtrim ()、 sin() のようなデータベースがサポートする SQL スカラ関数。 JDBC スタイルの位置パラメータ ? 名前付きパラメータ: :nam e, :start_date, :x1 SQL literals 'foo', 69, 6.66E+2, '1970-01-01 10:00:01.0' Java の public static final 定数: eg.Color.T ABBY in と between は以下のように使用できます: 184 第14章 HQL: Hibernate クエリ言語 from DomesticCat cat where cat.name between 'A' and 'B' from DomesticCat cat where cat.name in ( 'Foo', 'Bar', 'Baz' ) また、否定形で記述することもできます。 from DomesticCat cat where cat.name not between 'A' and 'B' from DomesticCat cat where cat.name not in ( 'Foo', 'Bar', 'Baz' ) 同様に is null や is not null は null 値をテストするために使用できます。 Hibernate 設定ファイルで HQL query substitutions を定義すれば、 boolean 値を式の中で簡単に使用で きます: <property name="hibernate.query.substitutions">true 1, false 0</property> こうすることで下記の HQL を SQL に変換するときに true 、 false キーワードは 1 、 0 に置き換えら れます: from Cat cat where cat.alive = true 特別なプロパティ size、または特別な関数 size() を使ってコレクションのサイズをテストできます: from Cat cat where cat.kittens.size > 0 from Cat cat where size(cat.kittens) > 0 インデックス付きのコレクションでは、 m inindex と m axindex 関数を使って、インデックスの最小値 と最大値を参照できます。同様に、 m inelem ent と m axelem ent を使って、基本型のコレクション要 素の最小値と最大値を参照できます。 from Calendar cal where maxelement(cal.holidays) > current_date from Order order where maxindex(order.items) > 100 from Order order where minelement(order.items) > 10000 コレクションの要素やインデックスのセット(elem ents と indices 関数)、または副問い合わせ(後 述)の結果が受け取れるときは、 SQL 関数 any, som e, all, exists, in がサポートされます。 select mother from Cat as mother, Cat as kit where kit in elements(foo.kittens) select p from NameList list, Person p where p.name = some elements(list.names) from Cat cat where exists elements(cat.kittens) 185 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide from Player p where 3 > all elements(p.scores) from Show show where 'fizard' in indices(show.acts) size、 elem ents、 indices、 m inindex、 m axindex、 m inelem ent、 m axelem ent は Hibernate3 の where 節だけで利用可能であることに注意してください。 インデックス付きのコレクション(arrays, lists, maps)の要素は、インデックスで参照できます(where 節内でのみ): from Order order where order.items[0].id = 1234 select person from Person person, Calendar calendar where calendar.holidays['national day'] = person.birthDay and person.nationality.calendar = calendar select item from Item item, Order order where order.items[ order.deliveredItemIndices[0] ] = item and order.id = 11 select item from Item item, Order order where order.items[ maxindex(order.items) ] = item and order.id = 11 [] 内部の式は、算術式でも構いません。 select item from Item item, Order order where order.items[ size(order.items) - 1 ] = item 一対多関連や値のコレクションの要素に対しては、 HQL は組み込みの index() 関数も用意しています。 select item, index(item) from Order order join order.items item where index(item) < 5 ベースとなるデータベースがサポートしているスカラー SQL 関数が使用できます from DomesticCat cat where upper(cat.name) like 'FRI%' もしまだ全てを理解していないなら、下のクエリを SQL でどれだけ長く、読みづらく出来るか考えてく ださい: select cust from Product prod, Store store inner join store.customers cust where prod.name = 'widget' and store.location.name in ( 'Melbourne', 'Sydney' ) and prod = all elements(cust.currentOrder.lineItems) ヒント: 例えばこのように出来ます。 186 第14章 HQL: Hibernate クエリ言語 SELECT cust.name, cust.address, cust.phone, cust.id, cust.current_order FROM customers cust, stores store, locations loc, store_customers sc, product prod WHERE prod.name = 'widget' AND store.loc_id = loc.id AND loc.name IN ( 'Melbourne', 'Sydney' ) AND sc.store_id = store.id AND sc.cust_id = cust.id AND prod.id = ALL( SELECT item.prod_id FROM line_items item, orders o WHERE item.order_id = o.id AND cust.current_order = o.id ) 14.11. order by 節 クエリが返す list は、返されるクラスやコンポーネントの任意の属性によって並べ替えられます: from DomesticCat cat order by cat.name asc, cat.weight desc, cat.birthdate オプションの asc と desc はそれぞれ昇順か降順の整列を示します。 14.12. group by 節 集約値を返すクエリは、返されるクラスやコンポーネントの任意のプロパティによってグループ化できま す: select cat.color, sum(cat.weight), count(cat) from Cat cat group by cat.color select foo.id, avg(name), max(name) from Foo foo join foo.names name group by foo.id having 節も使えます。 select cat.color, sum(cat.weight), count(cat) from Cat cat group by cat.color having cat.color in (eg.Color.TABBY, eg.Color.BLACK) もし使用するデータベースがサポートしているなら、 having と order by 節で SQL 関数と集約関数が 使えます(例えば MySQL にはありません)。 187 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide select cat from Cat cat join cat.kittens kitten group by cat.id, cat.name, cat.other, cat.properties having avg(kitten.weight) > 100 order by count(kitten) asc, sum(kitten.weight) desc Note that neither the group by clause nor the order by clause may contain arithmetic expressions. Also note that Hibernate currently does not expand a grouped entity, so you can't write group by cat if all properties of cat are non-aggregated. You have to list all non-aggregated properties explicitly. 14.13. 副 問 い 合 わ せ サブセレクトをサポートするデータベースのため、 Hibernate は副問い合わせをサポートしています。副 問い合わせは括弧で囲まなければなりません( SQL の集約関数呼び出しによる事が多いです)。関連副 問い合わせ (外部クエリ中の別名を参照する副問い合わせのこと) さえ許可されます。 from Cat as fatcat where fatcat.weight > ( select avg(cat.weight) from DomesticCat cat ) from DomesticCat as cat where cat.name = some ( select name.nickName from Name as name ) from Cat as cat where not exists ( from Cat as mate where mate.mate = cat ) from DomesticCat as cat where cat.name not in ( select name.nickName from Name as name ) select cat.id, (select max(kit.weight) from cat.kitten kit) from Cat as cat HQL 副問い合わせは、 select または where 節だけで使われることに注意してください。 Note that subqueries can also utilize row value constructor syntax. See 「行値コンストラクタ構 文」 for more details. 14.14. HQL の 例 Hibernate queries can be quite powerful and complex. In fact, the power of the query language is one of Hibernate's main selling points. Here are some example queries very similar to queries that I used on a recent project. Note that most queries you will write are much simpler than these! 以下のクエリは特定の顧客と与えられた最小の合計値に対する未払い注文の注文 ID 、商品の数、注文の 合計を合計値で整列して返します。価格を決定する際、現在のカタログを使います。結果として返される 188 第14章 HQL: Hibernate クエリ言語 SQL クエリは ORDER、 ORDER_LINE、 PRODUCT 、 CAT ALOG および PRICE テーブルに対し4つの内部 結合と (関連しない) 副問い合わせを持ちます。 select order.id, sum(price.amount), count(item) from Order as order join order.lineItems as item join item.product as product, Catalog as catalog join catalog.prices as price where order.paid = false and order.customer = :customer and price.product = product and catalog.effectiveDate < sysdate and catalog.effectiveDate >= all ( select cat.effectiveDate from Catalog as cat where cat.effectiveDate < sysdate ) group by order having sum(price.amount) > :minAmount order by sum(price.amount) desc What a monster! Actually, in real life, I'm not very keen on subqueries, so my query was really more like this: select order.id, sum(price.amount), count(item) from Order as order join order.lineItems as item join item.product as product, Catalog as catalog join catalog.prices as price where order.paid = false and order.customer = :customer and price.product = product and catalog = :currentCatalog group by order having sum(price.amount) > :minAmount order by sum(price.amount) desc 次のクエリは各ステータスの支払い数を数えます。ただしすべての支払いが現在の利用者による最新のス テータス変更である AWAIT ING_APPROVAL である場合を除きます。このクエリは2つの内部結合と PAYMENT , PAYMENT _ST AT US および PAYMENT _ST AT US_CHANGE テーブルに対する関連副問い合わせ を備えた SQL クエリに変換されます。 189 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide select count(payment), status.name from Payment as payment join payment.currentStatus as status join payment.statusChanges as statusChange where payment.status.name <> PaymentStatus.AWAITING_APPROVAL or ( statusChange.timeStamp = ( select max(change.timeStamp) from PaymentStatusChange change where change.payment = payment ) and statusChange.user <> :currentUser ) group by status.name, status.sortOrder order by status.sortOrder もし set の代わりに list として statusChanges コレクションをマッピングしたならば、はるかに簡単に クエリを記述できるでしょう。 select count(payment), status.name from Payment as payment join payment.currentStatus as status where payment.status.name <> PaymentStatus.AWAITING_APPROVAL or payment.statusChanges[ maxIndex(payment.statusChanges) ].user <> :currentUser group by status.name, status.sortOrder order by status.sortOrder 次のクエリは現在のユーザーが所属する組織に対するアカウントおよび未払いの支払いをすべて返す MS SQL Server の isNull() 関数を使用しています。このクエリは3つの内部結合と1つの外部結合、そして ACCOUNT 、 PAYMENT 、 PAYMENT _ST AT US、 ACCOUNT _T YPE、 ORGANIZAT ION および ORG_USER テーブルに対する副問い合わせ持った SQL に変換されます。 select account, payment from Account as account left outer join account.payments as payment where :currentUser in elements(account.holder.users) and PaymentStatus.UNPAID = isNull(payment.currentStatus.name, PaymentStatus.UNPAID) order by account.type.sortOrder, account.accountNumber, payment.dueDate いくつかのデータベースについては、 (関連させられた) 副問い合わせの使用を避ける必要があるでしょ う。 select account, payment from Account as account join account.holder.users as user left outer join account.payments as payment where :currentUser = user and PaymentStatus.UNPAID = isNull(payment.currentStatus.name, PaymentStatus.UNPAID) order by account.type.sortOrder, account.accountNumber, payment.dueDate 14.15. 大 量 の UPDATE と DELETE HQL now supports update, delete and insert ... select ... statements. See 「DML スタイル 190 第14章 HQL: Hibernate クエリ言語 の操作」 for details. 14.16. Tips & Tricks 実際に結果を返さなくてもクエリの結果数を数えることができます: ( (Integer) session.iterate("select count(*) from ....").next() ).intValue() コレクションのサイズにより結果を並べ替えるためには以下のクエリを使用します: select usr.id, usr.name from User as usr left join usr.messages as msg group by usr.id, usr.name order by count(msg) 使用しているデータベースがサブセレクトをサポートする場合、クエリの where 節でサイズによる選択条 件を設定できます: from User usr where size(usr.messages) >= 1 If your database doesn't support subselects, use the following query: select usr.id, usr.name from User usr.name join usr.messages msg group by usr.id, usr.name having count(msg) >= 1 As this solution can't return a User with zero messages because of the inner join, the following form is also useful: select usr.id, usr.name from User as usr left join usr.messages as msg group by usr.id, usr.name having count(msg) = 0 JavaBean のプロパティは、名前付きのクエリパラメータに結びつけることが出来ます: Query q = s.createQuery("from foo Foo as foo where foo.name=:name and foo.size=:size"); q.setProperties(fooBean); // fooBean has getName() and getSize() List foos = q.list(); コレクションはフィルタ付き Query インターフェースを使用することでページをつけることができます: Query q = s.createFilter( collection, "" ); // the trivial filter q.setMaxResults(PAGE_SIZE); q.setFirstResult(PAGE_SIZE * pageNumber); List page = q.list(); コレクションの要素はクエリフィルタを使って、並べ替えやグループ分けが出来ます: 191 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide Collection orderedCollection = s.filter( collection, "order by this.amount" ); Collection counts = s.filter( collection, "select this.type, count(this) group by this.type" ); コレクションを初期化せずにコレクションのサイズを得ることができます: ( (Integer) session.iterate("select count(*) from ....").next() ).intValue(); 14.17. コ ン ポ ー ネ ン ト HQL クエリでシンプルな値型を使用できるので、コンポーネントは、あらゆる点で使用できます。これは select 節の中に現われます: select p.name from from Person p select p.name.first from from Person p where the Person's name property is a component. Components can also be used in the where clause: from from Person p where p.name = :name from from Person p where p.name.first = :firstName コンポーネントは order by 節でも使用可能です: from from Person p order by p.name from from Person p order by p.name.first Another common use of components is in 「行値コンストラクタ構文」 row value constructors. 14.18. 行 値 コ ン ス ト ラ ク タ 構 文 下に位置するデータベースが ANSI SQL row value constructor 構文 (tuple 構文とよばれることも あります) をサポートしていないとしても、 HQL はその使用をサポートしています。ここでは、一般的に コンポーネントと連繋するマルチバリュー比較について触れます。ネームコンポーネントを定義する Person エンティティを考えましょう: from Person p where p.name.first='John' and p.name.last='Jingleheimer-Schmidt' T hat's valid syntax, although a little verbose. It be nice to make this a bit more concise and use row value constructor syntax: from Person p where p.name=('John', 'Jingleheimer-Schmidt') それを select 節で指定するのも効果的です。 select p.name from from Person p 次に row value constructor 構文の使用が有効なときは、サブクエリを使用して複数の値と比較する 192 第14章 HQL: Hibernate クエリ言語 必要があるときです: from Cat as cat where not ( cat.name, cat.color ) in ( select cat.name, cat.color from DomesticCat cat ) この構文を使用するかどうか決定するときに考慮しなければならないことは、クエリがメタデータ内のコ ンポーネントのサブプロパティの順番に依存していることです。 193 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 第 15章 Criteria クエリ Hibernate には、直感的で拡張可能な criteria クエリ API が用意されています。 15.1. Criteria イ ン ス タ ン ス の 作 成 org.hibernate.Criteria インターフェースは特定の永続性クラスに対するクエリを表現します。 Session は Criteria インスタンスのファクトリです。 Criteria crit = sess.createCriteria(Cat.class); crit.setMaxResults(50); List cats = crit.list(); 15.2. リ ザ ル ト セ ッ ト の 絞 込 み org.hibernate.criterion.Criterion インターフェースのインスタンスは、個別のクエリクライ テリオン(問い合わせの判定基準)を表します。 org.hibernate.criterion.Restrictions クラ スは、ある組み込みの Criterion 型を取得するためのファクトリメソッドを持っています。 List cats = sess.createCriteria(Cat.class) .add( Restrictions.like("name", "Fritz%") ) .add( Restrictions.between("weight", minWeight, maxWeight) ) .list(); Restriction (限定)は、論理的にグループ化できます。 List cats = sess.createCriteria(Cat.class) .add( Restrictions.like("name", "Fritz%") ) .add( Restrictions.or( Restrictions.eq( "age", new Integer(0) ), Restrictions.isNull("age") ) ) .list(); List cats = sess.createCriteria(Cat.class) .add( Restrictions.in( "name", new String[] { "Fritz", "Izi", "Pk" } ) ) .add( Restrictions.disjunction() .add( Restrictions.isNull("age") ) .add( Restrictions.eq("age", new Integer(0) ) ) .add( Restrictions.eq("age", new Integer(1) ) ) .add( Restrictions.eq("age", new Integer(2) ) ) ) ) .list(); 元々ある Criterion 型(Restrictions のサブクラス) はかなりの範囲に及びますが、特に有用なのは SQL を直接指定できるものです。 List cats = sess.createCriteria(Cat.class) .add( Restrictions.sqlRestriction("lower({alias}.name) like lower(?)", "Fritz%", Hibernate.STRING) ) .list(); {alias} というプレースホルダは、問い合わせを受けたエンティティの行の別名によって置き換えられ 194 第15章 Criteria クエリ ます。 criterion を得る別の手段は、 Property インスタンスから取得することです。 Property.forNam e() を呼び出して、 Property インスタンスを作成できます。 Property age = Property.forName("age"); List cats = sess.createCriteria(Cat.class) .add( Restrictions.disjunction() .add( age.isNull() ) .add( age.eq( new Integer(0) ) ) .add( age.eq( new Integer(1) ) ) .add( age.eq( new Integer(2) ) ) ) ) .add( Property.forName("name").in( new String[] { "Fritz", "Izi", "Pk" } ) ) .list(); 15.3. 結 果 の 整 列 org.hibernate.criterion.Order を使って結果を並び替えることができます。 List cats = sess.createCriteria(Cat.class) .add( Restrictions.like("name", "F%") .addOrder( Order.asc("name") ) .addOrder( Order.desc("age") ) .setMaxResults(50) .list(); List cats = sess.createCriteria(Cat.class) .add( Property.forName("name").like("F%") ) .addOrder( Property.forName("name").asc() ) .addOrder( Property.forName("age").desc() ) .setMaxResults(50) .list(); 15.4. 関 連 createCriteria() を使い、関連をナビゲートすることで、容易に関係するエンティティに制約を指定 できます。 List cats = sess.createCriteria(Cat.class) .add( Restrictions.like("name", "F%") ) .createCriteria("kittens") .add( Restrictions.like("name", "F%") ) .list(); 2番目の createCriteria() は、 kittens コレクションの要素を参照する新しい Criteria インス タンスを返すことに注意してください。 以下のような方法も、状況により有用です。 195 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide List cats = sess.createCriteria(Cat.class) .createAlias("kittens", "kt") .createAlias("mate", "mt") .add( Restrictions.eqProperty("kt.name", "mt.name") ) .list(); (createAlias() は新しい Criteria インスタンスを作成しません。) 前の2つのクエリによって返される Cat インスタンスによって保持される kittens コレクションは、 criteria によって事前にフィルタリング されない ことに注意してください。もし criteria に適合する kitten を取得したいなら、 ResultT ransform er を使わなければなりません。 List cats = sess.createCriteria(Cat.class) .createCriteria("kittens", "kt") .add( Restrictions.eq("name", "F%") ) .setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP) .list(); Iterator iter = cats.iterator(); while ( iter.hasNext() ) { Map map = (Map) iter.next(); Cat cat = (Cat) map.get(Criteria.ROOT_ALIAS); Cat kitten = (Cat) map.get("kt"); } 15.5. 関 連 の 動 的 フ ェ ッ チ setFetchMode() を使い、実行時に関連の復元方法を指定してもよいです。 List cats = sess.createCriteria(Cat.class) .add( Restrictions.like("name", "Fritz%") ) .setFetchMode("mate", FetchMode.EAGER) .setFetchMode("kittens", FetchMode.EAGER) .list(); T his query will fetch both m ate and kittens by outer join. See 「フェッチ戦略」 for more information. 15.6. ク エ リ の 例 org.hibernate.criterion.Exam ple クラスは、与えられたインスタンスからクエリクライテリオ ンを構築できます。 Cat cat = new Cat(); cat.setSex('F'); cat.setColor(Color.BLACK); List results = session.createCriteria(Cat.class) .add( Example.create(cat) ) .list(); バージョンプロパティ、識別子、関連は無視されます。デフォルトでは null 値のプロパティは除外されま す。 どのように Exam ple を適用するか調整することができます。 196 第15章 Criteria クエリ Example example = Example.create(cat) .excludeZeroes() //exclude zero valued properties .excludeProperty("color") //exclude the property named "color" .ignoreCase() //perform case insensitive string comparisons .enableLike(); //use like for string comparisons List results = session.createCriteria(Cat.class) .add(example) .list(); 関連オブジェクトに criteria を指定するために、 example を使うことも可能です。 List results = session.createCriteria(Cat.class) .add( Example.create(cat) ) .createCriteria("mate") .add( Example.create( cat.getMate() ) ) .list(); 15.7. 射 影 、 集 約 、 グ ル ー プ 化 org.hibernate.criterion.Projections クラスは Projection インスタンスのファクトリで す。 setProjection() を呼び出すことで、クエリに射影を適用します。 List results = session.createCriteria(Cat.class) .setProjection( Projections.rowCount() ) .add( Restrictions.eq("color", Color.BLACK) ) .list(); List results = session.createCriteria(Cat.class) .setProjection( Projections.projectionList() .add( Projections.rowCount() ) .add( Projections.avg("weight") ) .add( Projections.max("weight") ) .add( Projections.groupProperty("color") ) ) .list(); T here is no explicit "group by" necessary in a criteria query. Certain projection types are defined to be grouping projections, which also appear in the SQL group by clause. 任意で射影に別名を付けられるため、射影される値は restriction や ordering 内から参照できます。別名を つける2つの異なる方法を示します: List results = session.createCriteria(Cat.class) .setProjection( Projections.alias( Projections.groupProperty("color"), "colr" ) ) .addOrder( Order.asc("colr") ) .list(); List results = session.createCriteria(Cat.class) .setProjection( Projections.groupProperty("color").as("colr") ) .addOrder( Order.asc("colr") ) .list(); alias() と as() メソッドは、 Projection インスタンスを別の名前の Projection インスタンスで 197 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide ラップするだけです。ショートカットとして、射影を射影リストに追加する際に、別名をつけられます: List results = session.createCriteria(Cat.class) .setProjection( Projections.projectionList() .add( Projections.rowCount(), "catCountByColor" ) .add( Projections.avg("weight"), "avgWeight" ) .add( Projections.max("weight"), "maxWeight" ) .add( Projections.groupProperty("color"), "color" ) ) .addOrder( Order.desc("catCountByColor") ) .addOrder( Order.desc("avgWeight") ) .list(); List results = session.createCriteria(Domestic.class, "cat") .createAlias("kittens", "kit") .setProjection( Projections.projectionList() .add( Projections.property("cat.name"), "catName" ) .add( Projections.property("kit.name"), "kitName" ) ) .addOrder( Order.asc("catName") ) .addOrder( Order.asc("kitName") ) .list(); 射影の式に Property.forNam e() も使用できます: List results = session.createCriteria(Cat.class) .setProjection( Property.forName("name") ) .add( Property.forName("color").eq(Color.BLACK) ) .list(); List results = session.createCriteria(Cat.class) .setProjection( Projections.projectionList() .add( Projections.rowCount().as("catCountByColor") ) .add( Property.forName("weight").avg().as("avgWeight") ) .add( Property.forName("weight").max().as("maxWeight") ) .add( Property.forName("color").group().as("color" ) ) .addOrder( Order.desc("catCountByColor") ) .addOrder( Order.desc("avgWeight") ) .list(); 15.8. ク エ リ お よ び サ ブ ク エ リ の 分 離 DetachedCriteria クラスにより、セッションスコープ外にクエリを作成できます。後で、任意の Session を使って、実行できます。 DetachedCriteria query = DetachedCriteria.forClass(Cat.class) .add( Property.forName("sex").eq('F') ); Session session = ....; Transaction txn = session.beginTransaction(); List results = query.getExecutableCriteria(session).setMaxResults(100).list(); txn.commit(); session.close(); 198 第15章 Criteria クエリ DetachedCriteria は、サブクエリを表現するためにも使えます。サブクエリを伴う Criterion インス タンスは、 Subqueries もしくは Property から得られます。 DetachedCriteria avgWeight = DetachedCriteria.forClass(Cat.class) .setProjection( Property.forName("weight").avg() ); session.createCriteria(Cat.class) .add( Property.forName("weight).gt(avgWeight) ) .list(); DetachedCriteria weights = DetachedCriteria.forClass(Cat.class) .setProjection( Property.forName("weight") ); session.createCriteria(Cat.class) .add( Subqueries.geAll("weight", weights) ) .list(); 相互関係があるサブクエリでさえも可能です: DetachedCriteria avgWeightForSex = DetachedCriteria.forClass(Cat.class, "cat2") .setProjection( Property.forName("weight").avg() ) .add( Property.forName("cat2.sex").eqProperty("cat.sex") ); session.createCriteria(Cat.class, "cat") .add( Property.forName("weight).gt(avgWeightForSex) ) .list(); 15.9. 自 然 識 別 子 に よ る ク エ リ criteria クエリを含むたいていのクエリにとって、クエリキャッシュはあまり効率がよくないです。なぜな ら、クエリキャッシュが頻繁に無効になるためです。しかしながら、キャッシュを無効にするアルゴリズ ムを最適化できる特別なクエリの種類が1つあります。更新されない自然キーによる検索です。いくつか のアプリケーションでは、この種類のクエリが頻繁に現れます。このような使われ方のために、 criteria API は特別な対策を提供します。 First, you should map the natural key of your entity using <natural-id>, and enable use of the second-level cache. <class name="User"> <cache usage="read-write"/> <id name="id"> <generator class="increment"/> </id> <natural-id> <property name="name"/> <property name="org"/> </natural-id> <property name="password"/> </class> 注記: 変更される 自然キーを持つエンティティにこの機能を使うのは、意図されていない使い方です。 次に、 Hibernate クエリキャッシュを有効にします。 これで、 Restrictions.naturalId() により、より効率的なキャッシュアルゴリズムを使用できま す。 199 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide session.createCriteria(User.class) .add( Restrictions.naturalId() .set("name", "gavin") .set("org", "hb") ).setCacheable(true) .uniqueResult(); 200 第16章 ネイティブ SQL 第 16章 ネイティブ SQL データベースのネイティブ SQL 方言を使ってクエリを表現することもできます。クエリヒントや Oracle の CONNECT キーワードのように、データベース独自の機能を利用したいときに使えます。 SQL/JDBC を直接使用しているアプリケーションから Hibernate への移行も容易にしています。 Hibernate3 では、生成、更新、削除、読み込み処理のようなすべての SQL (ストアドプロシージャを含 む)を手書きできます。 16.1. SQLQuery の 使 用 ネイティブな SQL クエリの実行は SQLQuery インターフェースを通して制御します。 SQLQuery イン ターフェースは Session.createSQLQuery() を呼び出して取得します。この API を使って問い合わせ する方法を以下で説明します。 16.1.1. スカラーのクエリ 最も基本的な SQL クエリはスカラー(値)のリストを得ることです。 sess.createSQLQuery("SELECT * FROM CATS").list(); sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS").list(); これらはどちらも、 CAT S テーブルの各カラムのスカラー値を含む Object 配列(Object[])のリストを返 します。返すスカラー値の実際の順番と型を推定するために、 Hibernate は ResultSetMetadata を使用し ます。 ResultSetMetadata を使用するオーバーヘッドを避けるため、もしくは単に何が返されるか明確にす るため、 addScalar() を使えます。 sess.createSQLQuery("SELECT * FROM CATS") .addScalar("ID", Hibernate.LONG) .addScalar("NAME", Hibernate.STRING) .addScalar("BIRTHDATE", Hibernate.DATE) このクエリで指定されているものを下記に示します: SQL クエリ文字列 返されるカラムと型 これはまだ Object 配列を返しますが、 ResultSetMetdata を使用しません。ただし、その代わりに基 礎にあるリザルトセットから ID、NAME、BIRT HDAT E カラムをそれぞれ Long、String、Short として明 示的に取得します。これは3つのカラムを返すのみであることも意味します。たとえ、クエリが * を使用 し、列挙した3つより多くのカラムを返せるとしてもです。 スカラーの型情報を省くこともできます。 sess.createSQLQuery("SELECT * FROM CATS") .addScalar("ID", Hibernate.LONG) .addScalar("NAME") .addScalar("BIRTHDATE") これは本質的に前と同じクエリですが、 NAME と BIRT HDAT E の型を決めるために ResultSetMetaData を使用します。一方、 ID の型は明示的に指定されています。 ResultSetMetaData から返される java.sql.T ypes を Hibernate の型に マッピングすることは、 Dialect が 201 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 制御します。明示された型がマッピングされていないか、結果の型が期待したものと異なる場合、 Dialect の registerHibernateT ype を呼び出し、カスタマイズできます。 16.1.2. エンティティのクエリ T he above queries were all about returning scalar values, basically returning the "raw" values from the resultset. T he following shows how to get entity objects from a native sql query via addEntity(). sess.createSQLQuery("SELECT * FROM CATS").addEntity(Cat.class); sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE FROM CATS").addEntity(Cat.class); このクエリで指定されているものを下記に示します: SQL クエリ文字列 クエリが返すエンティティと SQL テーブルの別名 Cat が ID 、 NAME 、 BIRT HDAT E のカラムを使ってクラスにマッピングされる場合、上記のクエリはど ちらも、要素が Cat エンティティであるリストを返します。 If the entity is mapped with a m any-to-one to another entity it is required to also return this when performing the native query, otherwise a database specific "column not found" error will occur. T he additional columns will automatically be returned when using the * notation, but we prefer to be explicit as in the following example for a m any-to-one to a Dog: sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE, DOG_ID FROM CATS").addEntity(Cat.class); これにより cat.getDog() が正しく機能します。 16.1.3. 関連とコレクションの操作 プロキシを初期化するための余分な処理を避けるため、 Dog の中で即時結合できます。これは addJoin() メソッドにより行います。関連もしくはコレクションに結合できます。 sess.createSQLQuery("SELECT c.ID, NAME, BIRTHDATE, DOG_ID, D_ID, D_NAME FROM CATS c, DOGS d WHERE c.DOG_ID = d.D_ID") .addEntity("cat", Cat.class) .addJoin("cat.dog"); In this example the returned Cat's will have their dog property fully initialized without any extra roundtrip to the database. Notice that we added a alias name ("cat") to be able to specify the target property path of the join. It is possible to do the same eager joining for collections, e.g. if the Cat had a one-to-many to Dog instead. sess.createSQLQuery("SELECT ID, NAME, BIRTHDATE, D_ID, D_NAME, CAT_ID FROM CATS c, DOGS d WHERE c.ID = d.CAT_ID") .addEntity("cat", Cat.class) .addJoin("cat.dogs"); 現在のところ、 Hibernate で使いやすくするための SQL クエリの拡張なしに、ネイティブクエリで何か を可能にする限界に来ています。同じ型のエンティティを複数返す際や、デフォルトの別名や列名で十分 ではない場合に、問題は起こり始めます。 202 第16章 ネイティブ SQL 16.1.4. 複数エンティティの取得 ここまでは、リザルトセットのカラム名は、マッピングドキュメントで指定されたカラム名と同じである と仮定していました。複数のテーブルが同じカラム名を持つ場合があるため、複数テーブルを結合する SQL クエリで問題となる場合があります。 下記のような(失敗しそうな)クエリでは、カラム別名インジェクション(column alias injection)が必 要です: sess.createSQLQuery("SELECT c.*, m.* c.ID") .addEntity("cat", Cat.class) .addEntity("mother", Cat.class) FROM CATS c, CATS m WHERE c.MOTHER_ID = T he intention for this query is to return two Cat instances per row, a cat and its mother. T his will fail since there is a conflict of names since they are mapped to the same column names and on some databases the returned column aliases will most likely be on the form "c.ID", "c.NAME", etc. which are not equal to the columns specificed in the mappings ("ID" and "NAME"). 下記の形式は、カラム名が重複しても大丈夫です: sess.createSQLQuery("SELECT {cat.*}, {mother.*} c.MOTHER_ID = c.ID") .addEntity("cat", Cat.class) .addEntity("mother", Cat.class) FROM CATS c, CATS m WHERE このクエリで指定されているものを下記に示します: SQL クエリ文字列 (Hibernate がカラムの別名を挿入するためのプレースホルダを含む) クエリによって返されるエンティティ T he {cat.*} and {mother.*} notation used above is a shorthand for "all properties". Alternatively, you may list the columns explicity, but even in this case we let Hibernate inject the SQL column aliases for each property. T he placeholder for a column alias is just the property name qualified by the table alias. In the following example, we retrieve Cats and their mothers from a different table (cat_log) to the one declared in the mapping metadata. Notice that we may even use the property aliases in the where clause if we like. String sql = "SELECT ID as {c.id}, NAME as {c.name}, " + "BIRTHDATE as {c.birthDate}, MOTHER_ID as {c.mother}, {mother.*} " + "FROM CAT_LOG c, CAT_LOG m WHERE {c.mother} = c.ID"; List loggedCats = sess.createSQLQuery(sql) .addEntity("cat", Cat.class) .addEntity("mother", Cat.class).list() 16.1.4 .1. 別名とプロパティのリファレンス 多くの場合、上記のような別名インジェクションが必要です。ただし、複合プロパティ、継承識別子、コ レクションなどのようなより複雑なマッピングと関連するクエリがなければです。ある特定の別名を使用 することにより、 Hibernate は適切な別名を挿入できます。 別名インジェクションとして使用できるものを下表に示します。注記:下表の別名は一例です。それぞれ の別名は一意であり、使用する際にはおそらく異なる名前を持ちます。 203 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 表 16.1 別名に挿入する名前 説明 構文 例 単純なプロパティ {[aliasnam e].[propertyn am e] A_NAME as {item .nam e} 複合プロパティ {[aliasnam e].[com ponent nam e].[propertynam e]} CURRENCY as {item .am ount.currency}, VALUE as {item .am ount.value} エンティティのクラスを識別す る値 {[aliasnam e].class} DISC as {item .class} エンティティの全プロパティ {[aliasnam e].* } {item .* } コレクションのキー {[aliasnam e].key} ORGID as {coll.key} コレクションの ID {[aliasnam e].id} EMPID as {coll.id} コレクションの要素 {[aliasnam e].elem ent} XID as {coll.elem ent} コレクションの要素のプロパ ティ {[aliasnam e].elem ent.[p ropertynam e]} NAME as {coll.elem ent.nam e} コレクションの要素の全プロパ ティ {[aliasnam e].elem ent.* } {coll.elem ent.* } コレクションの全プロパティ {[aliasnam e].* } {coll.* } 16.1.5. 管理されていないエンティティの取得 ネイティブ SQL クエリに ResultT ransformer を適用できます。下記のように、例えば、管理されていな いエンティティを返します。 sess.createSQLQuery("SELECT NAME, BIRTHDATE FROM CATS") .setResultTransformer(Transformers.aliasToBean(CatDTO.class)) このクエリで指定されているものを下記に示します: SQL クエリ文字列 結果を変換したもの 上記のクエリは、インスタンス化し、 NAME と BIRT HDAT E の値を対応するプロパティもしくはフィー ルドに挿入した CatDT O のリストを返します。 16.1.6. 継承の制御 継承の一部としてマッピングされたエンティティを問い合わせるネイティブ SQL クエリは、ベースのク ラスとそのすべてのサブクラスのプロパティすべてを含まなければなりません。 16.1.7. パラメータ ネイティブ SQL クエリは、以下のように、名前付きパラメータ(:name)と同様に位置パラメータをサ ポートします: 204 第16章 ネイティブ SQL Query query = sess.createSQLQuery("SELECT * FROM CATS WHERE NAME like ?").addEntity(Cat.class); List pusList = query.setString(0, "Pus%").list(); query = sess.createSQLQuery("SELECT * FROM CATS WHERE NAME like :name").addEntity(Cat.class); List pusList = query.setString("name", "Pus%").list(); 16.2. 名 前 付 き SQL ク エ リ 名前付き SQL クエリはマッピングドキュメントで定義することができ、名前付き HQL クエリと全く同じ 方法で呼ぶことができます。この場合、 addEntity() を呼び出す必要は ありません 。 <sql-query name="persons"> <return alias="person" class="eg.Person"/> SELECT person.NAME AS {person.name}, person.AGE AS {person.age}, person.SEX AS {person.sex} FROM PERSON person WHERE person.NAME LIKE :namePattern </sql-query> List people = sess.getNamedQuery("persons") .setString("namePattern", namePattern) .setMaxResults(50) .list(); T he <return-join> and <load-collection> elements are used to join associations and define queries which initialize collections, respectively. <sql-query name="personsWith"> <return alias="person" class="eg.Person"/> <return-join alias="address" property="person.mailingAddress"/> SELECT person.NAME AS {person.name}, person.AGE AS {person.age}, person.SEX AS {person.sex}, adddress.STREET AS {address.street}, adddress.CITY AS {address.city}, adddress.STATE AS {address.state}, adddress.ZIP AS {address.zip} FROM PERSON person JOIN ADDRESS adddress ON person.ID = address.PERSON_ID AND address.TYPE='MAILING' WHERE person.NAME LIKE :namePattern </sql-query> A named SQL query may return a scalar value. You must declare the column alias and Hibernate type using the <return-scalar> element: 205 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide <sql-query name="mySqlQuery"> <return-scalar column="name" type="string"/> <return-scalar column="age" type="long"/> SELECT p.NAME AS name, p.AGE AS age, FROM PERSON p WHERE p.NAME LIKE 'Hiber%' </sql-query> You can externalize the resultset mapping informations in a <resultset> element to either reuse them accross several named queries or through the setResultSetMapping() API. <resultset name="personAddress"> <return alias="person" class="eg.Person"/> <return-join alias="address" property="person.mailingAddress"/> </resultset> <sql-query name="personsWith" resultset-ref="personAddress"> SELECT person.NAME AS {person.name}, person.AGE AS {person.age}, person.SEX AS {person.sex}, adddress.STREET AS {address.street}, adddress.CITY AS {address.city}, adddress.STATE AS {address.state}, adddress.ZIP AS {address.zip} FROM PERSON person JOIN ADDRESS adddress ON person.ID = address.PERSON_ID AND address.TYPE='MAILING' WHERE person.NAME LIKE :namePattern </sql-query> 代わりに、 hbm ファイル内のリザルトセットのマッピング情報を直接 Java コードの中で使用できます。 List cats = sess.createSQLQuery( "select {cat.*}, {kitten.*} from cats cat, cats kitten where kitten.mother = cat.id" ) .setResultSetMapping("catAndKitten") .list(); 16.2.1. 列と列の別名を明示的に指定するために return-property を使う With <return-property> you can explicitly tell Hibernate what column aliases to use, instead of using the {}-syntax to let Hibernate inject its own aliases. <sql-query name="mySqlQuery"> <return alias="person" class="eg.Person"> <return-property name="name" column="myName"/> <return-property name="age" column="myAge"/> <return-property name="sex" column="mySex"/> </return> SELECT person.NAME AS myName, person.AGE AS myAge, person.SEX AS mySex, FROM PERSON person WHERE person.NAME LIKE :name </sql-query> <return-property> also works with multiple columns. T his solves a limitation with the {}-syntax 206 第16章 ネイティブ SQL which can not allow fine grained control of multi-column properties. <sql-query name="organizationCurrentEmployments"> <return alias="emp" class="Employment"> <return-property name="salary"> <return-column name="VALUE"/> <return-column name="CURRENCY"/> </return-property> <return-property name="endDate" column="myEndDate"/> </return> SELECT EMPLOYEE AS {emp.employee}, EMPLOYER AS {emp.employer}, STARTDATE AS {emp.startDate}, ENDDATE AS {emp.endDate}, REGIONCODE as {emp.regionCode}, EID AS {emp.id}, VALUE, CURRENCY FROM EMPLOYMENT WHERE EMPLOYER = :id AND ENDDATE IS NULL ORDER BY STARTDATE ASC </sql-query> Notice that in this example we used <return-property> in combination with the {}-syntax for injection. Allowing users to choose how they want to refer column and properties. If your mapping has a discriminator you must use <return-discrim inator> to specify the discriminator column. 16.2.2. 問い合わせするためにストアドプロシージャを使う Hibernate はバージョン3から、ストアドプロシージャとストアド関数経由の問い合わせがサポートされま した。以降の文書の多くは、両方に当てはまります。ストアドプロシージャやストアド関数を Hibernate で使うためには、1番目の出力パラメータとしてリザルトセットを返さなければなりません。 Oracle 9(もしくはそれ以上のバージョン)のストアドプロシージャの例を以下に示します: CREATE OR REPLACE FUNCTION selectAllEmployments RETURN SYS_REFCURSOR AS st_cursor SYS_REFCURSOR; BEGIN OPEN st_cursor FOR SELECT EMPLOYEE, EMPLOYER, STARTDATE, ENDDATE, REGIONCODE, EID, VALUE, CURRENCY FROM EMPLOYMENT; RETURN st_cursor; END; Hibernate でこのクエリを使うためには、名前付きクエリでマッピングする必要があります。 207 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide <sql-query name="selectAllEmployees_SP" callable="true"> <return alias="emp" class="Employment"> <return-property name="employee" column="EMPLOYEE"/> <return-property name="employer" column="EMPLOYER"/> <return-property name="startDate" column="STARTDATE"/> <return-property name="endDate" column="ENDDATE"/> <return-property name="regionCode" column="REGIONCODE"/> <return-property name="id" column="EID"/> <return-property name="salary"> <return-column name="VALUE"/> <return-column name="CURRENCY"/> </return-property> </return> { ? = call selectAllEmployments() } </sql-query> Notice stored procedures currently only return scalars and entities. <return-join> and <loadcollection> are not supported. 16.2.2.1. ストアドプロシージャを使う上でのルールと制限 Hibernate でストアドプロシージャや関数を使うためには、そのプロシージャはいくつかのルールに準拠 する必要があります。ルールに準拠していないプロシージャは、 Hibernate で使うことはできません。そ れでも、準拠していないプロシージャを使いたいのであれば、 session.connection() を通じて実行 しなければなりません。ルールはデータベースごとに異なります。ストアドプロシージャのセマンティッ クスとシンタックスは、データベースベンダごとに異なるためです。 Stored procedure queries can't be paged with setFirstResult()/setMaxResults(). Recommended call form is standard SQL92: { ? = call functionNam e(<param eters>) } or { ? = call procedureNam e(<param eters>}. Native call syntax is not supported. Oracle には下記のルールが適用されます: 関数はリザルトセットを返さなければなりません。プロシージャの第一引数はリザルトセットを返す ため、 OUT でなければなりません。 Oracle 9 と 10 では、 SYS_REFCURSOR を使うことによってで きます。 Oracle では REF CURSOR 型を定義する必要があります。 Oracle の文献を参照してくださ い。 Sybase と MS SQL サーバーに適用されるルールを下記に示します: プロシージャはリザルトセットを返さなければなりません。サーバーは複数のリザルトセットと更新 カウントを返しますが、 Hibernate は1つ目のリザルトセットだけを返すことに注意してください。そ の他はすべて捨てられます。 プロシージャの中で SET NOCOUNT ON を有効にできれば、おそらく効率がよくなるでしょう。しか し、これは必要条件ではありません。 16.3. 作 成 、 更 新 、 削 除 の た め の カ ス タ ム SQL Hibernate3 can use custom SQL statements for create, update, and delete operations. T he class and collection persisters in Hibernate already contain a set of configuration time generated strings (insertsql, deletesql, updatesql etc.). T he mapping tags <sql-insert>, <sql-delete>, and <sql-update> override these strings: 208 第16章 ネイティブ SQL <class name="Person"> <id name="id"> <generator class="increment"/> </id> <property name="name" not-null="true"/> <sql-insert>INSERT INTO PERSON (NAME, ID) VALUES ( UPPER(?), ? )</sql-insert> <sql-update>UPDATE PERSON SET NAME=UPPER(?) WHERE ID=?</sql-update> <sql-delete>DELETE FROM PERSON WHERE ID=?</sql-delete> </class> SQL を直接データベースで実行するため、好みの方言を自由に使用できます。データベース独自の SQL を使えば、当然マッピングのポータビリティが下がります。 callable 属性をセットすれば、ストアドプロシージャを使用できます: <class name="Person"> <id name="id"> <generator class="increment"/> </id> <property name="name" not-null="true"/> <sql-insert callable="true">{call createPerson (?, ?)}</sql-insert> <sql-delete callable="true">{? = call deletePerson (?)}</sql-delete> <sql-update callable="true">{? = call updatePerson (?, ?)}</sql-update> </class> 今のところ、位置パラメータの順番はとても重要です。すなわち、 Hibernate が期待する順序でなければ なりません。 org.hiberante.persister.entity レベルのデバッグログを有効にすることによって、期待される 順番を確かめられます。このレベルを有効にすることにより、エンティティの作成、更新、削除などで使 用される静的な SQL が出力されます。(期待される順序を確認するためには、 Hibernate が生成する静 的な SQL をオーバーライドするカスタム SQL をマッピングファイルに含めないことを忘れないでくださ い。) ストアドプロシージャは挿入/更新/削除された行数を返す必要があります(読み込みの場合は、返さない よりは返す方がよいです)。実行時に Hibernate が SQL 文の成功をチェックするからです。 Hibernate は、 CUD 処理のための数値の出力パラメータとして、 SQL 文の最初のパラメータをいつも記録します: CREATE OR REPLACE FUNCTION updatePerson (uid IN NUMBER, uname IN VARCHAR2) RETURN NUMBER IS BEGIN update PERSON set NAME = uname, where ID = uid; return SQL%ROWCOUNT; END updatePerson; 16.4. ロ ー ド の た め の カ ス タ ム SQL エンティティを読み込むための独自の SQL (もしくは HQL)クエリも宣言できます: 209 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide <sql-query name="person"> <return alias="pers" class="Person" lock-mode="upgrade"/> SELECT NAME AS {pers.name}, ID AS {pers.id} FROM PERSON WHERE ID=? FOR UPDATE </sql-query> これは、まさに(以前議論した)名前付きクエリの宣言です。この名前付きクエリをクラスのマッピング から参照できます: <class name="Person"> <id name="id"> <generator class="increment"/> </id> <property name="name" not-null="true"/> <loader query-ref="person"/> </class> これはストアドプロシージャでさえも動作します。 次のように、コレクションをロードするためのクエリさえ定義してよいです: <set name="employments" inverse="true"> <key/> <one-to-many class="Employment"/> <loader query-ref="employments"/> </set> <sql-query name="employments"> <load-collection alias="emp" role="Person.employments"/> SELECT {emp.*} FROM EMPLOYMENT emp WHERE EMPLOYER = :id ORDER BY STARTDATE ASC, EMPLOYEE ASC </sql-query> 次のように、結合フェッチによりコレクションをロードするエンティティローダーを定義できます: <sql-query name="person"> <return alias="pers" class="Person"/> <return-join alias="emp" property="pers.employments"/> SELECT NAME AS {pers.*}, {emp.*} FROM PERSON pers LEFT OUTER JOIN EMPLOYMENT emp ON pers.ID = emp.PERSON_ID WHERE ID=? </sql-query> 210 第17章 データのフィルタリング 第 17章 データのフィルタリング Hibernate3 provides an innovative new approach to handling data with "visibility" rules. A Hibernate filter is a global, named, parameterized filter that may be enabled or disabled for a particular Hibernate session. 17.1. Hibernate の フ ィ ル タ Hibernate3 adds the ability to pre-define filter criteria and attach those filters at both a class and a collection level. A filter criteria is the ability to define a restriction clause very similiar to the existing "where" attribute available on the class and various collection elements. Except these filter conditions can be parameterized. T he application can then make the decision at runtime whether given filters should be enabled and what their parameter values should be. Filters can be used like database views, but parameterized inside the application. In order to use filters, they must first be defined and then attached to the appropriate mapping elements. T o define a filter, use the <filter-def/> element within a <hibernate-m apping/> element: <filter-def name="myFilter"> <filter-param name="myFilterParam" type="string"/> </filter-def> そうしてフィルタはクラスへと結び付けられます: <class name="myClass" ...> ... <filter name="myFilter" condition=":myFilterParam = MY_FILTERED_COLUMN"/> </class> また、コレクションに対しては次のようになります: <set ...> <filter name="myFilter" condition=":myFilterParam = MY_FILTERED_COLUMN"/> </set> どちらに対しても (また、それぞれを複数) 同時に設定することもできます。 Session 上のメソッドは enableFilter(String filterNam e)、 getEnabledFilter(String filterNam e)、 disableFilter(String filterNam e) です。デフォルトでは、フィルタは与え られたセッションに対して使用 できません 。 Filter インスタンスを返り値とする Session.enabledFilter() メソッドを使うことで、フィルタは明示的に使用可能となります。上で 定義した単純なフィルタの使用は、このようになります: session.enableFilter("myFilter").setParameter("myFilterParam", "some-value"); org.hibernate.Filter インターフェースのメソッドは、 Hibernate の多くに共通しているメソッド連鎖を許 していることに注意してください。 有効なレコードデータパターンを持つ一時データを使った完全な例です: 211 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide <filter-def name="effectiveDate"> <filter-param name="asOfDate" type="date"/> </filter-def> <class name="Employee" ...> ... <many-to-one name="department" column="dept_id" class="Department"/> <property name="effectiveStartDate" type="date" column="eff_start_dt"/> <property name="effectiveEndDate" type="date" column="eff_end_dt"/> ... <!-Note that this assumes non-terminal records have an eff_end_dt set to a max db date for simplicity-sake --> <filter name="effectiveDate" condition=":asOfDate BETWEEN eff_start_dt and eff_end_dt"/> </class> <class name="Department" ...> ... <set name="employees" lazy="true"> <key column="dept_id"/> <one-to-many class="Employee"/> <filter name="effectiveDate" condition=":asOfDate BETWEEN eff_start_dt and eff_end_dt"/> </set> </class> 常に現在の有効レコードを返却することを保証するために、単純に、社員データの検索より前にセッショ ン上のフィルタを有効にします: Session session = ...; session.enabledFilter("effectiveDate").setParameter("asOfDate", new Date()); List results = session.createQuery("from Employee as e where e.salary > :targetSalary") .setLong("targetSalary", new Long(1000000)) .list(); 上記の HQL では、結果の給料の制約について明示的に触れただけですが、有効になっているフィルタの おかげで、このクエリは給料が100万ドル以上の現役の社員だけを返します。 注記: (HQL かロードフェッチで)外部結合を持つフィルタを使うつもりなら、条件式の方向に注意して ください。これは左外部結合のために設定するのが最も安全です。一般的に、演算子の後カラム名に続け て最初のパラメータを配置してください。 After being defined a filter might be attached to multiple entities and/or collections each with its own condition. T hat can be tedious when the conditions are the same each time. T hus <filter-def/> allows defining a default condition, either as an attribute or CDAT A: <filter-def name="myFilter" condition="abc > xyz">...</filter-def> <filter-def name="myOtherFilter">abc=xyz</filter-def> このデフォルトのコンディションは、コンディションを指定せずに何かにアタッチされる場合いつでも使 われます。これは、特定のケースにおいてデフォルトのコンディションをオーバーライドするフィルター のアタッチメントの一部として、特定のコンディションを与えることができることを意味します。 212 第18章 XML マッピング 第 18章 XML マッピング Note that this is an experimental feature in Hibernate 3.0 and is under extremely active development. 18.1. XML デ ー タ で の 作 業 Hibernate では永続性の POJO を使って作業するのとほぼ同じようなやり方で、永続性の XML データを 使って作業できます。解析された XML ツリーは POJO の代わりにオブジェクトレベルで関係データを表 わす別の方法であるとみなされています。 Hibernate supports dom4j as API for manipulating XML trees. You can write queries that retrieve dom4j trees from the database and have any modification you make to the tree automatically synchronized to the database. You can even take an XML document, parse it using dom4j, and write it to the database with any of Hibernate's basic operations: persist(), saveOrUpdate(), m erge(), delete(), replicate() (merging is not yet supported). データのインポート/エクスポート、 JMS によるエンティティデータの外部化や SOAP 、 XSLT ベースの レポートなど、この機能には多くの用途があります。 単一のマッピングは、クラスのプロパティと XML ドキュメントのノードを同時にデータベースへマッピ ングするために使うことができます。またマッピングするクラスがなければ、 XML だけをマッピングす るために使うことができます。 18.1.1. XML とクラスのマッピングを同時に指定する これは POJO と XML を同時にマッピングする例です: <class name="Account" table="ACCOUNTS" node="account"> <id name="accountId" column="ACCOUNT_ID" node="@id"/> <many-to-one name="customer" column="CUSTOMER_ID" node="customer/@id" embed-xml="false"/> <property name="balance" column="BALANCE" node="balance"/> ... </class> 18.1.2. XML マッピングだけを指定する これは POJO クラスがないマッピングの例です: 213 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide <class entity-name="Account" table="ACCOUNTS" node="account"> <id name="id" column="ACCOUNT_ID" node="@id" type="string"/> <many-to-one name="customerId" column="CUSTOMER_ID" node="customer/@id" embed-xml="false" entity-name="Customer"/> <property name="balance" column="BALANCE" node="balance" type="big_decimal"/> ... </class> このマッピングにより、 dom4j ツリーか、プロパティ名/値の組のグラフ(java の Map)としてデータに アクセスできます。プロパティの名前は、 HQL クエリ内で参照できる純粋な論理構造です。 18.2. XML マ ッ ピ ン グ の メ タ デ ー タ Many Hibernate mapping elements accept the node attribute. T his let's you specify the name of an XML attribute or element that holds the property or entity data. T he format of the node attribute must be one of the following: "elem ent-nam e" - map to the named XML element "@ attribute-nam e" - map to the named XML attribute "." - map to the parent element "elem ent-nam e/@ attribute-nam e" - map to the named attribute of the named element For collections and single valued associations, there is an additional em bed-xm l attribute. If em bedxm l="true", the default, the XML tree for the associated entity (or collection of value type) will be embedded directly in the XML tree for the entity that owns the association. Otherwise, if em bedxm l="false", then only the referenced identifier value will appear in the XML for single point associations and collections will simply not appear at all. You should be careful not to leave em bed-xm l="true" for too many associations, since XML does not deal well with circularity! 214 第18章 XML マッピング <class name="Customer" table="CUSTOMER" node="customer"> <id name="id" column="CUST_ID" node="@id"/> <map name="accounts" node="." embed-xml="true"> <key column="CUSTOMER_ID" not-null="true"/> <map-key column="SHORT_DESC" node="@short-desc" type="string"/> <one-to-many entity-name="Account" embed-xml="false" node="account"/> </map> <component name="name" node="name"> <property name="firstName" node="first-name"/> <property name="initial" node="initial"/> <property name="lastName" node="last-name"/> </component> ... </class> この例では、実際の account のデータではなく、 account の id のコレクションを埋め込むことにしまし た。続きの HQL クエリです: from Customer c left join fetch c.accounts where c.lastName like :lastName このようなデータセットを返すでしょう: <customer id="123456789"> <account short-desc="Savings">987632567</account> <account short-desc="Credit Card">985612323</account> <name> <first-name>Gavin</first-name> <initial>A</initial> <last-name>King</last-name> </name> ... </customer> If you set em bed-xm l="true" on the <one-to-m any> mapping, the data might look more like this: 215 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide <customer id="123456789"> <account id="987632567" short-desc="Savings"> <customer id="123456789"/> <balance>100.29</balance> </account> <account id="985612323" short-desc="Credit Card"> <customer id="123456789"/> <balance>-2370.34</balance> </account> <name> <first-name>Gavin</first-name> <initial>A</initial> <last-name>King</last-name> </name> ... </customer> 18.3. XML デ ー タ を 扱 う Let's rearead and update XML documents in the application. We do this by obtaining a dom4j session: Document doc = ....; Session session = factory.openSession(); Session dom4jSession = session.getSession(EntityMode.DOM4J); Transaction tx = session.beginTransaction(); List results = dom4jSession .createQuery("from Customer c left join fetch c.accounts where c.lastName like :lastName") .list(); for ( int i=0; i<results.size(); i++ ) { //add the customer data to the XML document Element customer = (Element) results.get(i); doc.add(customer); } tx.commit(); session.close(); Session session = factory.openSession(); Session dom4jSession = session.getSession(EntityMode.DOM4J); Transaction tx = session.beginTransaction(); Element cust = (Element) dom4jSession.get("Customer", customerId); for ( int i=0; i<results.size(); i++ ) { Element customer = (Element) results.get(i); //change the customer name in the XML and database Element name = customer.element("name"); name.element("first-name").setText(firstName); name.element("initial").setText(initial); name.element("last-name").setText(lastName); } tx.commit(); session.close(); 216 第18章 XML マッピング It is extremely useful to combine this feature with Hibernate's replicate() operation to implement XML-based data import/export. 217 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 第 19章 パフォーマンスの改善 19.1. フ ェ ッ チ 戦 略 フェッチ戦略 は、アプリケーションが関連をナビゲートする必要があるときに、 Hibernate が関連オブ ジェクトを復元するために使用する戦略です。フェッチ戦略は O/R マッピングのメタデータに宣言する か、特定の HQL 、 Criteria クエリでオーバーライドします。 Hibernate3 は次に示すフェッチ戦略を定義しています: 結合フェッチ - Hibernate は OUT ER JOIN を使って、関連するインスタンスやコレクションを1つの SELECT で復元します。 Select fetching - a second SELECT is used to retrieve the associated entity or collection. Unless you explicitly disable lazy fetching by specifying lazy="false", this second select will only be executed when you actually access the association. Subselect fetching - a second SELECT is used to retrieve the associated collections for all entities retrieved in a previous query or fetch. Unless you explicitly disable lazy fetching by specifying lazy="false", this second select will only be executed when you actually access the association. バッチフェッチ - セレクトフェッチのための最適化された戦略 - Hibernate はエンティティのインスタ ンスやコレクションの一群を1回の SELECT で復元します。これは主キーや外部キーのリストを指定 することにより行います。 Hibernate は次に示す戦略とも区別をします: 即時フェッチ - 所有者のオブジェクトがロードされたときに、関連、コレクションは即時にフェッチ されます。 遅延コレクションフェッチ - アプリケーションがコレクションに対して操作を行ったときにコレク ションをフェッチします。(これはコレクションに対するデフォルトの動作です) "Extra-lazy" collection fetching - individual elements of the collection are accessed from the database as needed. Hibernate tries not to fetch the whole collection into memory unless absolutely needed (suitable for very large collections) プロキシフェッチ - 単一値関連は、識別子の getter 以外のメソッドが関連オブジェクトで呼び出され るときにフェッチされます。 "No-proxy" fetching - a single-valued association is fetched when the instance variable is accessed. Compared to proxy fetching, this approach is less lazy (the association is fetched even when only the identifier is accessed) but more transparent, since no proxy is visible to the application. T his approach requires buildtime bytecode instrumentation and is rarely necessary. 遅延属性フェッチ - 属性や単一値関連は、インスタンス変数にアクセスしたときにフェッチされま す。この方法はビルド時のバイトコード組み込みが必要になり、使う場面はまれです。 We have two orthogonal notions here: when is the association fetched, and how is it fetched (what SQL is used). Don't confuse them! We use fetch to tune performance. We may use lazy to define a contract for what data is always available in any detached instance of a particular class. 19.1.1. 遅延関連の働き デフォルトでは、 Hibernate3 はコレクションに対しては遅延セレクトフェッチを使い、単一値関連には 遅延プロキシフェッチを使います。これらのデフォルト動作はほぼすべてのアプリケーションのほぼすべ ての関連で意味があります。 注:hibernate.default_batch_fetch_size をセットしたときは、 Hibernate は遅延フェッチのた めのバッチフェッチ最適化を使うでしょう(この最適化はより細かいレベルで有効にすることも出来ま 218 第19章 パフォーマンスの改善 す)。 しかし、遅延フェッチは知っておかなければならない一つの問題があります。 Hibernate の session を オープンしているコンテキストの外から遅延関連にアクセスすると、例外が発生します。例: s = sessions.openSession(); Transaction tx = s.beginTransaction(); User u = (User) s.createQuery("from User u where u.name=:userName") .setString("userName", userName).uniqueResult(); Map permissions = u.getPermissions(); tx.commit(); s.close(); Integer accessLevel = (Integer) permissions.get("accounts"); // Error! Session がクローズされたとき、 permissions コレクションは初期化されていないため、このコレク ションは自身の状態をロードできません。 Hibernate は切り離されたオブジェクトの遅延初期化はサポー トしていません 。修正方法として、コレクションから読み込みを行うコードをトランザクションをコミッ トする直前に移動させます。 Alternatively, we could use a non-lazy collection or association, by specifying lazy="false" for the association mapping. However, it is intended that lazy initialization be used for almost all collections and associations. If you define too many non-lazy associations in your object model, Hibernate will end up needing to fetch the entire database into memory in every transaction! On the other hand, we often want to choose join fetching (which is non-lazy by nature) instead of select fetching in a particular transaction. We'll now see how to customize the fetching strategy. In Hibernate3, the mechanisms for choosing a fetch strategy are identical for single-valued associations and collections. 19.1.2. フェッチ戦略のチューニング セレクトフェッチ(デフォルト)は N+1 セレクト問題という大きな弱点があるため、マッピング定義で 結合フェッチを有効にすることができます: <set name="permissions" fetch="join"> <key column="userId"/> <one-to-many class="Permission"/> </set <many-to-one name="mother" class="Cat" fetch="join"/> マッピング定義で定義した フェッチ 戦略は次のものに影響します: get() や load() による復元 関連にナビゲートしたときに発生する暗黙的な復元 Criteria クエリ サブセレクト フェッチを使う HQL クエリ たとえどんなフェッチ戦略を使ったとしても、遅延ではないグラフはメモリに読み込まれることが保証さ れます。つまり、特定の HQL クエリを実行するためにいくつかの SELECT 文が即時実行されることがあ るので注意してください。 219 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide Usually, we don't use the mapping document to customize fetching. Instead, we keep the default behavior, and override it for a particular transaction, using left join fetch in HQL. T his tells Hibernate to fetch the association eagerly in the first select, using an outer join. In the Criteria query API, you would use setFetchMode(FetchMode.JOIN). もし get() や load() で使われるフェッチ戦略を変えたいと感じたときには、単純に Criteria クエ リを使ってください。例: User user = (User) session.createCriteria(User.class) .setFetchMode("permissions", FetchMode.JOIN) .add( Restrictions.idEq(userId) ) .uniqueResult(); (T his is Hibernate's equivalent of what some ORM solutions call a "fetch plan".) N+1 セレクト問題を避けるためのまったく違う方法は、第2レベルキャッシュを使うことです。 19.1.3. 単一端関連プロキシ Lazy fetching for collections is implemented using Hibernate's own implementation of persistent collections. However, a different mechanism is needed for lazy behavior in single-ended associations. T he target entity of the association must be proxied. Hibernate implements lazy initializing proxies for persistent objects using runtime bytecode enhancement (via the excellent CGLIB library). デフォルトでは、 Hibernate3 は(開始時に)すべての永続クラスのプロキシを生成し、それらを使っ て、 m any-to-one や one-to-one 関連の遅延フェッチを可能にしています。 マッピングファイルで proxy 属性によって、クラスのプロキシインターフェースとして使うインター フェースを宣言できます。デフォルトでは、 Hibernate はそのクラスのサブクラスを使います。 プロキシ クラスは少なくともパッケージ可視でデフォルトコンストラクタを実装しなければならないことに注意し てください。すべての永続クラスにこのコンストラクタを推奨します。 ポリモーフィズムのクラスに対してこの方法を適用するときにいくつか考慮することがあります。例: <class name="Cat" proxy="Cat"> ...... <subclass name="DomesticCat"> ..... </subclass> </class> 第一に、 Cat のインスタンスは Dom esticCat にキャストできません。たとえ基となるインスタンスが Dom esticCat であったとしてもです: Cat cat = (Cat) session.load(Cat.class, id); the db) if ( cat.isDomesticCat() ) { proxy DomesticCat dc = (DomesticCat) cat; .... } 第二に、プロキシの == は成立しないことがあります。 220 // instantiate a proxy (does not hit // hit the db to initialize the // Error! 第19章 パフォーマンスの改善 Cat cat = (Cat) session.load(Cat.class, id); // instantiate a Cat proxy DomesticCat dc = (DomesticCat) session.load(DomesticCat.class, id); // acquire new DomesticCat proxy! System.out.println(cat==dc); // false しかし、これは見かけほど悪い状況というわけではありません。たとえ異なったプロキシオブジェクトへ の二つの参照があったとしても、基となるインスタンスは同じオブジェクトです: cat.setWeight(11.0); // hit the db to initialize the proxy System.out.println( dc.getWeight() ); // 11.0 第三に、 final クラスや final メソッドを持つクラスに CGLIB プロキシを使えません。 最後に、もし永続オブジェクトのインスタンス化時 (例えば、初期化処理やデフォルトコンストラクタの 中で) になんらかのリソースが必要となるなら、そのリソースもまたプロキシを通して取得されます。実 際には、プロキシクラスは永続クラスのサブクラスです。 T hese problems are all due to fundamental limitations in Java's single inheritance model. If you wish to avoid these problems your persistent classes must each implement an interface that declares its business methods. You should specify these interfaces in the mapping file. eg. <class name="CatImpl" proxy="Cat"> ...... <subclass name="DomesticCatImpl" proxy="DomesticCat"> ..... </subclass> </class> CatIm pl は Cat インターフェースを実装するのに対し、 Dom esticCatIm pl は Dom esticCat を実 装します。すると、 load() や iterate() は、 Cat や Dom esticCat のインスタンスのプロキシを返 します。( list() は通常はプロキシを返さないことに注意してください。) Cat cat = (Cat) session.load(CatImpl.class, catid); Iterator iter = session.iterate("from CatImpl as cat where cat.name='fritz'"); Cat fritz = (Cat) iter.next(); 関連も遅延初期化されます。これはプロパティを Cat 型で宣言しなければならないことを意味します。 CatIm pl ではありません。 プロキシの初期化を 必要としない 操作も存在します。 equals() (永続クラスが equals() をオーバーライドしないとき) hashCode() (永続クラスが hashCode() をオーバーライドしないとき) 識別子の getter メソッド Hibernate は equals() や hashCode() をオーバーライドした永続クラスを検出します。 By choosing lazy="no-proxy" instead of the default lazy="proxy", we can avoid the problems associated with typecasting. However, we will require buildtime bytecode instrumentation, and all operations will result in immediate proxy initialization. 19.1.4. コレクションとプロキシの初期化 LazyInitializationException は、 Session のスコープ外から初期化していないコレクションや 221 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide プロキシにアクセスされたときに、 Hibernate によってスローされます。すなわち、コレクションやプロ キシへの参照を持つエンティティが分離された状態の時です。 Session をクローズする前にプロキシやコレクションの初期化を確実に行いたいときがあります。もち ろん、 cat.getSex() や cat.getKittens().size() などを常に呼び出すことで初期化を強制するこ とはできます。しかしこれはコードを読む人を混乱させ、汎用的なコードという点からも不便です。 static メソッドの Hibernate.initialize() や Hibernate.isInitialized() は遅延初期化のコ レクションやプロキシを扱うときに便利な方法をアプリケーションに提供します。 Hibernate.initialize(cat) は、 Session がオープンしている限りは cat プロキシを強制的に初 期化します。 Hibernate.initialize( cat.getKittens() ) は kittens コレクションに対して同 様の効果があります。 別の選択肢として、必要なすべてのコレクションやプロキシがロードされるまで Session をオープンに しておく方法があります。いくつかのアプリケーションのアーキテクチャでは、特に Hibernate による データアクセスを行うコードと、それを使うコードが異なるアプリケーションのレイヤーや、物理的に異 なるプロセッサのときには、コレクションが初期化されるときに Session がオープンしていることを保 証する問題があります。この問題に対しては2つの基本的な方法があります: In a web-based application, a servlet filter can be used to close the Session only at the very end of a user request, once the rendering of the view is complete (the Open Session in View pattern). Of course, this places heavy demands on the correctness of the exception handling of your application infrastructure. It is vitally important that the Session is closed and the transaction ended before returning to the user, even when an exception occurs during rendering of the view. See the Hibernate Wiki for examples of this "Open Session in View" pattern. In an application with a separate business tier, the business logic must "prepare" all collections that will be needed by the web tier before returning. T his means that the business tier should load all the data and return all the data already initialized to the presentation/web tier that is required for a particular use case. Usually, the application calls Hibernate.initialize() for each collection that will be needed in the web tier (this call must occur before the session is closed) or retrieves the collection eagerly using a Hibernate query with a FET CH clause or a FetchMode.JOIN in Criteria. T his is usually easier if you adopt the Command pattern instead of a Session Facade. 初期化されていないコレクション(もしくは他のプロキシ)にアクセスする前に、 m erge() や lock() を使って新しい Session に以前にロードされたオブジェクトを追加することも出来ます。ア ドホックなトランザクションのセマンティクスを導入したので、 Hibernate はこれを自動的に行わ ず、 行うべきでもありません 。 Sometimes you don't want to initialize a large collection, but still need some information about it (like its size) or a subset of the data. コレクションフィルタを使うことで、初期化せずにコレクションのサイズを取得することが出来ます: ( (Integer) s.createFilter( collection, "select count(*)" ).list().get(0) ).intValue() createFilter() メソッドは、コレクション全体を初期化する必要なしに、コレクションのサブセット を復元するために効果的に使えます: s.createFilter( lazyCollection, "").setFirstResult(0).setMaxResults(10).list(); 19.1.5. バッチフェッチの使用 Hibernate はバッチフェッチを効率的に使用できます。一つのプロキシ(もしくはコレクション)がアク セスされると、 Hibernate はいくつかの初期化していないプロキシをロードすることができます。バッチ 222 第19章 パフォーマンスの改善 フェッチは遅延セレクトフェッチ戦略に対する最適化です。バッチフェッチの調整には2つの方法があり ます。クラスレベルとコレクションレベルです。 Batch fetching for classes/entities is easier to understand. Imagine you have the following situation at runtime: You have 25 Cat instances loaded in a Session, each Cat has a reference to its owner, a Person. T he Person class is mapped with a proxy, lazy="true". If you now iterate through all cats and call getOwner() on each, Hibernate will by default execute 25 SELECT statements, to retrieve the proxied owners. You can tune this behavior by specifying a batch-size in the mapping of Person: <class name="Person" batch-size="10">...</class> Hibernate はクエリを3回だけを実行するようになります。パターンは 10, 10, 5 です。 コレクションのバッチフェッチも有効にすることが出来ます。例として、それぞれの Person が Cat の 遅延コレクションを持っており、 10 個の Person が Sesssion にロードされたとすると、すべての Person に対して繰り返し getCats() を呼び出すことで、計10回の SELECT が発生します。もし Person のマッピングで cats コレクションのバッチフェッチを有効にすれば、 Hibernate はコレクショ ンの事前フェッチが出来ます。 <class name="Person"> <set name="cats" batch-size="3"> ... </set> </class> batch-size が 8 なので、 Hibernate は 4 回の SELECT で 3 個、 3 個、 3 個、 1 個をロードします。 繰り返すと、属性の値は特定の Session の中の初期化されていないコレクションの期待数に依存しま す。 コレクションのバッチフェッチはアイテムのネストしたツリー、すなわち、代表的な部品表のパターンが ある場合に特に有用です。(しかし、読み込みが多いツリーでは ネストした set や 具体化したパス がよ りよい選択になります。) 19.1.6. サブセレクトフェッチの使用 一つの遅延コレクションや単一値プロキシがフェッチされなければいけないとき、 Hibernate はそれらす べてをロードし、サブセレクトのオリジナルクエリが再度実行されます。これはバッチフェッチと同じ方 法で動き、少しずつのロードは行いません。 19.1.7. 遅延プロパティフェッチの使用 Hibernate3 はプロパティごとの遅延フェッチをサポートしています。この最適化手法は グループの フェッチ としても知られています。これはほとんど要望から出た機能であることに注意してください。実 際には列読み込みの最適化よりも、行読み込みの最適化が非常に重要です。しかし、クラスのいくつかの プロパティだけを読み込むことは、既存のテーブルが何百もの列を持ち、データモデルを改善できないな どの極端な場合には有用です。 遅延プロパティ読み込みを有効にするには、対象のプロパティのマッピングで lazy 属性をセットしてく ださい: 223 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide <class name="Document"> <id name="id"> <generator class="native"/> </id> <property name="name" not-null="true" length="50"/> <property name="summary" not-null="true" length="200" lazy="true"/> <property name="text" not-null="true" length="2000" lazy="true"/> </class> 遅延プロパティ読み込みはビルド時のバイトコード組み込みを必要とします。もし永続クラスに組み込み がされていないなら、 Hibernate は黙って遅延プロパティの設定を無視して、即時フェッチに戻します。 バイトコード組み込みは以下の Ant タスクを使ってください: <target name="instrument" depends="compile"> <taskdef name="instrument" classname="org.hibernate.tool.instrument.InstrumentTask"> <classpath path="${jar.path}"/> <classpath path="${classes.dir}"/> <classpath refid="lib.class.path"/> </taskdef> <instrument verbose="true"> <fileset dir="${testclasses.dir}/org/hibernate/auction/model"> <include name="*.class"/> </fileset> </instrument> </target> 不要な列を読み込まないための、別の(よりよい?)方法は、少なくとも読み込みのみのトランザクショ ンにおいては、 HQL や Criteria クエリの射影機能を使うことです。この方法はビルド時のバイトコード組 み込みが不要になり、より良い解決方法です。 HQL で fetch all properties を使うことで、普通どおりのプロパティの即時フェッチングを強制す ることが出来ます。 19.2. 第 2レ ベ ル キ ャ ッ シ ュ Hibernate の Session は永続データのトランザクションレベルのキャッシュです。 class-by-class と collection-by-collection ごとの、クラスタレベルや JVM レベル ( SessionFactory レベル)のキャッ シュを設定することが出来ます。クラスタ化されたキャッシュにつなぐことさえ出来ます。しかし注意し てください。キャッシュは他のアプリケーションによる永続層の変更を考慮しません(キャッシュデータ を定期的に期限切れにする設定は出来ます)。 You have the option to tell Hibernate which caching implementation to use by specifying the name of a class that implements org.hibernate.cache.CacheProvider using the property hibernate.cache.provider_class. Hibernate comes bundled with a number of built-in integrations with open-source cache providers (listed below); additionally, you could implement your own and plug it in as outlined above. Note that versions prior to 3.2 defaulted to use EhCache as the default cache provider; that is no longer the case as of 3.2. 224 第19章 パフォーマンスの改善 表 19.1 キャッシュプロバイダ キャッシュ プロバイダクラス タイプ クラスタ セーフ Hashtable( 製品用とし て意図して いません) org.hibernate.cache.HashtableCa cheProvider メモリ EHCache org.hibernate.cache.EhCacheProv メモリ、 ディスク ider OSCache org.hibernate.cache.OSCacheProv メモリ、 ディスク ider SwarmCach e org.hibernate.cache.Swarm Cache Provider クラスタ (ip マルチ キャスト) yes(クラス タ無効化) JBoss T reeCache org.hibernate.cache.T reeCacheP rovider クラスタ (ip マルチ キャス ト)、トラ ンザクショ ナル yes(複製) クエリ キャッシュ のサポート yes yes yes yes(時刻同 期が必要) 19.2.1. キャッシュのマッピング T he <cache> element of a class or collection mapping has the following form: <cache usage="transactional|read-write|nonstrict-read-write|read-only" region="RegionName" include="all|non-lazy" /> usage (必須) キャッシング戦略を指定します: transactional、 read-write、 nonstrictread-write または read-only region (オプション、クラスまたはコレクションのロールネームのデフォルト) 2次レベルのキャッシュ 領域の名前を指定します include (optional, defaults to all) non-lazy specifies that properties of the entity mapped with lazy="true" may not be cached when attribute-level lazy fetching is enabled Alternatively (preferrably?), you may specify <class-cache> and <collection-cache> elements in hibernate.cfg.xm l. usage 属性は キャッシュの並列性戦略 を指定します。 19.2.2. read only 戦略 If your application needs to read but never modify instances of a persistent class, a read-only cache may be used. T his is the simplest and best performing strategy. It's even perfectly safe for use in a cluster. 225 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide <class name="eg.Immutable" mutable="false"> <cache usage="read-only"/> .... </class> 19.2.3. read/write 戦略 アプリケーションがデータを更新する必要があるなら、 read-write キャッシュが適当かもしれませ ん。このキャッシュ戦略は、シリアライザブルなトランザクション分離レベルが要求されるなら、決して 使うべきではありません。もしキャッシュが JT A 環境で使われるなら、 JT A T ransactionManager を取得するための方法を示す hibernate.transaction.m anager_lookup_class プロパティを指 定しなければなりません。他の環境では、 Session.close() や Session.disconnect() が呼ばれ たときに、確実にトランザクションが完了していなければなりません。もしクラスタでこの戦略を使いた いなら、基となるキャッシュの実装がロックをサポートしていることを保証しなければなりません。組み 込みのキャッシュプロバイダは サポートしていません 。 <class name="eg.Cat" .... > <cache usage="read-write"/> .... <set name="kittens" ... > <cache usage="read-write"/> .... </set> </class> 19.2.4. 厳密ではない read/write 戦略 アプリケーションがたまにしかデータを更新する必要はなく(すなわち二つのトランザクションが同時に 同じアイテムを更新しようとすることはほとんど起こらない)、厳密なトランザクション分離が要求され ないなら、 nonstrict-read-write キャッシュが適当かもしれません。もしキャッシュが JT A 環境で 使われるなら、 hibernate.transaction.m anager_lookup_class を指定しなければなりませ ん。他の環境では、 Session.close() や Session.disconnect() が呼ばれたときに、確実にトラ ンザクションが完了していなければなりません。 19.2.5. transactional 戦略 transactional キャッシュ戦略は JBoss T reeCache のような完全なトランザクショナルキャッシュプ ロバイダのサポートを提供します。このようなキャッシュは JT A 環境でのみ使用可能で、 hibernate.transaction.m anager_lookup_class を指定しなければなりません。 すべての同時並行性キャッシュ戦略をサポートしているキャッシュプロバイダはありません。以下の表は どのプロバイダがどの同時並列性戦略に対応するかを表しています。 226 第19章 パフォーマンスの改善 表 19.2 同時並行性キャッシュ戦略のサポート キャッシュ read-only 厳密ではない read-write read-write Hashtable(製品 用として意図して いません) yes yes yes EHCache yes yes yes OSCache yes yes yes SwarmCache yes yes JBoss T reeCache yes transactional yes 19.3. キ ャ ッ シ ュ の 管 理 オブジェクトを save() 、 update() 、 saveOrUpdate() に渡すとき、そして load() 、 get() 、 list() 、 iterate() 、 scroll() を使ってオブジェクトを復元するときには常に、そのオブジェクト は Session の内部キャッシュに追加されます。 次に flush() が呼ばれると、オブジェクトの状態はデータベースと同期化されます。もしこの同期が起 こることを望まないときや、膨大な数のオブジェクトを処理していてメモリを効率的に扱う必要があると きは、 evict() メソッドを使って一次キャッシュからオブジェクトやコレクションを削除することが出 来ます。 ScrollableResult cats = sess.createQuery("from Cat as cat").scroll(); //a huge result set while ( cats.next() ) { Cat cat = (Cat) cats.get(0); doSomethingWithACat(cat); sess.evict(cat); } Session はインスタンスがセッションキャッシュに含まれるかどうかを判断するための contains() メ ソッドも提供します。 すべてのオブジェクトをセッションキャッシュから完全に取り除くには、 Session.clear() を呼び出 してください。 二次キャッシュのために、 SessionFactory にはインスタンス、クラス全体、コレクションのインスタ ンス、コレクション全体をキャッシュから削除するためのメソッドがそれぞれ定義されています。 sessionFactory.evict(Cat.class, catId); //evict a particular Cat sessionFactory.evict(Cat.class); //evict all Cats sessionFactory.evictCollection("Cat.kittens", catId); //evict a particular collection of kittens sessionFactory.evictCollection("Cat.kittens"); //evict all kitten collections CacheMode は特定のセッションが二次キャッシュとどのように相互作用するかを指定します。 CacheMode.NORMAL - アイテムの読み込みと書き込みで二次キャッシュを使います CacheMode.GET - read items from the second-level cache, but don't write to the second-level cache except when updating data 227 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide CacheMode.PUT - write items to the second-level cache, but don't read from the second-level cache CacheMode.REFRESH - write items to the second-level cache, but don't read from the second-level cache, bypass the effect of hibernate.cache.use_m inim al_puts, forcing a refresh of the second-level cache for all items read from the database 二次キャッシュの内容やクエリキャッシュ領域を見るために、 Statistics API を使ってください: Map cacheEntries = sessionFactory.getStatistics() .getSecondLevelCacheStatistics(regionName) .getEntries(); You'll need to enable statistics, and, optionally, force Hibernate to keep the cache entries in a more human-understandable format: hibernate.generate_statistics true hibernate.cache.use_structured_entries true 19.4. ク エ リ キ ャ ッ シ ュ クエリのリザルトセットもキャッシュ出来ます。これは同じパラメータで何度も実行されるクエリに対し てのみ有用です。クエリキャッシュを使うには、まず設定で有効にしなくてはなりません: hibernate.cache.use_query_cache true この設定は新たに二つのキャッシュ領域の作成を行います。一つはクエリのリザルトセットのキャッシュ ( org.hibernate.cache.StandardQueryCache )を保持し、もう1つはクエリ可能なテーブルへ の最新の更新タイムスタンプ ( org.hibernate.cache.UpdateT im estam psCache )を保持しま す。クエリキャッシュはリザルトセットの実際の要素の状態はキャッシュしないことに注意してくださ い。キャッシュするのは識別子の値と、値型の結果のみです。そのため、クエリキャッシュは常に二次 キャッシュと一緒に使うべきです。 ほとんどのクエリはキャッシュの恩恵を受けないので、デフォルトではクエリはキャッシュされません。 キャッシュを有効にするには、 Query.setCacheable(true) を呼び出してください。そうすればクエ リが既存のキャッシュ結果を探し、クエリ実行時にその結果をキャッシュに追加するようになります。 クエリキャッシュの破棄ポリシーを細かく制御したいときは、 Query.setCacheRegion() を呼び出し て特定のクエリに対するキャッシュ領域を指定することが出来ます。 List blogs = sess.createQuery("from Blog blog where blog.blogger = :blogger") .setEntity("blogger", blogger) .setMaxResults(15) .setCacheable(true) .setCacheRegion("frontpages") .list(); クエリが自身のクエリキャッシュ領域のリフレッシュを強制しなければならないなら、 Query.setCacheMode(CacheMode.REFRESH) を呼び出すべきです。これは元となるデータが別の プロセスによって更新されたり(すなわち Hibernate を通じて更新されない)、アプリケーションに特定 のクエリリザルトセットを選択してリフレッシュさせる場合に特に有用です。さらに有用なもう一つの方 法は、 SessionFactory.evictQueries() によってクエリキャッシュ領域を消去することです。 228 第19章 パフォーマンスの改善 19.5. コ レ ク シ ョ ン の パ フ ォ ー マ ン ス の 理 解 We've already spent quite some time talking about collections. In this section we will highlight a couple more issues about how collections behave at runtime. 19.5.1. 分類 Hibernate は3つの基本的なコレクションの種類を定義しています: 値のコレクション 一対多関連 多対多関連 この分類はさまざまなテーブルや外部キー関連を区別しますが、私たちが知る必要のある関連モデルにつ いてほとんどなにも教えてくれません。関連構造やパフォーマンスの特徴を完全に理解するには、 Hibernate がコレクションの行を更新、削除するために使う主キーの構造もまた考えなければなりませ ん。これは以下の分類を提示します。 インデックス付きコレクション set bag All indexed collections (maps, lists, arrays) have a primary key consisting of the <key> and <index> columns. In this case collection updates are usually extremely efficient - the primary key may be efficiently indexed and a particular row may be efficiently located when Hibernate tries to update or delete it. Sets have a primary key consisting of <key> and element columns. T his may be less efficient for some types of collection element, particularly composite elements or large text or binary fields; the database may not be able to index a complex primary key as efficently. On the other hand, for one to many or many to many associations, particularly in the case of synthetic identifiers, it is likely to be just as efficient. (Side-note: if you want Schem aExport to actually create the primary key of a <set> for you, you must declare all columns as not-null="true".) <idbag> mappings define a surrogate key, so they are always very efficient to update. In fact, they are the best case. bag は最悪のケースです。 bag は要素の値の重複が可能で、インデックスカラムを持たないため、主キー は定義されないかもしれません。 Hibernate には重複した行を区別する方法がありません。 Hibernate は この問題の解決のために、変更があったときには常に完全な削除(一つの DELET E による)を行い、コレ クションの再作成を行います。これは非常に非効率的かもしれません。 Note that for a one-to-many association, the "primary key" may not be the physical primary key of the database table - but even in this case, the above classification is still useful. (It still reflects how Hibernate "locates" individual rows of the collection.) 19.5.2. 更新にもっとも効率的なコレクション list、 map、 idbag、 set 上での議論から、インデックス付きコレクションと(普通の) set は要素の追加、削除、更新でもっとも 効率的な操作が出来ることは明らかです。 T here is, arguably, one more advantage that indexed collections have over sets for many to many associations or collections of values. Because of the structure of a Set, Hibernate doesn't ever UPDAT E a row when an element is "changed". Changes to a Set always work via INSERT and DELET E (of individual rows). Once again, this consideration does not apply to one to many associations. 229 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide After observing that arrays cannot be lazy, we would conclude that lists, maps and idbags are the most performant (non-inverse) collection types, with sets not far behind. Sets are expected to be the most common kind of collection in Hibernate applications. T his is because the "set" semantics are most natural in the relational model. However, in well-designed Hibernate domain models, we usually see that most collections are in fact one-to-many associations with inverse="true". For these associations, the update is handled by the many-to-one end of the association, and so considerations of collection update performance simply do not apply. 19.5.3. inverse コレクションにもっとも最適な bag と list Just before you ditch bags forever, there is a particular case in which bags (and also lists) are much more performant than sets. For a collection with inverse="true" (the standard bidirectional one-tomany relationship idiom, for example) we can add elements to a bag or list without needing to initialize (fetch) the bag elements! T his is because Collection.add() or Collection.addAll() must always return true for a bag or List (unlike a Set). T his can make the following common code much faster. Parent p = (Parent) sess.load(Parent.class, id); Child c = new Child(); c.setParent(p); p.getChildren().add(c); //no need to fetch the collection! sess.flush(); 19.5.4. 一括削除 Occasionally, deleting collection elements one by one can be extremely inefficient. Hibernate isn't completely stupid, so it knows not to do that in the case of an newly-empty collection (if you called list.clear(), for example). In this case, Hibernate will issue a single DELET E and we are done! サイズ20のコレクションに一つの要素を追加し、それから二つの要素を削除するとします。 Hibernate は 一つの INSERT 文と二つの DELET E 文を発行します (コレクションが bag でなければ)。これは確かに 望ましい動作です。 しかし、18個の要素を削除して2つを残し、それから3つ新しい要素を追加するとします。このとき二つの 方法があります。 18行を一つ一つ削除して、3行を追加する コレクション全体を削除( DELET E の SQL を一回)し、そして5つの要素すべてを(一つずつ)追加 する Hibernate isn't smart enough to know that the second option is probably quicker in this case. (And it would probably be undesirable for Hibernate to be that smart; such behaviour might confuse database triggers, etc.) 幸いにも、元のコレクションを捨て(つまり参照をやめて)、現在の要素をすべて持つ新しいコレクショ ンのインスタンスを返すことで、いつでもこの振る舞い(2番目の戦略)を強制することが出来ます。時 にこれはとても便利で強力です。 Of course, one-shot-delete does not apply to collections mapped inverse="true". 19.6. パ フ ォ ー マ ン ス の モ ニ タ リ ン グ 最適化はモニタリングやパフォーマンスを示す数値がなければ十分に行えません。 Hibernate は内部処理 230 第19章 パフォーマンスの改善 のすべての範囲の数値を提供します。 Hibernate の統計情報は SessionFactory 単位で取得可能です。 19.6.1. SessionFactory のモニタリング SessionFactory のメトリクスにアクセスするには2つの方法があります。最初の方法は、 sessionFactory.getStatistics() を呼び出し、自分で Statistics の読み込みや表示を行いま す。 StatisticsService MBean を有効にしていれば、 Hibernate は JMX を使ってメトリクスを発行する こともできます。1つの MBean をすべての SessionFactory に対して有効にするか、 SessionFactory ごとに一つの MBean を有効にすることが出来ます。最小限の設定例である以下のコードを見てください: // MBean service registration for a specific SessionFactory Hashtable tb = new Hashtable(); tb.put("type", "statistics"); tb.put("sessionFactory", "myFinancialApp"); ObjectName on = new ObjectName("hibernate", tb); // MBean object name StatisticsService stats = new StatisticsService(); // MBean implementation stats.setSessionFactory(sessionFactory); // Bind the stats to a SessionFactory server.registerMBean(stats, on); // Register the Mbean on the server // MBean service registration for all SessionFactory's Hashtable tb = new Hashtable(); tb.put("type", "statistics"); tb.put("sessionFactory", "all"); ObjectName on = new ObjectName("hibernate", tb); // MBean object name StatisticsService stats = new StatisticsService(); // MBean implementation server.registerMBean(stats, on); // Register the MBean on the server T ODO: T his doesn't make sense: In the first case, we retrieve and use the MBean directly. In the second one, we must give the JNDI name in which the session factory is held before using it. Use hibernateStatsBean.setSessionFactoryJNDINam e("m y/JNDI/Nam e") SessionFactory に対してモニタリングの開始(終了)を行うことが出来ます。 設定時には、 hibernate.generate_statistics を false にします 実行時に、 sf.getStatistics().setStatisticsEnabled(true) または hibernateStatsBean.setStatisticsEnabled(true) を呼び出します 統計は clear() メソッドを使って手動でリセットすることが出来ます。サマリは logSum m ary() メ ソッドを使って logger に送ることが出来ます(info レベルです)。 19.6.2. メトリクス 多くのものがあります。すべての使用可能なカウンタは Statistics インターフェースの API に書かれ ており、3つの分類があります: メトリクスは一般的な Session の使い方と関係しています。オープンしたセッションの数が JDBC コネクションと関連しているのと同じです。 メトリクスは要素、コレクション、クエリやキャッシュなど全体に関係しています(別名はグローバ ルメトリクスです)。 メトリクスの詳細は特定のエンティティ、コレクション、クエリ、キャッシュ領域に関係していま す。 231 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 例として、キャッシュのヒット、ヒットミスや、要素、コレクション、クエリの割合、クエリの実行に必 要な平均時間を確認できます。ミリ秒の数値は Java の近似を受けることに注意してください。 Hibernate は JVM の精度に制限され、プラットフォームによっては10秒単位でしか正確でないかもしれません。 単純な getter はグローバルメトリクス(すなわち特定のエンティティ、コレクション、キャッシュ領域な どに縛られない)にアクセスするために使います。特定のエンティティ、コレクション、キャッシュ領域 のメトリクスは、それらの名前や、クエリの HQL 、 SQL 表現によってアクセスすることが出来ます。さ らに詳しい情報は、 Statistics 、 EntityStatistics 、 CollectionStatistics 、 SecondLevelCacheStatistics 、 QueryStatistics API の javadoc を参照してください。以下の コードは簡単な例です: Statistics stats = HibernateUtil.sessionFactory.getStatistics(); double queryCacheHitCount = stats.getQueryCacheHitCount(); double queryCacheMissCount = stats.getQueryCacheMissCount(); double queryCacheHitRatio = queryCacheHitCount / (queryCacheHitCount + queryCacheMissCount); log.info("Query Hit ratio:" + queryCacheHitRatio); EntityStatistics entityStats = stats.getEntityStatistics( Cat.class.getName() ); long changes = entityStats.getInsertCount() + entityStats.getUpdateCount() + entityStats.getDeleteCount(); log.info(Cat.class.getName() + " changed " + changes + "times" ); すべてのエンティティ、コレクション、クエリ、キャッシュ領域に対して行う場合は、 getQueries() 、 getEntityNam es()、 getCollectionRoleNam es() 、 getSecondLevelCacheRegionNam es() メソッドでそれぞれの名前のリストを取得することが出来 ます。 232 第20章 ツールセットガイド 第 20章 ツールセットガイド Hibernate を使ったラウンドトリップエンジニアリングは、 Eclipse プラグインやコマンドラインツー ル、もちろん Ant タスクを使うことで可能です。 Hibernate Tools は現在、既存データベースのリバースエンジニアリングの Ant タスクに加えて、 Eclipse IDE のプラグインを含みます: マッピングエディタ: Hibernate の XML マッピングファイル用のエディタで、自動補完と構文強調表示 をサポートしています。クラス名やプロパティ/フィールド名に対する自動補完もサポートし、通常の XML エディタよりも強力です。 Console: コンソールはエクリプスの新しいビューです。コンソールコンフィギュレーションのツリー オーバービューに加えて、永続クラスとその関連の相互作用ビューも得られます。データベースに HQL を実行し、結果を直接エクリプス上で見ることができます。 開発ウィザード Hibernate の Eclipse ツールはいくつかのウィザードを提供します。ウィザードを使っ て Hibernate の設定ファイル (cfg.xml) をすばやく生成したり、既存のデータベーススキーマを POJO のソースファイルと Hibernate のマッピングファイルへと、完全にリバースエンジニアリングするこ とができます。リバースエンジニアリングウィザードはカスタマイズ可能なテンプレートをサポート します。 Ant Tasks: Please refer to the Hibernate Tools package and it's documentation for more information. However, the Hibernate main package comes bundled with an integrated tool (it can even be used from "inside" Hibernate on-the-fly): SchemaExport aka hbm 2ddl. 20.1. ス キ ー マ の 自 動 生 成 DDL は Hibernate ユーティリティによりマッピングファイルから生成することができます。生成されたス キーマはエンティティやコレクションのテーブルに対する参照整合性制約 (主キーと外部キー) を含みま す。テーブルとシーケンスはマッピングする識別子ジェネレータに対して生成されます。 DDL はベンダー依存なので、このツールを使うときは、 hibernate.dialect プロパティで SQL の 方 言 を指定 しなければなりません 。 まず、生成されるスキーマを改善するように、マッピングファイルをカスタマイズしてください。 20.1.1. スキーマのカスタマイズ 多くの Hibernate のマッピング要素では、オプションの length、 precision、 scale という名の属 性を定義しています。この属性でカラムの長さ、精度、スケールを設定することができます。 <property name="zip" length="5"/> <property name="balance" precision="12" scale="2"/> not-null 属性(テーブルのカラムへ NOT NULL 制約を生成する)と unique 属性(テーブルのカラム へ UNIQUE 制約を生成する)が設定できるタグもあります。 <many-to-one name="bar" column="barId" not-null="true"/> <element column="serialNumber" type="long" not-null="true" unique="true"/> 233 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide unique-key 属性はカラムをグループ化して一つのキー制約にするために使われます。現在、 uniquekey 属性で指定された値は制約の指定には 使われず 、マッピングファイルでカラムをグループ化するこ とにのみ使われます。 <many-to-one name="org" column="orgId" unique-key="OrgEmployeeId"/> <property name="employeeId" unique-key="OrgEmployee"/> index 属性はマッピングするカラムを使って生成したインデックスの名前を指定します。複数カラムを1 つのインデックスにグループ化できます。単に、同じインデックス名を指定するだけです。 <property name="lastName" index="CustName"/> <property name="firstName" index="CustName"/> foreign-key 属性は、生成された外部キー制約の名前をオーバーライドするために使用できます。 <many-to-one name="bar" column="barId" foreign-key="FKFooBar"/> Many mapping elements also accept a child <colum n> element. T his is particularly useful for mapping multi-column types: <property name="name" type="my.customtypes.Name"/> <column name="last" not-null="true" index="bar_idx" length="30"/> <column name="first" not-null="true" index="bar_idx" length="20"/> <column name="initial"/> </property> default 属性はカラムのデフォルト値を指定します (マッピングしたクラスの新しいインスタンスを保存 する前に、マッピングしたプロパティへ同じ値を代入すべきです)。 <property name="credits" type="integer" insert="false"> <column name="credits" default="10"/> </property> <version name="version" type="integer" insert="false"> <column name="version" default="0"/> </property> sql-type 属性で、デフォルトの Hibernate 型から SQL のデータ型へのマッピングをオーバーライドで きます。 <property name="balance" type="float"> <column name="balance" sql-type="decimal(13,3)"/> </property> check 属性でチェック制約を指定することができます。 <property name="foo" type="integer"> <column name="foo" check="foo > 10"/> </property> 234 第20章 ツールセットガイド <class name="Foo" table="foos" check="bar < 100.0"> ... <property name="bar" type="float"/> </class> 表 20.1 まとめ 属性 値 説明 length 数値 カラムの長さ precision 数値 カラムの DECIMAL 型の精度 (precision) scale 数値 カラムの DECIMAL 型のスケール (scale) not-null true|false カラムが null 値を取らないこと を指定します unique true|false カラムがユニーク制約を持つこ とを指定します index index_nam e (複数カラムの)インデックスの名 前を指定します unique-key unique_key_nam e 複数カラムのユニーク制約の名 前を指定します foreign-key foreign_key_nam e specifies the name of the foreign key constraint generated for an association, for a <oneto-one>, <m any-to-one>, <key>, or <m any-to-m any> mapping element. Note that inverse="true" sides will not be considered by Schem aExport. sql-type SQL colum n type overrides the default column type (attribute of <colum n> element only) default SQL 式 カラムのデフォルト値を指定し ます check SQL 式 カラムかテーブルに SQL の チェック制約を作成します T he <com m ent> element allows you to specify comments for the generated schema. <class name="Customer" table="CurCust"> <comment>Current customers only</comment> ... </class> <property name="balance"> <column name="bal"> <comment>Balance in USD</comment> </column> </property> 235 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide これにより、生成した DDL に com m ent on table や com m ent on colum n 文が書かれます。 20.1.2. ツールの実行 Schem aExport は標準出力に対して DDL スクリプトを書き出し、 DDL 文を実行したりもします。 java -cp hibernate_classpathsorg.hibernate.tool.hbm 2ddl.Schem aExportoptions mapping_files 表 20.2 Schem aExport のコマンドラインオプション オプション 説明 --quiet don't output the script to stdout --drop テーブルの削除だけを行います --create テーブルの生成のみを行います --text don't export to the database --output=m y_schem a.ddl DDL スクリプトをファイルに出力します -nam ing=eg.MyNam ingStra tegy Nam ingStrategy を選択 -config=hibernate.cfg.xm l XML ファイルから Hibernate の定義情報を読み込みます -properties=hibernate.pr operties ファイルからデータベースのプロパティを読み込みます --form at スクリプト内に生成する SQL を読みやすいようにフォーマットしま す --delim iter=; スクリプトの行区切り文字を設定します アプリケーションに Schem aExport を組み込むこともできます: Configuration cfg = ....; new SchemaExport(cfg).create(false, true); 20.1.3. プロパティ 次のように、データベースのプロパティを指定することができます。 as system properties with -D<property> hibernate.properties ファイル内で --properties を使って指定したプロパティファイル内で 必要なプロパティは以下のものです: 236 第20章 ツールセットガイド 表 20.3 SchemaExport コネクションプロパティ プロパティ名 説明 hibernate.connection.dr iver_class jdbc のドライバークラス hibernate.connection.ur l jdbc の url hibernate.connection.us ernam e データベースのユーザー hibernate.connection.pa ssword ユーザーパスワード hibernate.dialect データベース方言 20.1.4. Ant を使用する Ant のビルドスクリプトから Schem aExport を呼び出すことができます: <target name="schemaexport"> <taskdef name="schemaexport" classname="org.hibernate.tool.hbm2ddl.SchemaExportTask" classpathref="class.path"/> <schemaexport properties="hibernate.properties" quiet="no" text="no" drop="no" delimiter=";" output="schema-export.sql"> <fileset dir="src"> <include name="**/*.hbm.xml"/> </fileset> </schemaexport> </target> 20.1.5. インクリメンタルなスキーマ更新 T he Schem aUpdate tool will update an existing schema with "incremental" changes. Note that Schem aUpdate depends heavily upon the JDBC metadata API, so it will not work with all JDBC drivers. java -cp hibernate_classpathsorg.hibernate.tool.hbm 2ddl.Schem aUpdateoptions mapping_files 237 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 表 20.4 Schem aUpdate のコマンドラインオプション オプション 説明 --quiet don't output the script to stdout --text don't export the script to the database -nam ing=eg.MyNam ingStra tegy Nam ingStrategy を選択 -properties=hibernate.pr operties ファイルからデータベースのプロパティを読み込みます -config=hibernate.cfg.xm l .cfg.xm l ファイルを指定 アプリケーションに Schem aUpdate を組み込むことができます: Configuration cfg = ....; new SchemaUpdate(cfg).execute(false); 20.1.6. インクリメンタルなスキーマ更新に対する Ant の使用 Ant スクリプトから Schem aUpdate を呼び出すことができます: <target name="schemaupdate"> <taskdef name="schemaupdate" classname="org.hibernate.tool.hbm2ddl.SchemaUpdateTask" classpathref="class.path"/> <schemaupdate properties="hibernate.properties" quiet="no"> <fileset dir="src"> <include name="**/*.hbm.xml"/> </fileset> </schemaupdate> </target> 20.1.7. Schema validation T he Schem aValidator tool will validate that the existing database schema "matches" your mapping documents. Note that Schem aValidator depends heavily upon the JDBC metadata API, so it will not work with all JDBC drivers. T his tool is extremely useful for testing. java -cp hibernate_classpathsorg.hibernate.tool.hbm 2ddl.Schem aValidatoroptions mapping_files 238 第20章 ツールセットガイド 表 20.5 Schem aValidator のコマンドラインオプション オプション 説明 -nam ing=eg.MyNam ingStra tegy Nam ingStrategy を選択 -properties=hibernate.pr operties ファイルからデータベースのプロパティを読み込みます -config=hibernate.cfg.xm l .cfg.xm l ファイルを指定 Schem aValidator をアプリケーションに組み込むことが出来ます: Configuration cfg = ....; new SchemaValidator(cfg).validate(); 20.1.8. スキーマのバリデーションに Ant を使用します Ant スクリプトから Schem aValidator を呼び出せます: <target name="schemavalidate"> <taskdef name="schemavalidator" classname="org.hibernate.tool.hbm2ddl.SchemaValidatorTask" classpathref="class.path"/> <schemavalidator properties="hibernate.properties"> <fileset dir="src"> <include name="**/*.hbm.xml"/> </fileset> </schemaupdate> </target> 239 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 第 21章 例: 親 /子供 One of the very first things that new users try to do with Hibernate is to model a parent / child type relationship. T here are two different approaches to this. For various reasons the most convenient approach, especially for new users, is to model both Parent and Child as entity classes with a <one-to-m any> association from Parent to Child. (T he alternative approach is to declare the Child as a <com posite-elem ent>.) Now, it turns out that default semantics of a one to many association (in Hibernate) are much less close to the usual semantics of a parent / child relationship than those of a composite element mapping. We will explain how to use a bidirectional one to many association with cascades to model a parent / child relationship efficiently and elegantly. It's not at all difficult! 21.1. コ レ ク シ ョ ン に 関 す る 注 意 Hibernate のコレクションは自身のエンティティの論理的な部分と考えられ、決して包含するエンティ ティのものではありません。これは致命的な違いです。これは以下のような結果になります: オブジェクトをコレクションから削除、またはコレクションに追加するとき、コレクションのオー ナーのバージョン番号はインクリメントされます。 もしコレクションから削除されたオブジェクトが値型のインスタンス(例えばコンポジットエレメン ト) だったならば、そのオブジェクトは永続的ではなくなり、その状態はデータベースから完全に削除 されます。同じように、値型のインスタンスをコレクションに追加すると、その状態はすぐに永続的 になります。 一方、もしエンティティがコレクション(一対多または多対多関連) から削除されても、デフォルトで はそれは削除されません。この動作は完全に一貫しています。すなわち、他のエンティティの内部状 態を変更しても、関連するエンティティが消滅すべきではないということです。同様に、エンティ ティがコレクションに追加されても、デフォルトではそのエンティティは永続的にはなりません。 その代わりに、デフォルトの動作では、エンティティをコレクションに追加すると単に二つのエンティ ティ間のリンクを作成し、一方エンティティを削除するとリンクも削除します。これはすべてのケースに おいて非常に適切です。これが適切でないのは親/子関係の場合です。この場合子供の生存は親のライフサ イクルに制限されるからです。 21.2. 双 方 向 一 対 多 Suppose we start with a simple <one-to-m any> association from Parent to Child. <set name="children"> <key column="parent_id"/> <one-to-many class="Child"/> </set> 以下のコードを実行すると、 Parent p = .....; Child c = new Child(); p.getChildren().add(c); session.save(c); session.flush(); Hibernate は二つの SQL 文を発行します: c に対するレコードを生成する INSERT 24 0 第21章 例: 親/子供 p から c へのリンクを作成する UPDAT E T his is not only inefficient, but also violates any NOT NULL constraint on the parent_id column. We can fix the nullability constraint violation by specifying not-null="true" in the collection mapping: <set name="children"> <key column="parent_id" not-null="true"/> <one-to-many class="Child"/> </set> しかしこの解決策は推奨できません。 この動作の根本的な原因は、 p から c へのリンク(外部キー parent_id) は Child オブジェクトの状 態の一部とは考えられず、そのため INSERT によってリンクが生成されないことです。ですから、解決策 はリンクを Child マッピングの一部にすることです。 <many-to-one name="parent" column="parent_id" not-null="true"/> (また Child クラスに parent プロパティを追加する必要があります。) それでは Child エンティティがリンクの状態を制御するようになったので、コレクションがリンクを更 新しないようにしましょう。それには inverse 属性を使います。 <set name="children" inverse="true"> <key column="parent_id"/> <one-to-many class="Child"/> </set> 以下のコードを使えば、新しい Child を追加することができます。 Parent p = (Parent) session.load(Parent.class, pid); Child c = new Child(); c.setParent(p); p.getChildren().add(c); session.save(c); session.flush(); これにより、 SQL の INSERT 文が一つだけが発行されるようになりました。 もう少し強化するには、 Parent の addChild() メソッドを作成します。 public void addChild(Child c) { c.setParent(this); children.add(c); } Child を追加するコードはこのようになります。 Parent p = (Parent) session.load(Parent.class, pid); Child c = new Child(); p.addChild(c); session.save(c); session.flush(); 24 1 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 21.3. ラ イ フ サ イ ク ル の カ ス ケ ー ド 明示的に save() をコールするのはまだ煩わしいものです。これをカスケードを使って対処します。 <set name="children" inverse="true" cascade="all"> <key column="parent_id"/> <one-to-many class="Child"/> </set> これにより先ほどのコードをこのように単純化します Parent p = (Parent) session.load(Parent.class, pid); Child c = new Child(); p.addChild(c); session.flush(); Similarly, we don't need to iterate over the children when saving or deleting a Parent. T he following removes p and all its children from the database. Parent p = (Parent) session.load(Parent.class, pid); session.delete(p); session.flush(); しかしこのコードは Parent p = (Parent) session.load(Parent.class, pid); Child c = (Child) p.getChildren().iterator().next(); p.getChildren().remove(c); c.setParent(null); session.flush(); データベースから c を削除しません。 p へのリンクを削除する(そしてこのケースでは NOT NULL 制約 違反を引き起こす)だけです。 Child の delete() を明示する必要があります。 Parent p = (Parent) session.load(Parent.class, pid); Child c = (Child) p.getChildren().iterator().next(); p.getChildren().remove(c); session.delete(c); session.flush(); Now, in our case, a Child can't really exist without its parent. So if we remove a Child from the collection, we really do want it to be deleted. For this, we must use cascade="all-delete-orphan". <set name="children" inverse="true" cascade="all-delete-orphan"> <key column="parent_id"/> <one-to-many class="Child"/> </set> Note: even though the collection mapping specifies inverse="true", cascades are still processed by iterating the collection elements. So if you require that an object be saved, deleted or updated by cascade, you must add it to the collection. It is not enough to simply call setParent(). 21.4. カ ス ケ ー ド と unsaved-value 24 2 第21章 例: 親/子供 Suppose we loaded up a Parent in one Session, made some changes in a UI action and wish to persist these changes in a new session by calling update(). T he Parent will contain a collection of childen and, since cascading update is enabled, Hibernate needs to know which children are newly instantiated and which represent existing rows in the database. Lets assume that both Parent and Child have genenerated identifier properties of type Long. Hibernate will use the identifier and version/timestamp property value to determine which of the children are new. (See 「自動的な状態検 出」.) In Hibernate3, it is no longer necessary to specify an unsaved-value explicitly. 以下のコードは parent と child を更新し、 newChild を挿入します。 //parent and child were both loaded in a previous session parent.addChild(child); Child newChild = new Child(); parent.addChild(newChild); session.update(parent); session.flush(); Well, that's all very well for the case of a generated identifier, but what about assigned identifiers and composite identifiers? T his is more difficult, since Hibernate can't use the identifier property to distinguish between a newly instantiated object (with an identifier assigned by the user) and an object loaded in a previous session. In this case, Hibernate will either use the timestamp or version property, or will actually query the second-level cache or, worst case, the database, to see if the row exists. 21.5. 結 論 ここではかなりの量を要約したので、最初の頃は混乱しているように思われるかもしれません。しかし実 際は、すべて非常に良く動作します。ほとんどの Hibernate アプリケーションでは、多くの場面で親子パ ターンを使用します。 We mentioned an alternative in the first paragraph. None of the above issues exist in the case of <com posite-elem ent> mappings, which have exactly the semantics of a parent / child relationship. Unfortunately, there are two big limitations to composite element classes: composite elements may not own collections, and they should not be the child of any entity other than the unique parent. 24 3 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 第 22章 例 : Weblog アプリケーション 22.1. 永 続 ク ラ ス 永続クラスがウェブログと、ウェブログに掲示された項目を表しています。それらは通常の親子関係とし てモデリングされますが、 set ではなく順序を持った bag を使用することにします。 package eg; import java.util.List; public class Blog { private Long _id; private String _name; private List _items; public Long getId() { return _id; } public List getItems() { return _items; } public String getName() { return _name; } public void setId(Long long1) { _id = long1; } public void setItems(List list) { _items = list; } public void setName(String string) { _name = string; } } 24 4 第22章 例: Weblog アプリケーション package eg; import java.text.DateFormat; import java.util.Calendar; public class BlogItem { private Long _id; private Calendar _datetime; private String _text; private String _title; private Blog _blog; public Blog getBlog() { return _blog; } public Calendar getDatetime() { return _datetime; } public Long getId() { return _id; } public String getText() { return _text; } public String getTitle() { return _title; } public void setBlog(Blog blog) { _blog = blog; } public void setDatetime(Calendar calendar) { _datetime = calendar; } public void setId(Long long1) { _id = long1; } public void setText(String string) { _text = string; } public void setTitle(String string) { _title = string; } } 22.2. Hibernate の マ ッ ピ ン グ XML マッピングは、今ではとても簡単なはずです。 24 5 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="eg"> <class name="Blog" table="BLOGS"> <id name="id" column="BLOG_ID"> <generator class="native"/> </id> <property name="name" column="NAME" not-null="true" unique="true"/> <bag name="items" inverse="true" order-by="DATE_TIME" cascade="all"> <key column="BLOG_ID"/> <one-to-many class="BlogItem"/> </bag> </class> </hibernate-mapping> 24 6 第22章 例: Weblog アプリケーション <?xml version="1.0"?> <!DOCTYPE hibernate-mapping PUBLIC "-//Hibernate/Hibernate Mapping DTD 3.0//EN" "http://hibernate.sourceforge.net/hibernate-mapping-3.0.dtd"> <hibernate-mapping package="eg"> <class name="BlogItem" table="BLOG_ITEMS" dynamic-update="true"> <id name="id" column="BLOG_ITEM_ID"> <generator class="native"/> </id> <property name="title" column="TITLE" not-null="true"/> <property name="text" column="TEXT" not-null="true"/> <property name="datetime" column="DATE_TIME" not-null="true"/> <many-to-one name="blog" column="BLOG_ID" not-null="true"/> </class> </hibernate-mapping> 22.3. Hibernate の コ ー ド 以下のクラスは、 Hibernate でこれらのクラスを使ってできるいくつかのことを示しています。 24 7 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide package eg; import import import import java.util.ArrayList; java.util.Calendar; java.util.Iterator; java.util.List; import import import import import import import org.hibernate.HibernateException; org.hibernate.Query; org.hibernate.Session; org.hibernate.SessionFactory; org.hibernate.Transaction; org.hibernate.cfg.Configuration; org.hibernate.tool.hbm2ddl.SchemaExport; public class BlogMain { private SessionFactory _sessions; public void configure() throws HibernateException { _sessions = new Configuration() .addClass(Blog.class) .addClass(BlogItem.class) .buildSessionFactory(); } public void exportTables() throws HibernateException { Configuration cfg = new Configuration() .addClass(Blog.class) .addClass(BlogItem.class); new SchemaExport(cfg).create(true, true); } public Blog createBlog(String name) throws HibernateException { Blog blog = new Blog(); blog.setName(name); blog.setItems( new ArrayList() ); Session session = _sessions.openSession(); Transaction tx = null; try { tx = session.beginTransaction(); session.persist(blog); tx.commit(); } catch (HibernateException he) { if (tx!=null) tx.rollback(); throw he; } finally { session.close(); } return blog; } public BlogItem createBlogItem(Blog blog, String title, String text) throws HibernateException { BlogItem item = new BlogItem(); 24 8 第22章 例: Weblog アプリケーション item.setTitle(title); item.setText(text); item.setBlog(blog); item.setDatetime( Calendar.getInstance() ); blog.getItems().add(item); Session session = _sessions.openSession(); Transaction tx = null; try { tx = session.beginTransaction(); session.update(blog); tx.commit(); } catch (HibernateException he) { if (tx!=null) tx.rollback(); throw he; } finally { session.close(); } return item; } public BlogItem createBlogItem(Long blogid, String title, String text) throws HibernateException { BlogItem item = new BlogItem(); item.setTitle(title); item.setText(text); item.setDatetime( Calendar.getInstance() ); Session session = _sessions.openSession(); Transaction tx = null; try { tx = session.beginTransaction(); Blog blog = (Blog) session.load(Blog.class, blogid); item.setBlog(blog); blog.getItems().add(item); tx.commit(); } catch (HibernateException he) { if (tx!=null) tx.rollback(); throw he; } finally { session.close(); } return item; } public void updateBlogItem(BlogItem item, String text) throws HibernateException { item.setText(text); Session session = _sessions.openSession(); Transaction tx = null; try { tx = session.beginTransaction(); session.update(item); 24 9 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide tx.commit(); } catch (HibernateException he) { if (tx!=null) tx.rollback(); throw he; } finally { session.close(); } } public void updateBlogItem(Long itemid, String text) throws HibernateException { Session session = _sessions.openSession(); Transaction tx = null; try { tx = session.beginTransaction(); BlogItem item = (BlogItem) session.load(BlogItem.class, itemid); item.setText(text); tx.commit(); } catch (HibernateException he) { if (tx!=null) tx.rollback(); throw he; } finally { session.close(); } } public List listAllBlogNamesAndItemCounts(int max) throws HibernateException { Session session = _sessions.openSession(); Transaction tx = null; List result = null; try { tx = session.beginTransaction(); Query q = session.createQuery( "select blog.id, blog.name, count(blogItem) " + "from Blog as blog " + "left outer join blog.items as blogItem " + "group by blog.name, blog.id " + "order by max(blogItem.datetime)" ); q.setMaxResults(max); result = q.list(); tx.commit(); } catch (HibernateException he) { if (tx!=null) tx.rollback(); throw he; } finally { session.close(); } return result; } 250 第22章 例: Weblog アプリケーション public Blog getBlogAndAllItems(Long blogid) throws HibernateException { Session session = _sessions.openSession(); Transaction tx = null; Blog blog = null; try { tx = session.beginTransaction(); Query q = session.createQuery( "from Blog as blog " + "left outer join fetch blog.items " + "where blog.id = :blogid" ); q.setParameter("blogid", blogid); blog = (Blog) q.uniqueResult(); tx.commit(); } catch (HibernateException he) { if (tx!=null) tx.rollback(); throw he; } finally { session.close(); } return blog; } public List listBlogsAndRecentItems() throws HibernateException { Session session = _sessions.openSession(); Transaction tx = null; List result = null; try { tx = session.beginTransaction(); Query q = session.createQuery( "from Blog as blog " + "inner join blog.items as blogItem " + "where blogItem.datetime > :minDate" ); Calendar cal = Calendar.getInstance(); cal.roll(Calendar.MONTH, false); q.setCalendar("minDate", cal); result = q.list(); tx.commit(); } catch (HibernateException he) { if (tx!=null) tx.rollback(); throw he; } finally { session.close(); } return result; } } 251 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide 第 23章 例: いろいろなマッピング この章では、より複雑な関連のマッピングをいくつか紹介します。 23.1. 雇 用 者 /従 業 員 Em ployer と Em ployee の関係を表す以下のモデルは、関連の表現に実際のエンティティクラス ( Em ploym ent ) を使います。なぜなら、同じ2つのパーティに複数の期間雇用されるということがあり えるからです。お金の値と従業員の名前をモデル化するためにコンポーネントを使っています。 マッピングドキュメントの一例です: 252 第23章 例: いろいろなマッピング <hibernate-mapping> <class name="Employer" table="employers"> <id name="id"> <generator class="sequence"> <param name="sequence">employer_id_seq</param> </generator> </id> <property name="name"/> </class> <class name="Employment" table="employment_periods"> <id name="id"> <generator class="sequence"> <param name="sequence">employment_id_seq</param> </generator> </id> <property name="startDate" column="start_date"/> <property name="endDate" column="end_date"/> <component name="hourlyRate" class="MonetaryAmount"> <property name="amount"> <column name="hourly_rate" sql-type="NUMERIC(12, 2)"/> </property> <property name="currency" length="12"/> </component> <many-to-one name="employer" column="employer_id" not-null="true"/> <many-to-one name="employee" column="employee_id" not-null="true"/> </class> <class name="Employee" table="employees"> <id name="id"> <generator class="sequence"> <param name="sequence">employee_id_seq</param> </generator> </id> <property name="taxfileNumber"/> <component name="name" class="Name"> <property name="firstName"/> <property name="initial"/> <property name="lastName"/> </component> </class> </hibernate-mapping> Schem aExport で生成したテーブルスキーマです。 253 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide create table employers ( id BIGINT not null, name VARCHAR(255), primary key (id) ) create table employment_periods ( id BIGINT not null, hourly_rate NUMERIC(12, 2), currency VARCHAR(12), employee_id BIGINT not null, employer_id BIGINT not null, end_date TIMESTAMP, start_date TIMESTAMP, primary key (id) ) create table employees ( id BIGINT not null, firstName VARCHAR(255), initial CHAR(1), lastName VARCHAR(255), taxfileNumber VARCHAR(255), primary key (id) ) alter table employment_periods add constraint employment_periodsFK0 foreign key (employer_id) references employers alter table employment_periods add constraint employment_periodsFK1 foreign key (employee_id) references employees create sequence employee_id_seq create sequence employment_id_seq create sequence employer_id_seq 23.2. 作 者 /作 品 Work 、 Author そして Person の関係を表す以下のモデルを考えてみてください。 Work と Author の関係を多対多関連で表しています。 Author と Person の関係は一対一関連として表しています。他 には Author が Person を拡張するという方法もあります。 以下のマッピングドキュメントはこのような関係を正確に表現しています: 254 第23章 例: いろいろなマッピング <hibernate-mapping> <class name="Work" table="works" discriminator-value="W"> <id name="id" column="id"> <generator class="native"/> </id> <discriminator column="type" type="character"/> <property name="title"/> <set name="authors" table="author_work"> <key column name="work_id"/> <many-to-many class="Author" column name="author_id"/> </set> <subclass name="Book" discriminator-value="B"> <property name="text"/> </subclass> <subclass name="Song" discriminator-value="S"> <property name="tempo"/> <property name="genre"/> </subclass> </class> <class name="Author" table="authors"> <id name="id" column="id"> <!-- The Author must have the same identifier as the Person --> <generator class="assigned"/> </id> <property name="alias"/> <one-to-one name="person" constrained="true"/> <set name="works" table="author_work" inverse="true"> <key column="author_id"/> <many-to-many class="Work" column="work_id"/> </set> </class> <class name="Person" table="persons"> <id name="id" column="id"> <generator class="native"/> </id> <property name="name"/> </class> </hibernate-mapping> このマッピングには4つのテーブルがあります。 works 、 authors , persons はそれぞれ、仕事、作 者、人のデータを保持します。 author_work は作者と作品をリンクする関連テーブルです。以下は Schem aExport で生成したテーブルスキーマです。 255 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide create table works ( id BIGINT not null generated by default as identity, tempo FLOAT, genre VARCHAR(255), text INTEGER, title VARCHAR(255), type CHAR(1) not null, primary key (id) ) create table author_work ( author_id BIGINT not null, work_id BIGINT not null, primary key (work_id, author_id) ) create table authors ( id BIGINT not null generated by default as identity, alias VARCHAR(255), primary key (id) ) create table persons ( id BIGINT not null generated by default as identity, name VARCHAR(255), primary key (id) ) alter table authors add constraint authorsFK0 foreign key (id) references persons alter table author_work add constraint author_workFK0 foreign key (author_id) references authors alter table author_work add constraint author_workFK1 foreign key (work_id) references works 23.3. 顧 客 /注 文 /製 品 Now consider a model of the relationships between Custom er, Order and LineItem and Product. T here is a one-to-many association between Custom er and Order, but how should we represent Order / LineItem / Product? I've chosen to map LineItem as an association class representing the many-to-many association between Order and Product. In Hibernate, this is called a composite element. マッピングドキュメント: 256 第23章 例: いろいろなマッピング <hibernate-mapping> <class name="Customer" table="customers"> <id name="id"> <generator class="native"/> </id> <property name="name"/> <set name="orders" inverse="true"> <key column="customer_id"/> <one-to-many class="Order"/> </set> </class> <class name="Order" table="orders"> <id name="id"> <generator class="native"/> </id> <property name="date"/> <many-to-one name="customer" column="customer_id"/> <list name="lineItems" table="line_items"> <key column="order_id"/> <list-index column="line_number"/> <composite-element class="LineItem"> <property name="quantity"/> <many-to-one name="product" column="product_id"/> </composite-element> </list> </class> <class name="Product" table="products"> <id name="id"> <generator class="native"/> </id> <property name="serialNumber"/> </class> </hibernate-mapping> custom ers 、 orders 、 line_item s 、 products はそれぞれ、顧客、注文、注文明細、製品の データを保持します。 line_item s は注文と製品をリンクする関連テーブルとしても働きます。 257 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide create table customers ( id BIGINT not null generated by default as identity, name VARCHAR(255), primary key (id) ) create table orders ( id BIGINT not null generated by default as identity, customer_id BIGINT, date TIMESTAMP, primary key (id) ) create table line_items ( line_number INTEGER not null, order_id BIGINT not null, product_id BIGINT, quantity INTEGER, primary key (order_id, line_number) ) create table products ( id BIGINT not null generated by default as identity, serialNumber VARCHAR(255), primary key (id) ) alter table orders add constraint ordersFK0 foreign key (customer_id) references customers alter table line_items add constraint line_itemsFK0 foreign key (product_id) references products alter table line_items add constraint line_itemsFK1 foreign key (order_id) references orders 23.4. 種 々 雑 多 な マ ッ ピ ン グ 例 ここにある例はすべて Hibernate のテストスイートから取りました。そこには、他にもたくさんのマッピ ングの例があります。 Hibernate ディストリビューションの test フォルダを見てください。 T ODO: ここに文章を埋める 23.4.1. "Typed" one-to-one association 258 第23章 例: いろいろなマッピング <class name="Person"> <id name="name"/> <one-to-one name="address" cascade="all"> <formula>name</formula> <formula>'HOME'</formula> </one-to-one> <one-to-one name="mailingAddress" cascade="all"> <formula>name</formula> <formula>'MAILING'</formula> </one-to-one> </class> <class name="Address" batch-size="2" check="addressType in ('MAILING', 'HOME', 'BUSINESS')"> <composite-id> <key-many-to-one name="person" column="personName"/> <key-property name="type" column="addressType"/> </composite-id> <property name="street" type="text"/> <property name="state"/> <property name="zip"/> </class> 23.4.2. 複合キーの例 259 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide <class name="Customer"> <id name="customerId" length="10"> <generator class="assigned"/> </id> <property name="name" not-null="true" length="100"/> <property name="address" not-null="true" length="200"/> <list name="orders" inverse="true" cascade="save-update"> <key column="customerId"/> <index column="orderNumber"/> <one-to-many class="Order"/> </list> </class> <class name="Order" table="CustomerOrder" lazy="true"> <synchronize table="LineItem"/> <synchronize table="Product"/> <composite-id name="id" class="Order$Id"> <key-property name="customerId" length="10"/> <key-property name="orderNumber"/> </composite-id> <property name="orderDate" type="calendar_date" not-null="true"/> <property name="total"> <formula> ( select sum(li.quantity*p.price) from LineItem li, Product p where li.productId = p.productId and li.customerId = customerId and li.orderNumber = orderNumber ) </formula> </property> <many-to-one name="customer" column="customerId" insert="false" update="false" not-null="true"/> <bag name="lineItems" fetch="join" inverse="true" cascade="save-update"> <key> <column name="customerId"/> <column name="orderNumber"/> </key> <one-to-many class="LineItem"/> </bag> 260 第23章 例: いろいろなマッピング </class> <class name="LineItem"> <composite-id name="id" class="LineItem$Id"> <key-property name="customerId" length="10"/> <key-property name="orderNumber"/> <key-property name="productId" length="10"/> </composite-id> <property name="quantity"/> <many-to-one name="order" insert="false" update="false" not-null="true"> <column name="customerId"/> <column name="orderNumber"/> </many-to-one> <many-to-one name="product" insert="false" update="false" not-null="true" column="productId"/> </class> <class name="Product"> <synchronize table="LineItem"/> <id name="productId" length="10"> <generator class="assigned"/> </id> <property name="description" not-null="true" length="200"/> <property name="price" length="3"/> <property name="numberAvailable"/> <property name="numberOrdered"> <formula> ( select sum(li.quantity) from LineItem li where li.productId = productId ) </formula> </property> </class> 23.4.3. 複合キー属性を共有する多対多 261 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide <class name="User" table="`User`"> <composite-id> <key-property name="name"/> <key-property name="org"/> </composite-id> <set name="groups" table="UserGroup"> <key> <column name="userName"/> <column name="org"/> </key> <many-to-many class="Group"> <column name="groupName"/> <formula>org</formula> </many-to-many> </set> </class> <class name="Group" table="`Group`"> <composite-id> <key-property name="name"/> <key-property name="org"/> </composite-id> <property name="description"/> <set name="users" table="UserGroup" inverse="true"> <key> <column name="groupName"/> <column name="org"/> </key> <many-to-many class="User"> <column name="userName"/> <formula>org</formula> </many-to-many> </set> </class> 23.4.4. discrimination に基づく内容 262 第23章 例: いろいろなマッピング <class name="Person" discriminator-value="P"> <id name="id" column="person_id" unsaved-value="0"> <generator class="native"/> </id> <discriminator type="character"> <formula> case when title is not null then 'E' when salesperson is not null then 'C' else 'P' end </formula> </discriminator> <property name="name" not-null="true" length="80"/> <property name="sex" not-null="true" update="false"/> <component name="address"> <property name="address"/> <property name="zip"/> <property name="country"/> </component> <subclass name="Employee" discriminator-value="E"> <property name="title" length="20"/> <property name="salary"/> <many-to-one name="manager"/> </subclass> <subclass name="Customer" discriminator-value="C"> <property name="comments"/> <many-to-one name="salesperson"/> </subclass> </class> 23.4.5. 代替キーの関連 263 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide <class name="Person"> <id name="id"> <generator class="hilo"/> </id> <property name="name" length="100"/> <one-to-one name="address" property-ref="person" cascade="all" fetch="join"/> <set name="accounts" inverse="true"> <key column="userId" property-ref="userId"/> <one-to-many class="Account"/> </set> <property name="userId" length="8"/> </class> <class name="Address"> <id name="id"> <generator class="hilo"/> </id> <property name="address" length="300"/> <property name="zip" length="5"/> <property name="country" length="25"/> <many-to-one name="person" unique="true" not-null="true"/> </class> <class name="Account"> <id name="accountId" length="32"> <generator class="uuid"/> </id> <many-to-one name="user" column="userId" property-ref="userId"/> <property name="type" not-null="true"/> </class> 264 第24章 ベストプラクティス 第 24章 ベストプラクティス Write fine-grained classes and map them using <com ponent>. street (通り)、 suburb (都市)、 state (州)、 postcode (郵便番号)をカプセル化 する Address (住所)クラスを使いましょう。そうすればコードが再利用しやすくなり、リ ファクタリングも簡単になります。 永続クラスには識別子プロパティを定義しましょう。 Hibernate makes identifier properties optional. T here are all sorts of reasons why you should use them. We recommend that identifiers be 'synthetic' (generated, with no business meaning). 自然キーを見つけましょう。 Identify natural keys for all entities, and map them using <natural-id>. Implement equals() and hashCode() to compare the properties that make up the natural key. クラスのマッピングはそれぞれのクラス専用のファイルに書きましょう。 Don't use a single monolithic mapping document. Map com .eg.Foo in the file com /eg/Foo.hbm .xm l. T his makes particularly good sense in a team environment. リソースとしてマッピングをロードしましょう。 マッピングを、それらがマッピングするクラスと一緒に配置しましょう。 クエリ文字列を外部に置くことを考えましょう クエリが ANSI 標準でない SQL 関数を呼んでいるなら、これはよいプラクティスです。クエリ文 字列をマッピングファイルへ外出しすればアプリケーションがポータブルになります。 バインド変数を使いましょう。 As in JDBC, always replace non-constant values by "?". Never use string manipulation to bind a non-constant value in a query! Even better, consider using named parameters in queries. Don't manage your own JDBC connections. Hibernate lets the application manage JDBC connections. T his approach should be considered a last-resort. If you can't use the built-in connections providers, consider providing your own implementation of org.hibernate.connection.ConnectionProvider. カスタム型の使用を考えましょう。 Suppose you have a Java type, say from some library, that needs to be persisted but doesn't provide the accessors needed to map it as a component. You should consider implementing org.hibernate.UserT ype. T his approach frees the application code from implementing transformations to / from a Hibernate type. ボトルネックを解消するには JDBC をハンドコードしましょう。 In performance-critical areas of the system, some kinds of operations might benefit from direct JDBC. But please, wait until you know something is a bottleneck. And don't assume that direct JDBC is necessarily faster. If you need to use direct JDBC, it might be worth opening a Hibernate Session and using that JDBC connection. T hat way you can still use the same 265 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide transaction strategy and underlying connection provider. Session のフラッシュを理解しましょう。 Session が永続状態をデータベースと同期させることがときどきあります。しかしこれがあまり に頻繁に起こるようだと、パフォーマンスに影響が出てきます。自動フラッシュを無効にした り、特定のトランザクションのクエリや操作の順番を変更することで、不必要なフラッシュを最 小限にできます。 3層アーキテクチャでは分離オブジェクトの使用を考えましょう。 サーブレット / セッション Bean アーキテクチャを使うとき、サーブレット層 / JSP 層間でセッ ション Bean でロードした永続オブジェクトをやり取りできます。その際リクエストごとに新し い Session を使ってください。また Session.m erge() や Session.saveOrUpdate() を 使って、オブジェクトとデータベースを同期させてください。 2層アーキテクチャでは長い永続コンテキストの使用を考えましょう。 最高のスケーラビリティを得るには、データベーストランザクションをできるだけ短くしなけれ ばなりません。しかし長い間実行する アプリケーショントランザクション の実装が必要なこと はしばしばです。これはユーザーの視点からは1個の作業単位(unit of work)になります。アプ リケーショントランザクションはいくつかのクライアントのリクエスト/レスポンスサイクルにま たがります。アプリケーショントランザクションの実装に分離オブジェクトを使うのは一般的で す。そうでなければ、2層アーキテクチャの場合は特に適切なことですが、アプリケーショント ランザクションのライフサイクル全体に対して単一のオープンな永続化コンテキスト(セッショ ン)を維持してください。そして単純にリクエストの最後に JDBC コネクションから切断し、次 のリクエストの最初に再接続します。決して複数のアプリケーショントランザクションユース ケースに渡って1個の Session を使い回さないでください。そうでなければ、古いデータで作業 することになります。 Don't treat exceptions as recoverable. T his is more of a necessary practice than a "best" practice. When an exception occurs, roll back the T ransaction and close the Session. If you don't, Hibernate can't guarantee that inmemory state accurately represents persistent state. As a special case of this, do not use Session.load() to determine if an instance with the given identifier exists on the database; use Session.get() or a query instead. 関連にはなるべく遅延フェッチを使いましょう。 Use eager fetching sparingly. Use proxies and lazy collections for most associations to classes that are not likely to be completely held in the second-level cache. For associations to cached classes, where there is an a extremely high probability of a cache hit, explicitly disable eager fetching using lazy="false". When an join fetching is appropriate to a particular use case, use a query with a left join fetch. フェッチされていないデータに関わる問題を避けるために、 ビューの中でオープンセッション を使う (open session in view) パターンか、統制された 組み立てフェーズ (assembly phase) を使いましょう。 Hibernate は Data Transfer Objects (DT O) を書く退屈な作業から開発者を解放します。伝統的 な EJB アーキテクチャでは DT O は2つ目的があります: 1つ目は、エンティティ Bean がシリ アライズされない問題への対策です。2つ目は、プレゼンテーション層に制御が戻る前に、 ビューに使われるすべてのデータがフェッチされて、 DT O に復元されるような組み立てフェー 266 第24章 ベストプラクティス ズを暗黙的に定義します。 Hibernate では1つ目の目的が不要になります。しかしビューのレン ダリング処理の間、永続コンテキスト(セッション)をオープンにしたままにしなければ、組み 立てフェーズはまだ必要です(分離オブジェクトの中のどのデータが利用可能かについて、プレ ゼンテーション層と厳密な取り決めをしているビジネスメソッドを考えてみてください)。これ は Hibernate 側の問題ではありません。トランザクション内で安全にデータアクセスするための 基本的な要件です。 Hibernate からビジネスロジックを抽象化することを考えましょう。 Hide (Hibernate) data-access code behind an interface. Combine the DAO and Thread Local Session patterns. You can even have some classes persisted by handcoded JDBC, associated to Hibernate via a UserT ype. (T his advice is intended for "sufficiently large" applications; it is not appropriate for an application with five tables!) Don't use exotic association mappings. Good usecases for a real many-to-many associations are rare. Most of the time you need additional information stored in the "link table". In this case, it is much better to use two one-tomany associations to an intermediate link class. In fact, we think that most associations are one-to-many and many-to-one, you should be careful when using any other association style and ask yourself if it is really neccessary. なるべく双方向関連にしましょう。 単方向関連は双方向に比べて検索が難しくなります。大きなアプリケーションでは、ほとんどす べての関連が双方向にナビゲーションできなければなりません。 267 JBoss Enterprise Application Platform 4.2 Hibernate Reference Guide Revision History 改訂 1.0-9.4 00 2013-10-30 Landmann Rüdiger [FAMILY Given] 2012-07-18 T owns Anthony [FAMILY Given] T hu Nov 19 2009 Bailey Laura [FAMILY Given] Rebuild with publican 4.0.0 改訂 1.0-9 Rebuild for Publican 3.0 改訂 1.0-0 Initial draft. 268