HIBERNATE - 慣用的な Java のためのリレーショナル永続性

Hibernate リファレンスドキュメント

2.1.4


目次

序文
1. Tomcat を使ったクイックスタート
1.1. Hibernate を使い始めましょう
1.2. 最初の永続クラス
1.3. 猫をマップする
1.4. 猫と遊ぶ
1.5. 最後に
2. アーキテクチャー
2.1. 概観
2.2. JMX 統合
2.3. JCA サポート
3. SessionFactory 設定
3.1. プログラミング的な設定
3.2. SessionFactory を取得する
3.3. ユーザー提供の JDBC 接続
3.4. Hibernate 提供の JDBC 接続
3.5. オプションの設定プロパティー
3.5.1. SQL 方言
3.5.2. 外部結合によるフェッチ
3.5.3. バイナリーストリーム
3.5.4. カスタム CacheProvider
3.5.5. トランザクション戦略設定
3.5.6. JNDI とバインドされた SessionFactory
3.5.7. クエリー言語の置換
3.6. ロギング
3.7. NamingStrategy を実装する
3.8. XML 設定ファイル
4. 永続クラス
4.1. 単純な POJO 例
4.1.1. 永続フィールド用のアクセサーとミューテイターを宣言する
4.1.2. デフォルトコンストラクターを実装する
4.1.3. 識別子プロパティーを提供する (オプション)
4.1.4. final ではないクラスを選ぶ (オプション)
4.2. 継承を実装する
4.3. equals() と hashCode() を実装する
4.4. ライフサイクルコールバック
4.5. 検証可能なコールバック
4.6. XDOclet マークアップを使う
5. 基本的な O/R マッピング
5.1. マッピング宣言
5.1.1. 文書型
5.1.2. hibernate マッピング
5.1.3. クラス
5.1.4. id
5.1.4.1. ジェネレーター
5.1.4.2. Hi/Lo アルゴリズム
5.1.4.3. UUID アルゴリズム
5.1.4.4. 同一性カラムとシーケンス
5.1.4.5. 割り当てる識別子
5.1.5. 複合 id
5.1.6. discriminator
5.1.7. version (オプション)
5.1.8. timestamp (オプション)
5.1.9. property
5.1.10. many-to-one
5.1.11. one-to-one
5.1.12. component, dynamic-component
5.1.13. subclass
5.1.14. joined-subclass
5.1.15. map, set, list, bag
5.1.16. import
5.2. Hibernate の型
5.2.1. エンティティーと値
5.2.2. 基本的な値の型
5.2.3. 永続的な列挙型
5.2.4. カスタム値の型
5.2.5. any 型マッピング
5.3. SQL のクオート付き識別子
5.4. モジュール化したマッピングファイル
6. コレクションのマッピング
6.1. 永続コレクション
6.2. コレクションをマップする
6.3. 値のコレクションと Many-To-Many 関連
6.4. One-To-Many 関連
6.5. 遅延初期化
6.6. ソート済みのコレクション
6.7. <idbag> を使う
6.8. 双方向関連
6.9. 3 者関連
6.10. 異種関連
6.11. コレクションの例
7. コンポーネントマッピング
7.1. 依存オブジェクト
7.2. 依存オブジェクトのコレクション
7.3. マップインデックスとしてのコンポーネント
7.4. 複合識別子としてのコンポーネント
7.5. 動的なコンポーネント
8. 継承マッピング
8.1. 3 つの戦略
8.2. 制限
9. 永続データを操作する
9.1. 永続オブジェクトを作る
9.2. オブジェクトをロードする
9.3. クエリーを行なう
9.3.1. スカラークエリー
9.3.2. クエリーインターフェース
9.3.3. スクロール可能な繰り返し
9.3.4. コレクションをフィルターする
9.3.5. クリテリアクエリー
9.3.6. ネイティブ SQL によるクエリー
9.4. オブジェクトを更新する
9.4.1. 同じセッション内で更新する
9.4.2. 分離されたオブジェクトを更新する
9.4.3. 分離されたオブジェクトを再結合する
9.5. 永続オブジェクトを削除する
9.6. フラッシュ
9.7. セッションを終了する
9.7.1. セッションをフラッシュする
9.7.2. データベーストランザクションをコミットする
9.7.3. セッションをクローズする
9.7.4. 例外処理
9.8. ライフサイクルとオブジェクトグラフ
9.9. インターセプター
9.10. メタデータ API
10. トランザクションと並行性
10.1. 設定、セッションとファクトリー
10.2. スレッドと接続
10.3. オブジェクトの同一性を考慮する
10.4. Optimistic な並行性コントロール
10.4.1. 自動バージョン付けのある長いセッション
10.4.2. 自動バージョン付けのある多数のセッション
10.4.3. アプリケーションによるバージョンチェック
10.5. セッションの切断
10.6. Pessimistic なロッキング
11. HQL: Hibernate クエリー言語
11.1. 大文字と小文字の区別
11.2. from 句
11.3. 関連と結合
11.4. select 句
11.5. 集計関数
11.6. ポリモーフィズム的クエリー
11.7. where 句
11.8. 式
11.9. order by 句
11.10. group by 句
11.11. サブクエリー
11.12. HQL の例
11.13. チップとトリック
12. クリテリアクエリー
12.1. Criteria インスタンスを作成する
12.2. 結果セットを狭める
12.3. 結果に順序を付けて並べる
12.4. 関連
12.5. 動的な関連をフェッチする
12.6. イグザンプルクエリー
13. ネイティブな SQL クエリー
13.1. SQL ベースの Query を作成する
13.2. エイリアスとプロパティー参照
13.3. 名前付き SQL クエリー
14. パフォーマンスを改善する
14.1. コレクションのパフォーマンスを理解する
14.1.1. 分類法
14.1.2. リスト、マップ、セットは、更新する上で最も効率的なコレクションである
14.1.3. バッグとリストは、最も効率的な逆コレクションである
14.1.4. 一発削除
14.2. 遅延初期化のためのプロキシー
14.3. 2 次レベルのキャッシュ
14.3.1. キャッシュマッピング
14.3.2. 戦略: 読み込み専用
14.3.3. 戦略: 読み込み/書き込み
14.3.4. 戦略: 制限のない読み込み/書き込み
14.3.5. 戦略: トランザクション的
14.4. セッションキャッシュを管理する
14.5. クエリーのキャッシュ
15. ツールセットガイド
15.1. スキーマ生成
15.1.1. スキーマをカスタマイズする
15.1.2. ツールを実行する
15.1.3. プロパティー
15.1.4. Ant を使う
15.1.5. 増分的なスキーマ更新
15.1.6. 増分的なスキーマ更新に Ant を使う
15.2. コード生成
15.2.1. 設定ファイル (オプション)
15.2.2. meta 属性
15.2.3. 基本ファインダージェネレーター
15.2.4. Velocity ベースのレンダラー/ジェネレーター
15.3. マッピングファイル生成
15.3.1. ツールを実行する
16. 例: 親/子
16.1. コレクションに関する注意
16.2. 双方向 1 対多
16.3. ライフサイクルをカスケードする
16.4. カスケードする update() を使う
16.5. まとめ
17. 例: ウェブログアプリケーション
17.1. 永続クラス
17.2. Hibernate マッピング
17.3. Hibernate コード
18. 例: 様々なマッピング
18.1. Employer/Employee
18.2. Author/Work
18.3. Customer/Order/Product
19. 最善の実践

序文

オブジェクト指向ソフトウェアとリレーショナルデータベースを使った作業は、今日のエンタープライズ環境では、厄介で時間を要するものかもしれません。Hibernate は、Java 環境のためのオブジェクト/リレーショナルマッピングツールです。用語オブジェクト/リレーションマッピング (ORM) は、オブジェクトモデルのデータ表現を、 SQL ベースのスキーマをもつリレーショナルデータモデルにマッピングするテクニックを指します。

Hibernate は、Java クラスのデータベーステーブルへの (そして Java データ型から SQL データ型への) マッピングの面倒を見るだけでなく、データクエリーと検索ファシリティーも提供し、SQL と JDBC による手作業でのデータハンドリングで時間を費やすことなく、開発時間の著しい削減を可能にします。

Hibernate の目標は、開発者が、プログラミングタスクが関係する一般的なデータ永続性に関わる時間の 95% を節約できるようにすることです。Hibernate は、データベースにビジネスロジックを実装するために、ストアドプロシジャーだけを使うデータ中心的なアプリケーションにとって、最善の解決策ではないかもしれません。Hibernate は、オブジェクト指向ドメインモデルと Java ベースの中間層内のビジネスロジックで非常に役立ちます。しかしながら、Hibernate が、ベンダー固有の SQL コードを取り除いたり、カプセル化するのに役立つことは確かであり、結果セットを、テーブル状の表現からオブジェクトのグラフに変換するという一般的なタスクについて役立つことでしょう。

Hibernate とオブジェクト/リレーショナルマッピング、あるいは Java すら初めてという場合には、次のステップに従って下さい。

  1. チュートリアルの第 1 章 Tomcat を使ったクイックスタートを、30 分間、Tomcat を使いながら読む。

  2. Hibernate を使うことのできる環境を理解するため、第 2 章 アーキテクチャーを読む。

  3. Hibernate 配布版の eg/ ディレクトリーを見る。ここには、サンプルアプリケーションが含まれている。JDBC ドライバーを lib/ ディレクトリーにコピーし、src/hibernate.properties を編集して、使用するデータベース用に正しい値を設定する。配布版のディレクトリーで、コマンドプロンプトから、(Ant を使って) ant eg と入力する。あるいは、Windows では、build eg と入力する。

  4. 情報の主ソースとして、このリファレンスドキュメントを使用する。

  5. FAQ とその答は、Hibernate ウェブサイト上にある。

  6. サードパーティーのデモ、例、チュートリアルのリンクは、Hibernate ウェブサイトにある。

  7. Hibernate ウェブサイト上のコミュニティーエリアは、デザインパターンと (Tomcat、JBoss、Spring、Struts、EJB などとの) 様々な統合解決策の優れたソースである。

  8. Hibernate ウェブサイトのオフラインバージョンは、doc/ サブディレクトリー中で、Hibernate とともに配布されている。

疑問があれば、Hibernate ウェブサイトでリンクされたユーザーフォーラムを使って下さい。私達は、バグレポートと機能リクエストのため、JIRA 問題追跡システムも使っています。Hibernate の開発に興味があれば、開発者メーリングリストに参加して下さい。

Hibernate の商用開発、製品サポート、トレーニングが、JBoss Inc. を通じて利用可能です (http://www.hibernate.org/SupportTraining/ を参照して下さい)。Hibernate は、JBoss プロフェッショナルオープンソース製品スイートのプロジェクトです。

第 1 章 Tomcat を使ったクイックスタート

1.1. Hibernate を使い始めましょう

このチュートリアルは、ウェブベースのアプリケーション用の Apache Tomcat サーブレットコンテナーとともに、Hibernate 2.1 をセットアップする方法を説明します。Hibernate は、主要な J2EE アプリケーションサーバーすべてを使って管理された環境で、あるいは、スタンドアロンの Java アプリケーション内部ですら、正しく動作します。このチュートリアルで使用するデータベースシステムは、PostgreSQL です。他のデータベースのサポートは、Hibernate SQL 方言 (dialect) 設定を変更するだけの問題です。

まず、必要なライブラリーすべてを Tomcat をインストールしたディレクトリーにコピーしなければなりません。このチュートリアルには、異なったウェブコンテキスト (webapps/quickstart) を使うので、グローバルライブラリーサーチパス (TOMCAT/common/lib) と、webapps/quickstart/WEB-INF/lib (JAR ファイル用) と webapps/quickstart/WEB-INF/classes 内のコンテキストレベルにおけるクラスローダーの両方を考慮しなければなりません。両方のクラスローダーレベルを、グローバルクラスパスとコンテキストクラスパスと呼ぶことにしましょう。

次に、ライブラリーを 2 つのクラスパスにコピーします。

  1. JDBC ドライバーをグローバルクラスパスにコピーする。これは、Tomcat にバンドルされた DBCP 接続プールソフトウェアのために必要である。Hibernate は、JDBC 接続を使って、データベース上で SQL を実行する。そのため、プールされる JDBC 接続を提供するか、直接サポートされるプール (C3P0, Proxool) の 1 つを使うように Hibernate を設定する必要がある。このチュートリアルの場合、pg73jdbc3.jar ライブラリー (PostgreSQL 7.3 と JDK 1.4 用) をグローバルクラスローダーパスにコピーする。別のデータベースを使いたい場合には、それ用の適切な JDBC ドライバーをコピーするだけである。

  2. Tomcat のグローバルクラスローダーに他の何もコピーしてはいけない。そうしておかないと、Log4j、commons-logging やその他を含む様々なツールに関して問題が発生する可能性があるからである。個々のアプリケーションには、常に、コンテキストパスを使用する。つまり、ライブラリーは WEB-INF/lib にコピーし、自分自身の設定/プロパティーファイルは WEB-INF/classes にコピーする。いずれのディレクトリーも、デフォルトで、コンテキストレベルのクラスパス内にある。

  3. Hibernate は、JAR ライブラリーとしてパッケージされている。hibernate2.jar ファイルは、アプリケーションの他のクラスとともに、コンテキストクラスパスにコピーすべきである。Hibernate は、実行時に、幾つかのサードパーティーライブラリーを必要とする。これらは、Hibernate の配布版の lib/ ディレクトリーにバンドルされている。テーブル 1.1 “Hibernate サードパーティーライブラリー” を参照せよ。必要なサードパーティーライブラリーをコンテキストクラスパスにコピーする。

テーブル 1.1. Hibernate サードパーティーライブラリー

ライブラリー 説明
dom4j (必須) Hibernate は、XML 設定と XML マッピングメタデータファイルをパースするのに dom4j を使用する。
CGLIB (必須) Hibernate は、実行時に、クラスを拡張するのに、(Java リフレクションと組み合わせて) コード生成ライブラリーを使用する。
Commons Collections, Commons Logging (必須) Hibernate は、Apache Commons プロジェクトの様々なユーティリティーライブラリーを使用する。
ODMG4 (必須) Hibernate は、オプションの ODMG 準拠の永続性マネージャーインターフェースを提供する。コレクションをマップしたい場合には、ODMG API を使う気がない場合であっても、これは必須である。このチュートリアルでは、コレクションをマップすることはないが、その場合であっても、JAR をコピーしておくのはよい考えである。
EHCache (必須) Hibernate は、2 次レベルのキャッシュに、様々なキャッシュプロバイダーを使用できる。EHCache は、設定を変更しない場合のデフォルトのキャッシュプロバイダーである。
Log4j (オプション) Hibernate は、Commons Logging API を使用する。この API は、基礎にあるロギングメカニズムとして Log4j を使用することができる。Log4j ライブラリーがコンテキストライブラリーディレクトリー内で利用可能であれば、Commons Logging は、Log4j と、コンテキストクラスパス内の log4j.properties 設定を使用する。Log4j 用のサンプルプロパティーファイルは、Hibernate 配布版にバンドルされている。そのため、舞台裏で何が起こっているのか見たい場合には、log4j.jar と (src/ にある) 設定ファイルを、コンテキストクラスパスにコピーしておくとよい。
必須かどうか Hibernate 配布版の lib/README.txt ファイルを見てもらいたい。これは、Hibernate とともに配布されるサードパーティーライブラリーの最新リストである。ここに、必須とオプションのライブラリーすべてがリストされているのが分かるだろう。

では、Tomcat と Hibernate の両方で、データベース接続プーリングと共有をセットアップすることにしましょう。この意味は、Tomcat が (組み込みの DBCP プーリング機能を使って) プールされる JDBC 接続を提供し、Hibernate が JNDI を通じてこれらの接続をリクエストするということです。Tomcat は、接続プールを JNDI にバインドします。そのためのリソース宣言を Tomcat のメイン設定ファイル TOMCAT/conf/server.xml に追加します。

<Context path="/quickstart" docBase="quickstart">
    <Resource name="jdbc/quickstart" scope="Shareable" type="javax.sql.DataSource"/>
    <ResourceParams name="jdbc/quickstart">
        <parameter>
            <name>factory</name>
            <value>org.apache.commons.dbcp.BasicDataSourceFactory</value>
        </parameter>

        <!-- DBCP データベース接続セッティング -->
        <parameter>
            <name>url</name>
            <value>jdbc:postgresql://localhost/quickstart</value>
        </parameter>
        <parameter>
            <name>driverClassName</name><value>org.postgresql.Driver</value>
        </parameter>
        <parameter>
            <name>username</name>
            <value>quickstart</value>
        </parameter>
        <parameter>
            <name>password</name>
            <value>secret</value>
        </parameter>

        <!-- DBCP 接続プーリングオプション -->
        <parameter>
            <name>maxWait</name>
            <value>3000</value>
        </parameter>
        <parameter>
            <name>maxIdle</name>
            <value>100</value>
        </parameter>
        <parameter>
            <name>maxActive</name>
            <value>10</value>
        </parameter>
    </ResourceParams>
</Context>

この例で設定するコンテキストの名前は、quickstart です。そのベースは、TOMCAT/webapp/quickstart ディレクトリーです。どのサーブレットにアクセスする場合にも、ブラウザーで、パス http://localhost:8080/quickstart を呼び出します (もちろん、web.xml に、サーブレットの名前を追加してマップおきます)。先回りして、ここで、空の process() のある単純なサーブレットを作っておいても構いません。

この設定によって、Tomcat は、DBCP 接続プールを使い、java:comp/env/jdbc/quickstart で JNDI を通じて、プールされる JDBC Connection を提供します。接続プールを実行する際に問題があれば、Tomcat のドキュメントを参照して下さい。JDBC ドライバーの例外メッセージを受け取ったら、まず、Hibernate なしに、JDBC 接続プールをセットアップしてみて下さい。Tomcat & JDBC チュートリアルは、ウェブ上で利用可能です。

次のステップは、JNDI とバインドされたプールの接続を使うように Hibernate を設定することです。これには、Hibernate の XML ベースの設定を使います。プロパティーを使うという基本的なアプローチは、機能においてこれと等価ですが、そうするメリットは何もありません。XML 設定を使う理由は、そうする方が便利だからです。XML 設定ファイルは、コンテキストクラスパス (WEB-INF/classes) に、hibernate.cfg.xml という名前で置かれています。

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration
    PUBLIC "-//Hibernate/Hibernate Configuration DTD//EN"
    "http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd">

<hibernate-configuration>

    <session-factory>

        <property name="connection.datasource">java:comp/env/jdbc/quickstart</property>
        <property name="show_sql">false</property>
        <property name="dialect">net.sf.hibernate.dialect.PostgreSQLDialect</property>

        <!-- マッピングファイル -->
        <mapping resource="Cat.hbm.xml"/>

    </session-factory>

</hibernate-configuration>

次に、SQL コマンドのロギングを無効にし、使用するデータベース SQL 方言が何であるかと、(Tomcat にバインドされたデータソースプールの JNDI アドレスを宣言することにより) JDBC 接続をどこで取得するのかを Hibernate に指示します。方言は、必須のセッティングです。データベースは、SQL "標準" について、異なった解釈をするためです。Hibernate がそうした違いの面倒を見てくれるので、Hibernate は、主要な商用データベースとオープンソースデータベース用の方言とバインドされます。

SessionFactory は、単一のデータストアに関する Hibernate の概念です。アプリケーション内に、複数の XML 設定ファイルと複数の ConfigurationSessionFactory オブジェクトを作ることによって、複数のデータベースを使うことができます。

hibernate.cfg.xml の最後の要素は、永続クラス Cat 用の Hibernate XML マッピングファイルの名前として、Cat.hbm.xml を宣言しています。このファイルには、POJO クラスの 1 つのデータベーステーブル (あるいは、複数のテーブル) へのマッピングのためのメタデータがあります。このファイルについては、すぐ後で、再度説明することにします。まず、POJO クラスを書き、そのためのマッピングメタデータを宣言することにしましょう。

1.2. 最初の永続クラス

Hibernate は、永続クラスのためのプレーンオールド Java オブジェクト (Plain Old Java Objects - POJO、時には、Plain Ordinary Java Objects とも呼ばれます) プログラミングモデルを使う場合に、最善の働きをします。POJO は、ゲッターとセッターメソッドによってアクセス可能なプロパティーがあり、外部から見えるインターフェースによってその内部表現を隠す点で、JavaBean と非常によく似ています。

package net.sf.hibernate.examples.quickstart;

public class Cat {

    private String id;
    private String name;
    private char sex;
    private float weight;

    public Cat() {
    }

    public String getId() {
        return id;
    }

    private void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public char getSex() {
        return sex;
    }

    public void setSex(char sex) {
        this.sex = sex;
    }

    public float getWeight() {
        return weight;
    }

    public void setWeight(float weight) {
        this.weight = weight;
    }

}

Hibernate には、プロパティーの型を使う点で制約はありません。すべての Java JDK 型とプリミティブ (String, charDate のような) は、マップ可能です。それには、Java コレクションフレームワークのクラスが含まれます。それらは、値として、値のコレクションとして、あるいは、他のエンティティーへの関連付けとしてマップすることができます。id は、そのクラスのデータベース識別子 (主キー) を表わす特殊プロパティーです。Cat のようなエンティティーを強く推奨します。Hibernate は、識別子を内部的にのみ使用しますが、ここでのアプリケーションアーキテクチャーでは、柔軟性を幾らか失わせることにします。

永続クラスのために、特殊なインターフェースを実装する必要はありません。同様に、特殊なルート永続クラスをサブクラス化する必要もありません。Hibernate は、バイトコード操作などといった、構築時の処理も使いません。それは、Java リフレクションと (CGLIB による) 実行時のクラス拡張だけに依存します。そのため、POJO クラスを Hibernate に依存させることなく、それをデータベースのテーブルにマップすることができます。

1.3. 猫をマップする

Cat.hbm.xml マッピングファイルには、オブジェクト/リレーショナルマッピングに必要なメタデータがあります。メタデータには、永続クラスの宣言と、(カラムと、他のエンティティーへの外部キー関係に対する) プロパティーのデータベースのテーブルへのマッピングが含まれています。

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping
    PUBLIC "-//Hibernate/Hibernate Mapping DTD//EN"
    "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">

<hibernate-mapping>

    <class name="net.sf.hibernate.examples.quickstart.Cat" table="CAT">

        <!-- 32 という 16 進文字は、ここでの代理 (surrogate) キーである。これは、
                UUID パターンを使って、Hibernate が自動的に生成する。-->
        <id name="id" type="string" unsaved-value="null" >
            <column name="CAT_ID" sql-type="char(32)" not-null="true"/>
            <generator class="uuid.hex"/>
        </id>

        <!-- 猫には、名前がなければならないが、長すぎるべきではない -->
        <property name="name">
            <column name="NAME" length="16" not-null="true"/>
        </property>

        <property name="sex"/>

        <property name="weight"/>

    </class>

</hibernate-mapping>

永続クラスはどれも、識別子 (identifier) 属性をもつべきです (実際には、エンティティーのコンポーネントとしてマップされる依存的な値オブジェクトではなく、エンティティーを表わすクラスのみ)。このプロパティーは、永続オブジェクトを区別するのに使われます。catA.getId().equals(catB.getId()) が真である場合、2 匹の猫は同じです。この概念は、データベース同一性 (database identity) と呼ばれます。Hibernate には、様々なシナリオ用の様々な識別子ジェネレーターがバンドルされています (データベースシーケンス用のネイティブジェネレーター、hi/lo 識別子テーブルとアプリケーションが割り当てる識別子を含みます)。ここでは、(テーブルの主キーとして) Hibernate が生成する識別子の値のため、UUID ジェネレーター (データベースが生成する整数代理キーが優先されるべきであるため、テストのためだけに推奨します) を使い、テーブル CAT のカラム CAT_ID も指定します。

Cat の他のすべてのプロパティーは、同じテーブルにマップされます。name プロパティーの場合には、明示的なデータベースのカラム宣言を使って、それをマップします。これは、Hibernate の SchemaExport ツールを使って、マッピング宣言からデータベーススキーマを自動生成する場合には、特に有用です。他のすべてのプロパティーは、Hibernate のデフォルトセッティングを使ってマップされます。これは、Hibernate を使っている間、非常にしばしばお世話になるものです。データベース内のテーブル CAT は、次のようなものです。

 カラム  |          型           | 修飾子
--------+-----------------------+-----------
 cat_id | character(32)         | not null
 name   | character varying(16) | not null
 sex    | character(1)          |
 weight | real                  |
インデックス: cat_pkey 主キー btree (cat_id)

このテーブルは、データベース内に、今、手作業で作るべきです。SchemaExport ツールを使ってこのステップを自動化したい場合には、後で、第 15 章 ツールセットガイドを読んで下さい。このツールは、テーブル定義、カスタムカラム型制約、一意な制約とインデックスを含む、完全な SQL DDL を作ることができます。

1.4. 猫と遊ぶ

これで、Hibernate の Session を開始する準備ができました。それは、永続性マネージャー (persistence manager) インターフェースです。これは、データベースに Cat を保存し、データベースから Cat を検索するのに使います。しかし、まず、SessionFactory から、Session (Hibernate の作業単位) を取得しなければなりません。

SessionFactory sessionFactory =
            new Configuration().configure().buildSessionFactory();

SessionFactory は、1 つのデータベースに責任があり、1 つの XML 設定ファイル (hibernate.cfg.xml) だけを使うかもしれません。他のプロパティーは、SessionFactory (これは、不変です) を構築するに、Configuration にアクセスすることによって、セット可能です (マッピングメタデータを変更することすら可能です)。どこに SessionFactory を作り、どうすれば、アプリケーション内からそれにアクセスできるのでしょうか。

SessionFactory は、通常、1 度だけ、例えば、load-on-startup サーブレットを使って起動時に作られます。この意味は、SessionFactory は、サーブレットのインスタンス変数に保持すべきではなく、他の何らかの場所に保持すべきだということです。何らかの種類の Singleton が必要です。そのため、SessionFactory には簡単にアクセスできます。次に示すアプローチは、SessionFactory の設定と、それへの簡単なアクセスという両方の問題を解決します。

HibernateUtil ヘルパークラスを実装します。

import net.sf.hibernate.*;
import net.sf.hibernate.cfg.*;

public class HibernateUtil {

    private static final SessionFactory sessionFactory;

    static {
        try {
            // SessionFactory を作る
            sessionFactory = new Configuration().configure().buildSessionFactory();
        } catch (HibernateException ex) {
            throw new RuntimeException("Configuration problem: " + ex.getMessage(), ex);
        }
    }

    public static final ThreadLocal session = new ThreadLocal();

    public static Session currentSession() throws HibernateException {
        Session s = (Session) session.get();
        // このスレッドにまだ何もなければ、新しいセッションをオープンする
        if (s == null) {
            s = sessionFactory.openSession();
            session.set(s);
        }
        return s;
    }

    public static void closeSession() throws HibernateException {
        Session s = (Session) session.get();
        session.set(null);
        if (s != null)
            s.close();
    }
}

このクラスは、そのスタティックな属性を使って SessionFactory の面倒を見るだけでなく、現在実行中のスレッドのための Session を保持する ThreadLocal ももちます。このヘルパーを使おうとする前に、スレッドローカル変数という Java の概念を理解していることを確かめて下さい。

SessionFactory は、スレッドセーフで、多くのスレッドが、同時に、それにアクセスでき、Session をリクエストできます。Session は、データベースを使う場合の 1 つの作業単位を表わすスレッドセーフではないオブジェクトです。Session は、SessionFactory がオープンし、すべての作業が完了すると、クローズされます。

Session session = HibernateUtil.currentSession();

Transaction tx= session.beginTransaction();

Cat princess = new Cat();
princess.setName("Princess");
princess.setSex('F');
princess.setWeight(7.4f);

session.save(princess);
tx.commit();

HibernateUtil.closeSession();

Session 内では、データベース操作はすべて、データベース操作 (読み込み専用操作であっても) を隔離するトランザクション中で発生します。基礎にあるトランザクション戦略 (ここでの場合、JDBC トランザクション) から抽象化するため、Hibernate の Transaction API を使います。これによって、開発するコードは、何も変更することなく、(JTA を使う) コンテナー管理のトランザクションを使うことができます。上の例では、例外を何も処理していない点に注意して下さい。

HibernateUtil.currentSession(); は、必要に応じて何度でも呼び出される場合がある点にも注意して下さい。これにより、いつでも、このスレッドの現在の Session を受け取ることになります。作業単位が完了したら、HTTP レスポンスを送る前に、サーブレットのコードかサーブレットフィルター内で、Session をクローズしたことを確かめなければなりません。サーブレットフィルターを用いる場合の望ましい副次効果は、簡単に初期化を遅らせることができることです。つまり、Session は、ビューをレンダリングする時にもオープンされたままであるため、Hibernaate は、グラフをナビゲートする間に、初期化されていないオブジェクトをロードすることができます。

Hibernate には、データベースからオブジェクトを検索するのに使うことのできるメソッドが数多くあります。最も柔軟性のある方法は、Hibernate クエリー言語 (Hibernate Query Language - HQL) を使うことです。これは、学びやすく、パワフルなオブジェクト指向による SQL 拡張です。

Transaction tx = session.beginTransaction();

Query query = session.createQuery("select c from Cat as c where c.sex = :sex");
query.setCharacter("sex", 'F');
for (Iterator it = query.iterate(); it.hasNext();) {
    Cat cat = (Cat) it.next();
    out.println("Female Cat: " + cat.getName() );
}

tx.commit();

Hibernate は、オブジェクト指向によるクリテリアを使ったクエリー (query by criteria) API も提供しています。これは、型セーフなクエリーを定式化するのに使うことができるものです。Hibernate は、もちろん、データベースとの SQL 通信すべてに PreparedStatement とパラメータバインディングを使用します。ごく稀にですが、Hibernate の直接的な SQL クエリー機能を使ったり、Session からプレーンな JDBC 接続を取得することがあるかもしれません。

1.5. 最後に

この小さなチュートリアルでは、Hibernate の表面をなでたにすぎません。ここでの例には、サーブレットに固有なコードが何も含まれていない点に注意して下さい。サーブレットは自分で作り、必要に適したように、Hibernate のコードを挿入しなければなりません。

Hibernate は、データアクセス層として、アプリケーションと強く統合される点を覚えておいて下さい。通常、その他の層はすべて、永続性のメカニズムに依存します。このデザインの意味合いが理解できているか確認して下さい。

第 2 章 アーキテクチャー

2.1. 概観

Hibernate アーキテクチャーの (非常に) レベルの高い概観

このダイアグラムは、アプリケーションに永続サービス (と永続オブジェクト) を提供するため、データベースと設定データを使う Hibernate を示しています。

実行時アーキテクチャーのもっと詳細な概観を示したいと考えています。残念ながら、Hibernate は、柔軟で、幾つかのアプローチをサポートしています。その両極を示すことにしましょう。"軽量" アーキテクチャーには、それ自身の JDBC 接続を提供するアプリケーションがあり、それ自身のトランザクションを管理します。このアプローチは、Hibernate API の最小限のサブセットを使用します。

"完全実装の" アーキテクチャーは、基礎にある JDBC/JTA API からアプリケーションを抽象化し、Hibernate に細部の面倒を見させます。

以下は、ダイアグラム中にあるオブジェクトの定義の幾つかです。

SessionFactory (net.sf.hibernate.SessionFactory)

単一データベース用にコンパイルされたマッピングのスレッドセーフな (不変の) キャッシュ。Session 用のファクトリーと、ConnectionProvider のクライアント。プロセスレベルやクラスターレベルで、トランザクション間で再利用可能なデータのオプションの (2 次レベルの) キャッシュを保持する場合がある。

Session (net.sf.hibernate.Session)

アプリケーションと永続ストア間での会話を表わす単一スレッド化された短命のオブジェクト。JDBC 接続をラップする。Transaction 用のファクトリー。オブジェクトグラフをナビゲートする際や、識別子によってオブジェクトをルックアップする際に使われる、永続オブジェクトの必須 (1 次レベルの) キャッシュを保持する。

Persistent Objects と Collections

永続状態とビジネス機能を含む、短命の単一スレッド化されたオブジェクト。これらは、JavaBeans/POJO である場合がある。これらについて唯一の特殊なことは、それらが、現在、(たった 1 つの) Session と関連付けられていることである。Session がクローズされるとすぐに、それらは、切り離され、(例えば、プレゼンテーションに対するデータ転送オブジェクトやプレゼンテーションからのデータ転送オブジェクトとして、直接) アプリケーション層で自由に使えるようになる。

Transient Objects と Collections

現在、Session と関連付けられていない永続クラスのインスタンス。それらは、アプリケーションによってインスタンス化され、(まだ) 永続化されていない場合もあれば、クローズされた Session によってインスタンス化された場合もある。

Transaction (net.sf.hibernate.Transaction)

作業の原子単位を指定するためにアプリケーションが使用する (オプションの) 単一スレッド化された短命のオブジェクト。基礎にある JDBC、JTA や CORBA トランザクションから、アプリケーションを抽象する。場合によっては、Session は、幾つかの Transaction にまたがることもある。

ConnectionProvider (net.sf.hibernate.connection.ConnectionProvider)

JDBC 接続 (とプール) のための (オプションの) ファクトリー。基礎にある DatasourceDriverManager からアプリケーションを抽象する。アプリケーションに対して公開されないが、開発者は、拡張/実装が可能である。

TransactionFactory (net.sf.hibernate.TransactionFactory)

Transaction インスタンスのための (オプションの) ファクトリー。アプリケーションに対して公開されないが、開発者は、拡張/実装が可能である。

"軽量" アーキテクチャーを指定すると、アプリケーションは、Transaction/TransactionFactory そして/あるいは ConnectionProvider API を迂回して、直接、JTA や JDBC と会話します。

2.2. JMX 統合

JMX は、Java コンポーネントの管理のための J2EE 標準です。Hibernate は、JMX の標準 MBean によって管理される場合があるかもしれませんが、アプリケーションサーバーの多くは、まだ、JMX をサポートしていないので、Hibernate も、非標準の設定メカニズムの幾つかを提供しています。

JBoss 内の JMX コンポーネントとして実行するように Hibernate を設定する方法に関する詳細情報は、Hibernate ウェブサイトを参照して下さい。

2.3. JCA サポート

Hibernate は、JCA コネクターとして設定される場合もあります。詳細は、ウェブサイトを参照して下さい。

第 3 章 SessionFactory 設定

Hibernate は、多くの異なった環境で動作するようにデザインされているので、非常に多くの設定パラメータがあります。幸いなことに、そのほとんどには、実用的なデフォルト値があるので、Hibernate は、様々なオプションを示すサンプル hibernate.properties ファイルを使って配布できます。通常しなければならないのは、そのファイルをクラスパスに置き、それをカスタマイズすることだけです。

3.1. プログラム的設定

net.sf.hibernate.cfg.Configuration のインスタンスは、アプリケーションの Java 型の SQL データベースへのマッピングのセット全体を表わします。Configuration は、(不変の) SessionFactory を構築するために使われます。マッピングは、様々な XML マッピングファイルから編成されます。

Configuration インスタンスは、それを直接インスタンス化することによって、取得されるかもしれません。以下は、データストアを、(クラスパス中にある) 2 つの XML 設定ファイルで定義したマッピングからセットアップする例です。

Configuration cfg = new Configuration()
    .addFile("Item.hbm.xml")
    .addFile("Bid.hbm.xml");

別の (時には、もっと望ましい) 方法は、getResourceAsStream() を使って、Hibernate に、マッピングファイルをロードさせることです。

Configuration cfg = new Configuration()
    .addClass(org.hibernate.auction.Item.class)
    .addClass(org.hibernate.auction.Bid.class);

すると、Hibernate は、クラスパス中で、/org/hibernate/autcion/Item.hbm.xml/org/hibernate/autcion/Bid.hbm.xml という名前のマッピングファイルを探します。このアプローチは、ファイル名をハードコーディングする必要をなくします。

Configuration も、様々なオプションのプロパティーを指定します。

Properties props = new Properties();
...
Configuration cfg = new Configuration()
    .addClass(org.hibernate.auction.Item.class)
    .addClass(org.hibernate.auction.Bid.class)
    .setProperties(props);

Configuration は、設定時オブジェクトであることを意図しています。それは、SessionFactory の構築が終われば捨てられます。

3.2. SessionFactory を取得する

マッピングすべてを Configuration がパースすると、アプリケーションは、Session インスタンスのためのファクトリーを取得しなければなりません。このファクトリーは、アプリケーションのスレッドすべてが共有することを意図されています。

SessionFactory sessions = cfg.buildSessionFactory();

しかしながら、Hibernate は、実際には、アプリケーションが複数の SessionFactory をインスタンス化することを可能にします。これは、複数のデータベースを使う場合に有用です。

3.3. ユーザー提供の JDBC 接続

SessionFactory は、ユーザー提供の JDBC 接続上に、Session をオープンする場合があります。このデザインの選択によって、アプリケーションは、それが望むどこからでも JDBC 接続を取得することができるようになりました。

java.sql.Connection conn = datasource.getConnection();
Session session = sessions.openSession(conn);

// 何らかのデータアクセス作業を行なう

アプリケーションは、同じ JDBC 接続上で、2 つの Session を同時にオープンしないように注意しなければなりません。

3.4. Hibernate 提供の JDBC 接続

それ以外に、SessionFactory に接続をオープンさせることができます。SessionFactory には、次の方法のいずれかで、JDBC 接続プロパティーを与えなければなりません。

  1. java.util.Properties のインスタンスを Configuration.setProperties() に渡す。

  2. hibernate.properties をクラスパスのルートディレクトリーに置く。

  3. java -Dproperty=value を使って、System プロパティーをセットする。

  4. hibernate.cfg.xml (後述) 内に <property> 要素をインクルードする。

このアプローチを取る場合、Session をオープンするのは、次のように簡単です。

Session session = sessions.openSession(); // 新しいセッションをオープンする
// 何らかのデータアクセス作業を行なう。JDBC 接続は、必要に応じて、使われる。

Hibernate プロパティーの名前とセマンティクスはすべて、クラス net.sf.hibernate.cfg.Environment 上で定義されています。次に、JDBC 接続設定にとって最も重要なセッティングを説明することにしましょう。

Hibernate は、次のプロパティーをセットすると、java.sql.DriverManager を使って接続を取得 (そしてプール) します。

テーブル 3.1. Hibernate JDBC プロパティー

プロパティー名 目的
hibernate.connection.driver_class jdbc ドライバークラス
hibernate.connection.url jdbc URL
hibernate.connection.username データベースユーザー
hibernate.connection.password データベースユーザーパスワード
hibernate.connection.pool_size プールされる接続の最大数

Hibernate 自身の接続プーリングアルゴリズムは、まったく初歩的です。それは、出発点として役立つことを意図したものであり、製品システムでの使用や、パフォーマンスのテストさえも意図したものではありません。最良のパフォーマンスと安定性が必要な場合は、サードパーティープールを使って下さい。つまり、hibernate.connection.pool_size プロパティーを、セッティングに固有な接続プールで置き換えて下さい。

C3P0 は、Hibernate とともに配布されているオープンソースの JDBC 接続プールで、lib ディレクトリーにあります。hibernate.c3p0.* プロパティーをセットした場合には、Hibernate は、接続プーリングに、組み込みの C3P0ConnectionProvider を使います。Apache DBCP と Proxool 用の組み込みのサポートもあります。DBCPConnectionProvider を有効にするには、プロパティー hibernate.dbcp.* (DBCP 接続プールプロパティー) をセットしなければなりません。hibernate.dbcp.ps.* (DBCP ステートメントキャッシュプロパティー) をセットした場合には、プリペアドステートメントキャッシュが有効になります (強く推奨します)。これらのプロパティーの解釈についていは、Apache commons-pool ドキュメントを参照して下さい。Proxool を使いたい場合には、hibernate.proxool.* プロパティーをセットすべきです。

以下は、C3P0 を使う例です。

hibernate.connection.driver_class = org.postgresql.Driver
hibernate.connection.url = jdbc:postgresql://localhost/mydatabase
hibernate.connection.username = myuser
hibernate.connection.password = secret
hibernate.c3p0.minPoolSize=5
hibernate.c3p0.maxPoolSize=20
hibernate.c3p0.timeout=1800
hibernate.c3p0.max_statement=50
hibernate.dialect = net.sf.hibernate.dialect.PostgreSQLDialect

アプリケーションサーバー内で使用する場合、Hibernate は、JNDI 内に登録された javax.sql.Datasource から接続を取得するかもしれません。その場合には、次のプロパティーをセットします。

テーブル 3.2. Hibernate Datasource プロパティー

プロパティー名 目的
hibernate.connection.datasource データソースの JNDI 名
hibernate.jndi.url JNDI プロバイダーの URL (オプション)
hibernate.jndi.class JNDI InitialContextFactory のクラス (オプション)
hibernate.connection.username データベースユーザー (オプション)
hibernate.connection.password データベースユーザーパスワード (オプション)

以下は、JNDI データソースを指定されたアプリケーションサーバーを使う例です。

hibernate.connection.datasource = java:/comp/env/jdbc/MyDB
hibernate.transaction.factory_class = \
    net.sf.hibernate.transaction.JTATransactionFactory
hibernate.transaction.manager_lookup_class = \
    net.sf.hibernate.transaction.JBossTransactionManagerLookup
hibernate.dialect = \
    net.sf.hibernate.dialect.PostgreSQLDialect

JNDI データソースから取得する JDBC 接続は、自動的に、アプリケーションサーバーのコンテナー管理のトランザクションに参加します。

"hibernate.connnection" をプロパティー名に付加することによって、任意の接続プロパティを指定する場合があるかもしれません。例えば、hibernate.connnection.charSet を使って、charSet を指定する場合があるがあるかもしれません。

インターフェース net.sf.hibernate.connection.ConnectionProvider を実装することにより、JDBC ドライバーを取得するために、自分自身のプラグイン戦略を定義する場合があるかもしれません。また、hibernate.connection.provider_class をセットすることによって、カスタム実装を選択する場合があるかもしれません。

3.5. オプションの設定プロパティー

実行時に Hibernate の振る舞いをコントロールするその他のプロパティーは数多くあります。そのすべてはオプションで、合理的なデフォルト値をもっています。

システムレベルのプロパティーは、java -Dproperty=value によってセット可能か、あるいは hibernate.properties によって定義可能で、Configuration に渡される Properties のインスタンスによっては指定できません。

テーブル 3.3. Hibernate 設定プロパティー

プロパティー名 目的
hibernate.dialect Hibernate Dialect のクラス名。プラットフォームに依存する何らかの機能を有効にする。

full.classname.of.Dialect

hibernate.default_schema 生成した SQL 内で指定したスキーマ/テーブルスペースを使って、修飾されていないテーブル名を修飾する。

SCHEMA_NAME

hibernate.session_factory_name SessionFactory は、それが作られた後には、自動的に、JNDI 内のこの名前にバインドされる。

jndi/composite/name

hibernate.use_outer_join 外部結合によるフェッチを可能にする。非推奨。max_fetch_depth を使用せよ。

true | false

hibernate.max_fetch_depth 単一終端関連 (single-ended association - one-to-one, may-to-one) に対する、外部結合によるフェッチツリーの最大の "深さ" をセットする。0 は、デフォルトの外部結合によるフェッチを無効にする。

0 から 3 までの値を推奨する。

hibernate.jdbc.fetch_size 0 以外の値は、JDBC フェッチサイズを決定する (Statement.setFetchSize() を呼び出す)。
hibernate.jdbc.batch_size 0 以外の値は、Hibernate による JDBC2 バッチ更新の使用を可能にする。

5 から 30 までの値を推奨する。

hibernate.jdbc.use_scrollable_resultset Hibernate による JDBC2 スクロール可能な結果セットの使用を可能にする。このプロパティーは、ユーザー指定の JDBC 接続を使う場合にだけ必要である。Hibernate は、そうでなければ、接続メタデータを使用する。

true | false

hibernate.jdbc.use_streams_for_binary JDBC との間で binaryserializable 型を読み書きする場合に、ストリームを使う (システムレベルのプロパティー)。

true | false

hibernate.jdbc.use_get_generated_keys 挿入の後、ネイティブに生成されたキーを検索するために、JDBC3 PreparedStatement.getGeneratedKeys() が使用できるようにする。JDBC3+ ドライバーと JRE1.4+ を必要とする。使用しているドライバーが Hibernate 識別子ジェネレーターに関して問題があれば、false にセットせよ。デフォルトで、接続メタデータを使ってドライバーの能力を判断しようとする。

true | false

hibernate.cglib.use_reflection_optimizer 実行時リフレクションの代わりに CGLIB が使用できるようにする (システムレベルのプロパティー。可能な場合には、CGLIB を使うというのがデフォルトである)。リフレクションは、トラブルシューティングの際には、役立つ場合もある。

true | false

hibernate.jndi.<propertyName> プロパティー propertyName を JNDI InitialContextFactory に渡す。
hibernate.connection.isolation JDBC トランザクションの隔離レベルをセットする。意味ある値については、java.sql.Connection をチェックせよ。ほとんどのデータベースは、隔離レベルのすべてをサポートしている訳ではないことに注意。

1, 2, 4, 8

hibernate.connection.<propertyName> JDBC プロパティー propertyNameDriverManager.getConnection() に渡す。
hibernate.connection.provider_class カスタム ConnectionProvider のクラス名。

classname.of.ConnectionProvider

hibernate.cache.provider_class カスタム CacheProvider のクラス名。

classname.of.CacheProvider

hibernate.cache.use_minimal_puts かなり頻繁な読み込みを犠牲にして、書き込みを最小限にするため、2 次レベルのキャッシュ操作を最適化する (クラスター化されたキャッシュには有用)。

true | false

hibernate.cache.use_query_cache クエリーのキャッシュを可能にする。その場合でも、個々のクエリーは、キャッシュ可能にセットしておく必要がある。

true | false

hibernate.cache.region_prefix 2 次レベルのキャッシュリージョン名用に使う接頭辞。

prefix

hibernate.transaction.factory_class Hibernate Transaction API とともに使用する TransactionFactory のクラス名 (デフォルトは、JDBCTransactionFactory になる)。

classname.of.TransactionFactory

jta.UserTransaction アプリケーションサーバーから JTA UserTransaction を取得するために、JTATransactionFactory が使用する JNDI 名。

jndi/composite/name

hibernate.transaction.manager_lookup_class TransactionManagerLookup のクラス名。JVM レベルのキャッシングが JTA 環境内で有効にされる時に必要。

classname.of.TransactionManagerLookup

hibernate.query.substitutions Hibernate クエリー内の from トークンを SQL トークンにマップする (トークンは、例えば、関数やリテラルの名前の場合がある)。

hqlLiteral=SQL_LITERAL, hqlFunction=SQLFUNC

hibernate.show_sql すべての SQL 文をコンソールに書き出す。

true | false

hibernate.hbm2ddl.auto SessionFactory が作られる時に、自動的に、スキーマ DDL をデータベースにエクスポートする。create-drop を使うと、SessionFactory を明示的にクローズする時に、データベーススキーマは削除される。

update | create | create-drop

3.5.1. SQL 方言

いつでも、hibernate.dialect は、データベース用の正しい net.sf.hibernate.dialect.Dialect サブクラスにセットすべきです。nativesequence 主キー生成や、(例えば、Session.lock()Query.setLockMode() を使った) ペシミスティックロッキングを使いたいのでなければ、この指定は、厳密に言えば本質的ではありません。しかしながら、方言を指定すると、Hibernate は、上でリストしたその他のプロパティーの幾つかに実用性のあるデフォルトを使うので、それらを手作業で指定する労力を省くことができます。

テーブル 3.4. Hibernate SQL 方言 (hibernate.dialect)

RDBMS 方言
DB2 net.sf.hibernate.dialect.DB2Dialect
DB2 AS/400 net.sf.hibernate.dialect.DB2400Dialect
DB2 OS390 net.sf.hibernate.dialect.DB2390Dialect
PostgreSQL net.sf.hibernate.dialect.PostgreSQLDialect
MySQL net.sf.hibernate.dialect.MySQLDialect
Oracle (全バージョン) net.sf.hibernate.dialect.OracleDialect
Oracle 9 net.sf.hibernate.dialect.Oracle9Dialect
Sybase net.sf.hibernate.dialect.SybaseDialect
Sybase Anywhere net.sf.hibernate.dialect.SybaseAnywhereDialect
Microsoft SQL Server net.sf.hibernate.dialect.SQLServerDialect
SAP DB net.sf.hibernate.dialect.SAPDBDialect
Informix net.sf.hibernate.dialect.InformixDialect
HypersonicSQL net.sf.hibernate.dialect.HSQLDialect
Ingres net.sf.hibernate.dialect.IngresDialect
Progress net.sf.hibernate.dialect.ProgressDialect
Mckoi SQL net.sf.hibernate.dialect.MckoiDialect
Interbase net.sf.hibernate.dialect.InterbaseDialect
Pointbase net.sf.hibernate.dialect.PointbaseDialect
FrontBase net.sf.hibernate.dialect.FrontbaseDialect
Firebird net.sf.hibernate.dialect.FirebirdDialect

3.5.2. 外部結合によるフェッチ

使用しているデータベースが ANSI や Oracle スタイルの外部結合をサポートしている場合、外部結合によるフェッチ (outer join fetching) は、データベースとの間でのラウンドトリップの数を制限することによって、(データベース自身が行なえたかもしれない多くの作業を犠牲にして) パフォーマンスを上げることができるかもしれません。外部結合によるフェッチによって、many-to-one、one-to-many や one-to-one 関連によって接続されたオブジェクトのグラフは、1 つの SQL SELECT で検索できるようになります。

デフォルトでは、フェッチされるグラフは、オブジェクトをロードする際、リーフオブジェクト、コレクション、プロキシーつきのオブジェクト、あるいは循環の発生する場所で終わります。

特定の関連 (particular association) の場合、フェッチは、XML マッピング内の outer-join 属性をセットすることによって、有効にされたり、無効にされたりします (そして、デフォルトの振る舞いが、オーバーライドされます)。

外部結合によるフェッチは、プロパティー hibernate.max_fetch_depth0 にセットすることにより、グローバルに無効になります。これを 1 かそれ以上にセットすると、デフォルトによっても、外部結合を auto にセットできる one-to-one、many-to-one 関連すべてに対する外部結合によるフェッチが可能になります。しかしながら、明示的に個々の特定関連に対して宣言しない場合には、one-to-many 関連とコレクションは、外部結合によってフェッチすることはできません。この振る舞いは、実行時に、Hibernate クエリーによって、オーバーライドすることもできます。

3.5.3. バイナリーストリーム

Oracle は、JDBC ドライバーとの間で受け渡しされる byte 配列のサイズを制限しています。binaryserializable 型の大きなインスタンスを使いたい場合には、hibernate.jdbc.use_streams_for_binary を有効にすべきです。これは、JVM レベルに限ったセッティングです。

3.5.4. カスタム CacheProvider

JVM レベルの (あるいは、クラスター化された) 2 次レベルのキャッシュシステムを、インターフェース net.sf.hibernate.cache.CacheProvider を実装することによって統合したいかもしれません。カスタム実装は、hibernate.cache.provider_class をセットすることによって、選択可能です。

3.5.5. トランザクション戦略設定

Hibernate の Transaction API を使いたい場合、プロパティー hibernate.transaction.factory_class をセットすることにより、Transaction インスタンス用のファクトリークラスを指定しなければなりません。Transaction API は、基礎にあるトランザクションメカニズムを隠蔽し、Hibernate のコードが、管理された環境でも、管理されない環境でも動作できるようにします。

2 つの標準 (組み込みの) 選択があります。

net.sf.hibernate.transaction.JDBCTransactionFactory

データベース (JDBC) トランザクションに委譲する (デフォルト)。

net.sf.hibernate.transaction.JTATransactionFactory

JTA に委譲する (既にあるトランザクションが進行中の場合、Session は、そのコンテキストでその作業を行ない、そうでなければ、新しいトランザクションが開始される)。

自分自身のトランザクション戦略を定義する場合があるかもしれません (例えば、CORBA トランザクションサービスのための)。

JTA 環境で変更可能なデータを JVM レベルでキャッシングしたい場合、JTA TransactionManager を取得するための戦略を指定しなければなりません。何故なら、これは、J2EE コンテナー用に標準化されていないからです。

テーブル 3.5. JTA TransactionManagers

トランザクションファクトリー アプリケーションサーバー
net.sf.hibernate.transaction.JBossTransactionManagerLookup JBoss
net.sf.hibernate.transaction.WeblogicTransactionManagerLookup Weblogic
net.sf.hibernate.transaction.WebSphereTransactionManagerLookup WebSphere
net.sf.hibernate.transaction.OrionTransactionManagerLookup Orion
net.sf.hibernate.transaction.ResinTransactionManagerLookup Resin
net.sf.hibernate.transaction.JOTMTransactionManagerLookup JOTM
net.sf.hibernate.transaction.JOnASTransactionManagerLookup JOnAS
net.sf.hibernate.transaction.JRun4TransactionManagerLookup JRun4

3.5.6. JNDI とバインドされた SessionFactory

JNDI とバインドされた Hibernate SessionFactory は、ファクトリーのルックアップと新しい Session の作成を簡素化することができます。

SessionFactory を JNDI ネームスペースとバインドさせたい場合には、プロパティー hibernate.session_factory_name を使って、名前 (例えば、java:comp/env/hibernate/SessionFactory) を指定します。このプロパティーを省略すると、SessionFactory は、JNDI にバインドされません。(読み込み専用の JNDI デフォルト実装のある環境 - 例えば、Tomcat - では、これは、特に有用です。)

SessionFactory を JNDI にバインドする場合、Hibernate は、hibernate.jndi.urlhibernate.jndi.class の値を使って、初期コンテキストを初期化します。それを指定しない場合には、デフォルトの InitialContext が使われます。

JNDI を使おうと決めたら、EJB やその他のユーティリティークラスは、JNDI ルックアップを使って、SessionFactory を取得する場合があります。

3.5.7. クエリー言語の置換

hibernate.query.substitutions を使って、新しい Hibernate クエリートークンを定義したい場合があるかもしれません。例えば

hibernate.query.substitutions true=1, false=0

これにより、トークン truefalse は、生成される SQL 内で整数リテラルに翻訳されます。

hibernate.query.substitutions toLowercase=LOWER

これにより、SQL の LOWER 関数の名前を変更することができます。

3.6. ロギング

Hibernate は、Apache commons-logging を使って、様々なイベントのログを取ります。

commons-logging サービスは、出力を、Apache Log4j (クラスパスに log4j.jar をインクルードしている場合) か JDK1.4 logging (JDK1.4 かそれ以上の下で実行している場合) にダイレクトします。Log4j は、http://jakarta.apache.org からダウンロードできます。Log4j を使うには、クラスパス内に log4j.properties を置く必要があります。サンプルプロパティーファイルは、Hibernate の src/ ディレクトリー内で配布されています。

Hibernate のログメッセージに慣れることを強くお勧めします。Hibernate にできる限り詳細なログを取らせ、なおかつ、それを判読できないものにしないために、多くの作業が行なわれてきました。これは、本質的なトラブルシューティングディバイスです。上で述べたように、SQL ロギングを有効にする (hibernate.show_sql) ことも忘れないで下さい。これは、パフォーマンスの問題を調べる場合の最初のステップです。

3.7. NamingStrategy を実装する

インターフェース net.sf.hibernate.cfg.NamingStrategy によって、データベースオブジェクトとスキーマ要素に "ネーミング標準 (naming standard)" を指定することができます。

Java 識別子から自動的に生成されるデータベース識別子に、あるいは、マッピングファイル内で指定する "論理的な" カラムとテーブルの名前を "物理的な" テーブルとカラムの名前に変換処理するのに、ルールを与えたいかもしれません。この機能は、マッピング文書の冗長性を削減し、反復して現われる余分な情報 (例えば、TBL_ 接頭辞) をなくす上で役立ちます。Hibernate が使うデフォルト戦略は、最小限度のものです。

マッピングを追加する前に、Configuration.setNamingStrategy() を呼び出すことにより、異なった戦略を指定することができます。

SessionFactory sf = new Configuration()
    .setNamingStrategy(ImprovedNamingStrategy.INSTANCE)
    .addFile("Item.hbm.xml")
    .addFile("Bid.hbm.xml")
    .buildSessionFactory();

net.sf.hibernate.cfg.ImprovedNamingStrategy は、アプリケーションによっては出発点として役立つ組み込みの戦略です。

3.8. XML 設定ファイル

代替アプローチは、hibernate.cfg.xml という名前のファイルに、完全な設定を指定することです。このファイルは、hibernate.properties ファイルの置き換えとして使うことができ、両方のファイルが存在する場合には、プロパティーをオーバーライドすることができます。

XML 設定ファイルは、デフォルトでは、CLASSPATH のルートにあると想定されています。以下は、その例です。

<?xml version='1.0' encoding='utf-8'?>
<!DOCTYPE hibernate-configuration PUBLIC
        "-//Hibernate/Hibernate Configuration DTD 2.0//EN"

 "http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd">

<hibernate-configuration>

    <!-- /jndi/name としてリストされる SessionFactory インスタンス -->
    <session-factory
        name="java:comp/env/hibernate/SessionFactory">

        <!-- プロパティー -->
        <property name="connection.datasource">my/first/datasource</property>
        <property name="dialect">net.sf.hibernate.dialect.MySQLDialect</property>
        <property name="show_sql">false</property>
        <property name="use_outer_join">true</property>
        <property name="transaction.factory_class">
            net.sf.hibernate.transaction.JTATransactionFactory
        </property>
        <property name="jta.UserTransaction">java:comp/UserTransaction</property>

        <!-- マッピングファイル -->
        <mapping resource="org/hibernate/auction/Item.hbm.xml"/>
        <mapping resource="org/hibernate/auction/Bid.hbm.xml"/>

    </session-factory>

</hibernate-configuration>

Hibernate を設定するのは、次のように、簡単です。

SessionFactory sf = new Configuration().configure().buildSessionFactory();

次のようにすれば、別の XML 設定ファイルを指定することができます。

SessionFactory sf = new Configuration()
    .configure("catdb.cfg.xml")
    .buildSessionFactory();

第 4 章 永続クラス

永続クラスとは、ビジネス問題 (例えば、E-コマースアプリケーションにおけるカスタマーと注文) の実体を実行するアプリケーション内のクラスです。永続クラスには、その名前が示すように、データベースに保存される一時的および永続的インスタンスがあります。

これらのクラスが、プレーンオールド Java オブジェクト (POJO) プログラミングモデルとも呼ばれる、単純なルールに従う場合に、Hibernate は、最良の働きをします。

4.1. 単純な POJO 例

多くの Java アプリケーションは、例えば、猫科の動物を表わすような永続クラスを必要とします。

package eg;
import java.util.Set;
import java.util.Date;

public class Cat {
    private Long id; // 識別子
    private String name;
    private Date birthdate;
    private Cat mate;
    private Set kittens
    private Color color;
    private char sex;
    private float weight;

    private void setId(Long id) {
        this.id=id;
    }
    public Long getId() {
        return id;
    }

    void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }

    void setMate(Cat mate) {
        this.mate = mate;
    }
    public Cat getMate() {
        return mate;
    }

    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 setKittens(Set kittens) {
        this.kittens = kittens;
    }
    public Set getKittens() {
        return kittens;
    }
    // addKitten は、Hibernate には必要ない
    public void addKitten(Cat kitten) {
        kittens.add(kitten);
    }
    void setSex(char sex) {
        this.sex=sex;
    }
    public char getSex() {
        return sex;
    }
}

ここには、従わなければならない 4 つの主要なルールがあります。

4.1.1. 永続フィールド用のアクセサーとミューテイターを宣言する

Cat は、その永続フィールドすべてにアクセサーメソッドを宣言しています。ORM ツールの多くは、直接、インスタンス変数を永続化します。しかし、私達は、この実装の詳細を永続化メカニズムから分離する方がはるかに望ましいと信じています。Hibernate は、JavaBeans スタイルのプロパティーを永続化し、getFooisFoosetFoo といった形式のメソッド名を認識します。

プロパティーが public 宣言されている必要はありません。Hibernate は、デフォルトの protectedprivate get / set ペアのあるプロパティーを永続化することができます。

4.1.2. デフォルトコンストラクターを実装する

Cat には、暗黙的なデフォルト (引数を取らない) コンストラクターがあります。永続クラスにはすべて、デフォルトコンストラクターがなければなりません (これは、public ではないかもしれません)。そのため、Hibernate は、それらを、Constructor.newInstance() を使ってインスタンス化することができます。

4.1.3. 識別子プロパティーを提供する (オプション)

Cat には、id という名前のプロパティーがあります。このプロパティーは、データベースのテーブルの主キーカラムを保持します。このプロパティーは、どのような名前であってもよかったし、その型は、プリミティブ型やプリミティブ "ラッパー" 型、java.lang.Stringjava.util.Date であっても構いませんでした。(レガシーデータベーステーブルに複合キーがある場合は、これらの型のプロパティーをもつユーザー定義のクラスを使うことすら可能でしょう - 以下の複合識別子に関するセクションを参照して下さい。)

識別子 (identifier) プロパティーは、オプションです。それを指定しないでおくことも、Hibernate に内部的にオブジェクト識別子を追跡させるようにすることもできます。しかしながら、多くのアプリケーションにとって、識別子プロパティーを指定することは、優れた (そして非常に一般的な) デザイン上の決定であることに変わりはありません。

更に、機能の中には、識別子プロパティーを宣言するクラスだけが利用できるものもあります。

  • カスケードされた (次々に波及する) 更新 ("ライフサイクルオブジェクト" を参照せよ)。

  • Session.saveOrUpdate()

永続クラスに一貫した名前をもつ識別子プロパティーを宣言するようお勧めします。また、null を取ることのできる (プリミティブではない) 型を使うようにお勧めします。

4.1.4. final ではないクラスを選ぶ (オプション)

Hibernate の中心的な機能である プロキシー (proxies) は、final ではないか、パブリックメソッドすべてを宣言するインターフェースの実装のいずれかである永続クラスに依存します。

Hibernate を使ってインターフェースを実装するのではない final クラスを永続化することはできますが、プロキシーを使うことはできません。これは、パフォーマンスのチューニングのためのオプションを幾らか制限することになります。

4.2. 継承を実装する

サブクラスも、上の最初と 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() を継承する

永続クラスのオブジェクトを (例えば、Set 内で) 組み合わせようとしている場合には、equals()hashCode() メソッドをオーバーライドしなければなりません。

Hibernate は、Session の内部でのみ、JVM の同一性 (equals() のデフォルト実装である a == b ) を保証するので、このことは、これらのオブジェクトが 2 つの異なった Session にロードされる場合にのみ当てはまります。

両方のオブジェクト ab が同じデータベースの行である (それらが識別子として同じ主キーをもつ) 場合であっても、特定の Session コンテキストの外部では、同じ Java インスタンスであることを保証することはできません。

最も明らかな方法は、両方のオブジェクトの識別子の値を比較することによって、equals()/hashCode() を実装することです。その値が同じであれば、両方は、同じデータベースの行でなければならず、それ故、両方は等しいということになります(両方を Set に追加する場合には、Set 内には、1 つの要素だけがあることになります)。残念ながら、このアプローチを使うことはできません。Hibernate は、識別子の値を、永続的なオブジェクトだけに割り当て、新しく作られるインスタンスは、識別子の値を何ももつことができないからです。そのため、ビジネスキーの等価性 (Business key equality) を使って、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 (!getName().equals(cat.getName())) return false;
        if (!getBirthday().equals(cat.getBirthday())) return false;

        return true;
    }

    public int hashCode() {
        int result;
        result = getName().hashCode();
        result = 29 * result + getBirthday().hashCode();
        return result;
    }

}

候補キー (この場合、名前と出生日の複合) は、 (ひょっとすると、1 つのユースケース内だけであるにしても) 少なくとも特定の比較操作に対しては妥当でなければならないことを覚えておいて下さい。現実の主キーに対して、通常、適用する安定したクリテリア (criteria) は必要ないのです。

4.4. ライフサイクルコールバック

オプションで、永続クラスは、Lifecycle インターフェースを実装する場合があります。これによって、永続オブジェクトは、保存やロードの後や、削除や更新の前に、必要な初期化/クリーンアップを行なう幾つかのコールバックを提供することができるようになります。

しかしながら、Hibernate の Interceptor は、もっと差し出がましくない代替を提供しています。

public interface Lifecycle {
        public boolean onSave(Session s) throws CallbackException;   (1)
        public boolean onUpdate(Session s) throws CallbackException; (2)
        public boolean onDelete(Session s) throws CallbackException; (3)
        public void onLoad(Session s, Serializable id);              (4)
}
(1)

onSave - オブジェクトが保存されるか、挿入される直前に呼び出される。

(2)

onUpdate - オブジェクトが更新される直前 (オブジェクトが Session.update() に渡される時) に呼び出される。

(3)

onDelete - オブジェクトが削除される直前に呼び出される。

(4)

onLoad - オブジェクトがロードされた直後に呼び出される。

onSave()onDelete()onUpdate() は、依存するオブジェクトの保存と削除をカスケードするのに使われる場合があります。これは、マッピングファイル内でカスケードされる操作を宣言する代わりとなるものです。onLoad() は、オブジェクトの一時的プロパティーを、その永続的状態から、初期化するのに使われる場合があります。それは、依存するオブジェクトをロードするためには使われないかもしれません。Session インターフェースは、このメソッドの内部からは呼び出されない場合があるからです。onLoad()onSave()onUpdate() の本来意図された用法は、後で使用するために、現在の Session への参照を保存することです。

onUpdate() は、オブジェクトの永続状態が更新されるたびに呼び出される訳ではない点に注意して下さい。それが呼び出されるのは、一時的なオブジェクトが Session.update() に渡される時だけです。

onSave()onUpdate()onDelete()true を返す場合には、その操作が拒否されたということを表わしています。CallbackException が投げられた場合、その操作は拒否され、例外は、アプリケーションに戻されます。

ネイティブなキー生成を使っているのでなければ、onSave() は、識別子をオブジェクトに割り当てた後に呼び出される点に注意して下さい。

4.5. 妥当性検証可能なコールバック

永続クラスは、その状態が永続化される前に、不変式 (invariants) をチェックする必要がある場合、次のインターフェースを実装するかもしれません。

public interface Validatable {
        public void validate() throws ValidationFailure;
}

オブジェクトは、不変式が侵害された場合には、ValidationFailure を投げるべきです。Validatable のインスタンスは、validate() の内部から、その状態を変えるべきではないからです。

Lifecycle インターフェースのコールバックメソッドとは違って、validate() は、何度でも、呼び出される場合があります。アプリケーションは、ビジネス機能に対して validate() を呼び出すことを当てにすべきではありません。

4.6. XDOclet マークアップを使う

次の章では、Hibernate のマッピングが、単純な可読性のある XML フォーマットでどのように表現されるかを示すことにします。Hibernate のユーザーの多くは、マッピング情報を、XDoclet の @hibernate.tags を使ってソースコードに直接埋め込むことを好みます。このアプローチについては、この文書では解説しません。それは、厳密に言えば、XDoclet の一部と考えられるからです。しかしながら、XDoclet を使った Cat クラスの次の例を含めておくことにします。

package eg;
import java.util.Set;
import java.util.Date;

/**
 * @hibernate.class
 *  table="CATS"
 */
public class Cat {
    private Long id; // 識別子
    private Date birthdate;
    private Cat mate;
    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="MATE_ID"
     */
    public Cat getMate() {
        return mate;
    }
    void setMate(Cat mate) {
        this.mate = mate;
    }

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

    /**
     * @hibernate.property
     *  column="COLOR"
     *  not-null="true"
     */
    public Color getColor() {
        return color;
    }
    void setColor(Color color) {
        this.color = color;
    }
    /**
     * @hibernate.set
     *  lazy="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 は、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;
    }
}

第 5 章 基本的な O/R マッピング

5.1. マッピング宣言

オブジェクト/リレーショナルマッピングは、XML 文書で定義します。マッピング文書は、可読的で、手で編集できるようにデザインされています。マッピングの言語は、Java が中心です。この意味は、マッピングは、テーブルの宣言ではなく、永続クラスの宣言を軸に組み立てられるということです。

Hibernate ユーザーの多くは、XML マッピングを手で定義しますが、マッピング文書を生成する多くのツールがあることに注意して下さい。それには、XDoclet、Middlegen や AndroMDA が含まれます。

サンプルマッピングを使って、始めることにしましょう。

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">

<hibernate-mapping package="eg">

        <class name="Cat" table="CATS" discriminator-value="C">
                <id name="id" column="uid" type="long">
                        <generator class="hilo"/>
                </id>
                <discriminator column="subclass" type="character"/>
                <property name="birthdate" type="date"/>
                <property name="color" not-null="true"/>
                <property name="sex" not-null="true" update="false"/>
                <property name="weight"/>
                <many-to-one name="mate" column="mate_id"/>
                <set name="kittens">
                        <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">
                <!-- Dog のマッピングがここにくる -->
        </class>

</hibernate-mapping>

では、マッピング文書の内容を説明することにしましょう。Hibernate が実行時に使う文書の要素と属性だけを説明することにします。マッピング文書には、スキーマエクスポートツールがエクスポートするデータベーススキーマに影響を与える、幾つかの余分なオプション属性と要素が含まれています。(例えば、not-null 属性。)

5.1.1. Doctype

XML マッピングはすべて、ここに示した文書型 (doctype) を宣言すべきです。実際の DTD は、上の URL の hibernate-x.x.x/src/net/sf/hibernate にあるかもしれませんし、hibernate.jar にあるかもしれません。Hibernate は、いつでも、まず、そのクラスパス内で、DTD を探します。

5.1.2. hibernate-mapping

この要素には、3 つのオプション属性があります。schema 属性は、このマッピングが参照するテーブルが、この属性によって指定したスキーマに属することを指定します。この属性を指定する場合、テーブル名は、与えたスキーマ名によって修飾されます。この属性を指定しない場合には、テーブル名は、修飾されません。default-cascade 属性は、cascade 属性を指定しないプロパティーとコレクションに、どのカスケードスタイルを想定すべきかを指定します。auto-import 属性によって、修飾されないクラス名をクエリー言語中で、デフォルトで使用することができます。

<hibernate-mapping
         schema="schemaName"                          (1)
         default-cascade="none|save-update"           (2)
         auto-import="true|false"                     (3)
         package="package.name"                       (4)
 />
(1)

schema (オプション): データベーススキーマの名前。

(2)

default-cascade (オプション - デフォルトは none): デフォルトのカスケードスタイル。

(3)

auto-import (オプション - デフォルトは true): クエリー言語内で、(このマッピング内にあるクラスの) 修飾されないクラス名が使えるかどうかを指定する。

(4)

package (オプション): マッピング文書内にある修飾されないクラス名に想定されるパッケージの接頭辞を指定する。

同じ (修飾されない) 名前をもつ永続クラスが 2 つある場合には、auto-import="false" をセットすべきです。2 つのクラスに、"インポートされた" 同じ名前を割り当てようとすると、Hibernate は、例外を投げます。

5.1.3. class

class 要素を使って、永続クラスを宣言する場合があるかもしれません。

<class
        name="ClassName"                              (1)
        table="tableName"                             (2)
        discriminator-value="discriminator_value"     (3)
        mutable="true|false"                          (4)
        schema="owner"                                (5)
        proxy="ProxyInterface"                        (6)
        dynamic-update="true|false"                   (7)
        dynamic-insert="true|false"                   (8)
        select-before-update="true|false"             (9)
        polymorphism="implicit|explicit"              (10)
        where="arbitrary sql where condition"         (11)
        persister="PersisterClass"                    (12)
        batch-size="N"                                (13)
        optimistic-lock="none|version|dirty|all"      (14)
        lazy="true|false"                             (15)
/>
(1)

name: 永続クラス (あるいはインターフェース) の完全に修飾された Java クラス名。

(2)

table: データベースのテーブル名。

(3)

discriminator-value (オプション - デフォルトは、クラス名): ポリモーフィズム的振る舞いに使われる個々のサブクラスを識別する値。受け付けられる値には、nullnot null がある。

(4)

mutable (オプション - デフォルトは、true): 変更可能な (変更可能ではない) クラスのインスタンスを指定する。

(5)

schema (オプション): ルート <hibernate-mapping> 要素が指定するスキーマ名をオーバーライドする。

(6)

proxy (オプション): プロキシーを遅延初期化するのに使用するインターフェースを指定する。クラス自身の名前を指定する場合があるかもしれない。

(7)

dynamic-update (オプション - デフォルトは、false): 値が変更されたカラムだけからなる UPDATE SQL を実行時に生成すべきことを指定する。

(8)

dynamic-insert (オプション - デフォルトは、false): 値が null ではないカラムだけからなる INSERT SQL を実行時に生成すべきことを指定する。

(9)

select-before-update (オプション - デフォルトは、false): オブジェクトが実際に変更されたことが確かでなければ、Hibernate が、SQL UPDATE決して実行すべきではないことを指定する。場合によっては (実際には、一時的オブジェクトが update() を使って、新しいセッションと関連付けられた時のみ)、この意味は、UPDATE が実際に必要かどうかを判断するために、Hibernate が、余分な SQL SELECT を実行するということである。

(10)

polymorphism (オプション - デフォルトは、implicit): 暗黙的あるいは明示的なクエリーポリモーフィズムを使うかどうかを決定する。

(11)

where (オプション): このクラスのオブジェクトを検索する際に使用する任意の SQL WHERE 条件。

(12)

persister (オプション): カスタム ClassPersister を指定する。

(13)

batch-size (オプション - デフォルトは、1): 識別子によって、このクラスのインスタンスをフェッチするための "バッチサイズ" を指定する。

(14)

optimistic-lock (オプション - デフォルトは、version): optimistic ロッキング戦略を決定する。

(15)

lazy (オプション): lazy="true" をセットするのは、proxy インターフェースとしてクラス自身の名前を指定するのと等価なショートカットである。

指定する永続クラスがインターフェースであっても何の問題もありません。その時には、<subclass> 要素を使って、そのインターフェースを実装するクラスを宣言することになります。任意の static な内部クラスを永続化する場合があるかもしれません。その場合には、標準形式、つまり、eg.Foo$Bar を使って、クラス名を指定すべきです。

不変クラス、つまり mutable="false" を指定していると、アプリケーションが更新や削除ができない場合があります。これによって、Hibernate は、パフォーマンスのマイナーな最適化が可能になります。

オプションの proxy 属性によって、クラスの永続インスタンスの遅延初期化が、可能になります。Hibernate は、まず、指定したインターフェースを実装する CGLIB プロキシーを返します。実際の永続オブジェクトは、プロキシーのメソッドが呼び出される時にロードされます。以下の "遅延初期化のためのプロキシー" を参照して下さい。

暗黙的なポリモーフィズムとは、クラスのインスタンスを、任意のスーパークラスや実装するインターフェースやクラスを指定するクエリーが返す、あるいは、クラスの任意のサブクラスのインスタンスをクラス自身を指定するクエリーが返すということです。一方、明示的なポリモーフィズムとは、クラスのインスタンスを、そのクラスを明示的に指定するクエリーだけが返し、クラスを指定するクエリーは、この <class> 宣言の内部で <subclass><joined-subclass> としてマップされたサブクラスのインスタンスだけを返すということです。多くの目的にとって、デフォルトの polymorphism="implicit" は適切です。明示的なポリモーフィズムが役に立つのは、2 つの異なったクラスが同じテーブルにマップされる場合です (これによって、テーブルカラムのサブセットを含む "軽量 (lightweight)" クラスが可能になります)。

persister 属性によって、そのクラスに使用する永続戦略をカスタマイズすることができます。例えば、net.sf.hibernate.persister.EntityPersister の自分自身のサブクラスを指定したり、ストアドプロシージャーコールやフラットファイルへのシリアル化、LDAP などによって永続性を実装する、インターフェース net.sf.hibernate.persister.ClassPersister の完全に新しい実装を提供したい場合があるかもしれません。(Hashtable への "永続化" の) 単純な例については、net.sf.hibernate.test.CustomPersister を参照して下さい。

dynamic-updatedynamic-insert セッティングは、スーパークラスから継承されないため、<subclass><joined-subclass> 要素で指定しなければならない場合がある点に注意して下さい。こうしたセッティングは、パフォーマンスを向上させることもあれば、パフォーマンスを悪化させることもあります。よく考えてから、使用して下さい。

select-before-update を使用すると、通常、パフォーマンスは悪くなります。しかしながら、不必要に呼び出されるデータベース更新のトリガーとなるのを防ぐという点で、非常に有用です。

dynamic-update を有効にすると、optimistic なロッキング戦略を選ぶことができます。

  • version: バージョン/タイムスタンプカラムをチェックする

  • all: すべてのカラムをチェックする

  • dirty: 変更されたカラムをチェックする

  • none: optimistic ロッキングを使わない

Hibernate を使った optimistic ロッキングには、バージョン/タイムスタンプカラムを使うことを非常に強くお勧めします。これは、パフォーマンスに関する最適な戦略で、セッションの外部で行なわれる (つまり、Session.update() を使っている場合) 変更を正しく処理する唯一の戦略だからです。どんな unsaved-value 戦略を使っている場合でも、また、インスタンスが一時的なものとして検知される場合であっても、バージョンやタイムスタンププロパティーは null であってはならないことを覚えておいて下さい。

5.1.4. id

マップされるクラスは、データベースのテーブルの主キーカラムを宣言しなければなりません。ほとんどのクラスは、インスタンスの一意な識別子を保持する JavaBeans スタイルのプロパティーももちます。<id> 要素は、そうしたプロパティーから主キーカラムへのマッピングを定義します。

<id
        name="propertyName"                      (1)
        type="typename"                          (2)
        column="column_name"                     (3)
        unsaved-value="any|none|null|id_value"   (4)
        access="field|property|ClassName">       (5)

        <generator class="generatorClass"/>
</id>
(1)

name (オプション): 識別子プロパティーの名前。

(2)

type (オプション): Hibernate の型を指示する名前。

(3)

column (オプション - デフォルトは、プロパティー名): 主キーカラムの名前。

(4)

unsaved-value (オプション - デフォルトは、null): インスタンスが新しくインスタンス化された (保存されていない) ことを指示する識別子プロパティー。これ以前のセッションに保存されたかロードされた一時的インスタンスから区別するのに使われる。

(5)

access (オプション - デフォルトは、property): プロパティーの値にアクセスするのに Hibernate が使用する戦略。

name 属性を指定しない場合、クラスは、識別子プロパティーをもたないと見なされます。

unsaved-value 属性は重要です。クラスの識別子プロパティーのデフォルトが null ではない場合には、実際のデフォルトを指定すべきです。

複合キーを使ってレガシーデータへのアクセスを可能にする代替の <composite-id> がありますが、それ以外のためには、これを使用しないよう強くお勧めします。

5.1.4.1. generator

必須の <generator> 子供要素は、永続クラスのインスタンスに一意な識別子を生成するために使用する Java クラスを指定します。ジェネレーターインスタンスを設定したり、初期化するのに、パラメータが必要な場合、それらは、<param> 要素を使って渡されます。

<id name="id" type="long" column="uid" unsaved-value="0">
        <generator class="net.sf.hibernate.id.TableHiLoGenerator">
                <param name="table">uid_table</param>
                <param name="column">next_hi_value_column</param>
        </generator>
</id>

ジェネレーターはすべて、インターフェース net.sf.hibernate.id.IdentifierGenerator を実装します。これは、非常に単純なインターフェースです。アプリケーションによっては、自分自身の専門用途化された実装を提供しようとするかもしれません。しかしながら、Hibernate は、広範な組み込みの実装を提供しています。以下は、組み込みのジェネレーターのショートカット名です。

increment

型が longshortint の識別子を生成する。これらは、他のプロセスが同じテーブルにデータを挿入しない場合にのみ一意である。クラスター内では使用してはならない。

identity

DB2、MySQL、MS SQL Server、Sybase、HypersonicSQL で、同一性カラム (identity columns) をサポートする。返される識別子の型は、longshortint である。

sequence

DB2、PostgreSQL、Oracle、SAP DB、McKoi や Interbase のジェネレーターでシーケンスを使用する。返される識別子の型は、longshortint である。

hilo

hi の値としてテーブルとカラム (デフォルトでは、それぞれ、hibernate_unique_keynext_hi) を指定すると、型が longshortint の識別子を効率的に生成するために、hi/lo アルゴリズムを使用する。hi/lo アルゴリズムは、特定のデータベースのためだけに一意な識別子を生成する。このジェネレーターを、JTA でリストされた接続や、ユーザー提供の接続とともに使用してはならない。

seqhilo

型が longshortint、指定したデータベースシーケンスの識別子を効率的に生成するため、hi/lo アルゴリズムを使用する。

uuid.hex

(IP アドレスを使用する) ネットワーク内で、一意な文字列方の識別子を生成するため、128-bit UUID アルゴリズムを使用する。UUID は、長さが 32 ビットの 16 進数の文字列としてエンコーディングされる。

uuid.string

同じ UUID アルゴリズムを使用する。UUID h、(任意の) ASCII 文字からなる長さが 16 ビットの文字列としてエンコーディングされる。PostgreSQL とともに使用してはならない。

native

基礎にあるデータベースの機能に応じて、identitysequencehilo を使用する。

assigned

save() を呼び出す前に、アプリケーションに、オブジェクトの識別子を割り当てさせる。

foreign

その他の関連するオブジェクトの識別子を使用する。通常、<one-to-one> 主キー関連と組み合わせて使われる。

5.1.4.2. Hi/Lo アルゴリズム

hiloseqhilo ジェネレーターは、識別子生成の本命のアプローチである hi/lo アルゴリズムに対する 2 つの代替実装を提供します。最初の実装は、次に利用可能な "hi" 値を保持する "特殊な" データベースのテーブルを必要とします。2 つ目の実装は、 (サポートされる場合には) Oracle スタイルのシーケンスを使用します。

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

残念ながら、Hibernate に、自分自身の Connection を提供する場合や、Hibernate が、JTA でリストされている接続を取得するために、アプリケーションサーバーデータソースを使っている場合には、hilo は使用できません。Hibernate は、新しいトランザクションで、"hi" 値をフェッチできなければなりません。EJB 環境における標準的なアプローチは、ステートレスセッション Bean を使って、hi/lo アルゴリズムを実装することです。

5.1.4.3. UUID アルゴリズム

UUID には、次のものが含まれます。IP アドレス、(1/4 秒の正確さでの) JVM の起動時刻、システム時刻そして (JVM 内で一意な) カウンター値です。Java コードから MAC アドレスやメモリーアドレスを取得することはできません。そのため、これが、JNI を使わずに行なうことのできる最善です。

PostgreSQL で uuid.string を使おうとはしないで下さい。

5.1.4.4. 同一性カラムとシーケンス

同一性カラムをサポートするデータベース (DB2、MySQL、Sybase、MS SQL) では、identity キー生成を使いたいかもしれません。シーケンスをサポートするデータベース (DB2、Oracle、PostgreSQL、Interbase、McKoi、SAP DB) では、sequence スタイルのキー生成を使いたいかもしれません。これら両方の戦略には、新しいオブジェクトを挿入するために、2 つの SQL クエリーが必要です。

<id name="id" type="long" column="uid">
        <generator class="sequence">
                <param name="sequence">uid_sequence</param>
        </generator>
</id>
<id name="id" type="long" column="uid" unsaved-value="0">
        <generator class="identity"/>
</id>

クロスプラットフォームでの開発の場合、native 戦略は、基礎となるデータベースの機能に応じて、identitysequencehilo から選ばれることでしょう。

5.1.4.5. 割り当てる識別子

(Hibernate に識別子を生成させるのに対して) アプリケーションに識別子を割り当てさせたい場合には、assigned ジェネレーターを使うとよいかもしれません。この特殊なジェネレーターは、オブジェクトの識別子プロパティーに既に割り当てられている識別子を使います。この機能を使ってビジネス的な意味をもつキーを割り当てる場合には、注意をして下さい (これは、ほとんど常に、危険なデザイン上の決定となります)。

その固有な性質によって、このジェネレーターを使うエンティティーは、セッションの saveOrUpdate() メソッドによって保存することはできません。代わりに、セッションの save()update() を使ってオブジェクトを保存か更新することを、明示的に、Hibernate に指定しなければなりません。

5.1.5. composite-id

<composite-id
        name="propertyName"
        class="ClassName"
        unsaved-value="any|none"
        access="field|property|ClassName">

        <key-property name="propertyName" type="typename" column="column_name"/>
        <key-many-to-one name="propertyName class="ClassName" column="column_name"/>
        ......
</composite-id>

複合キーをもつテーブルの場合、クラスの複数プロパティーを識別子プロパティーとしてマップしたいかもしれません。<composite-id> 要素は、子供要素として、<key-property> プロパティーマッピングと <key-many-to-one> を受け付けます。

<composite-id>
        <key-property name="medicareNumber"/>
        <key-property name="dependent"/>
</composite-id>

永続クラスは、equals()hashCode() をオーバーライドして、複合識別子の等価性を実装しなければなりません。これは、Serializable も実装しなければなりません。

残念ながら、識別子を複合するこのアプローチは、永続オブジェクトが、それ自身の識別子でなければならないという意味になります。オブジェクト自身よりも手軽な "ハンドル" はありません。複合キーと関連付けられた永続状態を load() できる以前に、永続クラス自身のインスタンスをインスタンス化して、その識別子プロパティーを作り出さなければならないからです。セクション 7.4 “ 複合識別子としてのコンポーネント”で、複合識別子を、別のクラスとして実装するはるかに手軽なアプローチを説明することにします。以下で説明する属性は、この代替アプローチだけに適用されるものです。

  • name (オプション): 複合識別子を保持するコンポーネント型のプロパティー (次のセクションを参照せよ)。

  • class (オプション - デフォルトは、リフレクションが決定するプロパティーの型): 複合識別子として使われるコンポーネントクラス (次のセクションを参照せよ)。

  • unsaved-value (オプション - デフォルトは、none): any にセットした場合、一時的なインスタンスを新しくインスタンス化されたものと見なすように指示する。

5.1.6. discriminator

<discriminator> は、クラスごとのテーブル階層 (table-per-class-hierarchy) マッピング戦略を使ってポリモーフィズム的な永続性の場合に必要であり、テーブルの判別子 (discriminator) カラムを宣言します。判別子カラムには、特定の行のためにどのサブクラスをインスタンス化するかを永続層に指示するマーカー値が含まれます。制限付きの型セットには、次のものがあります。string, character, integer, byte, short, boolean, yes_no, true_false です。

<discriminator
        column="discriminator_column"  (1)
        type="discriminator_type"      (2)
        force="true|false"             (3)
/>
(1)

column (オプション - デフォルトは、class): 判別子カラムの名前。

(2)

type (オプション - デフォルトは、string): Hibernate の型を指示する名前。

(3)

force (オプション - デフォルトは、false): ルートクラスのすべてのインスタンスを検索する場合であっても、Hibernate に、許される判別子の値を指定するように "強制する"。

判別子カラムの実際の値は、<class><subclass> 要素の discriminator-value 属性によって指定します。

force 属性は、テーブルが、永続クラスにマップされない "特別な" 判別子の値をもつ行が含まれる場合に (のみ) 役立ちます。これは、通常、あり得るケースではありません。

5.1.7. version (オプション)

<version> 要素はオプションで、テーブルにバージョン付きのデータがあることを指示します。これは、長いトランザクションを使おうと計画している場合に、特に役立ちます (以下を参照して下さい)。

<version
        column="version_column"                            (1)
        name="propertyName"                                (2)
        type="typename"                                    (3)
        access="field|property|ClassName"                  (4)
        unsaved-value="null|negative|undefined"            (5)
/>
(1)

column (オプション - デフォルトは、プロパティーの名前): バージョン番号を保持するカラムの名前。

(2)

name: 永続クラスのプロパティーの名前。

(3)

type (オプション - デフォルトは、integer): バージョン番号の型。

(4)

access (オプション - デフォルトは、property): Hibernate がプロパティーの値にアクセスするのに使用する戦略。

(5)

unsaved-value (オプション - デフォルトは、undefined): インスタンスを新しくインスタンス化し (保存せずに)、それを、以前のセッションで保存やロードされた一時的なインスタンスから区別するように指示するバージョンプロパティーの値。

バージョン番号の型は、long, integer, short, timestamp, calendar のいずれかです。

5.1.8. timestamp (オプション)

オプションの <timestamp> 要素は、テーブルがタイムスタンプデータを含むことを示します。これは、バージョン付けの代替であることが意図されています。タイムスタンプは、本質的に、optimistic ロッキングのあまり安全ではない実装です。しかしながら、時には、アプリケーションは、それ以外の方法で、タイムスタンプを使う場合があるかもしれません。

<timestamp
        column="timestamp_column"           (1)
        name="propertyName"                 (2)
        access="field|property|ClassName"   (3)
        unsaved-value="null|undefined"      (4)
/>
(1)

column (オプション - デフォルトは、プロパティー名): タイムスタンプを保持するカラムの名前。

(2)

name: Java 型の Date や、永続クラスの Timestamp の JavaBeans スタイルのプロパティーの名前。

(3)

access (オプション - デフォルトは、property): Hibernate がプロパティーの値にアクセスするために使用する戦略。

(4)

unsaved-value (オプション - デフォルトは、null): インスタンスを新しくインスタンス化し (保存せずに)、それを、以前のセッションで保存やロードされた一時的なインスタンスから区別するように指示するバージョンプロパティーの値。(undefined は、識別子プロパティーの値を使用することを指定する。)

<timestamp> は、<version type="timestamp"> と等価である点に注意して下さい。

5.1.9. property

<property> 要素は、そのクラスの永続的な、JavaBean スタイルのプロパティーを宣言します。

<property
        name="propertyName"                 (1)
        column="column_name"                (2)
        type="typename"                     (3)
        update="true|false"                 (4)
        insert="true|false"                 (4)
        formula="arbitrary SQL expression"  (5)
        access="field|property|ClassName"   (6)
/>
(1)

name: 小文字で始まるプロパティーの名前。

(2)

column (オプション - デフォルトは、プロパティーの名前): マップされたデータベースのテーブルのカラムの名前。

(3)

type (オプション): Hibernate の型を示す名前。

(4)

update, insert (オプション - デフォルトは、true) :マップされたカラムが、SQL の UPDATE そして/あるいは INSERT 文に含まれることを指定する。両方を false にセットすると、その値が同じカラム (群) にマップされるプロパティーから初期化されるか、トリガーや他のアプリケーションによって初期化される純粋な "派生" プロパティーであることが可能になる。

(5)

formula (オプション): computed プロパティーのための値を定義する SQL 式。computed プロパティーは、それ自身のカラムマッピングをもたない。

(6)

access (オプション - デフォルトは、property): Hibernate がプロパティーの値にアクセスするために使用する戦略。

typename は、次のいずれかを取ることができます。

  1. Hibernate の基本型の名前 (例えば、integer, string, character, date, timestamp, float, binary, serializable, object, blob)。

  2. デフォルトの基本型をもつ Java クラスの名前 (例えば、int, float, char, java.lang.String, java.util.Date, java.lang.Integer, java.sql.Clob)。

  3. PersistentEnum のサブクラスの名前 (例えば、eg.Color)。

  4. シリアル化可能な Java クラスの名前。

  5. カスタム型のクラス名 (例えば、com.illflow.type.MyCustomType)。

型を指定しない場合、Hibernate は、正しい Hibernate の型を推測するために、指定されたプロパティーに対してリフレクションを使います。Hibernate は、ルール 2, 3, 4 をこの順で使って、ゲッター (getter) プロパティーについて返されるクラスの名前を解釈しようとします。しかしながら、これがいつでも十分だとは限りません。場合によっては、更に、type 属性が必要です。(例えば、Hibernate.DATEHibernate.TIMESTAMP を識別するために、あるいは、カスタム型を指定するためにです。)

access 属性によって、Hibernate が、実行時に、プロパティーにアクセスする方法をコントロールすることができます。デフォルトでは、Hibernate は、プロパティーの get/set ペアを呼び出します。access="field" を指定する場合、Hibernate は、get/set ぺを迂回して、リフレクションを使ってフィールドに、直接、アクセスします。インターフェース net.sf.hibernate.property.PropertyAccessor を実装するクラスを指定することにより、プロパティーにアクセスする自分自身の戦略を指定する場合があるかもしれません。

5.1.10. many-to-one

別の永続クラスに対する通常の関連は、many-to-one 要素を使って宣言します。リレーショナルモデルは、多対 1 (many-to-one) 関連です。(それは、実際には、オブジェクト参照にすぎません。)

<many-to-one
        name="propertyName"                                (1)
        column="column_name"                               (2)
        class="ClassName"                                  (3)
        cascade="all|none|save-update|delete"              (4)
        outer-join="true|false|auto"                       (5)
        update="true|false"                                (6)
        insert="true|false"                                (6)
        property-ref="propertyNameFromAssociatedClass"     (7)
        access="field|property|ClassName"                  (8)
/>
(1)

name: プロパティーの名前。

(2)

column (オプション): カラムの名前。

(3)

class (オプション - デフォルトは、リフレクションが決定するプロパティーの型): 関連付けられたクラスの名前。

(4)

cascade (オプション): どのオペレーションが、親オブジェクトから関連付けられたオブジェクトにカスケードするかを指定する。

(5)

outer-join (オプション - デフォルトは、auto): hibernate.use_outer_join がセットしている場合に、この関連に対して外部結合によるフェッチを可能にする。

(6)

update, insert (オプション - デフォルトは、true): マップされたカラムが、SQL の UPDATE そして/あるいは INSERT 文に含まれることを指定する。両方を false にセットすると、その値が同じカラム (群) にマップされるプロパティーから初期化されるか、トリガーや他のアプリケーションによって初期化される純粋な "派生" プロパティーであることが可能になる。

(7)

property-ref (オプション): この外部キーに結合される関連付けられたクラスのプロパティーの名前。指定しない場合には、関連付けられたクラスの主キーが使われる。

(8)

access (オプション - デフォルトは、property): プロパティーの値にアクセスするために Hibernate が使用する戦略。

cascade 属性は、次の値を許します。all, save-update, delete, none です。none 以外の値をセットすると、操作によっては、関連付けられた (子供) オブジェクトに伝播します。以下の "ライフサイクルオブジェクト" を参照して下さい。

outer-join 属性は、3 つの異なった値を受け付けます。

  • auto (デフォルト): 関連付けされたクラスにプロキシーがない場合、外部結合を使って関連をフェッチする。

  • true: いつでも、外部結合を使って、関連をフェッチする。

  • false: 外部結合を使って、関連をフェッチすることはしない。

よくある many-to-one 宣言は、次のように単純です。

<many-to-one name="product" class="Product" column="PRODUCT_ID"/>

property-ref 属性は、外部キーが関連付けられたテーブルの主キーではなく、一意なキーを参照しているレガシーデータをマップするためだけに使うべきです。これは、美しくないリレーショナルモデルです。例えば、主クラス Product に、主キーではない一意なシリアル番号があるとしましょう。(unique 属性は、SchemaExport ツールを使った Hibernate のDDL 生成をコントロールします。)

<property name="serialNumber" unique="true" type="string" column="SERIAL_NUMBER"/>

次に、OrderItem のためのマッピングは、次のものを使うかもしれません。

<many-to-one name="product" property-ref="serialNumber" column="PRODUCT_SERIAL_NUMBER"/>

しかしながら、これが、お勧めできないものであることは確かです。

5.1.11. one-to-one

別の永続クラスへの 1 対 1 (one-to-one) 関連は、one-to-one 要素を使って宣言します。

<one-to-one
        name="propertyName"                                (1)
        class="ClassName"                                  (2)
        cascade="all|none|save-update|delete"              (3)
        constrained="true|false"                           (4)
        outer-join="true|false|auto"                       (5)
        property-ref="propertyNameFromAssociatedClass"     (6)
        access="field|property|ClassName"                  (7)
/>
(1)

name: プロパティーの名前。

(2)

class (オプション - デフォルトは、リフレクションが決定したプロパティーの型): 関連付けられたクラスの名前。

(3)

cascade (オプション): どの操作が親オブジェクトから関連付けられたオブジェクトにカスケードするかを指定する。

(4)

constrained (オプション): マップされたテーブルの主キーに対する外部キー制約が関連付けられたクラスのテーブルを参照することを指定する。このオプションは、save()delete() がカスケードされる順序に影響を与える (そして、スキーマエクスポートツールも使用する)。

(5)

outer-join (オプション - デフォルトは、auto): hibernate.use_outer_join をセットする場合、この関連に対する外部結合によるフェッチを有効にする。

(6)

property-ref: (オプション) このクラスの主キーに結合される関連付けられたクラスのプロパティーの名前。指定しない場合、関連付けられたクラスの主キーが使われる。

(7)

access (オプション - デフォルトは、property): プロパティーの値にアクセスするために Hibernate が使用する戦略。

1 対 1 (one-to-one) 関連には、2 つの変種があります。

  • 主キー関連

  • 一意な外部キー関連

主キー関連は、特別なテーブルカラムを必要としません。2 つの行を関連によって関連付ける場合には、2 つのテーブル行は、同じ主キー値を共有します。そのため、2 つのオブジェクトを主キー関連によって関連付けたい場合には、それらに同じ識別子の値を割り当てていることを確かめなければなりません。

主キー関連の場合、次のマッピングを、それぞれ、EmployeePerson に追加します。

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

Person の新しく保存されるインスタンスは、その Personemployee プロパティーによって参照される Employee インスタンスと同じ主キー値を割り当てられます。

それ以外の方法として、Employee から 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. component, dynamic-component

<component> 要素は、子供オブジェクトのプロパティーを親クラスのテーブルのカラムにマップします。コンポーネントは、次に、それ自身のプロパティー、コンポーネントやコレクションを宣言する場合があります。以下の "コンポーネント" を参照して下さい。

<component 
        name="propertyName"                 (1)
        class="className"                   (2)
        insert="true|false"                 (3)
        upate="true|false"                  (4)
        access="field|property|ClassName">  (5)
        
        <property ...../>
        <many-to-one .... />
        ........
</component>
(1)

name: プロパティーの名前。

(2)

class (オプション - デフォルトは、リフレクションが決定するプロパティーの型): コンポーネント (子供) クラスの名前。

(3)

insert: マップされるカラムは、SQL の INSERT 内に現われるかどうかを指定する。

(4)

update: マップされるカラムは、 SQL の UPDATE 内に現われるかどうかを指定する。

(5)

access (オプション - デフォルトは、property): プロパティーの値にアクセスするために Hibernate が使用する戦略。

子供の <property> タグは、子供クラスのプロパティーをテーブルのカラムにマップする場合があります。

<component> 要素は、<parent> サブ要素を許容します。それは、内包するエンティティーを参照し返すものとしてコンポーネントクラスのプロパティーをマップするものです。

<dynamic-component> は、プロパティー名がマップのキーを参照するコンポーネントとしてマップされる Map を許容します。

5.1.13. subclass

最後に、ポリモーフィズム的永続性は、ルート永続クラスの個々のサブクラスの宣言を必要とします。(推奨される) クラスごとのテーブル階層マッピング戦略の場合、<subclass> 宣言が使われます。

<subclass
        name="ClassName"                              (1)
        discriminator-value="discriminator_value"     (2)
        proxy="ProxyInterface"                        (3)
        lazy="true|false"                             (4)
        dynamic-update="true|false"
        dynamic-insert="true|false">

        <property .... />
        .....
</subclass>
(1)

name: サブクラスを完全に修飾したクラス名。

(2)

discriminator-value (オプション - デフォルトは、クラス名): 個々のサブクラスを識別する値。

(3)

proxy (オプション): プロキシーを遅延初期化するために使用するクラスかインターフェースを指定する。

(4)

lazy (オプション): lazy="true" をセットするのは、クラス自身の名前を proxy インターフェースとして指定するのと等価なショートカットである。

個々のサブクラスには、それ自身の永続プロパティーとサブクラスを宣言すべきです。<version><id> プロパティーは、ルートクラスから継承されているものと想定されます。継承内の個々のサブクラスには、一意な discriminator-value を定義しなければなりません。何も指定しなければ、完全に修飾された Java クラス名が使用されます。

5.1.14. joined-subclass

それ自身のテーブルに永続化されるサブクラス (サブクラスごとのテーブルマッピング戦略) は、<joined-subclass> 要素を使って宣言されます。

<joined-subclass
        name="ClassName"                    (1)
        proxy="ProxyInterface"              (2)
        lazy="true|false"                   (3)
        dynamic-update="true|false"
        dynamic-insert="true|false">

        <key .... >

        <property .... />
        .....
</joined-subclass>
(1)

name: サブクラスを完全に修飾したクラス名。

(2)

proxy (オプション): プロキシーを遅延初期化するのに使用するクラスかインターフェースを指定する。

(3)

lazy (オプション): lazy="true" をセットするのは、クラス自身の名前を proxy インターフェースとして指定するのと等価なショートカットである。

判別子 (discriminator) カラムは、このマッピング戦略には必要ありません。しかしながら、個々のサブクラスは、<key> 要素を使ってオブジェクト識別子を保持するテーブルのカラムを宣言しなければなりません。この章の最初にあるマッピングは、次のように書き換えることができます。

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC
        "-//Hibernate/Hibernate Mapping DTD//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-2.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>

5.1.15. map, set, list, bag

コレクションについては、後で説明します。

5.1.16. import

アプリケーションに、同じ名前をもつ 2 つの永続クラスがあり、Hibernate のクエリー内で完全に修飾した (パッケージの) 名前を指定したくないとしましょう。クラスは、auto-import="true" によることなく、明示的に、"インポートされる" 場合があります。明示的にマップされた訳ではないクラスとインターフェースをインポートする場合もあるかもしれません。

<import class="java.lang.Object" rename="Universe"/>
<import
        class="ClassName"              (1)
        rename="ShortName"             (2)
/>
(1)

class: 任意の Java クラスを完全に修飾したクラス名。

(2)

rename (オプション - デフォルトは、修飾されていないクラス名): クエリー言語内で使用する名前。

5.2. Hibernate の型

5.2.1. エンティティーと値

永続化サービスと関連して様々な Java 言語レベルの振る舞いを理解するには、それらを 2 つのグループに分類する必要があります。

エンティティー (entity) は、エンティティーへの参照を保持する他のどんなオブジェクトとも無関係に存在します。これを、参照されないオブジェクトがガベージコレクトされる通常の Java モデルと対比して下さい。エンティティーは、(保存と削除が親エンティティーからその子供にカスケードされている以外は)、明示的に、保存し、削除しなければなりません。これは、到達可能性 (reachability) によるオブジェクト永続性の ODMG モデルとは異なります。そして、アプリケーションオブジェクトが大規模システムで使われる方法ともっと密接に対応しています。エンティティーは、循環し、共有される参照をサポートします。それらは、バージョン付けされる場合もあります。

エンティティーの永続状態は、他のエンティティーとの型への参照からなります。値はプリミティブ、コレクション、コンポーネントと不変オブジェクトのいずれかです。エンティティーとは違って、値 (特に、コレクションとコンポーネント) は、到達可能性によって、永続化され、削除されます。値オブジェクト (とプリミティブ) は、包含するエンティティーとともに永続化、削除されるので、それらは、独立にバージョン付けされない場合があります。値は、独立した同一性をもちません。そのため、それらは、2 つのエンティティーやコレクションが共有することはできません。

コレクションを除く Hibernate の型はすべて、null セマンティクスをサポートします。

ここまで、エンティティーを参照するのに "永続クラス" という用語を使ってきました。これからも、それを使い続けることにしますが、厳密に言えば、永続状態をもつユーザー定義クラスのすべてがエンティティーという訳ではありません。コンポーネントは、値セマンティクスをもつユーザー定義クラスです。

5.2.2. Basic value types

基本的な型は、大まかには、次のようにカテゴリー化されます。

integer, long, short, float, double, character, byte, boolean, yes_no, true_false

Java プリミティブやラッパークラスから適切な (ベンダー固有の) SQL カラム型への型マッピング。boolean, yes_notrue_false はすべて、Java の booleanjava.lang.Boolean に対する代替エンコーディングである。

string

java.lang.String から VARCHAR (あるいは Oracle の VARCHAR2) への型マッピング。

date, time, timestamp

java.util.Date とそのサブクラスの SQL 型 DATE, TIMETIMESTAMP (あるいは、その等価物) への型マッピング。

calendar, calendar_date

java.util.Calendar から SQL 型 TIMESTAMPDATE (あるいは、その等価物) への型マッピング。

big_decimal

java.math.BigDecimal から NUMERIC (あるいは Oracle の NUMBER) への型マッピング。

locale, timezone, currency

java.util.Locale, java.util.TimeZonejava.util.Currency から VARCHAR (あるいは Oracle の VARCHAR2) への型マッピング。LocaleCurrency のインスタンスは、ISO コードにマップされる。TimeZone のインスタンスは、その ID にマップされる。

class

java.lang.Class から VARCHAR (あるいは Oracle の VARCHAR2) への型マッピング。Class は、それを完全に修飾した名前にマップされる。

binary

バイト配列を適切な SQL バイナリー型にマップする。

text

長い Java の文字列を SQL の CLOBTEXT にマップする。

serializable

シリアル化可能な Java 型を適切な SQL バイナリー型にマップする。デフォルトが基本的な型ではなく、PersistentEnum を実装しない、シリアル化可能な Java クラスやインターフェースの名前を使って Hibernate の型 serializable を指示することもできる。

clob, blob

JDBC クラス java.sql.Clobjava.sql.Blob のための型マッピング。これらの型は、アプリケーションによっては、不便かもしれない。blob や clob オブジェクトは、トランザクションの外部で再利用されることはないかもしれないからである。(更に、ドライバーサポートは、つぎはぎだらけで不便である。)

エンティティーとコレクションの一意な識別子は、binary, blobclob 以外の任意の基本型であるかもしれません。(複合識別子も許されます、以下を参照して下さい。)

基本的な値の型は、net.sf.hibernate.Hibernate で定義された、対応する Type 定数をもちます。例えば、Hibernate.STRING は、string 型を表わします。

5.2.3. 永続的な列挙型

列挙される (enumerated) 型は、クラスが一定の (少ない) 数の不変インスタンスをもつ一般的な Java の慣用です。toInt()fromInt() 操作を定義する net.sf.hibernate.PersistentEnum を実装することによって、永続的な列挙される型を作る場合があるかもしれません。

package eg;
import net.sf.hibernate.PersistentEnum;

public class Color implements PersistentEnum {
    private final int code;
    private Color(int code) {
        this.code = code;
    }
    public static final Color TABBY = new Color(0);
    public static final Color GINGER = new Color(1);
    public static final Color BLACK = new Color(2);

    public int toInt() { return code; }

    public static Color fromInt(int code) {
        switch (code) {
            case 0: return TABBY;
            case 1: return GINGER;
            case 2: return BLACK;
            default: throw new RuntimeException("Unknown color code");
        }
    }
}

Hibernate の型名は、列挙されるクラスの名前にすぎません。この場合は、eg.Color です。

5.2.4. カスタム値の型

開発者が、自分自身の値型を作るのは、比較的簡単です。例えば、java.lang.BigInteger 型の永続プロパティーを VARCHAR カラムに永続化させたい場合があるかもしれません。Hibernate は、このための組み込みの型を提供していません。しかし、カスタム型は、プロパティー (やコレクション要素) を 1 つのテーブルのカラムにマップするのに限定されません。そのため、例えば、カラム FIRST_NAME, INITIAL, SURNAME に永続化される java.lang.String 型の Java プロパティー getName()/setName() をもつかもしれません。

カスタム型を実装するには、net.sf.hibernate.UserTypenet.sf.hibernate.CompositeUserType を実装し、その型の完全に修飾されたクラス名を使ってプロパティーを宣言します。どんな種類のものが可能か調べるには、net.sf.hibernate.test.DoubleStringType をチェックして下さい。

<property name="twoStrings" type="net.sf.hibernate.test.DoubleStringType">
    <column name="first_string"/>
    <column name="second_string"/>
</property>

複数のカラムにプロパティーをマップするには、<column> タグを使う点に注意して下さい。

Hibernate の広範囲に渡る内容豊かな組み込みの型とコンポーネントのサポートによって、カスタム型を使う必要はほとんどないにも関わらず、アプリケーション内で頻繁に発生する (エンティティー以外の) クラスのためにカスタム型を使うにはよいことだと考えられます。例えば、MonetoryAmount クラスは、コンポーネントとして簡単にマップできるにも関わらず、CompositeUserType の優れた候補です。このための動機付けの 1 つは、抽象化です。カスタム型を使うと、マッピング文書は、通貨価値を表わす方法において、起こり得る将来の変更に備えたものとなるでしょう。

5.2.5. any 型マッピング

型マッピングには、もう 1 つ別の型があります。<any> マッピング要素は、複数のテーブルから、クラスへのポリモーフィズム的関連を定義します。この型のマッピングは、常に、複数のカラムを必要とします。最初のカラムは、関連付けられるエンティティーの型を保持します。残りのカラムは、識別子を保持します。この種の関連に外部キー制約を指定することはできません。そのため、これを、マッピング (ポリモーフィズム的) 関連の通常の方法と言うことができないのはほとんど確かです。これは、非常に特殊なケース (例えば、ログやユーザーセッションデータの監査など) でだけ使うべきです。

<any name="anyEntity" id-type="long" meta-type="eg.custom.Class2TablenameType">
    <column name="table_name"/>
    <column name="id"/>
</any>

meta-type 属性によって、アプリケーションは、データベースのカラムの値を id-type が指定する型の識別子プロパティーをもつ永続クラスにマップするカスタム型を指定することができます。meta-type が java.lang.Class のインスタンスを返す場合には、他の何も必要ありません。一方、それが stringcharacter などの基本型の場合は、値からクラスへのマップを指定しなければなりません。

<any name="anyEntity" 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"                      (1)
        id-type="idtypename"                     (2)
        meta-type="metatypename"                 (3)
        cascade="none|all|save-update"           (4)
        access="field|property|ClassName"        (5)
>
        <meta-value ... />
        <meta-value ... />
        .....
        <column .... />
        <column .... />
        .....
</any>
(1)

name: プロパティー名。

(2)

id-type: 識別子の型。

(3)

meta-type (オプション - デフォルトは、class): java.lang.Class を 1 つのデータベースのカラムにマップする型か、それ以外の場合、判別子のマッピングに許される型。

(4)

cascade (オプション - デフォルトは、none): カスケードのスタイル。

(5)

access (オプション - デフォルトは、property): プロパティーの値にアクセスするために Hibernate が使用する戦略。

Hibernate 1.2 の場合と類似したロールを満たす古い object 型は、今でも、サポートされていますが、それは、今では、半ば、非推奨となっています。

5.3. SQL のクオート付き識別子

Hibernate に、マッピングファイル内で、テーブルやカラムの名前をバッククオートで囲むことによって、生成される SQL 内で、識別子をクオートで囲むように強制する場合があるかもしれません。Hibernate は、SQL の Dialect に正しいクオートスタイルを使います (通常は、ダブルクオートですが、SQL Server の場合は、カッコ、MySQL の場合は、バッククオートです)。

<class name="LineItem" table="`Line Item`">
    <id name="id" column="`Item Id`"/><generator class="assigned"/></id>
    <property name="itemNumber" column="`Item #`"/>
    ...
</class>

5.4. モジュール化したマッピングファイル

hibernate-mapping の直下で、subclassjoined-subclass マッピングを異なったマッピング文書で定義することができます。これによって、新しいマッピングファイルを追加するだけで、クラス階層を拡張することが可能になります。その場合、サブクラスマッピングに、前にマップしたスーパークラスを指定する extends 属性を指定しなければなりません。この機能を使用する場合は、マッピングファイルを指定する順序が重要になります。

<hibernate-mapping>
        <subclass name="eg.subclass.DomesticCat" extends="eg.Cat" discriminator-value="D">
             <property name="name" type="string"/>
        </subclass>
</hibernate-mapping>

第 6 章 コレクションのマッピング

6.1. 永続コレクション

このセクションには、サンプルの Java コードは、あまりありません。ここでは、Java のコレクションフレームワークの使い方が既に分かっていると仮定しています。もしそうなら、ここには、実際上、新しく知るようなものはありません。1 つ注意しておくと、既に知っているように、Java コレクションを使えばよいということです。

Hibernate は、java.util.Map, java.util.Set, java.util.SortedMap, java.util.SortedSet, java.util.List と任意の永続エンティティーや値の配列のインスタンスを永続化できます。java.util.Collectionjava.util.List 型のプロパティーは、"bag" セマンティクスを使って永続化することもできます。

注意を 1 つ。永続コレクションは、コレクションインターフェースを実装するクラスによって付加される特別なセマンティクスを保持することはありません (例えば、LinkedHashSet の繰り返し順序)。永続コレクションは、実際には、それぞれ、HashMap, HashSet, TreeMap, TreeSetArrayList のように振る舞います。更に、コレクションを保持する Java 型は、インターフェース型でなければなりません (つまり、Map, SetList であって、HashMap, TreeSetArrayList であってはなりません)。こうした制限が存在するのは、気が付かないかもしれませんが、Hibernate は、密かに、Map, SetList のインスタンスを、Map, SetList のそれ自身の実装で置き換えるからです。(そのため、コレクション上で、== を使う場合には、注意が必要です。)

Cat cat = new DomesticCat();
Cat kitten = new DomesticCat();
....
Set kittens = new HashSet();
kittens.add(kitten);
cat.setKittens(kittens);
session.save(cat);
kittens = cat.getKittens(); // OK, kittens コレクションは、Set である
(HashSet) cat.getKittens(); // エラー !

コレクションは、共有される参照はなく、包含するエンティティーとともに作成、削除されるという、値型のための通常のルールに従います。基礎にあるリレーショナルモデルによって、それらは、null 値のセマンティクスをサポートしません。Hibernate は、null コレクション参照と空コレクションの区別をしません。

永続オブジェクトが参照する場合、コレクションは、自動的に永続化され、参照がなくなると、自動的に削除されます。ある永続オブジェクトがコレクションを別の永続オブジェクトに渡すと、その要素は、あるテーブルから別のテーブルに移されます。これについて、あまり思い悩む必要はありません。通常の Java コレクションを使うのと同じように Hibernate のコレクションを使えばよいからです。しかし、それらを使う前に、(後に説明する) 双方向関連を理解していることを確認しておく必要があります。

コレクションのインスタンスは、データベース内では、その所有エンティティーに対する外部キーによって区別されます。この外部キーは、コレクションキーと呼ばれます。コレクションキーは、<key> 要素によってマップされます。

コレクションは、その他のほとんどすべての Hibernate 型を含む場合があります。これには、すべての基本型、カスタム型、エンティティー型やコンポーネントが含まれます。これは、重要な定義です。つまり、コレクション内のオブジェクトは、"値渡し" セマンティクス (それ故、それは、コレクションの所有者に完全に依存します) によって処理することもできれば、それ自身のライフサイクルをもつ別のエンティティーへの参照であることもできます。コレクションは、他のコレクションを含まない場合があります。包含される型は、コレクション要素型と呼ばれます。コレクション要素は、<element>, <composite-element>, <one-to-many>, <many-to-many><many-to-any> によってマップされます。値セマンティクスをもつ最初の 2 つの要素と残りの 3 つは、エンティティー関連をマップするために使われます。

Set と bag を除くすべてのコレクション型は、インデックスカラムをもちます。これは、配列や List インデックスや Map キーにマップされるカラムです。Map のインデックスは、任意の基本型、エンティティー型である場合、あるいは複合型である場合さえあります (それは、コレクションではないかもしれません)。配列やリストのインデックスは、常に、integer 型です。インデックスは、<index>, <index-many-to-many>, <composite-index><index-many-to-any> を使ってマップされます。

コレクション用に生成可能なマッピングは、非常に広範囲に渡ります。これは、多くの一般的なリレーショナルモデルをカバーするものです。様々なマッピング宣言がどのようにデータベースのテーブルに翻訳されるのかの感覚をつかむために、スキーマ生成ツールを使って実験してみることをお勧めします。

6.2. コレクションをマップする

コレクションは、<set>, <list>, <map>, <bag>, <array><primitive-array> を使って宣言します。<map> は、代表的なものです。

<map
    name="propertyName"                                         (1)
    table="table_name"                                          (2)
    schema="schema_name"                                        (3)
    lazy="true|false"                                           (4)
    inverse="true|false"                                        (5)
    cascade="all|none|save-update|delete|all-delete-orphan"     (6)
    sort="unsorted|natural|comparatorClass"                     (7)
    order-by="column_name asc|desc"                             (8)
    where="arbitrary sql where condition"                       (9)
    outer-join="true|false|auto"                                (10)
    batch-size="N"                                              (11)
    access="field|property|ClassName"                           (12)
>

    <key .... />
    <index .... />
    <element .... />
</map>
(1)

name: コレクションプロパティーの名前。

(2)

table (オプション - デフォルトは、プロパティーの名前): コレクションテーブルの名前 (one-to-many 関連には使われない)。

(3)

schema (オプション): ルート要素で宣言されたスキーマをオーバーライドするためのテーブルスキーマの名前。

(4)

lazy (オプション - デフォルトは、false): 遅延初期化を有効にする (配列には使われない)。

(5)

inverse (オプション - デフォルトは、false): このコレクションを、双方向関連の "逆 (inverse)" 端としてマークする。

(6)

cascade (オプション - デフォルトは、none): 子供エンティティーをカスケードする操作を有効にする。

(7)

sort (オプション): natural ソート順か、指定された比較対象 (comparator) クラスによってソートされたコレクションを指定する。

(8)

order-by (オプション - JDK1.4 のみ): オプションの ascdesc とともに、Map, Set や bag の繰り返し順序を定義するテーブルのカラム (群) を指定する。

(9)

where (オプション): コレクションの検索や削除に使用する任意の SQL の WHERE 条件を指定する (コレクションに利用可能なデータのサブセットしかない場合に役立つ)。

(10)

outer-join (オプション): 可能な場合にはいつでも、コレクションを外部結合によってフェッチすることを指定する。SQL の SELECT ごとの外部結合によってフェッチされるのは、ただ 1 つのコレクションだけである。

(11)

batch-size (オプション - デフォルトは、1): このコレクションのインスタンスを遅延フェッチするための "バッチサイズ" を指定する。

(12)

access (オプション - デフォルトは、property): プロパティーの値にアクセスするために Hibernate が使用する戦略。

List や配列のマッピングには、配列やリストのインデックスを保持する異なったテーブルのカラムが必要です (foo[i] における i)。リレーショナルモデルにインデックスカラムがない場合、例えば、レガシーデータを使って作業している場合には、代わりに、順序のない Set を使います。これは、List の方が、順序のないコレクションにアクセスするもっと便利な方法であると想定する人々をはぐらかせるように思えるかもしれません。しかし、Hibernate は、Set, ListMap インターフェースに付随する実際のセマンティクスに厳密に従います。List 要素は、自発的に、それ自身を再配列するものではないのです。

一方、bag セマンティクスをエミュレートするために List を使おうと計画している人々は、ここで、不満に思うのが自然でしょう。bag は、同じ要素を何度でも含むことのできる、順序とインデックスのないコレクションです。Java コレクションフレームワークには、Bag インターフェースがありません。そのため、それは、List を使ってエミュレートしなければなりません。Hibernate では、ListCollection 型のプロパティーは、<bag> 要素を使ってマップすることができます。bag セマンティクスは、実際には、Collection 構文 (contract) の一部ではなく、事実上、List 構文のセマンティクスと衝突する点に注意して下さい (しかしながら、この章で後ほど説明するように、bag を任意の順にソートすることはできます)。

注意: inverse="false" を使ってマップされた Hibernate の bag は、非効率で、使用を避けるべきです。Hibernate は、個別に、行の作成、削除、更新をすることができません。その理由は、個々の行を特定するのに使用できるキーがないためです。

6.3. 値のコレクションと Many-to-Many 関連

コレクションテーブルが必要なのは、値のコレクションと、many-to-many (多対多) 関連 (Java コレクションのための自然なセマンティクス) としてマップされる他のエンティティーへの参照のコレクションのためです。テーブルは、(外部) キーカラム (群)、要素カラム (群)、そしておそらくインデックスカラム (群) を必要とします。

コレクションテーブルからそれを所有するクラスのコレクションテーブルへの外部キーは、<key> 要素を使って宣言します。

<key column="column_name"/>
(1)

column (必須): 外部キーカラムの名前。

マップやリストのようなインデックス付きのコレクションの場合、<index> 要素が必要です。リストの場合、このカラムは、0 から番号付けされた連続する整数をもちます。レガシーデータを扱わなければならない場合には、インデックスが本当に 0 から始まっているか確認して下さい。マップの場合、カラムは、任意の Hibernate 型の値をもちます。

<index
        column="column_name"                (1)
        type="typename"                     (2)
/>
(1)

column (必須): コレクションのインデックス値を保持するカラムの名前。

(2)

type (オプション - デフォルトは、integer): コレクションのインデックスの型。

それ以外に、マップは、エンティティー型のオブジェクトによってインデックス付けされる場合があります。ここでは、<index-many-to-many> 要素を使います。

<index-many-to-many
        column="column_name"                (1)
        class="ClassName"                   (2)
/>
(1)

column (必須): コレクションのインデックス値のための外部キーカラムの名前。

(2)

class (必須): コレクションのインデックスとして使用するエンティティークラス。

値のコレクションの場合には、<element> タグを使います。

<element
        column="column_name"                (1)
        type="typename"                     (2)
/>
(1)

column (必須): コレクション要素の値を保持するカラムの名前。

(2)

type (必須): コレクション要素の型。

それ自身のテーブルをもつエンティティーのコレクションは、many-to-many 関連 というリレーショナルな考え方に対応します。many-to-many 関連は、Java コレクションの最も自然なマッピングですが、通常、最善のリレーショナルモデルという訳ではありません。

<many-to-many
        column="column_name"                               (1)
        class="ClassName"                                  (2)
        outer-join="true|false|auto"                       (3)
    />
(1)

column (必須): 要素の外部キーカラムの名前。

(2)

class (必須): 関連付けられたクラスの名前。

(3)

outer-join (オプション - デフォルトは、auto): hibernate.use_outer_join をセットした場合、この関連に対する外部結合によるフェッチを有効にする。

例を幾つか示すことにしましょう。まず、文字列セットです。

<set name="names" table="NAMES">
    <key column="GROUPID"/>
    <element column="NAME" type="string"/>
</set>

(order-by 属性によって決められた繰り返しの順序をもつ) 整数を含む bag。

<bag name="sizes" table="SIZES" order-by="SIZE ASC">
    <key column="OWNER"/>
    <element column="SIZE" type="integer"/>
</bag>

エンティティーの配列 - この場合、多対多関連です (エンティティーは、cascade="all" の、ライフサイクルオブジェクトである点に注意して下さい)。

<array name="foos" table="BAR_FOOS" cascade="all">
    <key column="BAR_ID"/>
    <index column="I"/>
    <many-to-many column="FOO_ID" class="org.hibernate.Foo"/>
</array>

string から date へのインデックスをもつマップ。

<map name="holidays" table="holidays" schema="dbo" order-by="hol_name asc">
    <key column="id"/>
    <index column="hol_name" type="string"/>
    <element column="hol_date" type="date"/>
</map>

コンポーネントのリスト (次の章で説明します)。

<list name="carComponents" table="car_components">
    <key column="car_id"/>
    <index column="posn"/>
    <composite-element class="org.hibernate.car.CarComponent">
            <property name="price" type="float"/>
            <property name="type" type="org.hibernate.car.ComponentType"/>
            <property name="serialNumber" column="serial_no" type="string"/>
    </composite-element>
</list>

6.4. One-To-Many 関連

1 対多関連は、媒介するコレクションテーブルなしに、2 つのクラスのテーブルを、直接、リンクします。(これは、one-to-many リレーショナルモデルを実装します。) このリレーショナルモデルでは、Java コレクションのセマンティクスによって、失われるものがあります。

  • null 値は、マップ、セット、リストに含まれない場合がある。

  • 包含されるエンティティークラスのインスタンスは、コレクションの複数のインスタンスに属さない場合がある。

  • 包含されるエンティティークラスのインスタンスは、コレクションのインデックスの複数の値に現われない場合がある。

Foo から Bar への関連の場合、キーカラムとおそらくはインデックスカラムを、包含されるエンティティークラス Bar のテーブルに追加する必要があります。これらのカラムは、上で説明したように、<key><index> を使ってマップされます。

<one-to-many> タグは、1 対多関連を指示します。

<one-to-many class="ClassName"/>
(1)

class (必須): 関連付けられたクラスの名前。

<set name="bars">
    <key column="foo_id"/>
    <one-to-many class="org.hibernate.Bar"/>
</set>

<one-to-many> 要素は、カラムを何も宣言する必要はありません。同様に、table 名をどこにも指定する必要はありません。

非常に重要な注意: <one-to-many> 関連の <key> カラムに NOT NULL を宣言する場合、Hibernate は、関連の作成や更新を行なう際に、制約違反を引き起こす場合があります。この問題を防ぐには、inverse="true" とマークした多くの値のある終端 (訳注: 要するに "many" 側) (Set か bag) に、双方向関連を使わなければなりません。これについては、この章の後ろの部分にある双方向関連の説明を参照して下さい。

6.5. 遅延初期化

(配列以外の) コレクションは、遅延して初期化される場合があります。この意味は、そうしたコレクションは、アプリケーションがその状態にアクセスする必要がある場合にのみ、それをデータベースからロードするということです。初期化は、ユーザーに透過的に発生します。そのため、アプリケーションは、通常、これについて思い悩む必要はありません (事実、Hibernate がそれ自身のコレクション実装を必要とする主要な理由は、透過的な遅延初期化があるためです)。しかしながら、アプリケーションが次のようなことをしようとする場合

s = sessions.openSession();
User u = (User) s.find("from User u where u.name=?", userName, Hibernate.STRING).get(0);
Map permissions = u.getPermissions();
s.connection().commit();
s.close();

Integer accessLevel = (Integer) permissions.get("accounts");  // エラー!

アプリケーションの汚さに驚かれるかもしれません。Session がコミットされた時に、権限 (permissions) コレクションは初期化されなかったので、コレクションは、その状態をロードすることは決してできないでしょう。解決策は、コレクションから読み込む行をコミットの直前に移すことです。(しかしながら、この問題を解決するには、他にもっと高度な方法があります。)

それ以外の方法としては、非遅延コレクションを使います。遅延初期化は、上のようなバグをもたらす可能性があるので、遅延しないというのがデフォルトになっています。しかしながら、遅延初期化は、(効率性の理由から) ほとんどすべてのコレクションで、特に、エンティティーのコレクションで、使われることが意図されています。

コレクションを遅延して初期化する間に発生する例外は、LazyInitializationException にラップされます。

オプションの lazy 属性を使って、遅延コレクションを宣言します。

<set name="names" table="NAMES" lazy="true">
    <key column="group_id"/>
    <element column="NAME" type="string"/>
</set>

アプリケーションのアーキテクチャーによっては、特に、Hibernate を使ってデータにアクセスするコードや、Hibernate を使うコードが様々なアプリケーション内の層にあるようなアーキテクチャーでは、コレクションを初期化する際に Session がオープンされているかどうかを確かめることが問題になる場合があります。この問題に対処するには、2 つの基本的な方法があります。

  • ウェブベースのアプリケーションでは、ビューのレンダリングが完了した後、ユーザーリクエストのまさに最後で Session をクローズするためだけであれば、サーブレットフィルターを使うことができる。もちろん、これは、アプリケーションインフラストラクチャーの例外処理の正しさに重大な要求を課すものである。ビューのレンダリング中に例外が発生する時ですら、制御がユーザーに戻る前に、Session がクローズされ、トランザクションが終了することは、決定的に重要である。サーブレットフィルターは、このアプローチのために、Session にアクセスできなければならない。現在の Session を保持するために、ThreadLocal 変数を使うことをお勧めする (サンプル実装については、第 1 章の セクション 1.4 “猫と遊ぶ” を参照せよ)。

  • 分離したビジネス層をもつアプリケーションでは、ビジネスロジックは、制御を戻す前に、ウェブ層が必要とするコレクションのすべてを "準備" しなければならない。この意味は、ビジネス層は、すべてのデータをロードし、初期化の終わったデータすべてを、特定ユースケースに必要なプレゼンテーション/ウェブ層に返すべきだということである。通常、アプリケーションは、ウェブ層で必要とされる個々のコレクションに対して Hibernate.initialize() を呼び出す (この呼び出しは、セッションをクローズする前に行なわなければならない) か、FETCH 句のある Hibernate クエリーを使って真剣にコレクションの検索をする。

  • 初期化していないコレクション (やその他のプロキシー) にアクセスする前に、以前にロードしたオブジェクトを、update()lock() を使って、新しい Session に貼り付ける場合もあるかもしれない。Hibernate は、これを自動的に行なうことはできない。そのためには、特別なトランザクションセマンティクスを導入することが必要になるためである。

コレクションを初期化することなく、コレクションのサイズを取得するために、Hibernate セッション API の filter() メソッドを使うことができます。

( (Integer) s.filter( collection, "select count(*)" ).get(0) ).intValue()

filter()createFilter() は、コレクション全体を初期化することなく、コレクションのサブセットを効率的に検索するのにも使われます。

6.6. ソート済みのコレクション

Hibernate は、java.util.SortedMapjava.util.SortedSet を実装するコレクションをサポートしています。これを使うには、マッピングファイル内で、比較対象 (comparator) を指定しなければなりません。

<set name="aliases" table="person_aliases" sort="natural">
    <key column="person"/>
    <element column="name" type="string"/>
</set>

<map name="holidays" sort="my.custom.HolidayComparator" lazy="true">
    <key column="year_id"/>
    <index column="hol_name" type="string"/>
    <element column="hol_date" type="date"/>
</map>

sort 属性に指定可能な値は、unsorted, naturaljava.util.Comparator を実装するクラスの名前です。

ソート済みのコレクションは、実際上、java.util.TreeSetjava.util.TreeMap のように振る舞います。

データベース自身にコレクション要素を並べ替えさせたい場合には、set, bag あるいは map マッピングの order-by 属性を使います。この解決策は、JDK 1.4 かそれ以上でのみ利用可能です (それは、LinkedHashSetLinkedHashMap を使って実装されています)。これは、メモリー内ではなく、SQL クエリー内で並べ替えを行ないます。

<set name="aliases" table="person_aliases" order-by="name asc">
    <key column="person"/>
    <element column="name" type="string"/>
</set>

<map name="holidays" order-by="hol_date, hol_name" lazy="true">
    <key column="year_id"/>
    <index column="hol_name" type="string"/>
    <element column="hol_date type="date"/>
</map>

order-by 属性の値は、HQL の並べ替えではなく、SQL の並べ替えである点に注意して下さい。

関連は、filter() を使って、実行時に、何らかの任意の基準を使ってソートされる場合もあります。

sortedUsers = s.filter( group.getUsers(), "order by this.name" );

6.7. <idbag> を使う

複合キーとは悪いものだとか、エンティティーは総合的な識別子 (代用キー : surrogate key) をもつべきだという私達の見解を完全に受け入れる場合には、これまでに示した多対多関連と値のコレクションすべてが、複合キーをもつテーブルにマップされることは、少々奇妙に感じるかもしれません。この点は、議論の余地のあるところです。純粋な関連テーブルは、代用キーからあまり恩恵を受けているとは思えません (複合値のコレクションは、恩恵を受けているかもしれませんが)。にも関わらず、Hibernate は、(少々、実験的な) 機能を提供しています。それは、多対多関連と値のコレクションを、代用キーを使って、テーブルにマップすることができるようにするものです。

<idbag> 要素によって、bag セマンティクスを使って、List (あるいは、Collection) をマップすることができます。

<idbag name="lovers" table="LOVERS" lazy="true">
    <collection-id column="ID" type="long">
        <generator class="hilo"/>
    </collection-id>
    <key column="PERSON1"/>
    <many-to-many column="PERSON2" class="eg.Person" outer-join="true"/>
</idbag>

見てお分かりのように、<idbag> には、エンティティークラスのような、総合的な id ジェネレーターがあります。異なった代用キーが、個々のコレクションの行に割り当てられます。しかしながら、Hibernate は、特定行の代用キーの値を見つけるためのどんなメカニズムも提供していません。

<idbag> の更新パフォーマンスは、通常の <bag> よりもはるかに優れている点に注意して下さい。Hibernate は、リストやマップ、セットの場合と同じように、個々の行を効率的に見つけ出し、個別にそれらの更新と削除ができます。

現在の実装では、identity 識別子生成戦略は、<idbag> コレクション識別子用にはサポートされていません。

6.8. 双方向関連

双方向関連によって、関連の両 "端" からナビゲーションができます。2 種類の双方向関連がサポートされています。

one-to-many

1 端が多値のセットかバッグ、他端が単一値

many-to-many

両端が多値のセットかバッグ

Hibernate は、多端側としてインデックス付きのコレクション (リスト、マップや配列) をもつ双方向 1 対多関連をサポートしていない点に注意して下さい。セットやバッグのマッピングを使わなければなりません。

2 つの多対多関連を同じデータベースのテーブルにマップし、1 端を inverse として宣言するだけで、双方向多対多関連を指定する場合があるかもしれません (これは、選択肢の 1 つです)。以下の例は、クラスからそれ自身に戻る双方向多対多関連です (個々の category は、多 item をもつことができ、個々の item は、多 category 内に存在することができます)。

<class name="org.hibernate.auction.Category">
    <id name="id" column="ID"/>
    ...
    <bag name="items" table="CATEGORY_ITEM" lazy="true">
        <key column="CATEGORY_ID"/>
        <many-to-many class="org.hibernate.auction.Item" column="ITEM_ID"/>
    </bag>
</class>

<class name="org.hibernate.auction.Item">
    <id name="id" column="ID"/>
    ...

    <!-- 逆端 (inverse end) -->
    <bag name="categories" table="CATEGORY_ITEM" inverse="true" lazy="true">
        <key column="ITEM_ID"/>
        <many-to-many class="org.hibernate.auction.Category" column="CATEGORY_ID"/>
    </bag>
</class>

関連の逆端 (inverse end) に対してだけ行なった変更は、永続化されません。この意味は、Hibernate は、すべての双方向関連のために、メモリー内に 2 つの表現をもつということです。A から B へのリンクと、B から A へのリンクです。Java オブジェクトモデルと、多対多関係を Java でどのように作るかを考えれば、これを理解するのは簡単です。

category.getItems().add(item);          // category は、これで、関係について "知る"
item.getCategories().add(category);     // item は、これで、関係について "知る"

session.update(item);                     // 効果なし、何も保存されない
session.update(category);                 // 関係は保存される

逆ではない (non-inverse) 側は、メモリー内の表現をデータベースに保存するために使われます。両方が変更のトリガーとなる場合には、不必要な INSERT/UPDATE や、おそらく、外部キー違反を引き起こすことになるでしょう。もちろん、同じことは、双方向 1 対多関連にもあてはまります。

多対 1 関連として 1 対多関連を同じテーブルのカラム (群) にマップし、多値端を inverse="true" と宣言することによって、双方向 1 対多関連をマップする場合があるかもしれません。

<class name="eg.Parent">
    <id name="id" column="id"/>
    ....
    <set name="children" inverse="true" lazy="true">
        <key column="parent_id"/>
        <one-to-many class="eg.Child"/>
    </set>
</class>

<class name="eg.Child">
    <id name="id" column="id"/>
    ....
    <many-to-one name="parent" class="eg.Parent" column="parent_id"/>
</class>

関連の 1 端を inverse="true" を使ってマップしても、カスケードの操作には影響はありません。両者は、異なった概念だからです。

6.9. 3 者関連

3 者関連をマップするには、2 つのアプローチが可能です。その 1 つは、複合要素 (以下で説明します) を使うもので、もう 1 つは、インデックスとして関連をもつ Map を使うものです。

<map name="contracts" lazy="true">
    <key column="employer_id"/>
    <index-many-to-many column="employee_id" class="Employee"/>
    <one-to-many column="contract_id" class="Contract"/>
</map>
<map name="connections" lazy="true">
    <key column="node1_id"/>
    <index-many-to-many column="node2_id" class="Node"/>
    <many-to-many column="connection_id" class="Connection"/>
</map>

6.10. 異種関連

<many-to-any><index-many-to-any> 要素は、本当の異種関連を提供します。これらのマッピング要素は、<any> 要素と同じ方法で働きます。しかし、これを使うことはまずないでしょう。

6.11. コレクションの例

前のセクションは、かなり混乱させるものでした。ここで、例を見ることにしましょう。

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

    ....
    ....
}

このクラスには、eg.Child インスタンスのコレクションがあります。個々の子供に最大でも 1 つの親しかない場合、最も自然なマッピングは、1 対多関連です。

<hibernate-mapping>

    <class name="eg.Parent">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <set name="children" lazy="true">
            <key column="parent_id"/>
            <one-to-many class="eg.Child"/>
        </set>
    </class>

    <class name="eg.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

親が必須の場合には、双方向の 1 対多関連を使います。

<hibernate-mapping>

    <class name="eg.Parent">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <set name="children" inverse="true" lazy="true">
            <key column="parent_id"/>
            <one-to-many class="eg.Child"/>
        </set>
    </class>

    <class name="eg.Child">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <property name="name"/>
        <many-to-one name="parent" class="eg.Parent" column="parent_id" not-null="true"/>
    </class>

</hibernate-mapping>

NOT NULL 制約に注意して下さい。

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

一方、子供に複数の親があるかもしれない場合には、多対多関連が適切です。

<hibernate-mapping>

    <class name="eg.Parent">
        <id name="id">
            <generator class="sequence"/>
        </id>
        <set name="children" lazy="true" table="childset">
            <key column="parent_id"/>
            <many-to-many class="eg.Child" column="child_id"/>
        </set>
    </class>

    <class name="eg.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) )
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

第 7 章 コンポーネントマッピング

コンポーネント (component) という考え方は、Hibernate 全体を通して、幾つかの異なったコンテキストで、様々な目的のため、再利用されます。

7.1. 依存オブジェクト

コンポーネントとは、包含されるオブジェクトで、エンティティーとしてではなく、値の型として永続化されます。"コンポーネント" という用語は、(アーキテクチャーレベルのコンポーネントではなく) 組み立て/構成に関するオブジェクト指向の考え方を指します。例えば、個人は、次のようにモデル化されるかもしれません。

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

Name は、Person のコンポーネントとして永続化されるかもしれません。Name は、永続プロパティーのためにゲッターとセッターメソッドを定義していますが、インターフェースや識別子プロパティーを何も宣言する必要がない点に注意して下さい。

ここでの Hibernamte マッピングは、次のようになるでしょう。

<class name="eg.Person" table="person">
    <id name="Key" column="pid" type="string">
        <generator class="uuid.hex"/>
    </id>
    <property name="birthday" type="date"/>
    <component name="Name" class="eg.Name"> <!-- クラス属性はオプション -->
        <property name="initial"/>
        <property name="first"/>
        <property name="last"/>
    </component>
</class>

person テーブルは、pid, birthday, initial, firstlast カラムをもつことになります。

あらゆる値の型と同様に、コンポーネントは、共有参照をサポートしません。コンポーネントの null 値セマンティクスは、特別です。包含オブジェクトをリロードする場合、Hibernate は、コンポーネントのカラムがすべて null であれば、コンポーネント全体が null であると想定します。これは、ほとんどの目的にとって、問題ではありません。

コンポーネントのプロパティーは、Hibernate の任意の型 (コレクション、多対 1 関連、他のコンポーネントなど) であって構いません。ネスとするコンポーネントに、風変わりな用法を考えるべきではありません。Hibernate は、非常に粒度の細かいオブジェクトモデルをサポートすることを意図しています。

<component> 要素によって、<parent> サブ要素は、コンポーネントクラスのプロパティーを包含するエンティティーに戻る参照としてマップすることができます。

<class name="eg.Person" table="person">
    <id name="Key" column="pid" type="string">
        <generator class="uuid.hex"/>
    </id>
    <property name="birthday" type="date"/>
    <component name="Name" class="eg.Name">
        <parent name="namedPerson"/> <!-- Person に戻る参照 -->
        <property name="initial"/>
        <property name="first"/>
        <property name="last"/>
    </component>
</class>

7.2. 依存オブジェクトのコレクション

コンポーネントのコレクションもサポートされています (例えば、Name 型の配列)。コンポーネントのコレクションは、<element> タグを <composite-element> タグで置き換えることによって宣言します。

<set name="someNames" table="some_names" lazy="true">
    <key column="id"/>
    <composite-element class="eg.Name"> <!-- クラス属性が必要 -->
        <property name="initial"/>
        <property name="first"/>
        <property name="last"/>
    </composite-element>
</set>

注意: 複合要素の Set を定義する場合、equals()hashCode() を正しく実装することが非常に重要です。

複合要素は、コレクションではなく、コンポーネントを含む場合があります。複合要素自身がコンポーネントを含む場合には、<nested-composite-element> タグを使います。これは、非常に風変わりなケースです。それ自身がコンポーネントをもつコンポーネントのコレクションだからです。この段階までに、1 対多関連の方が適切ではないかを検討すべきです。複合要素をエンティティーとして再モデル化することを試してみて下さい。しかし、Java モデルが同じである場合であっても、リレーショナルモデルと永続化セマンティクスは、依然として、微妙に異なるものである点に注意して下さい。

<set> を使っている場合には、複合要素のマッピングは、null を取ることのできるプロパティーをサポートしない点に注意して下さい。Hibernate は、オブジェクトを削除する場合、レコードを識別するために個々のカラムを使わなければなりません (複合要素のテーブルには、異なった主キーカラムはありません)。null 値を使ってこれを行なうことはできません。複合要素内で null を取ることのできないプロパティーだけを使うか、<list>, <map>, <bag><idbag> のいずれかを選ばなければなりません。

複合要素の特殊ケースは、ネストした <many-to-one> 要素のある複合要素です。このようなマッピングによって、多対多関連のテーブルの特別なカラムを複合要素のクラスにマップすることができます。以下は、Order から Item への多対多関連です。ここで、purchaseDate, pricequantity は、関連のプロパティーです。

<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"/> <!-- クラス属性はオプション -->
        </composite-element>
    </set>
</class>

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>

複合要素は、他のエンティティーへの関連と同じシンタックスを使って、クエリー内に現われる場合があります。

7.3. インデックスとしてのコンポーネント

<composite-index> 要素によって、コンポーネントクラスを、Map のキーとしてマップすることができます。コンポーネントクラス上で、hashCode()equals() を正しくオーバーライドしていることを確認して下さい。

7.4. 複合識別子としてのコンポーネント

コンポーネントをエンティティークラスの識別子として使う場合があるかもしれません。コンポーネントクラスは、ある要件を満足しなければなりません。

  • それは、java.io.Serializable を実装しなければならない。

  • それは、複合キーの等価性という考え方に一貫した形で equals()hashCode() を再実装しなければならない。

複合キーを生成するために、IdentifierGenerator を使うことはできません。代わりに、アプリケーションは、それ自身の識別子を割り当てなければなりません。

オブジェクトを保存する前に、オブジェクトには、複合識別子を割り当てなければならないので、新しくインスタンス化したインスタンスと以前のセッションで保存されたインスタンスを区別するために、識別子の unsaved-value を使うことはできません。

saveOrUpdate() やカスケードする保存/更新を使いたい場合には、その代わりに、Interceptor.isUnsaved() を実装することになるかもしれません。それ以外の方法として、新しい一時的インスタンスであることを示す値を指定するために、<version> (あるいは、<timestamp>) 要素上で、unsaved-value 属性をセットすることになるかもしれません。この場合には、(割り当てられる) 識別子の代わりに、エンティティーのバージョンを使います。その場合には、Interceptor.isUnsaved() を自分で実装する必要はありません。

複合識別子のクラスの宣言のための <id> の位置に、(<component> と同じ属性と要素をもつ) <composite-id> タグを使います。

<class name="eg.Foo" table"FOOS">
    <composite-id name="compId" class="eg.FooCompositeID">
        <key-property name="string"/>
        <key-property name="short"/>
        <key-property name="date" column="date_" type="date"/>
    </composite-id>
    <property name="name"/>
    ....
</class>

これにより、テーブル FOOS 内への外部キーはすべて、複合キーになります。これは、他のクラスのためのマッピング内で宣言しなければなりません。Foo に対する関連は、次のように宣言します。

<many-to-one name="foo" class="eg.Foo">
<!-- the "class" attribute is optional, as usual -->
    <column name="foo_string"/>
    <column name="foo_short"/>
    <column name="foo_date"/>
</many-to-one>

この新しい <column> は、複数カラムのカスタム型からも使われます。実際、これは、いつの場合でも、column 属性に対する代替です。型 Foo の要素をもつコレクションは、次のセットを使うことになります。

<set name="foos">
    <key column="owner_id"/>
    <many-to-many class="eg.Foo">
        <column name="foo_string"/>
        <column name="foo_short"/>
        <column name="foo_date"/>
    </many-to-many>
</set>

一方、<one-to-many> では、いつものように、カラムの宣言を行ないません。

Foo 自身にコレクションがある場合には、コレクションも、複合外部キーを必要とします。

<class name="eg.Foo">
    ....
    ....
    <set name="dates" lazy="true">
        <key>   <!-- 複合キー型を継承するコレクション -->
            <column name="foo_string"/>
            <column name="foo_short"/>
            <column name="foo_date"/>
        </key>
        <element column="foo_date" type="date"/>
    </set>
</class>

7.5. 動的なコンポーネント

Map 型のプロパティーをマップする場合すらあるかもしれません。

<dynamic-component name="userAttributes">
    <property name="foo" column="FOO"/>
    <property name="bar" column="BAR"/>
    <many-to-one name="baz" class="eg.Baz" column="BAZ"/>
</dynamic-component>

<dynamic-component> マッピングのセマンティクスは、<component> と同じです。この種のマッピングの利点は、開発時に、マッピング文書を編集するだけで、Bean の実際のプロパティーを決めることができることです。(マッピング文書の実行時の操作も、DOM パーサーを使えば可能です。)

第 8 章 継承マッピング

8.1. 3 つの戦略

Hibernate は、3 つの基本的な継承マッピング戦略をサポートしています。

  • クラス単位のテーブル階層

  • サブクラス単位のテーブル

  • 具象クラス単位のテーブル (幾つかの制限付き)

同じ継承階層の異なったブランチに、異なったマッピング戦略を使うことすら可能です。しかし、具象テーブル単位のテーブルマッピングに適用されるのと同じ制限が適用されます。Hibernate は、同じ <class> 要素内部で、<subclass> マッピングと <joined-subclass> を組み合わせることをサポートしていないからです。

CreditCardPayment, CashPayment, ChequePayment 実装をもつインターフェース Payment があるとしましょう。階層単位のテーブルマッピングは、次のようになります。

<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">
        ...
    </subclass>
    <subclass name="CashPayment" discriminator-value="CASH">
        ...
    </subclass>
    <subclass name="ChequePayment" discriminator-value="CHEQUE">
        ...
    </subclass>
</class>

テーブルは、1 つだけ必要です。このマッピング戦略には、1 つの大きな制限があります。サブクラスが宣言するカラムは、NOT NULL 制約をもつことができない場合があるという点です。

サブクラス単位のテーブルマッピングは、次のようになります。

<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"/>
        ...
    </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 つのサブクラスのテーブルは、スーパークラスのテーブルへの主キー関連をもちます (そのため、リレーショナルモデルは、実際には、1 対 1 関連です)。

サブクラス単位のテーブルの Hibernate の実装は、判別子カラムを必要としません。他のオブジェクト/リレーショナルマッパーは、スーパークラス内に型判別子カラムを必要とする、サブクラス単位のテーブルの異なった実装を使います。Hibernate が取るアプローチは、非常に異なった実装ですが、リレーショナルという観点からすると、議論の余地はあるとしても、もっと正しいものです。

これら 2 つのマッピング戦略のいずれの場合も、Payment に対するポリモーフィズム的関連は、<many-to-one> を使ってマップされます。

<many-to-one name="payment"
    column="PAYMENT"
    class="Payment"/>

具象クラス単位のテーブル戦略は、非常に異なっています。

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

3 つのテーブルが必要でした。Payment インターフェースについて、どこにも、明示的に述べていない点に注意して下さい。その代わりに、Hibernate の暗黙的ポリモーフィズムを使っています。Payment のプロパティーを、サブクラスのそれぞれにマップしている点にも注意して下さい。

この場合には、Payment に対するポリモーフィズム的関連は、<any> を使ってマップしています。

<any name="payment"
        meta-type="class"
        id-type="long">
    <column name="PAYMENT_CLASS"/>
    <column name="PAYMENT_ID"/>
</any>

型判別子文字列から Payment サブクラスへのマッピングを処理するため、UserTypemeta-type として定義することができれば、その方が望ましいでしょう。

<any name="payment"
        meta-type="PaymentMetaType"
        id-type="long">
    <column name="PAYMENT_TYPE"/> <!-- クレジット、キャッシュあるいはチェック -->
    <column name="PAYMENT_ID"/>
</any>

このマッピングには、もう 1 つ、注意すべきことがあります。サブクラスはそれぞれ、それ自身の <class> 要素にマップされるので (そして、Payment はインターフェースにすぎないので)、サブクラスのそれぞれは、簡単に、別のクラス単位のテーブルやサブクラス単位のテーブル継承階層の一部になることができるという点です。(そして、Payment インターフェースに対して、ポリモーフィズム的クエリーを使うことができるという点です。)

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

再度言うと、ここでも、明示的に、Payment について述べてはいません。Payment インターフェースに対してクエリー - 例えば、from Payment - を実行する場合、Hibernate は、自動的に、CreditCardPayment (およびそのサブクラス、それらも、Payment を実装しているためです)、CashPaymentChequePayment のインスタンスを返します。しかし、NonelectronicTransaction のインスタンスは返しません。

8.2. 制限

Hibernate は、関連がたった 1 つの外部キーカラムにマップされると想定しています。外部キー単位の複数関連も許されます (inverse="true"insert="false" update="false" を指定する必要があります) が、関連を複数の外部キーにマップする方法はありません。この意味は、次の通りです

  • 関連を変更する場合、更新されるのは、常に、同じ外部キーである。

  • 関連を遅延フェッチする場合、1 つのデータベースクエリーが使われる。

  • 関連を真剣にフェッチする場合、それは、1 つの外部結合を使ってフェッチされる場合がある。

特に、これは、具象クラス単位のテーブル戦略を使ってマップされるクラスに対するポリモーフィズム的な 1 対多関連は、サポートされていないということを示唆しています。(こうした関連をフェッチするには、複数のクエリーか複数の結合が必要です。)

以下のテーブルは、Hibernate における、具象クラス単位のテーブルマッピングの制限と、暗黙的なポリモーフィズムの制限を示しています。

テーブル 8.1. 継承マッピングの機能

継承戦略 ポリモーフィズム的多対 1 ポリモーフィズム的 1 対 1 ポリモーフィズム的 1 対多 ポリモーフィズム的多対多 ポリモーフィズム的 load()/get() ポリモーフィズム的クエリー ポリモーフィズム的結合 外部結合によるフェッチ
クラス単位のテーブル階層 <many-to-one> <one-to-one> <one-to-many> <many-to-many> s.get(Payment.class, id) from Payment p from Order o join o.payment p サポート済み
サブクラス単位のテーブル <many-to-one> <one-to-one> <one-to-many> <many-to-many> s.get(Payment.class, id) from Payment p from Order o join o.payment p サポート済み
具象クラス単位のテーブル (暗黙的なポリモーフィズム) <any> サポート外 サポート外 <many-to-any> クエリーを使う from Payment p サポート外 サポート外

第 9 章 永続データを操作する

9.1. 永続オブジェクトを作る

オブジェクト (エンティティーインスタンス) は、特定の Session に関して、一時的であるか永続的であるかのいずれかです。新しくインスタンス化されたオブジェクトは、もちろん、一時的です。セッションは、一時的なオブジェクトを保存する (つまり、永続化する) サービスを提供します。

DomesticCat fritz = new DomesticCat();
fritz.setColor(Color.GINGER);
fritz.setSex('M');
fritz.setName("Fritz");
Long generatedId = (Long) sess.save(fritz);
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) );

引数が 1 つの save() は、fritz を生成し、それに一意な識別子を割り当てます。2 引数形式の save() は、指定した識別子を使って pk を永続化しようとします。一般的に言えば、2 引数形式を使うことはお勧めしません。何故なら、それは、ビジネス的な意味をもつ主キーを作るのに使われる場合があるからです。それは、BMP エンティティー Bean を永続化させるために Hibernate を使うといった特殊な状況で、非常に役立ちます。

関連付けられたオブジェクトは、外部キーカラムに NOT NULL 制約を指定していない場合には、お望みの任意の順序で永続化されることがあります。外部キー制約に違反するようなリスクはまったくありません。しかしながら、オブジェクトを間違った順序で save() する場合には、NOT NULL 制約に違反することになるかもしれません。

9.2. オブジェクトをロードする

Sessionload() メソッドは、既に永続インスタンスの識別子を知っている場合には、それを検索する方法を与えてくれます。このメソッドの最初のバージョンは、クラスオブジェクトを受け取り、その状態を、新しくインスタンス化されたオブジェクトにロードします。2 番目のバージョンを使えば、状態をロードするインスタンスを指定することができます。BMP エンティティー Beans とともに Hibernate を使おうと計画していて、まさにその目的のために Hibernate が使える場合には、インスタンスを受け取る形式は、特に有用です。それ以外の用法を見つけることがあるかもしれません。(例えば、DIY インスタンスプーリング。)

Cat fritz = (Cat) sess.load(Cat.class, generatedId);
// プリミティブの識別子をラップする必要がある
long pkId = 1234;
DomesticCat pk = (DomesticCat) sess.load( Cat.class, new Long(pkId) );
Cat cat = new DomesticCat();
// pk の状態を cat にロードする
sess.load( cat, new Long(pkId) );
Set kittens = cat.getKittens();

load() は、データベースの行にマッチしない場合には、回復できない例外を投げるかもしれないことに注意して下さい。プロキシーを使ってクラスをマップしている場合には、load() は、初期化されていないプロキシーオブジェクトを返し、そのオブジェクトのメソッドを呼び出すまでは、実際上、データベースにヒットしません。実際にオブジェクトをデータベースからロードすることなく、オブジェクトへの関連を作りたい場合には、この振る舞いは、非常に有用です。

マッチする行が存在するかどうかが確かではない場合には、get() メソッドを使うべきです。これは、直ちにデータベースにヒットするかを調べ、マッチする行がなければ、null を返します。

Cat cat = (Cat) sess.get(Cat.class, id);
if (cat==null) {
    cat = new Cat();
    sess.save(cat, id);
}
return cat;

SQL の SELECT ... FOR UPDATE を使って、オブジェクトをロードする場合もあるかもしれません。Hibernate の LockMode の説明は、次のセクションを参照して下さい。

Cat cat = (Cat) sess.get(Cat.class, id, LockMode.UPGRADE);

関連付けられたインスタンスや包含されるコレクションはどれも、FOR UPDATE によって選択されることはない点に注意して下さい。

refresh() メソッドを使えば、いつでも、オブジェクトとそのコレクションすべてを再ロードすることができます。オブジェクトのプロパティーの幾つかを初期化するのに、データベースのトリガーを使う場合には、これは役に立ちます。

sess.save(cat);
sess.flush(); // SQL INSERT を強制する
sess.refresh(cat); // (トリガーを実行した後に) 状態を再度読み込む

9.3. クエリーを行なう

探しているオブジェクト (群) の識別子 (群) が分からない場合には、Sessionfind() メソッドを使います。Hibernate は、単純ですがパワフルなオブジェクト指向クエリー言語をサポートしています。

List cats = sess.find(
    "from Cat as cat where cat.birthdate = ?",
    date,
    Hibernate.DATE
);

List mates = sess.find(
    "select mate from Cat as cat join cat.mate as mate " +
    "where cat.name = ?",
    name,
    Hibernate.STRING
);

List cats = sess.find( "from Cat as cat where cat.mate.bithdate is null" );

List moreCats = sess.find(
    "from Cat as cat where " + 
    "cat.name = 'Fritz' or cat.id = ? or cat.id = ?",
    new Object[] { id1, id2 },
    new Type[] { Hibernate.LONG, Hibernate.LONG }
);

List mates = sess.find(
    "from Cat as cat where cat.mate = ?",
    izi,
    Hibernate.entity(Cat.class)
);

List problems = sess.find(
    "from GoldFish as fish " +
    "where fish.birthday > fish.deceased or fish.birthday is null"
);

find() に対する 2 番目の引数は、オブジェクトやオブジェクトの配列を受け付けます。3 番目の引数は、Hibernate の型や Hibernate の型の配列を受け付けます。指定したこれらの型は、指定したオブジェクトを ? クエリープレースホルダーにバインドするのに使われます (これは、JDBC PreparedStatement の IN パラメータにマップされます)。JDBC の場合と同様に、文字列操作よりもむしろ、このバインドメカニズムを使用すべきです。

Hibernate クラスは、net.sf.hibernate.type.Type のインスタンスとして、組み込みの型のほとんどへのアクセスを提供する多数のスタティックメソッドと定数を定義しています。

クエリーが非常に多くのオブジェクトを返すことが予想され、それらのすべてを使うことはないと考えられる場合には、java.util.Iterator を返す iterate() メソッドからよりよいパフォーマンスが得られるかもしれません。イテレーターは、必要に応じて、初期 SQL クエリーが返す識別子を使って、オブジェクトをロードするからです (n+1 は、すべてを選択します)。

// id をフェッチする
Iterator iter = sess.iterate("from eg.Qux q order by q.likeliness"); 
while ( iter.hasNext() ) {
    Qux qux = (Qux) iter.next();  // オブジェクトをフェッチする
    // クエリー内で表現できないもの
    if ( qux.calculateComplicatedAlgorithm() ) {
        // 現在のインスタンスを削除する
        iter.remove();
        // 残りを処理する必要はない
        break;
    }
}

残念ながら、java.util.Iterator には、例外が何も宣言されていません。そのため、発生する SQL や Hibernate の例外すべては、(RuntimeException のサブクラスである) LazyInitializationException 内にラップされます。

iterate() メソッドは、オブジェクトの多くが既にロードされていて、セッションがキャッシュしていると想定される場合や、クエリーの結果に、何度も、同じオブジェクトが含まれる場合には、もっと優れた働きをします。(データが何もキャッシュされていない場合やデータ上での繰り返しが必要ない場合には、ほとんど常に、find() の方が高速です。) 以下は、iterate() を使って呼び出されるクエリーの例です。

Iterator iter = sess.iterate(
    "select customer, product " + 
    "from Customer customer, " +
    "Product product " +
    "join customer.purchases purchase " +
    "where product = purchase.product"
);

find() を使って前のクエリーを呼び出すと、同じデータを何度も含む非常に大きな JDBC ResultSet が返されます。

Hibernate のクエリーは、時には、オブジェクトのタプル (tuples) を返します。どの場合にも、個々のタプルは、配列として返されます。

Iterator foosAndBars = sess.iterate(
    "select foo, bar from Foo foo, Bar bar " +
    "where bar.date = foo.date"
);
while ( foosAndBars.hasNext() ) {
    Object[] tuple = (Object[]) foosAndBars.next();
    Foo foo = tuple[0]; Bar bar = tuple[1];
    ....
}

9.3.1. スカラークエリー

クエリーには、select 句内で、クラスのプロパティーを指定する場合があるかもしれません。それらは、SQL の集計関数を呼び出す場合もあります。プロパティーや集合は、"スカラー (scalar)" の結果であると考えることができます。

Iterator results = sess.iterate(
        "select cat.color, min(cat.birthdate), count(cat) from Cat cat " +
        "group by cat.color"
);
while ( results.hasNext() ) {
    Object[] row = results.next();
    Color type = (Color) row[0];
    Date oldest = (Date) row[1];
    Integer count = (Integer) row[2];
    .....
}
Iterator iter = sess.iterate(
    "select cat.type, cat.birthdate, cat.name from DomesticCat cat"
);
List list = sess.find(
    "select cat, cat.mate.name from DomesticCat cat"
);

9.3.2. クエリーインターフェース

(検索したい行の最大数そして/あるいは検索したい最初の行が) 結果セットにバインドされていることを指定する必要がある場合、net.sf.hibernate.Query のインスタンスを取得すべきです。

Query q = sess.createQuery("from DomesticCat cat");
q.setFirstResult(20);
q.setMaxResults(10);
List cats = q.list();

マッピング文書内に指定するクエリーを定義する場合もあるかもしれません。(クエリーにマークアップとして解釈されてはならない文字が含まれている場合には、CDATA セクションを使うことを思い出して下さい。)

<query name="eg.DomesticCat.by.name.and.minimum.weight"><![CDATA[
    from eg.DomesticCat as cat
        where cat.name = ?
        and cat.weight > ?
] ]></query>
Query q = sess.getNamedQuery("eg.DomesticCat.by.name.and.minimum.weight");
q.setString(0, name);
q.setInt(1, minWeight);
List cats = q.list();

クエリーインターフェースは、名前付きパラメータをサポートします。名前付きパラメータとは、クエリー文字列中のフォーム :name の識別子です。Query 上には、値を名前付きパラメータや JDBC スタイルの ? パラメータにバインドするためのメソッドがあります。JDBC とは違って、Hibernate は、パラメータを 0 から数えます。名前付きパラメータの利点は、次の通りです。

  • 名前付きパラメータの場合、それらがクエリー文字列中に現われる順序は問題ではない。

  • それらは、同じクエリー中で、複数回現われる場合がある。

  • それらは、自己説明的である。

// (選択された) 名前付きパラメータ
Query q = sess.createQuery("from DomesticCat cat where cat.name = :name");
q.setString("name", "Fritz");
Iterator cats = q.iterate();
// 位置に関するパラメータ
Query q = sess.createQuery("from DomesticCat cat where cat.name = ?");
q.setString(0, "Izi");
Iterator cats = q.iterate();
// 名前付きパラメータのリスト
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();

9.3.3. スクロール可能な繰り返し

使用している JDBC ドライバーがスクロール可能な (scrollable) ResultSet をサポートしている場合、クエリーの結果のもっと柔軟なナビゲーションを可能にする ScrollableResults を取得するために、Query インターフェースが使われる場合があります。

Query q = sess.createQuery("select cat.name, cat from DomesticCat cat " +
                            "order by cat.name");
ScrollableResults cats = q.scroll();
if ( cats.first() ) {

    // 名前によるアルファベット順のリストの個々のページ上にある最初の名前を見つける
    firstNamesOfPages = new ArrayList();
    do {
        String name = cats.getString(0);
        firstNamesOfPages.add(name);
    }
    while ( cats.scroll(PAGE_SIZE) );

    // 次に、cats の最初のページを取得する
    pageOfCats = new ArrayList();
    cats.beforeFirst();
    int i=0;
    while( ( PAGE_SIZE > i++ ) && cats.next() ) pageOfCats.add( cats.get(1) );

}

scroll() の振る舞いは、iterate() に似ていますが、オブジェクトは、行全体を一度に初期化されるのではなく、get(int) によって選択されたフィールドだけが初期化される場合がある点が違います。

9.3.4. コレクションをフィルターする

コレクションフィルター (filter) は、永続コレクションや配列に適用される特殊な型のクエリーです。クエリー文字列は、現在のコレクション要素を表わす this を参照する場合があります。

Collection blackKittens = session.filter(
    pk.getKittens(), "where this.color = ?", Color.BLACK, Hibernate.enum(Color.class)
);

返されるコレクションは、バッグであると考えられます。

フィルターには、from 句が必要ないことを見ておいて下さい (必要ならば、それをもつ場合もあります)。フィルターは、コレクション要素自身を返すことに制限されません。

Collection blackKittenMates = session.filter(
    pk.getKittens(), "select this.mate where this.color = eg.Color.BLACK"
);

9.3.5. クリテリアクエリー

HQL は、非常にパワフルですが、人によっては、Java コード中に文字列を埋め込むのではなく、オブジェクト指向 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();

SQL 風のシンタックスがお気に召さない場合、おそらく、これが、Hibernate を使い始める最も簡単な方法でしょう。この API は、HQL よりも拡張可能です。アプリケーションが、Criterion インターフェースのそれ自身の実装を提供する場合があるかもしれません。

9.3.6. ネイティブな SQL によるクエリー

createSQLQuery() を使って、SQL によるクエリーを表現する場合があるかもしれません。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 クエリーは、Hibernate クエリーの場合と同様に、名前付きパラメータと位置パラメータを含む場合があります。

9.4. オブジェクトを更新する

9.4.1. 同じセッション内で更新する

トランザクションによる永続インスタンス (つまり、Session によってロード、保存、作成あるいはクエリーされたオブジェクト) は、アプリケーションが操作する場合があります。そして、永続状態に対するあらゆる変更は、Sessionフラッシュされる際に、永続化されます (これについては、この章で後ほど説明します) 。そのため、オブジェクトの状態を更新する最も単純な方法は、セッションがオープンしている間に、オブジェクトを load() して、その状態を直接操作することです。

DomesticCat cat = (DomesticCat) sess.load( Cat.class, new Long(69) );
cat.setName("PK");
sess.flush();  // cat に対する変更は自動的に検出され、永続化される

時には、このプログラミングモデルは、非効率です。何故なら、それは、同じセッション内で、(オブジェクトをロードするための) SQL の SELECT と (更新された状態を永続化するための) SQL の UPDATE の両方を必要とするからです。そのため、Hibernate は、別のアプローチを提供しています。

9.4.2. 分離されたオブジェクトを更新する

アプリケーションの多くは、1 つのトランザクションでオブジェクトを検索し、それを操作のため UI 層に送り、そしてその変更を新しいトランザクションで保存します。(高度の並行的な環境でこの種のアプローチを使うアプリケーションは、通常、トランザクションが互いに隔離されていることを保証するため、バージョン付きのデータを使います。) このアプローチは、直前のセクションで説明したモデルとは微妙に異なったプログラミングモデルを必要とします。Hibernate は、Session.update() メソッドを提供することにより、このモデルをサポートしています。

// 最初のセッションで
Cat cat = (Cat) firstSession.load(Cat.class, catId);
Cat potentialMate = new Cat();
firstSession.save(potentialMate);

// アプリケーションのもっと高次の層で
cat.setMate(potentialMate);

// 後に、新しいセッションで
secondSession.update(cat);  // cat を更新する
secondSession.update(mate); // mate を更新する

アプリケーションが識別子 catId をもつ Cat を更新しようとした時に、それが secondSession によって既にロードされていた場合には、例外が投げられることになります。

アプリケーションは、その状態も更新しようとする場合に限っては、指定した一時的なインスタンスからたどることのできる一時的なインスタンスを、個々に、update() すべきです。(後で説明するライフサイクルオブジェクトを除きます。)

Hibernate のユーザーは、新しい識別子を生成することにより一時的なインスタンスを保存するか、そのインスタンスの現在の識別子と関連付けられた永続的状態を更新するための一般目的のメソッドを求めました。それに応える形で、saveOrUpdate() メソッドが、この機能を実装しています。

Hibernate は、identifier (あるいは、version か timestamp) プロパティーの値によって、"新しい" (保存されていない) インスタンスを、"既にある" (以前のセッションで保存されたかロードされた) インスタンスから区別します。<id> (あるいは、<version><timestamp>) マッピングの unsaved-value 属性は、どの値を "新しい" インスタンスとして解釈すべきかを指定します。

<id name="id" type="long" column="uid" unsaved-value="null">
    <generator class="hilo"/>
</id>

unsaved-value に許される値は、次の通りです。

  • any - 常に保存する

  • none - 常に更新する

  • null - 識別子が null の場合、保存する (これがデフォルトである)

  • 妥当な識別子の値 - 識別子が null であるか指定した値の場合、保存する

  • undefined - versiontimestamp がデフォルトであれば、識別子チェックが使われる

// 最初のセッションで
Cat cat = (Cat) firstSession.load(Cat.class, catID);

// アプリケーションのもっと高次の層で
Cat mate = new Cat();
cat.setMate(mate);

// 後に、新しいセッションで
secondSession.saveOrUpdate(cat);   // 既にある状態を更新する (cat の id は、null ではない)
secondSession.saveOrUpdate(mate);  // 新しいインスタンスを保存する (mate の id は、null である)

saveOrUpdate() の用法とセマンティクスは、新しいユーザーにとっては、混乱するものかもしれません。まず、あるセッションのインスタンスを別の新しいセッション内で使おうとしているのではない限りは、update()saveOrUpdate() を使う必要はないはずです。アプリケーションによっては、その全体を通して、これらのメソッドのどちらも使うことはないでしょう。

通常、update()saveOrUpdate() は、次のようなシナリオで使われます。

  • アプリケーションが、最初のセッションでオブジェクトをロードする。

  • オブジェクトが、 UI 層に渡される。

  • オブジェクトに対して、何らかの変更が行なわれる。

  • オブジェクトが、ビジネスロジック層に戻される。

  • アプリケーションが、2 番目のセッションで、update() を呼び出すことにより、こうした変更を永続化する。

saveOrUpdate() は、以下のことを行ないます。

  • オブジェクトがこのセッションで既に永続化されていれば、何もしない。

  • オブジェクトに識別子プロパティーがなければ、オブジェクトを save() する。

  • オブジェクトの識別子が unsaved-value が指定するクリテリア (criteria) にマッチすれば、オブジェクトを save() する。

  • バージョンが unsaved-value="undefined" (デフォルト値) ではない場合には、、オブジェクトがバージョン付き (versiontimestamp) であれば、バージョンは、識別子チェックに優先する。

  • セッションと関連付けられた別のオブジェクトが同じ識別子をもっていれば、例外を投げる。

9.4.3. 分離されたオブジェクトを再結合する

lock() メソッドを使えば、アプリケーションは、変更されていないオブジェクトを新しいセッションと再度関連付けすることができます。

// 再度関連付けするだけ
sess.lock(fritz, LockMode.NONE);
// バージョンチェックを行ない、再度関連付けする
sess.lock(izi, LockMode.READ);
// SELECT ... FOR UPDATE を使ってバージョンチェックを行ない、再度関連付けする
sess.lock(pk, LockMode.UPGRADE);

9.5. 永続的オブジェクトを削除する

Session.delete() は、オブジェクトの状態をデータベースから削除します。もちろん、アプリケーションは、それへの参照を保持し続ける場合があります。そのため、delete() は、永続インスタンスを一時的なものにすると考えるのが最善です。

sess.delete(cat);

Hibernate のクエリー文字列を delete() に渡すことにより、多数のオブジェクトを同時に削除する場合もあるかもしれません。

外部キー制約違反という危険を犯すことなく、オブジェクトを、お望みの任意の順序で削除することがあるかもしれません。もちろん、オブジェクトを誤った順序で削除することにより、外部キーカラム上にある NOT NULL 制約に違反する可能性は、依然として、存在します。

9.6. フラッシュ

時々、Session は、JDBC 接続の状態をメモリー中に保持しているオブジェクトの状態と同期させるのに必要な SQL 文を実行します。このプロセス フラッシュ (flush) は、デフォルトで、次の時点で発生します。

  • find()iterate() を何度か呼び出すことによって。

  • net.sf.hibernate.Transaction.commit() から。

  • Session.flush() から。

SQL 文は、次の順序で発行されます。

  1. 対応するオブジェクトを Session.save() を使って保存するのと同じ順序で、すべてのエンティティーを挿入。

  2. すべてのエンティティーを更新。

  3. すべてのコレクションを削除。

  4. すべてのコレクション要素の削除、更新、挿入。

  5. すべてのコレクションの挿入。

  6. 対応するオブジェクトを Session.delete() を使って削除するのと同じ順序で、すべてのエンティティーを削除。

(例外は、native ID 生成を使うオブジェクトが、それらが保存される時に挿入される場合です。)

明示的に flush() する場合以外、いつSession が JDBC 呼び出しを実行するかについて何の保証もありません。保証されるのは、それらが実行される順序だけです。しかしながら、Hibernate は、Session.find(..) メソッドが、無効なったデータを返すことがないことと、誤ったデータを返さないことは保証します。

フラッシュがあまり頻繁に起こらないようにデフォルトの振る舞いを変更することができます。FlushMode クラスは、3 つの異なったモードを定義しています。これは、(非常に) わずかなパフォーマンスの向上を達成するのに使われる場合がある "読み込み専用の" トランザクションで、非常に役立ちます。

sess = sf.openSession();
Transaction tx = sess.beginTransaction();
sess.setFlushMode(FlushMode.COMMIT); // クエリーが無効な状態を返せるようにする
Cat izi = (Cat) sess.load(Cat.class, id);
izi.setName(iznizi);
// 幾つかのクエリーを実行する ....
sess.find("from Cat as cat left outer join cat.kittens kitten");
// izi に対する変更は、フラッシュされない
...
tx.commit(); // フラッシュが発生する

9.7. セッションを終了する

セッションの終了には、4 つの異なったフェーズが関わります。

  • セッションをフラッシュする。

  • トランザクションをコミットする。

  • セッションをクローズする。

  • 例外を処理する。

9.7.1. セッションをフラッシュする

たまたま Transaction API を使うことになったとしても、このステップに思い悩む必要はありません。これは、トランザクションがコミットされる際に、暗黙的に行なわれます。そうでなければ、変更すべてがデータベースと同期することを保証するために、Session.flush() を呼び出すべきです。

9.7.2. データベーストランザクションをコミットする

Hibernate Transaction API を使っている場合、これは、次のようになります。

tx.commit(); // セッションをフラッシュし、トランザクションをコミットする

JDBC トランザクションを自分で管理している場合には、手作業で、JDBC 接続を commit() すべきです。

sess.flush();
sess.connection().commit();  // JTA データソースには、必要ない

変更をコミットしないと決めた場合には、次のようにします。

tx.rollback();  // トランザクションをロールバックする

あるいは

// JTA データソースには、必要ない。そうでなければ、重要
sess.connection().rollback();

トランザクションをロールバックする場合には、Hibernate の内部状態が一貫していることを保証するために、直ちに、現在のセッションをクローズし、廃棄すべきです。

9.7.3. セッションをクローズする

Session.close() の呼び出しは、セッション終了の合図となります。close() の主な意味合いは、セッションが、JDBC 接続を放棄することを示すことです。

tx.commit();
sess.close();
sess.flush();
sess.connection().commit();  // JTA データソースには、必要ない
sess.close();

自分自身の接続を提供している場合には、close() は、それへの参照を返します。そのため、それを主導でクローズし、プールに返すことができます。そうでなければ、close() は、それをプールに返します。

9.7.4. 例外処理

Session が例外 (あらゆる SQLException を含む) を投げる場合、直ちにトランザクションをロールバックし、Session.close() を呼び出して、Session インスタンスを廃棄すべきです。Session のメソッドによっては、セッションが一貫した状態にならないものもあります。

次の慣用的な例外処理を推奨します。

Session sess = factory.openSession();
Transaction tx = null;
try {
    tx = sess.beginTransaction();
    // 何らかの作業を行なう
    ...
    tx.commit();
}
catch (Exception e) {
    if (tx!=null) tx.rollback();
    throw e;
}
finally {
    sess.close();
}

あるいは、手作業で JDBC トランザクションを管理している場合

Session sess = factory.openSession();
try {
    // 何らかの作業を行なう
    ...
    sess.flush();
    sess.connection().commit();
}
catch (Exception e) {
    sess.connection().rollback();
    throw e;
}
finally {
    sess.close();
}

あるいは、JTA がサポートするデータソースを使っている場合

UserTransaction ut = .... ;
Session sess = factory.openSession();
try {
    // 何らかの作業を行なう
    ...
    sess.flush();
}
catch (Exception e) {
    ut.setRollbackOnly();
    throw e;
}
finally {
    sess.close();
}

9.8. ライフサイクルとオブジェクトグラフ

関連付けられたオブジェクトのグラフ内にあるオブジェクトすべての保存と更新を行なうには、次のいずれかを行なわなければなりません。

  • 個々のオブジェクトを save(), saveOrUpdate() あるいは update() する。あるいは

  • 関連付けられたオブジェクトを cascade="all" あるいは cascade="save-update" を使ってマップする。

同様に、グラフ内のオブジェクトすべてを削除するには、次のいずれかを行なわなければなりません。

  • 個々のオブジェクトを delete() する。あるいは

  • 関連付けられたオブジェクトを cascade="all", cascade="all-delete-orphan" あるいは cascade="delete" を使ってマップする。

推奨

  • 子供オブジェクトのライフスパン (生存期間) が親オブジェクトのライフスパンとバインドしている場合には、cascade="all" を指定することにより、それをライフサイクルオブジェクトにする。

  • そうではない場合、アプリケーションのコードから、明示的に、それを save() して delete() する。 何らかの余分な入力をするのを節約したい場合には、cascade="save-update" と明示的な delete() を使う。

関連 (多対 1 あるいはコレクション) を cascade="all" を使ってマップすることは、関連を親/子供スタイルの関連としてマークすることです。ここでは、親を保存/更新/削除すると、子供 (達) の保存/更新/削除という結果になります。更に、永続的な親から子供を参照するだけで、子供の保存/更新が結果として生じます。しかしながら、この比喩は、不完全です。その親から参照されなくなる子供は、cascade="all-delete-orphan" を使ってマップされた <one-to-many> 関連の場合以外、自動的に削除される訳ではありません。操作をカスケードする正確なセマンティクスは、次の通りです。

  • 親が保存される場合、子供はすべて、saveOrUpdate() に渡される。

  • 親が update()saveOrUpdate() に渡される場合、子供はすべて、saveOrUpdate() に渡される。

  • 一時的な子供が永続的な親によって参照されるようになると、それは、saveOrUpdate() に渡される。

  • 親が削除される場合、子供はすべて、delete() に渡される。

  • 一時的な子供が永続的な親によって参照を外される (dereference) と、"孤児となった" 子供が削除される cascade="all-delete-orphan" ではない場合には、特別なことは何も起こらない (アプリケーションは、必要な場合には、明示的に子供を削除すべきである)。

Hibernate は、(非効率な) 永続ガベージコレクションを暗示する "到達可能性による永続化" を完全に実装している訳ではありません。しかしながら、よくある要求によって、Hibernate は、別の永続オブジェクトによって参照されると、永続的になるエンティティーという考え方をサポートしています。cascade="save-update" をマークされた関連は、このような振る舞いをします。このアプローチをアプリケーション全体で使いたい場合には、<hibernate-mapping> 要素の default-cascade 属性を指定するのが比較的簡単でしょう。

9.9. インターセプター

Interceptor インターフェースは、セッションからアプリケーションへのコールバックを提供します。これによって、アプリケーションは、永続オブジェクトの保存、更新、削除やロードが行なわれる前に、そのプロパティーの検査そして/あるいは操作を行なうことができます。これが使える場合の 1 つは、監査情報を追跡することです。例えば、次の Interceptor は、Auditable が作られる時に、自動的に、createTimestamp をセットし、Auditable が更新される際に、lastUpdateTimestamp を更新します。

package net.sf.hibernate.test;

import java.io.Serializable;
import java.util.Date;
import java.util.Iterator;

import net.sf.hibernate.Interceptor;
import net.sf.hibernate.type.Type;

public class AuditInterceptor implements Interceptor, Serializable {

    private int updates;
    private int creates;

    public void onDelete(Object entity,
                         Serializable id,
                         Object[] state,
                         String[] propertyNames,
                         Type[] types) {
        // 何もしない
    }

    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) {
        return false;
    }

    public boolean onSave(Object entity,
                          Serializable id,
                          Object[] state,
                          String[] propertyNames,
                          Type[] types) {

        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 postFlush(Iterator entities) {
        System.out.println("Creations: " + creates + ", Updates: " + updates);
    }

    public void preFlush(Iterator entities) {
        updates=0;
        creates=0;
    }
    
    ......
    ......
    
}

インターセプターは、セッションを作る時に指定します。

Session session = sf.openSession( new AuditInterceptor() );

9.10. メタデータ API

Hibernate は、エンティティーと値型すべてに非常に内容豊かなメタデータモデルを必要とします。時には、このモデルは、アプリケーション自身にとって、非常に役立ちます。例えば、アプリケーションは、Hibernate のメタデータを使って、"スマートな" ディープコピー (deep-copy) アルゴリズムを実装するかもしれません。これは、どのオブジェクトをコピー (例えば、変更可能な値型) し、どのオブジェクトをコピーすべきではないか (例えば、変更できない値型や、多分、関連付けられたエンティティー) を知っているものです。

Hibernate は、メタデータを ClassMetadataCollectionMetadata インターフェースと Type 階層によって公開します。メタデータインターフェースのインスタンスは、SessionFactory から取得します。

Cat fritz = ......;
Long id = (Long) catMeta.getIdentifier(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
// TODO: コンポーネントはどうなのか
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] );
    }
}

第 10 章 トランザクションと並行性

Hibernate 自身は、データベースではありません。それは、軽量のオブジェクト - リレーショナルマッピングツールです。トランザクション管理は、基礎にあるデータベース接続に委譲されます。接続が JTA がサポートされている場合、Session が行なう操作は、もっと広い JTA トランザクションの原子的な一部になります。Hibernate は、JDBC に対するシン (thin) アダプターと見なすことができます。それにオブジェクト指向のセマンティクスを加えたものと考えればよいでしょう。

10.1. 設定、セッションとファクトリー

SessionFactory は、作成費用の高い、スレッドセーフなオブジェクトで、アプリケーションのスレッドすべてによって共有されることが意図されています。Session は、安価な、スレッドセーフではないオブジェクトで、1 つのビジネスプロセスが、一度だけ使って、廃棄されるべきものです。例えば、サーブレットベースのアプリケーションで Hibernate を使う場合、サーブレットは、次のようにして、SessionFactory を取得できるでしょう。

SessionFactory sf = (SessionFactory)getServletContext().getAttribute("my.session.factory");

サービスメソッドの個々の呼び出しは、新しい Session を作り、それを flush() し、その接続を commit() して、それを close() して、最後にそれを廃棄することができます。(SessionFactory も、JNDI 内や、スタティックな Singleton ヘルパー変数内に保持される場合があります。)

ステートレスセッション Bean 内でも、同様なアプローチを使うことができます。Bean は、setSessionContext() 内で、SessionFactory を取得します。次に、個々のビジネスメソッドは、Session を作り、それを flush() して、それを close() します。もちろん、アプリケーションは、接続を commit() すべきではありません。(それは、JTA に委ねます。データベース接続は、自動的に、コンテナー管理のトランザクションに参加します。)

Hibernate Transaction API は、既に説明したような使い方をします。Hibernate Transaction の 1 つの commit() は、状態をフラッシュし、(JTA トランザクションの特殊な処理によって) 基礎にあるデータベース接続をコミットします。

flush() のセマンティクスを理解していることを確認して下さい。フラッシュすると、永続ストアは、メモリー内の変更と同期しますが、その逆は真ではありません。Hibernate JDBC 接続/トランザクションすべての場合、その接続にとってのトランザクションの隔離レベルは、Hibernate が実行するすべての操作に適用される点に注意して下さい。

次からの幾つかのセクションでは、トランザクションの原子性を保証するために、バージョン付けを活用する代替アプローチを説明します。これらは、慎重に使うべき "高度な" アプローチと考えることができます。

10.2. スレッドと接続

Hibernate のセッションを作る場合に、次の実践を注意して見ておくべきです。

  • 決して、並行的な Session を複数作ったり、データベース接続単位に Transaction インスタンスを作らない。

  • データベース単位、トランザクション単位に複数の Session を作る場合は、特に注意する。Session 自身は、ロードしたオブジェクトに対して行なった更新を追跡している。そのため、別の Session は、無効なデータを見ることになるかもしれない。

  • Session は、スレッドセーフではない。2 つの並行的なスレッドから、決して、同じ Session にアクセスしてはならない。Session は、通常、1 つの作業単位にすぎない。

10.3. オブジェクトの同一性を考慮する

アプリケーションは、同時並行的に、2 つの異なった作業単位から、同じ永続状態にアクセスする場合があるかもしれません。しかしながら、永続クラスのインスタンスは、決して、2 つの Session インスタンス間で共有されることはありません。このため、同一性 (identity) には、2 つの異なった考え方があります。

データベース同一性

foo.getId().equals( bar.getId() )

JVM 同一性

foo==bar

次に、特定の Session に結び付けられるオブジェクトにとって、この 2 つの考え方は、等価です。しかしながら、アプリケーションは、2 つの異なったセッション内で "同じ" (永続的同一性) ビジネスオブジェクトに同時並行的にアクセスするかもしれませんが、2 つのインスタンスは、実際には、"異なって" います (JVM 同一性)。

このアプローチは、Hibernate とデータベースに、並行性について考えさせることになります。アプリケーションは、それが、Session 単位の単一スレッドやオブジェクトの同一性を遵守する限りにおいては、ビジネスオブジェクト上で同期を取る必要はまったくありません (Session 内部では、アプリケーションは、オブジェクトを比較するのに、安心して、== を使うことができます)。

10.4. Optimistic 並行性コントロール

ビジネスプロセスの多くでは、データベースアクセスと関わっているユーザーとの一連のやり取りの全体が必要です。ウェブアプリケーションやエンタープライズアプリケーションでは、データベーストランザクションがユーザーとのやり取りを超えることは、受け入れられません。

ビジネスプロセスの隔離性を維持することは、一部は、アプリケーション層の責任となります。このため、このプロセスのことを、実行に時間を要するアプリケーショントランザクションと呼びます。1 つのアプリケーショントランザクションは、通常、幾つかのデータベーストランザクションにまたがります。こうしたデータベーストランザクションのどれか 1 つだけ (最後のトランザクション) が更新されたデータを保存し、他のすべてはデータを読むだけならば、それは、もっと原子的となるでしょう。

高度の並行性と高度のスケーラビリティーと矛盾しないただ 1 つのアプローチは、バージョン付きの optimistic な並行性コントロールです。Hibernate は、optimistic な並行性を使うアプリケーションコードを書くための 3 つの可能なアプローチを提供しています。

10.4.1. 自動バージョン付けのある長いセッション

1 つの Session インスタンスとその永続インスタンスは、アプリケーショントランザクション全体のために使われます。

Session は、多くのデータベーストランザクションがアプリケーションに対して 1 つの論理的アプリケーショントランザクションとして現われることを保証するため、バージョン付きの optimistic ロッキングを使います。Session は、ユーザートランザクションを待つようになると、基礎にある JDBC 接続から切断されます。このアプローチは、データベースアクセスに関して、最も効率的です。アプリケーションは、バージョンチェックや分離されたインスタンスを再結合することに関わりをもつ必要はありません。

// foo は、セッションが以前にロードしたインスタンスである
session.reconnect();
foo.setProperty("bar");
session.flush();
session.connection().commit();
session.disconnect();

foo オブジェクトは、どのセッションがそれをロードしたのかを知っています。Session が JDBC 接続されるとすぐに、変更は、オブジェクトにコミットされます。

ここでの Session が大きすぎて、ユーザーの思考時間内に保存できない場合には、このパターンは、問題です。例えば、HttpSession は、できる限り小さくしておくべきです。Session は (必須の) 最初のレベルのキャッシュでもあり、ロードされたすべてのオブジェクトを含むことができるので、多分、この戦略が使えるのは、幾つかのリクエスト/レスポンスサイクルに対してだけでしょう。Session はすぐに無効なデータももつことになるので、この戦略が、お勧めです。

10.4.2. 自動バージョン付けのある多数のセッション

永続ストアとの個々のやり取りは、新しい Session 内で起こります。しかしながら、データベースとの個々のやり取りでは、同じ永続インスタンスが再利用されます。アプリケーションは、元々は別の Session にロードされた、分離されたインスタンスの状態を操作し、Session.update()Session.saveOrUpdate() を使ってそれらを "再関連付けします"。

// foo は、以前のセッションがロードしたインスタンスである
foo is an instance loaded by a previous Session
foo.setProperty("bar");
session = factory.openSession();
session.saveOrUpdate(foo);
session.flush();
session.connection().commit();
session.close();

オブジェクトが変更されていないことが確かな場合には、update() の代わりに lock() を呼び出し、LockMode.READ を使うことがあるかもしれません (これは、バージョンチェックを行ない、すべてのキャッシュを迂回します)。

10.4.3. アプリケーションによるバージョンチェック

データベースとの個々のやり取りは、新しい Session 内で起こります。そこでは、永続インスタンスすべては、それらを操作する前に、データベースから再ロードされます。このアプローチは、アプリケーショントランザクションの隔離性を確かめるため、それ自身によるバージョンチェックを行なうことをアプリケーションに強制します。(もちろん、Hibernate が、この場合でも、バージョン番号の更新を、代わって、行なってくれます。) このアプローチは、データベースアクセスという観点からすると、まったく効率的ではありません。これは、エンティティー EJB に非常に似たアプローチです。

// foo は、以前のセッションがロードしたインスタンスである
session = factory.openSession();
int oldVersion = foo.getVersion();
session.load( foo, foo.getKey() );
if ( oldVersion!=foo.getVersion ) throw new StaleObjectStateException();
foo.setProperty("bar");
session.flush();
session.connection().commit();
session.close();

もちろん、データが並行して存在することが少ない (low-data-concurrency) 環境で作業していて、バージョンチェックが必要ない場合には、このアプローチを使い、バージョンチェックをスキップするだけですむかもしれません。

10.5. セッションの切断

上で説明した最初のアプローチは、ユーザーの思考時間を超えるビジネスプロセス全体に対して、1 つの Session を維持します。(例えば、サーブレットは、ユーザーの HttpSession 内で、Session を維持します。) パフォーマンス上の理由から、ユーザーのアクションを待つ前に、次のことを行なうべきです。

  1. Transaction (あるいは JDBC 接続) をコミットし、次に

  2. JDBC 接続から Session を切断する

Session.disconnect() メソッドは、セッションを JDBC 接続から切断し、接続をプールに返します (自分で接続を提供しているのではない場合)。

Session.reconnect() は、新しい接続を取得 (あるいは、自分でそれを提供) し、セッションを再開します。再接続の後、更新していないデータに対するバージョンチェックを強制するには、別のトランザクションが更新したかもしれないオブジェクトすべてで Session.lock() を呼び出します。更新中のデータをロックする必要はありません。

以下は、その例です。

SessionFactory sessions;
List fooList;
Bar bar;
....
Session s = sessions.openSession();

Transaction tx = null;
try {
    tx = s.beginTransaction();

    fooList = s.find(
    	"select foo from eg.Foo foo where foo.Date = current date"
        // db2 の日付関数を使う
    );
    bar = (Bar) s.create(Bar.class);

    tx.commit();
}
catch (Exception e) {
    if (tx!=null) tx.rollback();
    s.close();
    throw e;
}
s.disconnect();

後になって

s.reconnect();

try {
    tx = s.beginTransaction();

    bar.setFooTable( new HashMap() );
    Iterator iter = fooList.iterator();
    while ( iter.hasNext() ) {
        Foo foo = (Foo) iter.next();
        s.lock(foo, LockMode.READ);    // foo が無効でないことをチェックする
        bar.getFooTable().put( foo.getName(), foo );
    }

    tx.commit();
}
catch (Exception e) {
    if (tx!=null) tx.rollback();
    throw e;
}
finally {
    s.close();
}

この例から、TransactionSession 間が、どのような多対 1 関係であるのかと、Session が、アプリケーションとデータベース間の会話を表現していることが理解できます。Transaction は、この対話を、データベースレベルでの原子的な作業単位に細分化します。

10.6. Pessimistic ロッキング

ユーザーが多くの時間を費やして、ロッキング戦略に思い悩むことは意図されていません。通常は、JDBC 接続の隔離レベルを指定し、データベースにそれに必要なすべての作業をを行なわせるだけで十分です。しかしながら、先進的なユーザーの場合には、排他的な pessimistic ロックを取得し、新しいトランザクションの開始時にロックを再取得することを望むかもしれません。

Hibernate は、常に、データベースのロッキングメカニズムを使い、メモリー中にあるオブジェクトをロックすることは決してありません。

LockMode クラスは、Hibernate が取得する様々なロックレベルを定義します。ロックは、次のメカニズムによって取得します。

  • LockMode.WRITE は、Hibernate が行の更新や挿入を行なう際に、自動的に、取得される。

  • LockMode.UPGRADE は、SELECT ... FOR UPDATE シンタックスをサポートするデータベース上でこれを使うことにより、明示的なユーザーリクエストによって取得される。

  • LockMode.UPGRADE_NOWAIT は、Oracle の下で、SELECT ... FOR UPDATE NOWAIT を使うことにより、明示的なユーザーリクエストによって取得される。

  • LockMode.READ は、Hibernate が、繰り返し可能な読み込みやシリアル化可能な隔離レベルの下で、データを読み込む際に、自動的に、取得される。明示的なユーザーリクエストによって、再取得される場合がある。

  • LockMode.NONE は、ロックが存在しないことを表わす。オブジェクトはすべて、Transaction の最後でこのロックモードにスイッチする。update()saveOrUpdate() の呼び出しによってセッションと関連付けられたオブジェクトも、このロックモードで開始する。

"明示的なユーザーリクエスト" は、次のいずれかの方法で、表現されます。

  • LockMode を指定する Session.load() の呼び出し。

  • Session.lock() の呼び出し。

  • Query.setLockMode() の呼び出し。

Session.load()UPGRADEUPGRADE_NOWAIT とともに呼び出され、リクエストされたオブジェクトをセッションがまだロードしていない場合、オブジェクトは、SELECT ... FOR UPDATE を使ってロードされます。一方、load() が、リクエストされたロックよりも制限の少ないロックを使って既にロードされているオブジェクトに対して呼び出されると、Hibernate は、そのオブジェクトのため、lock() を呼び出します。

指定されたロックモードが READ, UPGRADEUPGRADE_NOWAIT の場合、Session.lock() は、バージョン番号チェックを行います。(UPGRADEUPGRADE_NOWAIT の場合、SELECT ... FOR UPDATE が使われます。)

データベースがリクエストされたロックモードをサポートしていない場合、Hibernate は、(例外を投げるのではなく) 適切な代替モードを使います。これによって、アプリケーションはポータブルであることが保証されます。

第 11 章 HQL: Hibernate クエリー言語

Hibernate には、非常にパワフルなクエリー言語が付属しています。それは、SQL に非常によく似ています (それは意図的なものです)。しかし、そのシンタックスにだまされないで下さい。HQL は、完全にオブジェクト指向で、継承はポリモーフィズム、関連などの考え方を理解しています。

11.1. 大文字と小文字の区別

クエリーは、Java クラスとプロパティーの名前以外は、大文字と小文字を区別します。そのため、SeLeCT は、sELEctSELECT と同じです。しかし、net.sf.hibernate.eg.FOO は、net.sf.hibernate.eg.Foo と同じではなく、foo.barSet も、foo.BARSET と同じではありません。

このマニュアルは、HQL のキーワードを小文字で表わします。ユーザーによっては、大文字のキーワードを使ったクエリーの方が読みやすいかもしれませんが、この慣習は、Java コードに埋め込んだ時には美しくないと思われます。

11.2. from 句

おそらく最も単純な Hibernate クエリーは、次の形式のものでしょう。

from eg.Cat

これは、クラス eg.Cat のすべてのインスタンスを返すだけのものです。

ほとんどの場合、エイリアス (alias) を割り当てる必要があります。何故なら、クエリーの別の場所で、Cat に言及したいからです。

from eg.Cat as cat

このクエリーは、Cat インスタンスに、エイリアス cat を割り当てています。そのため、エイリアスをクエリーの後の箇所で使うことができます。as キーワードはオプションです。そのため、次のように書くこともできます。

from eg.Cat cat

複数のクラスが現われ、結果として、直積 (cartesian product: 訳注: 2つの表から、あり得るすべての組み合わせを作ること) や "クロス" 結合が作られる場合があります。

from Formula, Parameter
from Formula as form, Parameter as param

ローカル変数に対する Java のネーミング標準と一貫するように、クエリーのエイリアスに最初を小文字にした名前を付けることはよい習慣であると考えられます (例えば、domesticCat)。

11.3. 関連と結合

join を使って、関連付けられたエンティティーに対してや、値のコレクションの要素に対してさえ、エイリアスを割り当てる場合があるかもしれません。

from eg.Cat as cat 
    inner join cat.mate as mate
    left outer join cat.kittens as kitten

from eg.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 joinright outer join 構成要素には、省略形を使う場合があります。

from eg.Cat as cat 
    join cat.mate as mate
    left join cat.kittens as kitten

更に、"fetch" による結合によって、関連や値のコレクションを、select を 1 つ使うことで、その親オブジェクトとともに初期化することができます。これは、コレクションの場合には、特に役立ちます。それは、実質上、外部結合と、関連とコレクションのためのマッピングファイルの遅延宣言をオーバーライドします。

from eg.Cat as cat 
    inner join fetch cat.mate
    left join fetch cat.kittens

フェッチによる結合には、通常、エイリアスを割り当てる必要がありません。これは、関連付けられたオブジェクトを where 句 (あるいは、他の句) 内で使うべきではないからです。また、関連付けられたオブジェクトが、クエリーの結果内に、直接、返されることはありません。それらは、親オブジェクトからアクセスされます。

現在の実装では、たった 1 つのコレクションロールだけがクエリー内でフェッチされる (それ以外のすべては、実行されない) 点に注意して下さい。fetch 構成要素は、scroll()iterate() を使って呼び出されるクエリー内では使われない点にも注意して下さい。最後に、full join fetchright join fetch には、何の意味もない点にも注意して下さい。

11.4. select 句

select 句は、どのオブジェクトやプロパティーをクエリーの結果セット内に返すべきかを選択します。次の例を考えて下さい。

select mate 
from eg.Cat as cat 
    inner join cat.mate as mate

クエリーは、他の Catmate を選択します。実際には、このクエリーは、もっとコンパクトに次のように表現することができます。

select cat.mate from eg.Cat cat

特殊な elements 関数を使えば、コレクション要素さえ選択することもできます。以下のクエリーは、あらゆる cat のすべての kittens を返します。

select elements(cat.kittens) from eg.Cat cat

クエリーは、コンポーネントの型のプロパティーを含むあらゆる値型のプロパティーを返す場合もあります。

select cat.name from eg.DomesticCat cat
where cat.name like 'fri%'

select cust.name.firstName from Customer as cust

クエリは、Object[] 型の配列として、複数のオブジェクトそして/あるいはプロパティーを返す場合があります。

select mother, offspr, mate.name 
from eg.DomesticCat as mother
    inner join mother.mate as mate
    left outer join mother.kittens as offspr

あるいは、型に依存しない (typesafe) 実際の Java オブジェクトとして

select new Family(mother, mate, offspr)
from eg.DomesticCat as mother
    join mother.mate as mate
    left join mother.kittens as offspr

ここでは、クラス Family に、適切なコンストラクターがあると想定しています。

11.5. 集計関数

HQL クエリーは、プロパティーに関する集計関数の結果を返す場合すらあります。

select avg(cat.weight), sum(cat.weight), max(cat.weight), count(cat)
from eg.Cat cat

コレクションは、select 句内の集計関数内に現われる場合もあります。

select cat, count( elements(cat.kittens) )
from eg.Cat cat group by cat

サポート済みの集計関数は、次の通りです。

  • avg(...), sum(...), min(...), max(...)

  • count(*)

  • count(...), count(distinct ...), count(all...)

distinctall キーワードは、SQL と同様に使われ、それと同じセマンティクスをもちます。

select distinct cat.name from eg.Cat cat

select count(distinct cat.name), count(cat) from eg.Cat cat

11.6. ポリモーフィズム的クエリー

次のクエリー

from eg.Cat as cat

これは、Cat のインスタンスだけでなく、DomesticCat のようなサブクラスのインスタンスも返します。Hibernate のクエリーは、from 句内で、あらゆる Java クラスやインターフェースを指定することができます。クエリーは、そのクラスを拡張し、あるいはそのインターフェースを実装する永続クラスすべてのインスタンスを返します。次のクエリーは、すべての永続オブジェクトを返します。

from java.lang.Object o

インターフェース Named は、様々な永続クラスによって実装される場合があります。

from eg.Named n, eg.Named m where n.name = m.name

これら 2 つのクエリーは、複数の SQL SELECT を必要とします。この意味は、order by 句が、結果セット全体を正しい順序で並べないということです。(また、Query.scroll() を使って、これらのクエリーを呼び出すことはできないという意味もあります。)

11.7. where 句

where 句を使えば、返されるインスタンスのリストの範囲を狭めることができます。

from eg.Cat as cat where cat.name='Fritz'

これは、'Fritz' という名前の Cat のインスタンスを返します。

select foo 
from eg.Foo foo, eg.Bar bar
where foo.startDate = bar.date

これは、FoostartDate プロパティーと等しい date プロパティーをもつ bar のインスタンスが存在する場合、Foo のインスタンスすべてを返します。複合パス式によって、where 句は、非常にパワフルになります。次の例を考えて下さい。

from eg.Cat cat where cat.mate.name is not null

このクエリーは、テーブルの (内部) 結合をもつ SQL クエリーに翻訳されます。次のような SQL を書いたとすると

from eg.Foo foo  
where foo.bar.baz.customer.address.city is not null

SQL では 4 つのテーブル結合が必要なクエリーができあがるでしょう。

= オペレーターは、プロパティーだけでなく、インスタンスを比較する場合に使用します。

from eg.Cat cat, eg.Cat rival where cat.mate = rival.mate

select cat, mate 
from eg.Cat cat, eg.Cat mate
where cat.mate = mate

特殊プロパティー (小文字) id は、オブジェクトの一意な識別子を参照するのに使われます。(そのプロパティーの名前を使うこともできます。)

from eg.Cat as cat where cat.id = 123

from eg.Cat as cat where cat.mate.id = 69

2 番目のクエリーは、効率的です。テーブルの結合が必要ないからです。

複合識別子のプロパティーを使うこともできます。Person に、countrymedicareNumber からなる複合識別子があるとしましょう。

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 は、ポリモーフィズム的永続性の場合、インスタンスの判別値にアクセスします。where 句に埋め込まれた Java クラス名は、その判別値に翻訳されます。

from eg.Cat cat where cat.class = eg.DomesticCat

コンポーネントや複合ユーザー型のプロパティー (そしてコンポーネントのコンポーネントのプロパティーなど) を指定する場合があるかもしれません。決して、(コンポーネントのプロパティーに対して) 最終的にコンポーネント型のプロパティーになるパス式を使おうとしてはいけません。例えば、store.owner が、コンポーネント address をもつエンティティーの場合

store.owner.address.city    // ok
store.owner.address         // エラー!

"any" 型は、特殊プロパティー idclass をもちます。これによって、次の方法で、結合を表現することができます (ここで、AuditLog.item は、<any> とマップされるプロパティーです)。

from eg.AuditLog log, eg.Payment payment 
where log.item.class = 'eg.Payment' and log.item.id = payment.id

log.item.classpayment.class は、上のクエリーでは、まったく異なったデータベースカラムの値を参照する点に注意して下さい。

11.8. 式

式 (expression) を使うと、where 句に、SQL 内に書くことのできるほとんどのものを取り込むことができます。

  • 数学的オペレーター +, -, *, /

  • バイナリー比較オペレーター =, >=, <=, <>, !=, like

  • 論理オペレーター and, or, not

  • 文字列連結 ||

  • upper()lower() のような SQL スカラー関数

  • グルーピングを示すカッコ ( )

  • in, between, is null

  • JDBC IN パラメータ ?

  • 名前付きパラメータ :name, :start_date, :x1

  • SQL リテラル 'foo', 69, '1970-01-01 10:00:01.0'

  • Java public static final 定数 eg.Color.TABBY

inbetween は、次のように使用します。

from eg.DomesticCat cat where cat.name between 'A' and 'B'

from eg.DomesticCat cat where cat.name in ( 'Foo', 'Bar', 'Baz' )

否定形のフォームは、次のように書きます。

from eg.DomesticCat cat where cat.name not between 'A' and 'B'

from eg.DomesticCat cat where cat.name not in ( 'Foo', 'Bar', 'Baz' )

同様に、is nullis not null は、null 値かどうかをテストするために使います。

Hibernate の設定で、HQL クエリー置換を宣言することにより、ブール値を、式内で簡単に使うことができます。

<property name="hibernate.query.substitutions">true 1, false 0</property>

これは、この HQL から翻訳された SQL 内で、キーワード truefalse を、リテラル 10 で置き換えます。

from eg.Cat cat where cat.alive = true

特殊プロパティー size か、特殊関数 size() を使って、コレクションのサイズをテストすることができます。

from eg.Cat cat where cat.kittens.size > 0

from eg.Cat cat where size(cat.kittens) > 0

インデックス付きのコレクションの場合、minIndexmaxIndex を使って、インデックスの最小値と最大値を参照することができます。同様に、minElementmaxElement を使って、基本型のコレクションの最小要素と最大要素を参照することができます。

from Calendar cal where cal.holidays.maxElement > current date

関数形式もあります (上の構成要素とは違って、これは、大文字と小文字を区別しません)。

from Order order where maxindex(order.items) > 100

from Order order where minelement(order.items) > 10000

コレクションの要素やインデックスセット (elementsindices 関数) やサブクエリーの結果を渡す場合には、SQL 関数 any, some, all, exists, in もサポートしています (以下を参照して下さい)。

select mother from eg.Cat as mother, eg.Cat as kit
where kit in elements(foo.kittens)

select p from eg.NameList list, eg.Person p
where p.name = some elements(list.names)

from eg.Cat cat where exists elements(cat.kittens)

from eg.Player p where 3 > all elements(p.scores)

from eg.Show show where 'fizard' in indices(show.acts)

これらの構成要素 - size, elements, indices, minIndex, maxIndex, minElement, maxElement - には、使用法の制限があります。

  • where 句: サブセレクトのあるデータベースのみ

  • select 句: elementsindices だけが意味をもつ

インデックス付きコレクション (配列、リスト、マップ) の要素は、インデックス (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 は、1 対多関連や値のコレクションの要素に対して、組み込みの index() 関数も提供しています。

select item, index(item) from Order order 
    join order.items item
where index(item) < 5

基礎にあるデータベースがサポートするスカラー SQL 関数が使われる場合もあります。

from eg.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)

ヒントは、次のようなものです。

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
    )

11.9. order by 句

クエリーが返すリストは、返されるクラスやコンポーネントの任意のプロパティーによって順番に並べることができます。

from eg.DomesticCat cat
order by cat.name asc, cat.weight desc, cat.birthdate

オプションの ascdesc は、それぞれ、昇順か降順かを指定します。

11.10. group by 句

集計対を返すクエリーは、返されるクラスやコンポーネントの任意のプロパティーによってグループ化することができます。

select cat.color, sum(cat.weight), count(cat)
from eg.Cat cat
group by cat.color

select foo.id, avg( elements(foo.names) ), max( indices(foo.names) )
from eg.Foo foo
group by foo.id

注意: サブセレクトのないデータベースであっても、セレクト句内で、elementsindices を使うことができます。

having 句を使うこともできます。

select cat.color, sum(cat.weight), count(cat) 
from eg.Cat cat
group by cat.color 
having cat.color in (eg.Color.TABBY, eg.Color.BLACK)

基礎となるデータベース (つまり、MySQL 以外) がサポートしている場合には、SQL 関数と集計関数を、havingorder by 句内で使うことができます。

select cat
from eg.Cat cat
    join cat.kittens kitten
group by cat
having avg(kitten.weight) > 100
order by count(kitten) asc, sum(kitten.weight) desc

group by 句も order by 句も、代数式を含むことができない点に注意して下さい。

11.11. サブクエリー

サブセレクトをサポートするデータベースのため、Hibernate は、クエリー内でのサブクエリーをサポートしています。サブクエリーは、カッコによって (多くの場合、SQL 集計関数呼び出しによって) 取り囲まれていなければなりません。相関サブクエリー (外部クエリー内のエイリアスを参照するサブクエリー) も許されます。

from eg.Cat as fatcat 
where fatcat.weight > ( 
    select avg(cat.weight) from eg.DomesticCat cat 
)

from eg.DomesticCat as cat 
where cat.name = some ( 
    select name.nickName from eg.Name as name 
)

from eg.Cat as cat 
where not exists ( 
    from eg.Cat as mate where mate.mate = cat 
)

from eg.DomesticCat as cat 
where cat.name not in ( 
    select name.nickName from eg.Name as name 
)

11.12. HQL の例

Hibernate クエリーは、非常にパワフルな場合もあれば、複雑な場合もあります。事実、クエリー言語のパワーは、Hibernate の主要なセールスポイントの 1 つです。以下は、筆者が最近のプロジェクトで使ったクエリーによく似たサンプルクエリーです。読者が書くことになるクエリーのほとんどは、これらよりもはるかに単純なはずです。

次のクエリーは、特定カスタマーの未払いのすべての注文のうち、指定の最低合計額以上の注文の、注文 id、項目の数、注文の合計値を、合計値によって結果を並べて返します。価格を決めるため、それは、現在のカタログを使います。結果の SQL クエリーは、ORDER, ORDER_LINE, PRODUCT, CATALOGPRICE に対して、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

なんと巨大なのでしょうか。実際のところ、現実には、筆者は、サブクエリーがあまり好きではありません。そのため、クエリーは、実際には、次のようなものでした。

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

次のクエリーは、AWAITING_APPROVAL ステータスの支払いのすべてを除いた、個々のステータスごとの支払い数をカウントします。そこでは、最新のステータスの変更は、現在のユーザーが行ないます。それは、PAYMENT, PAYMENT_STATUSPAYMENT_STATUS_CHANGE テーブルに対する 2 つの内部結合と対応するサブセレクトをもつ SQL クエリーに翻訳されます。

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

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() 関数を使って、現在のユーザーが属する組織に対するすべてのアカウントと未払いの支払いを返します。それは、ACCOUNT, PAYMENT, PAYMENT_STATUS, ACCOUNT_TYPE, ORGANIZATIONORG_USER テーブルに対する 3 つの内部結合と 1 つの外部結合、サブセレクトのある 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

11.13. チップとトリック

クエリの結果を実際に返すことなく、その数をカウントすることができます。

( (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

使用しているデータベースがサブセレクトをサポートしていない場合には、次のクエリーを使います。

select usr.id, usr.name
from User usr.name
    join usr.messages msg
group by usr.id, usr.name
having count(msg) >= 1

この解決策は、内部結合のため、メッセージのない User を返すことができないので、次の形式も役に立ちます。

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 in class Foo where foo.name=:name and foo.size=:size");
q.setProperties(fooBean); // fooBean は、getName() と getSize() をもつ
List foos = q.list();

コレクションは、フィルターと一緒に Query インターフェースを使うことにより、ページ付けが可能です。

Query q = s.createFilter( collection, "" ); // 小さなフィルター
q.setMaxResults(PAGE_SIZE);
q.setFirstResult(PAGE_SIZE * pageNumber);
List page = q.list();

コレクション要素は、クエリーフィルターを使うことにより、順序付けやグループ化ができます。

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();

Chapter 12. クリテリアクエリー

Hibernate は、直感的で拡張可能なクリテリア (criteria) クエリー API を提供しています。今のところ、この API は、あまりパワフルではありませんし、もっと成熟した HQL クエリーファシリティーよりも劣っています。特に、criteria クエリーは、投影と集計をサポートしていません。

12.1. Criteria インスタンスを作成する

インターフェース net.sf.hibernate.Criteria は、特定の永続クラスに対するクエリーを表わします。Session は、Criteria インスタンスのためのファクトリーです。

Criteria crit = sess.createCriteria(Cat.class);
crit.setMaxResults(50);
List cats = crit.list();

12.2. 結果セットを狭める

個々のクエリーの選択基準は、インターフェース net.sf.hibernate.expression.Criterion のインスタンスです。クラス net.sf.hibernate.expression.Expression は、組み込みの Criterion 型を取得するためのファクトリーメソッドを定義しています。

List cats = sess.createCriteria(Cat.class)
    .add( Expression.like("name", "Fritz%") )
    .add( Expression.between("weight", minWeight, maxWeight) )
    .list();

式は、論理的にグループ化されます。

List cats = sess.createCriteria(Cat.class)
    .add( Expression.like("name", "Fritz%") )
    .add( Expression.or(
        Expression.eq( "age", new Integer(0) ),
        Expression.isNull("age")
    ) )
    .list();
List cats = sess.createCriteria(Cat.class)
    .add( Expression.in( "name", new String[] { "Fritz", "Izi", "Pk" } ) )
    .add( Expression.disjunction()
        .add( Expression.isNull("age") )
    	.add( Expression.eq("age", new Integer(0) ) )
    	.add( Expression.eq("age", new Integer(1) ) )
    	.add( Expression.eq("age", new Integer(2) ) )
    ) )
    .list();

広範囲に渡る組み込みの criterion (選択基準) 型 (Expression のサブクラス) がありますが、特に役立つのは、直接、SQL を指定できるものです。

List cats = sess.createCriteria(Cat.class)
    .add( Expression.sql("lower({alias}.name) like lower(?)", "Fritz%", Hibernate.STRING) )
    .list();

{alias} プレースホルダーは、クエリーされたエンティティーの行エイリアスによって置き換えられます。

12.3. 結果に順序を付けて並べる

net.sf.hibernate.expression.Order を使って、結果に順序を付けて並べることができます。

List cats = sess.createCriteria(Cat.class)
    .add( Expression.like("name", "F%")
    .addOrder( Order.asc("name") )
    .addOrder( Order.desc("age") )
    .setMaxResults(50)
    .list();

12.4. 関連

createCriteria() を使って関連をナビゲートすることにより、関連するエンティティーに簡単に制約を指定することができます。

List cats = sess.createCriteria(Cat.class)
    .add( Expression.like("name", "F%")
    .createCriteria("kittens")
        .add( Expression.like("name", "F%")
    .list();

2 番目の createCriteria() が、kittens コレクションの要素を参照する Criteria の新しいインスタンスを返す点に注意して下さい。

次の代替形式は、環境によっては、役立ちます。

List cats = sess.createCriteria(Cat.class)
    .createAlias("kittens", "kt")
    .createAlias("mate", "mt")
    .add( Expression.eqProperty("kt.name", "mt.name") )
    .list();

(createAlias() は、Criteria の新しいインスタンスを作りません。)

前の 2 つのクエリーが返す Cat インスタンスが保持する kittens コレクションは、クリテリアによってあらかじめフィルターされたものではない点に注意して下さい。クリテリアにマッチする kittens だけを検索したい場合には、returnMaps() を使わなければなりません。

List cats = sess.createCriteria(Cat.class)
    .createCriteria("kittens", "kt")
        .add( Expression.eq("name", "F%") )
    .returnMaps()
    .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");
}

12.5. 動的な関連をフェッチする

実行時に setFetchMode() を使って、関連をフェッチするセマンティクスを指定する場合があるかもしれません。

List cats = sess.createCriteria(Cat.class)
    .add( Expression.like("name", "Fritz%") )
    .setFetchMode("mate", FetchMode.EAGER)
    .setFetchMode("kittens", FetchMode.EAGER)
    .list();

このクエリーは、外部結合により、matekittens の両方をフェッチします。

12.6. イグザンプルクエリー

クラス net.sf.hibernate.expression.Example を使えば、指定したインスタンスからクエリーの選択基準を組み立てることができます。

Cat cat = new Cat();
cat.setSex('F');
cat.setColor(Color.BLACK);
List results = session.createCriteria(Cat.class)
    .add( Example.create(cat) )
    .list();

バージョンプロパティー、識別子と関連は、無視されます。デフォルトでは、null 値のプロパティーは、除外されます。

どのように Example が適用されるか調整することができます。

Example example = Example.create(cat)
    .excludeZeroes()           // ゼロ値のプロパティーを除外する
    .excludeProperty("color")  // "color" という名前のプロパティーを除外する
    .ignoreCase()              // 大文字と小文字を区別しない文字列比較を行なう
    .enableLike();             // 文字列比較に like を使う
List results = session.createCriteria(Cat.class)
    .add(example)
    .list();

関連付けられたオブジェクトにクリテリアを置くために、イグザンプルを使うことすら可能です。

List results = session.createCriteria(Cat.class)
    .add( Example.create(cat) )
    .createCriteria("mate")
        .add( Example.create( cat.getMate() ) )
    .list();

第 13 章 ネイティブな SQL クエリー

使用しているデータベースのネイティブな SQL 方言で、クエリーを表現する場合があるかもしれません。Oracle における CONNECT キーワードなどといったデータベース固有の機能を活用したい場合には、これは役立ちます。これによって、直接的な SQL/JDBC ベースのアプリケーションから Hibernate へのもっときれいな移行パスが可能になるからです。

13.1. SQL ベースの Query を作成する

SQL クエリーは、通常の HQL クエリーの場合と同じ Query インターフェースによって公開されます。ただ 1 つの違いは、Session.createSQLQuery() を使うことです。

Query sqlQuery = sess.createSQLQuery("select {cat.*} from cats {cat}", "cat", Cat.class);
sqlQuery.setMaxResults(50);
List cats = sqlQuery.list();

createSQLQuery() に指定する 3 つのパラメータは、次の通りです。

  • SQL クエリー文字列

  • テーブルのエイリアス名

  • クエリーが返す永続クラス

エイリアス名は、マップされたクラス (この場合、Cat) のプロパティーを参照するため、SQL 文字列内で使われます。エイリアス名の String 配列と、対応するクラスの Class 配列を指定することにより、行ごとに複数のオブジェクトを検索する場合があるかもしれません。

13.2. エイリアスとプロパティー参照

上で使用している {cat.*} 記法は、"すべてのプロパティー" の簡略表現です。プロパティーを明示的にリストすることすらあるかもしれませんが、個々のプロパティーに対して Hibernate に SQL カラムのエイリアスを指定させなければなりません。これらのカラムのためのプレースホルダーは、テーブルエイリアスによって修飾したプロパティーの名前です。次の例では、マッピングメタデータ内で宣言されたテーブルとは異なったテーブル (cat_log) から Cat を検索します。where 句内でプロパティーのエイリアスを使う場合すらある点に注意して下さい。

String sql = "select cat.originalId as {cat.id}, "
    + "  cat.mateid as {cat.mate}, cat.sex as {cat.sex}, "
    + "  cat.weight*10 as {cat.weight}, cat.name as {cat.name}"
    + "     from cat_log cat where {cat.mate} = :catId"
List loggedCats = sess.createSQLQuery(sql, "cat", Cat.class)
    .setLong("catId", catId)
    .list();

注意: 個々のプロパティーを明示的にリストする場合、クラスとそのサブクラスのすべてのプロパティーを取り込まなければなりません。

13.3. 名前付き SQL クエリー

名前付き SQL クエリーは、マッピング文書内で定義し、名前付き HQL クエリーとまったく同じ方法で呼び出します。

List people = sess.getNamedQuery("mySqlQuery")
    .setMaxResults(50)
    .list();
<sql-query name="mySqlQuery">
    <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 'Hiber%'
</sql-query>

第 14 章 パフォーマンスを改善する

14.1. コレクションのパフォーマンスを理解する

コレクションについては、その説明に、既に、多くの時間を費やしてきました。このセクションでは、コレクションが実行時にどのように振る舞うかに関するもう 1 つ別の問題にスポットを当てることにしましょう。

14.1.1. 分類法

Hibernate は、3 つの基本的な種類のコレクションを定義しています。

  • 値のコレクション

  • 1 対多関連

  • 多対多関連

この分類は、様々なテーブルと外部キー関係を区別しますが、リレーショナルモデルについて知る必要のあるすべてについて語っている訳ではありません。リレーショナル構造とパフォーマンスの特性を完全に理解するには、コレクションの行の更新と削除を行なうために Hibernate が使用する主キーの構造も考慮しなければなりません。これは、次の分類が必要なことを示唆しています。

  • インデックス付きのコレクション

  • セット

  • バッグ

インデックス付きのコレクション (マップ、リスト、配列) は、<key><index> カラムからなる主キーをもっています。この場合、コレクションの更新は、非常に効率的です。主キーは、効率的に、インデックスを付けられており、Hibernate がそれを更新、削除しようとすると、効率的に特定行の位置をつきとめることができるからです。

セットは、<key> と要素カラムからなる主キーをもちます。これは、何らかの型のコレクション要素がある場合には、効率が悪いことがあります。特に、複合要素や大量のテキスト、バイナリーフィールドがある場合にはそうです。データベースは、効率的に、複合主キーにインデックスを付けることができない場合があります。他方、1 対多や多対多関連の場合、特に合成識別子の場合には、効率的である可能性が高いでしょう。(副次的な注意: SchemaExport に、実際に、<set> の主キーを作らせたい場合には、すべてのカラムを not-null="true" として宣言しなければなりません。)

バッグの場合は、最悪です。バッグは重複した要素の値を許し、インデックスカラムをもたないので、主キーは、定義しません。Hibernate には、重複した行を区別する方法がないのです。Hibernate は、コレクションに変更があった場合には、コレクションを完全に削除 (1 つの DELETE で) し、それを再作成することによって、この問題を解決します。これは、非常に非効率かもしれません。

1 対多関連の場合、"主キー" は、データベーステーブルの物理的な主キーではない場合があります。しかし、この場合であっても、上の分類は、依然として、役立ちます。(それは、Hibernate が、どのようにして、コレクションの個々の行を "見つけ出す" かを反映しているからです。)

14.1.2. リスト、マップ、セットは、更新する上で最も効率的なコレクションである

上の説明から、インデックス付きのコレクションと (通常) セットが、要素の追加、削除、更新に関して、最も効率的な操作であることが明らかなはずです。

インデックス付きのコレクションが、多対多関連や値のコレクションに関してセットに勝る利点がもう 1 つあるという点には、議論の余地があります。Set の構造によって、Hibernate は、要素が "変更されても"、行の UPDATE を行ないません。Set に対する変更は、常に、(個々の行の) INSERTDELETE を通じて起こります。もう一度言うと、こうした考慮は、1 対多関連には当てはまらないのです。

配列の遅延処理ができないことが分かった後では、リスト、マップ、セットが最も効率的なコレクション型であるという結論が得られます。(とは言え、セットは、値のコレクションによっては、あまり効率的ではないという欠点もあります。)

セットは、Hibernate アプリケーションの場合、最も一般的な種類のコレクションであると考えられます。

Hibernate のこのリリースには、文書化されていない機能があります。<idbag> マッピングは、値のコレクションや多対多関連のためのバッグセマンティクスを実装しており、この場合、他のあらゆるスタイルのコレクションよりも効率的だということです。

14.1.3. バッグとリストは、最も効率的な逆コレクションである

バッグを永久に見捨てる前に、バッグ (とリストも) が、セットよりもはるかに効率的な特定ケースがあるということを覚えておいて下さい。inverse="true" をもつコレクションの場合 (例えば、標準的な双方向 1 対多関係の慣用)、バッグ要素を初期化 (フェッチ) しなくとも、バッグやリストに要素を追加することができます。この理由は、Collection.add()Collection.addAll() は、常に、(Set とは違って) バッグや List に真を返さなければならないためです。これにより、次のよくあるコードを、はるかに高速にすることができます。

Parent p = (Parent) sess.load(Parent.class, id);
    Child c = new Child();
    c.setParent(p);
    p.getChildren().add(c);  // コレクションをフェッチする必要はない
    sess.flush();

14.1.4. 一発削除

場合によっては、コレクションの要素を 1 つずつ削除するのは、非常に非効率です。Hibernate は、完全に愚かという訳ではありません。そのため、Hibernate は、新しい空コレクションの場合 (例えば、list.clear() を呼び出した場合) に、それを行なう必要がないことを理解しています。この場合、Hibernate は、1 つの DELETE を発行し、それで終わりです。

1 つの要素を、サイズが 20 のコレクションに追加し、次に、2 つの要素を削除するとしましょう。Hibernate は、(コレクションがバッグではない場合) 1 つの INSERT 文と 2 つの DELETE 文を発行します。これが望ましいことは確かです。

しかしながら、今度は、 2 つの要素を残して、18 の要素を削除し、次に、3 つの新しい要素を追加します。これを行なうには、2 つの方法が可能です。

  • 18 の行を 1 つずつ削除し、3 つの行を挿入する。

  • (1 つの SQL SQL DELETE で) コレクション全体を削除し、全部で 5 つの必要な要素を (1 つずつ) 挿入する。

Hibernate は、この場合、2 番目のオプションの方が、多分、早いことを知るほど賢くありません。(そして、Hibernate がそれほど賢いことは、おそらく、望ましくはないでしょう。こうした振る舞いは、データベースのトリガーを混乱させるなどといったことになるかもしれないからです。)

幸い、元々のコレクションを廃棄し (つまり、参照を外し)、新しくインスタンス化した、必要なすべての要素をもつコレクションを返すことによって、この振る舞いをいつでも強制することが可能です。これは、場合によっては、非常に有用でパワフルです。

コレクションのマッピングに関する章で、永続コレクションの遅延初期化をどのように使うことができるかについては、既に示しました。類似した効果は、CGLIB プロキシーを使えば、通常のオブジェクト参照の場合も、達成可能です。また、Hibernate が、Session レベルで、どのように永続オブジェクトをキャッシュするかも述べました。もっと積極的なキャッシング戦略は、クラス単位を基礎に設定することができます。

次のセクションでは、こうした機能の使い方を見ることにします。これは、必要な場合に、はるかに高いパフォーマンスを成し遂げる際に使うことのできるものです。

14.2. 遅延初期化のためのプロキシー

Hibernate は、(卓越した CGLIB ライブラリーによる) 実行時のバイトコード拡張を使った永続オブジェクトのための遅延初期化プロキシーを実装しています。

マッピングファイルに、クラスやインターフェースを宣言します。これは、そのクラスのためのプロキシーインターフェースとして使用するものです。推奨されるアプローチは、クラス自身を指定することです。

<class name="eg.Order" proxy="eg.Order">

プロキシーの実行時の型は、Order のサブクラスとなります。プロキシーにされるクラスは、最小限の可視性を備えたパッケージをもつデフォルトコンストラクターを実装しなければなりません。

このアプローチをポリモーフィズム的クラスに拡張する場合に、自覚しておかなければならない幾つかの問題があります。例えば

<class name="eg.Cat" proxy="eg.Cat">
    ......
    <subclass name="eg.DomesticCat" proxy="eg.DomesticCat">
        .....
    </subclass>
</class>

まず、Cat のインスタンスは、基礎にあるインスタンスが DomesticCat のインスタンスであっても、DomesticCat にキャストできません。

Cat cat = (Cat) session.load(Cat.class, id);  // プロキシーを初期化する (db にはヒットしない)
if ( cat.isDomesticCat() ) {                  // プロキシーを初期化するために、db にヒットする
    DomesticCat dc = (DomesticCat) cat;       // エラー !
    ....
}

第 2 に、プロキシーに == を適用することができます。

Cat cat = (Cat) session.load(Cat.class, id);            // Cat プロキシーをインスタンス化する
DomesticCat dc = 
    (DomesticCat) session.load(DomesticCat.class, id);  // 新しい DomesticCat プロキシーをリクエスト
System.out.println(cat==dc);                            // 偽

しかしながら、状況は、見た目ほど悪くはありません。異なったプロキシーオブジェクトへの参照が 2 つありますが、基礎にあるインスタンスは、依然として、同じオブジェクトです。

cat.setWeight(11.0);  // プロキシーを初期化するために、db にヒットする
System.out.println( dc.getWeight() );  // 11.0

第 3 に、final クラスや、何らかの final メソッドをもつクラスに、CGLIB プロキシーを使うことはないでしょう。

最後に、永続オブジェクトが、インスタンス化に当たって、何らかのリソースを取得する (例えば、イニシャライザーやデフォルトコンストラクター内で) 場合、こうしたリソースは、プロキシーが取得することもできます。プロキシークラスは、実際には、永続クラスのサブクラスだからです。

こうした問題は、すべて Java の単一継承モデルにおける根本的な制限のためです。こうした問題を回避したい場合には、永続クラスはそれぞれ、そのビジネスメソッドを宣言するインターフェースを実装しなければなりません。こうしたインターフェースは、マッピングファイル内で指定すべきです。例えば

<class name="eg.Cat" proxy="eg.ICat">
    ......
    <subclass name="eg.DomesticCat" proxy="eg.IDomesticCat">
        .....
    </subclass>
</class>

ここで、Cat は、インターフェース ICat を実装し、DomesticCat は、インターフェース IDomesticCat を実装します。次に、CatDomesticCat のインスタンスのためのプロキシーは、load()iterate() が返します。(find() は、プロキシーを返さない点に注意して下さい。)

ICat cat = (ICat) session.load(Cat.class, catid);
Iterator iter = session.iterate("from cat in class eg.Cat where cat.name='fritz'");
ICat fritz = (ICat) iter.next();

関係も、遅延して初期化されます。この意味は、Cat ではなく、ICat 型となるべきプロパティーを宣言しなければならないということです。

操作によっては、プロキシーの初期化は、必要ありません

  • 永続クラスが equals() をオーバーライドしない場合の equals()

  • 永続クラスが hashCode() をオーバーライドしない場合の hashCode()

  • 識別子ゲッターメソッド

Hibernate は、equals()hashCode() をオーバーライドする永続クラスを検出します。

プロキシーを初期化する間に発生する例外は、LazyInitializationException にラップされます。

時には、Session をクローズする前に、プロキシーやコレクションが初期化されることを保証する必要があります。もちろん、いつでも、例えば cat.getSex()cat.getKittens().size() を呼び出すことにより、初期化を強制することができます。しかし、これは、コードを読む人を混乱させる原因になり、汎用的なコードにとって便利なものとは言えません。スタティックメソッド Hibernate.initialize()Hibernate.isInitialized() は、アプリケーションに、遅延して初期化されたコレクションやプロキシーを使って作業する便利な方法を提供します。Hibernate.initialize(cat) は、その Session がオープンされている限りは、プロキシー cat の初期化を強制します。Hibernate.initialize( cat.getKittens() ) は、kittens のコレクションに対して同じ効果をもちます。

14.3. 2 次レベルのキャッシュ

Hibernate の Session は、永続データのトランザクションレベルのキャッシュです。クラスごと、コレクションごとを基礎に、クラスターや JVM レベル (SessionFactory レベル) のキャッシュを設定することもできます。クラスター化されたキャッシュをプラグインする場合すらあるかもしれません。しかし、注意して下さい。キャッシュは、別のアプリケーションによって永続ストアに対して行なわれた変更を認識することはありません (とは言え、それは、定期的に、キャッシュされたデータを期限切れにするように設定することはできます)。

デフォルトで、Hibernate は、JVM レベルのキャッシングに EHCache を使います。(JCS サポートは、今では、推奨されません。それは、Hibernate の将来のバージョンで削除されることになるでしょう。) プロパティー hibernate.cache.provider_class を使って、net.sf.hibernate.cache.CacheProvider を実装するクラスの名前を指定することにより、別の実装を選ぶこともできます。

テーブル 14.1. キャッシュプロバイダー

キャッシュ プロバイダークラス クラスターセーフ クエリーキャッシュのサポート
Hashtable (製品での使用は、意図されていない) net.sf.hibernate.cache.HashtableCacheProvider メモリー  yes
EHCache net.sf.ehcache.hibernate.Provider メモリー、ディスク   yes
OSCache net.sf.hibernate.cache.OSCacheProvider メモリー、ディスク   yes
SwarmCache net.sf.hibernate.cache.SwarmCacheProvider クラスターによる (ip マルチキャスト) yes (クラスターにより無効化)  
JBoss TreeCache net.sf.hibernate.cache.TreeCacheProvider クラスターによる (ip マルチキャスト), トランザクションによる yes (レプリケーション)  

14.3.1. キャッシュマッピング

クラスやコレクションマッピングの <cache> 要素は、次の形式を取ります。

<cache 
    usage="transactional|read-write|nonstrict-read-write|read-only"  (1)
/>
(1)

usage は、次のキャッシング戦略を指定する。transactional, read-write, nonstrict-read-write あるいは read-only である。

それ以外の方法として (むしろ、こちらの方が好まれるかもしれませんが)、hibernate.cfg.xml 内に <class-cache><collection-cache> 要素を指定することができます。

usage 属性には、キャッシュ並行性戦略 (cache concurrency strategy) を指定します。

14.3.2. 戦略: 読み込み専用

アプリケーションは、読み込みを必要とするが、永続クラスのインスタンスを変更することがない場合には、read-only キャッシュを使います。これは、最も単純で、パフォーマンスが最高の戦略です。それは、クラスター内で使う場合であっても、安全性に問題はありません。

<class name="eg.Immutable" mutable="false">
    <cache usage="read-only"/>
    ....
</class>

14.3.3. 戦略: 読み込み/書き込み

アプリケーションがデータを更新する必要がある場合、read-write キャッシュが適切でしょう。このキャッシュ戦略は、シリアル化可能なトランザクションの隔離レベルが必要な場合には、決して使うべきではありません。JTA 環境でキャッシュが必要な場合、JTA TransactionManager を取得するための戦略を指定するプロパティー hibernate.transaction.manager_lookup_class を指定しなければなりません。その他の環境では、Session.close()Session.disconnect() が呼び出される時、トランザクションが完了していることを保証すべきです。この戦略をクラスター内で使いたい場合には、基礎にあるキャッシュ実装がロッキングをサポートしていることを確かめるべきです。組み込みのキャッシュプロバイダーは、それをサポートしていません

<class name="eg.Cat" .... >
    <cache usage="read-write"/>
    ....
    <set name="kittens" ... >
        <cache usage="read-write"/>
        ....
    </set>
</class>

14.3.4. 戦略: 制限のない読み込み/書き込み

アプリケーションが時にはデータを更新する必要があり (つまり、2 つのトランザクションが同時に同じ項目を更新しようとする可能性が非常に低い場合)、厳密なトランザクション隔離が必要ない場合には、nonstrict-read-write キャッシュが適切でしょう。これを JTA 環境で使う場合には、hibernate.transaction.manager_lookup_class を指定しなければなりません。その他の環境では、Session.close()Session.disconnect() が呼び出される時、トランザクションが完了していることを保証すべきです。

14.3.5. 戦略: トランザクション的

transactional キャッシュ戦略は、JBoss TreeCache などの、完全にトランザクション的なキャッシュプロバイダーを提供します。こうしたキャッシュは、JTA 環境だけで使われます。これを使う場合には、hibernate.transaction.manager_lookup_class を指定しなければなりません。

キャッシュプロバイダーはどれも、キャッシュ並行性戦略のいずれもサポートしていません。次のテーブルは、どのプロバイダーがどの並行性戦略と互換性があるかを示しています。

テーブル 14.2. キャッシュ並行性戦略サポート

キャッシュ 読み込み専用 制限のない読み込み/書き込み 読み込み/書き込み トランザクションtransactional
Hashtable (製品での使用は、意図していない) yes yes yes  
EHCache yes yes yes  
OSCache yes yes yes  
SwarmCache yes yes    
JBoss TreeCache yes     yes

14.4. Session キャッシュを管理する

オブジェクトを save(), update()saveOrUpdate() に渡す場合にはいつでも、そして、load(), find(), iterate()filter() を使ってオブジェクトを検索する場合にはいつでも、そのオブジェクトは、Session の内部キャッシュに追加されます。flush() を引き続いて呼び出すと、そのオブジェクトの状態は、データベースと同期が取られます。この同期を取りたくない場合や、大量のオブジェクトを処理していてメモリーを効率的に管理したい場合には、オブジェクトとそのコレクションをキャッシュから削除するために、evict() メソッドが使えます。

Iterator cats = sess.iterate("from eg.Cat as cat"); // 大量の結果セット
while ( cats.hasNext() ) {
    Cat cat = (Cat) iter.next();
    doSomethingWithACat(cat);
    sess.evict(cat);
}

Hibernate は、関連が cascade="all"cascade="all-delete-orphan" とマップされている場合には、自動的に、関連付けられたエンティティーを退去 (evict) させます。

Session も、インスタンスがセッションのキャッシュに属するかどうかを判断する contains() メソッドを提供しています。

すべてのオブジェクトをセッションのキャッシュから退去させるには、Session.clear() を呼び出します。

2 次レベルのキャッシュのため、インスタンスのキャッシュされた状態、クラス全体、コレクションインスタンスやコレクション全体を退去させるためのメソッドが SessionFactory に定義されています。

14.5. クエリーのキャッシュ

クエリーの結果セットも、キャッシュされる場合があります。これが有用なのは、同じパラメータを使って頻繁に実行されるクエリーの場合です。クエリーキャッシュを使うには、まず、プロパティー hibernate.cache.use_query_cache=true をセットすることによって、それを有効にしなければなりません。これによって、2 つのキャッシュ領域が作成されます。1 つは、キャッシュされたクエリーの結果セットを保持するもの (net.sf.hibernate.cache.QueryCache) で、もう 1 つは、クエリーされたテーブルに対して行なわれた最も新しい更新のタイムスタンプを保持するものです (net.sf.hibernate.cache.UpdateTimestampsCache)。クエリーキャッシュは、結果セット内のいかなるエンティティーの状態もキャッシュするものではない点に注意して下さい。それは、識別子の値と値型の結果だけをキャッシュします。そのため、クエリーのキャッシュは、通常、2 次レベルのキャッシュと組み合わせて使われます。

クエリーの多くは、キャッシングから恩恵を受けることはありません。そのため、デフォルトでは、クエリーはキャッシュされません。キャッシングを有効にするには、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();

第 15 章 ツールセットガイド

Hibernate を使ったラウンドトリップエンジニアリングは、XDoclet、Middlegen や AndroMDA に組み込まれた Hibernate サポートとともに、Hibernate プロジェクトの一部としてメンテナンスされるコマンドラインツールセットを使えば、可能です。

Hibernate のメインパッケージは、最も重要なツールと一緒にバンドルされています (その場で、Hibernate "内部" から使用することすら可能です)。

  • マッピングファイルから DDL スキーマ生成 (別名 SchemaExport, hbm2ddl)

Hibernate プロジェクトが直接提供するその他のツールは、別のパッケージ Hibernate Extensions とともに配布されています。このパッケージには、次のタスクのためのツールが含まれています。

  • マッピングファイルから Java ソースコード生成 (別名 CodeGenerator, hbm2java)

  • コンパイル済みの Java クラスや、XDoclet マークアップのある Java ソースからマッピングファイル生成 (別名 MapGenerator, class2hbm)

Hibernate 拡張には、実際には、別のユーティリティーがあります。ddl2hbm です。これは、推奨されないものと見なされており、もはやメンテナンスもされていません。Middlegen は、同じタスク用にもっとよい仕事を行なっています。

Hibernate をサポートするサードパーティーツールには、次のものがあります。

  • Middlegen (既にあるデータベーススキーマから、マッピングファイルを生成する)

  • AndroMDA (UML ダイアグラムやその XML/XMI 表現から永続クラス用のコードを生成する、MDA (Model-Driven Architecture - モデル駆動アーキテクチャー) アプローチ)

こうしたサードパーティーツールは、このリファレンスには文書化されていません。更新情報については、Hibernate ウェブサイトを参照して下さい (サイトのスナップショットには、Hibernate メインパッケージが含まれています)。

15.1. スキーマ生成

DDL は、コマンドラインユーティリティーによって、マッピングファイルから生成される場合があります。バッチファイルは、コア Hibernate パッケージの hibernate-x.x.x/bin ディレクトリー内にあります。

生成されるスキーマには、エンティティーとコレクションテーブルのための参照統合制約 (主キーと外部キー) が含まれます。テーブルとシーケンスも、マップされる識別子ジェネレーター用に作られます。

このツールを使う場合には、hibernate.dialect プロパティーによって、SQL Dialect を指定しなければなりません

15.1.1. スキーマをカスタマイズする

Hibernate 要素の多くは、length という名前のオプションの属性を定義しています。この属性を使って、カラム長をセットする場合があるかもしれません (あるいは、数値/小数データ型、精度)。

タグによっては、(テーブルカラムに対する NOT NULL 制約を生成するための) not-null 属性と、(テーブルカラムに対する UNIQUE 制約を生成するための) unique 属性を受け入れるものもあります。

タグによっては、そのカラムのインデックスの名前を指定するための index 属性を受け付けるものもあります。unique-key 属性は、1 つの単位キー制約内でカラムをグループ化するために使うことができます。現在のところ、unique-key に指定する値は、マッピングファイル内でカラムをグループ化するためだけに使われ、制約を指定するためには使われません

例:

<property name="foo" type="string" length="64" not-null="true"/>

<many-to-one name="bar" foreign-key="fk_foo_bar" not-null="true"/>

<element column="serial_number" type="long" not-null="true" unique="true"/>

それ以外に、こうした要素は、子供の <column> 要素も受け付けます。これは、複数カラム型の場合に特に有用です。

<property name="foo" type="string">
    <column name="foo" length="64" not-null="true" sql-type="text"/>
</property>

<property name="bar" type="my.customtypes.MultiColumnType"/>
    <column name="fee" not-null="true" index="bar_idx"/>
    <column name="fi" not-null="true" index="bar_idx"/>
    <column name="fo" not-null="true" index="bar_idx"/>
</property>

sql-type 属性によって、ユーザーは、SQL データ型に対する Hibernate のデフォルトマッピングをオーバーライドすることが可能です。

check 属性によって、チェック制約を指定することができます。

<property name="foo" type="integer">
    <column name="foo" check="foo > 10"/>
</property>

<class name="Foo" table="foos" check="bar < 100.0">
    ...
    <property name="bar" type="float"/>
</class>

テーブル 15.1. まとめ

属性 解釈
length number カラムの長さ/小数精度
not-null true|false カラムに null 以外が可能かどうかを指定する
unique true|false カラムが一意制約をもつかどうかを指定する
index インデックス名 (複数カラム) インデックスの名前を指定する
unique-key 一意キー名 複数カラムの一意制約の名前を指定する
foreign-key 外部キー名 関連に対して生成される外部キー制約の名前を指定する
sql-type カラム型 デフォルトカラム型をオーバーライドする (<column> の属性のみ)
check SQL 式 カラムかテーブルに SQL チェック制約を作成する

15.1.2. ツールを実行する

SchemaExport ツールは、DDL スクリプトを標準出力に書き出します。そして/あるいは DDL 文を実行します。

java -cp hibernate_classpaths net.sf.hibernate.tool.hbm2ddl.SchemaExport options mapping_files

テーブル 15.2. SchemaExport コマンドラインオプション

オプション 説明
--quiet スクリプトを標準出力に出力しない
--drop テーブルの削除だけを行なう
--text データベースにエクスポートしない
--output=my_schema.ddl ddl スクリプトをファイルに出力する
--config=hibernate.cfg.xml XML ファイルから Hibernate 設定を読み込む
--properties=hibernate.properties ファイルからデータベースプロパティーを読み込む
--format スクリプト内に生成される SQL を読みやすくフォーマットする
--delimiter=x スクリプトに行の区切りの終わりをセットする

アプリケーション内に SchemaExport を組み込むこともできます。

Configuration cfg = ....;
new SchemaExport(cfg).create(false, true);

15.1.3. プロパティー

次のように、データベースのプロパティーを指定することもできます。

  • -D<property> を使って、システムプロパティーとして

  • hibernate.properties 内で

  • --properties を使って指定したプロパティーファイル内で

必須プロパティーは、次の通りです。

テーブル 15.3. SchemaExport 接続プロパティー

プロパティー名 説明
hibernate.connection.driver_class jdbc ドライバークラス
hibernate.connection.url jdbc url
hibernate.connection.username データベースユーザー
hibernate.connection.password ユーザーパスワード
hibernate.dialect 方言

15.1.4. Ant を使う

Ant 構築スクリプトから SchemaExport を呼び出すこともできます。

<target name="schemaexport">
    <taskdef name="schemaexport"
        classname="net.sf.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>

15.1.5. 増分的なスキーマ更新

SchemaUpdate ツールは、"増分的な (incremental)" 変更によって、既にあるスキーマを更新します。SchemaUpdate は、JDBC メタデータ API に著しく依存することに注意して下さい。そのため、それは、JDBC ドライバーすべてで動作する訳ではありません。

java -cp hibernate_classpathsnet.sf.hibernate.tool.hbm2ddl.SchemaUpdate options mapping_files

テーブル 15.4. SchemaUpdate コマンドラインオプション

オプション 説明
--quiet スクリプトを標準出力に出力しない
--properties=hibernate.properties ファイルからデータベースプロパティーを読み込む

アプリケーション内に SchemaUpdate を埋め込むこともできます。

Configuration cfg = ....;
new SchemaUpdate(cfg).execute(false);

15.1.6. 増分的なスキーマ更新に Ant を使う

Ant スクリプトから、SchemaUpdate を呼び出すこともできます。

<target name="schemaupdate">
    <taskdef name="schemaupdate"
        classname="net.sf.hibernate.tool.hbm2ddl.SchemaUpdateTask"
        classpathref="class.path"/>
    
    <schemaupdate
        properties="hibernate.properties"
        quiet="no">
        <fileset dir="src">
            <include name="**/*.hbm.xml"/>
        </fileset>
    </schemaupdate>
</target>

15.2. コード生成

Hibernate コードジェネレーターは、Hibernate マッピングファイルから、骨格となる Java 実装クラスを生成するのに使われます。このツールは、Hibernate 拡張パッケージに含まれています (別途、ダウンロードが必要)。

hbm2java は、マッピングファイルをパースして、これらのファイルから完全に動作する Java ソースファイルを生成します。このため、hbm2java を使えば、.hbm ファイルを提供することができるため、Java ファイルを手作業で書く/コーディングすることに思い悩む必要はありません。

java -cp hibernate_classpathsnet.sf.hibernate.tool.hbm2java.CodeGenerator options mapping_files

テーブル 15.5. コードジェネレーターコマンドラインオプション

オプション 説明
--output=output_dir 生成されるコードのためのルートディレクトリー
--config=config_file hbm2java を設定するためのオプションファイル

15.2.1. 設定ファイル (オプション)

設定ファイルは、ソースコード用に複数の "レンダラー" を指定し、スコープ内の "グローバル" な <meta> 属性を宣言する方法を提供します。これに関する詳細は、<meta> 属性セクションを参照して下さい。

<codegen>
    <meta attribute="implements">codegen.test.IAuditable</meta>
    <generate renderer="net.sf.hibernate.tool.hbm2java.BasicRenderer"/>
    <generate
        package="autofinders.only"
        suffix="Finder"
        renderer="net.sf.hibernate.tool.hbm2java.FinderRenderer"/>
</codegen>

この設定ファイルは、グローバルなメタ属性 "implements" を宣言し、デフォルトレンダラー (BasicRenderer) と、Finder のレンダラーを生成するレンダラー (詳細は、以下の "基本ファインダー生成" を参照して下さい) という 2 つのレンダラーを指定しています。

2 番目のレンダラーは、package と suffix 属性を使って指定されています。

package 属性は、このレンダラーから生成されるソースファイルが、.hbm ファイル内で指定されたパッケージスコープの代わりに、ここに置かれることを指定します。

suffix 属性は、生成されるファイルの接頭辞を指定します。例えば、ここで指定したファイルが Foo.java の場合、それは、FooFinder.java になります。

<generate> 要素に <param> 属性を追加することにより、任意のパラメータをレンダラーに送ることも可能です。

hbm2java は、現在のところ、次のパラメータをサポートしています。generate-concrete-empty-classes です。これは、BasicRenderer に、使用するクラスすべてのためにベースクラスを拡張する空の具象クラスだけを生成するように指示します。次の config.xml サンプルは、この機能の例を示しています。

            <codegen>
              <generate prefix="Base" renderer="net.sf.hibernate.tool.hbm2java.BasicRenderer"/> 
              <generate renderer="net.sf.hibernate.tool.hbm2java.BasicRenderer">
                <param name="generate-empty-concrete-classes">true</param>
                <param name="baseclass-prefix">Base</param>
              </generate>
            </codegen>

この config.xml は、2 つのレンダラーの設定を行なう点に注意して下さい。1 つは、Base クラスを生成するレンダラーで、もう 1 つは、空の具象クラスだけを生成するレンダラーです。

15.2.2. meta 属性

<meta> タグは、情報をもつ hbm.xml に注釈を付ける単純な方法です。そのため、ツールは、Hibernate のコアとは直接関係しない情報の保存/読み込みを行なうのにふさわしい場所をもちます。

<meta> タグを使って、hbm2java に、"protected" セッターだけを生成するように指示することも、クラスに何らかのインターフェースセットを実装させるように、更にまた、クラスに何らかのベースクラスを拡張させるといったことを指示することもできます。

次の例

<class name="Person">
    <meta attribute="class-description">
        Javadoc for the Person class
        @author Frodo
    </meta>
    <meta attribute="implements">IAuditable</meta>
    <id name="id" type="long">
        <meta attribute="scope-set">protected</meta>
        <generator class="increment"/>
    </id>
    <property name="name" type="string">
        <meta attribute="field-description">The name of the person</meta>
    </property>
</class>

これは、次のようなコードを作り出します (理解を容易にするため、コードは、省略しています)。Javadoc コメントと protected メソッドセットがあることに注意して下さい。

// デフォルトパッケージ

import java.io.Serializable;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.apache.commons.lang.builder.ToStringBuilder;

/** 
 *         Person クラスのための Javadoc
 *         @author Frodo
 *     
 */
public class Person implements Serializable, IAuditable {

    /** 識別子フィールド */
    public Long id;

    /** null を取ることのできる永続フィールド */
    public String name;

    /** 完全なコンストラクター */
    public Person(java.lang.String name) {
        this.name = name;
    }

    /** デフォルトコンストラクター */
    public Person() {
    }

    public java.lang.Long getId() {
        return this.id;
    }

    protected void setId(java.lang.Long id) {
        this.id = id;
    }

    /** 
     * person の名前
     */
    public java.lang.String getName() {
        return this.name;
    }

    public void setName(java.lang.String name) {
        this.name = name;
    }

}

テーブル 15.6. サポートされる meta タグ

属性 説明
class-description クラスのための javadoc に挿入される。
field-description フィールド/プロパティーのための javadoc に挿入される。
interface true の場合、クラスの代わりにインターフェースが生成される。
implements クラスが実装すべきインターフェース。
extends クラスが拡張すべきクラス (サブクラスの場合、無視される)。
generated-class 生成される実際のクラスの名前を無効にする。
scope-class クラスのスコープ。
scope-set セッターメソッドのスコープ。
scope-get ゲッターメソッドのスコープ。
scope-field 実際のフィールドのスコープ。
use-in-tostring toString() に、このプロパティーをインクルードする。
implement-equals このクラスに、equals()hashCode() メソッドをインクルードする。
use-in-equals equals()hashCode() メソッドに、このプロパティーをインクルードする。
bound プロパティーに propertyChangeListener サポートを追加する。
constrained プロパティーに bound + vetoChangeListener サポートを追加する。
gen-property false の場合、プロパティーは生成されない (慎重に使うこと)。
property-type プロパティーのデフォルト型をオーバーライドする。単なる Object の代わりに具象型を指定するには、任意のタグのプロパティーとともに使用する。
class-code クラスの末尾に挿入される特別なコード。
extra-import 他の import の末尾に挿入される特別な import。
finder-method 以下の "基本ファインダージェネレーター" を参照せよ。
session-method 以下の "基本ファインダージェネレーター" を参照せよ。

<meta> タグによって宣言した属性は、デフォルトで、hbm.xml ファイル内で "継承されます"。

これはどういう意味でしょうか。その意味は、例えば、すべてのクラスに IAuditable を実装させたい場合には、hbm.xml ファイルの先頭で <hibernate-mapping> の直後に、<meta attribute="implements">IAuditable</meta> を追加するだけで済むということです。これですべてのクラスの定義が終わったので、hbm.xml ファイルは、IAuditable を実装することになります。(クラスに "implements" メタ属性がある場合を除きます。その理由は、ローカルに指定した meta タグは、常に、継承された meta タグを無効にする/置き換えるからです。)

注意: これは、すべての <meta> タグに適用されます。このため、それは、例えば、すべてのフィールドを、デフォルトの private ではなく、protected 宣言しなければならないことを指定するのに使うことができます。これは、例えば、<class> タグの直後に、<meta attribute="scope-field">protected</meta> を追加することによって行ないます。これにより、そのクラスのすべてのフィールドは、protected になります。

<meta> タグの継承を避けるには、属性に inherit="false" を指定するだけで済みます。例えば、<meta attribute="scope-class" inherit="false">public abstract</meta> は、"クラススコープ" を、サブクラスではなく、現在のクラスだけに限定します。

15.2.3. 基本ファインダージェネレーター

hbm2java に Hibernate プロパティーのための基本ファインダーを生成させることが可能です。これには、hbm.xml ファイル内に 2 つのものが必要です。

最初は、どのフィールドをファインダーに生成させたいのかの指示です。これは、次のような、property タグ内の meta ブロックを使って指示します。

<property name="name" column="name" type="string">
     <meta attribute="finder-method">findByName</meta>
</property>

ファインダーメソッド名は、meta タグに囲まれたテキストです。

2 つ目は、次のようなフォーマットの hbm2java のための設定ファイルを作ることです。

<codegen>
    <generate renderer="net.sf.hibernate.tool.hbm2java.BasicRenderer"/>
    <generate suffix="Finder" renderer="net.sf.hibernate.tool.hbm2java.FinderRenderer"/>
</codegen>

次に、hbm2java --config=xxx.xml に、パラメータを使います。ここで、xxx.xml とは、たった今作った設定ファイルです。

オプションパラメータは、次のようなフォーマットのクラスレベルの meta タグです。

<meta attribute="session-method">
    com.whatever.SessionTable.getSessionTable().getSession();
</meta>

これは、(Hibernate のウェブサイトにあるデザインパターンエリアで文書化されている) スレッドローカルセッション (Thread Local Session) パターンを使う場合に、セッションを取得する方法となるでしょう。

15.2.4. Velocity ベースのレンダラー/ジェネレーター

Velocity を代替のレンダリングメカニズムとして使うことができます。次の config.xml は、Velocity レンダラーを使うように hbm2java を設定する方法を示しています。

    <codegen>
     <generate renderer="net.sf.hibernate.tool.hbm2java.VelocityRenderer">
      <param name="template">pojo.vm</param>
     </generate>
    </codegen>

template という名前のパラメータは、使用したい Velocity マクロファイルへのリソースパスです。このファイルは、hbm2java のためのクラスパスから利用可能でなければなりません。pojo.vm があるディレクトリーを、Ant タスクやシェルスクリプトに追加しなければならないことを思い出して下さい。(デフォルトの場所は、./tools/src/velocity です。)

現在の pojo.vm は、Java Beans の最も基本的な部分しか生成しない点に注意して下さい。これは、デフォルトのレンダラーほど、完全でも機能的に豊かという訳でもありません。特に、meta タグの多くは、サポートされていません。

15.3. マッピングファイル生成

骨格となるマッピングファイルは、MapGenerator という名前のコマンドラインユーティリティーを使って、コンパイルされた永続クラスから生成されます。このユーティリティーは、Hibernate 拡張パッケージの一部です。

Hibernate マッピングジェネレーターは、コンパイルされたクラスからマッピングを作成するメカニズムを提供します。それは、プロパティーを見つけるため、Java リフレクションを使い、プロパティーの型から適切なマッピングを推測するため、発見的方法 (heutistics) を使います。生成されるマッピングは、出発点であることだけを意図しています。ユーザーの特別な入力なしに、完全な Hibernate マッピングを作成する方法はありません。しかしながら、ツールは、マッピングを作成するのに伴う反復的な "不満の多い" 作業の幾つかを取り去ってくれます。

クラスは、1 つずつ、マッピングに追加されます。ツールは、Hibernate で永続的ではないと判断したクラスを拒否します。

Hibernate で永続的であるためには、クラスは次の条件を満たさなければなりません。

  • プリミティブ型であってはならない

  • 配列であってはならない

  • インターフェースであってはならない

  • ネストしたクラスであってはならない

  • デフォルト (引数のない) コンストラクターがなければならない

インターフェースとネストしたクラスは、実際には、Hibernate によって永続化可能ですが、これは、通常、ユーザーが意図したものではないでしょう。

MapGenerator は、同じデータベースのテーブルに、できるだけ多くの Hibernate にとって永続化可能なスーパークラスを追加しようとして、追加されたクラスすべてのスーパークラスチェーンをさかのぼります。サーチが止まるのは、候補 UID 名 (candidate UID names) のリストに現われる名前をもつプロパティーが見つかった時です。

候補 UID プロパティー名のデフォルトリストは、uid, UID, id, ID, key, KEY, pk, PK です。

プロパティーが見つかるのは、クラスに、セッターとゲッターの 2 つのメソッドがある場合です。ここで、セッターが取る 1 つの引数の型は、引数を取らないゲッターの戻り値の型と同じで、セッターの戻り値は、void です。更に、セッターの名前は、文字列 set でなければならず、同様に、ゲッターの名前は、get で始まるか、プロパティーの型が boolean の場合には、ゲッターの名前は、is で始まります。いずれの場合でも、それらの名前の残りの部分は、マッチしなければなりません。プロパティー名の 2 番目の文字が小文字の場合には、最初の文字を小文字にすることを除けば、このマッチする部分は、プロパティーの名前です。

個々のプロパティーのデータベースの型を決めるためのルールは、次の通りです。

  1. Java の型が Hibernate.basic() の場合、プロパティーは、その型の単純なカラムである。

  2. hibernate.type.Type カスタム型と PersistentEnum の場合、同様に、その型の単純なカラムが使われる。

  3. プロパティーの型が配列の場合、Hibernate 配列が使われ、MapGenerator は、配列の要素型を反映しようとする。

  4. プロパティーが java.util.List, java.util.Map あるいは java.util.Set であれば、対応する Hibernate 型が使われるが、MapGenerator は、それらの型の内部を更に処理することはできない。

  5. プロパティーの型が他のクラスの場合、MapGenerator は、すべてのクラスの処理が終わるまで、データベース表現に関する決定を遅らせる。処理が終わった時点で、上で説明したスーパークラスサーチを通じてクラスが発見された場合には、プロパティーは、many-to-one 関連になる。クラスにプロパティーがある場合には、それは、component になる。そうでなければ、それは、シリアル化可能であるか、永続的ではないと見なされる。

15.3.1. ツールを実行する

ツールは、XML マッピングを標準出力、そして/あるいは、ファイルに書き出します。

ツールを呼び出す場合、クラスパスにコンパイル済みのクラスを置かなければなりません。

java -cp hibernate_and_your_class_classpaths net.sf.hibernate.tool.class2hbm.MapGenerator オプションとクラス名

操作には、2 つのモードがあります。コマンドラインとインタラクティブ (双方向) です。

インタラクティブモードは、コマンドライン引数 --interact を与えることによって指定します。このモードは、プロンプト応答コンソールを提供します。これを使うと、uid=XXX コマンドを使って、個々のクラスに UID プロパティー名をセットすることができます。ここで、XXX は、UID プロパティー名です。他の代替となるコマンドは、完全に修飾されたクラス名にすぎないか、XML を発行し、終了するコマンドです。

コマンドラインモードでは、引数は、処理すべきクラスの完全に修飾されたクラス名を指定した、以下に掲げるオプションです。オプションのほとんどは、複数回使われることが想定されており、個々のオプションを使うと、その後に追加されるクラスに影響を及ぼします。

テーブル 15.7. MapGenerator コマンドラインオプション

オプション 説明
--quiet O-R マッピングを標準出力に出力しない。
--setUID=uid 候補 UID のリストを singleton uid にセットする。
--addUID=uid uid を候補 UID リストの最初の部分に追加する。
--select=mode その後に追加されるクラスに使用する選択モードに、mode (例えば、distinctall) を指定する。
--depth=<small-int> その後に追加されるクラスに対するコンポーネントデータの再帰の深さを制限する。
--output=my_mapping.xml O-R マッピングをファイルに出力する。
full.class.Name クラスをマッピングに追加する。
--abstract=full.class.Name 以下を参照せよ。

抽象スイッチ (abstract switch) は、マップジェネレーターツールに、特定スーパークラスを無視するように指示します。そのため、共通した継承をもつクラスが、1 つの大きなテーブルにマップされることはありません。例えば、次のクラス階層を考えて下さい。

Animal-->Mammal-->Human

Animal-->Mammal-->Marsupial-->Kangaroo

--abstract スイッチを使用していない場合、すべてのクラスは、Animal のサブクラスとしてマップされ、その結果、すべてのクラスのプロパティーのすべてと、どのサブクラスが実際に保存されたかを示す判別子クラスからなる大きなテーブルができあがります。Mammalabstract としてマークした場合、HumanMarsupial は、<class> 宣言を分ける形でマップされ、別々のテーブルに保存されます。Kangaroo は、Marsupialabstract としてマークしない場合には、Marsupial のサブクラスのままです。

第 16 章 例: 親/子

新しいユーザーが Hibernate を使って行なおうとするまさに最初のことの 1 つは、親/子型の関係をモデル化することです。これには、2 種類のアプローチがあります。様々な理由から、とりわけ新しいユーザーにとって、最も便利なアプローチは、Parent から Child への <one-to-many> 関連をもつエンティティークラスとして ParentChild の両方をモデル化することです。(それ以外のアプローチは、Child<composite-element>として宣言することです。) これで、1 対多関連のデフォルトセマンティクスが、(Hibernate では) 複合要素マッピングのセマンティクスよりも、親/子関係の通常のセマンティクスにはるかに近いというものではないことが分かります。親/子関係を効率的かつエレガントにモデル化するために、カスケードのある双方向 1 対多関連を使う方法を説明することにします。これは、決して難しいものではありません。

16.1. コレクションに関する注意

Hibernate のコレクションは、それ自身が所有するエンティティーの論理的な一部分と考えられます。決して、包含するエンティティーの一部分ではありません。これは、決定的な違いです。それは、以下のような帰結を伴います。

  • オブジェクトをコレクションから/へ削除/追加する場合、コレクションの所有者のバージョン番号は、増分される。

  • コレクションから削除されたオブジェクトが値型のインスタンス (例えば、複合要素) の場合、そのオブジェクトは、永続的ではなくなり、その状態は、データベースから完全に削除される。同様に、値型のインスタンスをコレクションに追加すると、その状態は、直ちに、永続的となる。

  • 一方、エンティティーをコレクションから削除する場合 (1 対多あるいは多対多関連)、それは、デフォルトでは、削除されない。この振る舞いは、完全に一貫している。別のエンティティーの内部状態を変更しても、関連付けられたエンティティーが消失すべきではない。同様に、エンティティーをコレクションに追加しても、デフォルトでは、そのエンティティーが永続的になることはない。

その代わり、デフォルトの振る舞いは、エンティティーをコレクションに追加しても、2 つのエンティティー間にリンクが作られるにすぎないということです。一方、それを削除すると、リンクも削除されます。これは、あらゆる場合にとって、非常に適切なものです。これが適切ではないのは、親/子関係の場合です。そこでは、子の生存は、親のライフサイクルにバインドされているからです。

16.2. 双方向 1 対多

Parent から Child への単純な <one-to-many> 関連から始めることにしましょう。

<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 は、2 つの SQL 文を発行します。

  • c のためのレコードを作る INSERT

  • p から c へのリンクを作るための UPDATE

これは、非効率であるだけでなく、parent_id カラム上の NOT NULL 制約にも違反しています。

根本となる原因は、p から c へのリンク (外部キー parent_id) が、Child オブジェクトの状態の一部と考えられていないため、INSERT によって作られないからです。そのため、解決策は、リンクを Child マッピングの一部にすることです。

<many-to-one name="parent" column="parent_id" not-null="true"/>

(parent プロパティーを Child クラスに追加する必要もあります。)

これで、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();

これにより、1 つの SQL INSERT だけが発行されます。

処理をもう少し堅牢にするには、ParentaddChild() メソッドを作ることができます。

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();

16.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();

同様に、Parent の保存や削除をする場合、子供上で繰り返しを行なう必要はありません。次のコードは、p とその子供すべてをデータベースから削除します。

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 制約違反を引き起こします)。そのため、明示的に、Childdelete() する必要があります。

Parent p = (Parent) session.load(Parent.class, pid);
Child c = (Child) p.getChildren().iterator().next();
p.getChildren().remove(c);
session.delete(c);
session.flush();

この場合、Child は、実際には、その親なしに存在することはできません。そのため、コレクションから Child を削除すると言う場合、必要なのは、それが孤児になることなく、本当に削除されることです。このためには、cascade="all-delete-orphan" を使わなければなりません。

<set name="children" inverse="true" cascade="all-delete-orphan">
    <key column="parent_id"/>
    <one-to-many class="Child"/>
</set>

注意: コレクションマッピングは、inverse="true" を指定しているにも関わらず、カスケードは、依然として、コレクションの要素上で繰り返しを行なうことによって処理されます。そのため、カスケードによってオブジェクトの保存、削除、更新が必要な場合には、それをコレクションに追加しなければなりません。単純に setParent() を呼び出すだけでは十分ではないのです。

16.4. カスケードする update() を使う

ある SessionParent をロードし、UI アクション内で何らかの変更を行ない、(update() を呼び出すことにより) こうした変更を新しいセッションで永続化させたいとしましょう。Parent は、子供のコレクションを含みます。カスケードするアップデートは有効なので、Hibernate は、どの子供が新しくインスタンス化され、どれがデータベース内に既にある行を表わしているのかを知る必要があります。ParentChild の両方に、java.lang.Long 型の (合成) 識別子プロパティーがあるとしましょう。Hibernate は、識別子プロパティーを使って、子供のどれが新しいのかを判断します。(version や timestamp プロパティーも使うことになるかもしれません。セクション 9.4.2, “分離されたオブジェクトを更新する” を参照して下さい。)

unsaved-value 属性は、新しくインスタンス化されたインスタンスの識別子の値を指定するのに使われます。unsaved-value のデフォルトは、"null" です。これは、識別子の型が Long の場合には、何の問題もありません。プリミティブな識別子プロパティーを使っている場合には、

Child マッピングのために、次のように、指定する必要があります。

<id name="id" type="long" unsaved-value="0">

(version と timestamp プロパティーマッピング用の unsaved-value もあります。)

次のコードは、parentchild を更新し、newChild を挿入します。

// 親と子はいずれも、以前のセッションでロード済み
parent.addChild(child);
Child newChild = new Child();
parent.addChild(newChild);
session.update(parent);
session.flush();

生成された識別子の場合は、これで万全です。では、割り当てられた識別子や複合識別子の場合はどうでしょうか。これは、もっと難しい問題です。というのも、unsaved-value は、(ユーザーが割り当てた識別子を使って) 新しくインスタンス化されたオブジェクトと以前のセッションでロードされたオブジェクトを区別できないからです。こうした場合には、次のようにして、Hibernate にヒントを与えてやる必要があります。

  • クラスのための <version><timestamp> プロパティーマッピング上で、unsaved-value="null"unsaved-value="negative" を定義する。

  • unsaved-value="none" をセットし、update(parent) を呼び出す前に、明示的に、新しくインスタンス化された子供を save() する。

  • unsaved-value="any" をセットし、update(parent) を呼び出す前に、明示的に、以前に永続化された子供を update() する。

割り当てられた識別子と複合識別子の場合、none が、デフォルトの unsaved-value です。

もう 1 つ別の可能性があります。isUnsaved() という名前の新しい Interceptor メソッドがあります。これによって、アプリケーションは、新しくインスタンス化されたオブジェクトのためのそれ自身の戦略を実装することができます。例えば、次のように、永続クラスに対して、ベースクラスを定義することができます。

public class Persistent {
    private boolean _saved = false;
    public void onSave() {
        _saved=true;
    }
    public void onLoad() {
        _saved=true;
    }
    ......
    public boolean isSaved() {
        return _saved;
    }
}

(saved プロパティーは、永続的ではありません。) 次のように、onLoad()onSave() とともに、isUnsaved() を実装します。

public Boolean isUnsaved(Object entity) {
    if (entity instanceof Persistent) {
        return new Boolean( !( (Persistent) entity ).isSaved() );
    }
    else {
        return null;
    }
}

public boolean onLoad(Object entity, 
    Serializable id,
    Object[] state,
    String[] propertyNames,
    Type[] types) {

    if (entity instanceof Persistent) ( (Persistent) entity ).onLoad();
    return false;
}

public boolean onSave(Object entity,
    Serializable id,
    Object[] state,
    String[] propertyNames,
    Type[] types) {
        
    if (entity instanceof Persistent) ( (Persistent) entity ).onSave();
    return false;
}

16.5. まとめ

ここには、少々謎めいた個所があります。そのため、最初の頃は、混乱しているように見えるかもしれません。しかしながら、これは、まったく問題なく動作します。Hibernate アプリケーションのほとんどは、多くの場所で、親/子パターンを使います。

最初のパラグラフで、代替案について述べました。上の問題のどれも、親/子関係とまったく同じセマンティクスをもつ <composite-element> マッピングには存在しません。残念ながら、複合要素クラスには、大きな制限が 2 つあります。複合要素は、コレクションを所有できないことと、一意な親以外のいかなるエンティティーの子供にもなるべきではないという点です。(しかしながら、複合要素は、<idbag> マッピングを使って、代用主キーをもつ場合があります。)

第 17 章 例: ウェブログアプリケーション

17.1. 永続クラス

永続クラスは、ウェブログ (weblog) と、ウェブログにポストされた項目を表わします。それらは、標準的な親/子関係としてモデル化されますが、セットではなく、順序のあるバッグを使うことにします。

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

17.2. Hibernate マッピング

XML マッピングは、今では、まったく簡単なはずです。

<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC 
        "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd">

<hibernate-mapping package="eg">

    <class
        name="Blog"
        table="BLOGS"
        lazy="true">

        <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"
            lazy="true"
            order-by="DATE_TIME"
            cascade="all">

            <key column="BLOG_ID"/>
            <one-to-many class="BlogItem"/>

        </bag>

    </class>

</hibernate-mapping>
<?xml version="1.0"?>
<!DOCTYPE hibernate-mapping PUBLIC 
        "-//Hibernate/Hibernate Mapping DTD 2.0//EN"
        "http://hibernate.sourceforge.net/hibernate-mapping-2.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>

17.3. Hibernate コード

次のクラスは、Hibernate を使って、これらのクラスにより行なうことのできるものの幾つかの例を示しています。

package eg;

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Iterator;
import java.util.List;

import net.sf.hibernate.HibernateException;
import net.sf.hibernate.Query;
import net.sf.hibernate.Session;
import net.sf.hibernate.SessionFactory;
import net.sf.hibernate.Transaction;
import net.sf.hibernate.cfg.Configuration;
import net.sf.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.save(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();
        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);
            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;
    }

    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.list().get(0);
            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;
    }
}

第 18 章 例: 様々なマッピング

この章では、もっと複雑な関連マッピングを幾つか示します。

18.1. Employer/Employee

EmployerEmployee 間の関係に関する次のモデルは、関連を表現するために、実際のエンティティークラス (Employment) を使っています。このようにした理由は、2 つの同じようなケースに対して、複数の雇用期間があるかもしれないからです。金銭的な費用と雇用者の名前をモデル化するために、コンポーネントを使っています。

以下は、可能なマッピング文書です。

<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="MonetoryAmount">
            <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>

そして、以下は、SchemaExport が生成したテーブルスキーマです。

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

18.2. Author/Work

Work, AuthorPerson の関係に関する次のモデルを考えて下さい。ここでは、WorkAuthor 間の関係を、多対多関連として表現しています。AuthorPerson 間の関係は、1 対 1 関連として表わすことにしました。AuthorPerson を拡張するような別の可能性もあったかもしれません。

次のマッピング文書は、現在のところ、こうした関係を表わしています。

<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" lazy="true">
            <key>
                <column name="work_id" not-null="true"/>
            </key>
            <many-to-many class="Author">
                <column name="author_id" not-null="true"/>
            </many-to-many>
        </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">
            <!-- Author は、Person と同じ識別子をもたなければならない -->
            <generator class="assigned"/> 
        </id>

        <property name="alias"/>
        <one-to-one name="person" constrained="true"/>

        <set name="works" table="author_work" inverse="true" lazy="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, authorspersons は、それぞれ、work, author, person に関するデータを保持しています。author_work は、authors から works にリンクする関連テーブルです。以下は、SchemaExport が生成したテーブルスキーマです。

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

18.3. Customer/Order/Product

次に、Customer, Order, LineItemProduct 間の関係に関するモデルを考えます。CustomerOrder 間には、1 対多関連がありますが、Order / LineItem / Product は、どのように表現すればよいでしょうか。ここでは、LineItem を、OrderProduct 間の多対多関連を表わす関連クラスとしてマップすることにしました。Hibernate では、これを、複合要素と呼びます。

マッピング文書

<hibernate-mapping>

    <class name="Customer" table="customers">
        <id name="id">
            <generator class="native"/>
        </id>
        <property name="name"/>
        <set name="orders" inverse="true" lazy="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" lazy="true">
            <key column="order_id"/>
            <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>

customers, orders, line_itemsproducts は、それぞれ、customer, order, order line item と product データを保持します。line_items は、orders を products とリンクする関連テーブルとしても振る舞います。

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

第 19 章 最善の実践

粒度の細かいクラスを書き、それらを <component> を使ってマップする。

Address クラスを使って、street, suburb, state, postcode をカプセル化する。これによって、コードの再利用が促進され、リファクタリングが容易になる。

永続クラス上で、識別子プロパティーを宣言する。

Hibernate は、識別子プロパティーをオプションにする。何故それらを使うべきかについては、幾つもの理由がある。識別子は、'合成的' (ビジネス的な意味合いをもたずに、生成された) で、プリミティブ型ではないことを推奨する。柔軟性を最大にするため、java.lang.Longjava.lang.String を使う。

個々のクラスマッピングを、それ自身のファイル内に置く。

単一のモノリシックな文書を使わない。com.eg.Foo は、ファイル com/eg/Foo.hbm.xml 内でマップする。これは、特に、チーム環境で、意味をなす。

マッピングをリソースとしてロードする。

マッピングを、それらがマップするクラスとともに配置する。

クエリー文字列を外部に置くことを考える。

クエリーが ANSI 標準の SQL 機能以外を呼び出す場合には、これは、優れた実践である。クエリー文字列をマッピングファイルの外に置くことによって、アプリケーションは、もっとポータブルになる。

バインド変数を使う。

JDBC 内のように、いつでも、非定数値を "?" で置き換える。クエリー内で非定数値をバインドするために、決して、文字列操作を使わない。

自分自身の JDBC 接続を管理しない。

Hibernate は、アプリケーションに、JDBC 接続を管理させる。このアプローチは、最後の切り札と考えるべきである。組み込みの接続プロバイダーを使うことができない場合には、net.sf.hibernate.connection.ConnectionProvider の自分自身の実装を提供できないか考える。

カスタム型を使うことを考える。

例えば、何らかのライブラリーの、永続化する必要があるが、コンポーネントとしてそれをマップするために必要なアクセサーを提供しない Java 型があるとする。こうした場合には、net.sf.hibernate.UserType を実装することを考慮すべきである。このアプローチは、Hibernate 型との間で変換することから、アプリケーションのコードを解放してくれる。

ボトルネックには、手書きの JDBC コードを使用する。

システムのパフォーマンスが重要な領域では、何らかの種類の操作 (例えば、大量の更新/削除) は、直接、JDBC から恩恵を受けるかもしれない。しかし、何がボトルネックであるのかが分かるまで、待ってもらいたい。そして、JDBC を直接使う方が、必ず、速いとは考えないでもらいたい。JDBC を直接使う必要がある場合には、Hibernate の Session をオープンし、その SQL を使う価値があるかもしれない。こうすることにより、同じトランザクション戦略と、その基礎にある接続プロバイダーを使い続けることができるからである。

Session をフラッシュすることを理解する。

時々、セッションは、その永続状態をデータベースと同期させる。パフォーマンスは、このプロセスがどの程度頻繁に発生するかによって影響を受ける。時には、自動フラッシングを無効にするか、特定トランザクション内でのクエリーやその他の操作の順序を変更することによって、不必要なフラッシングを最小限にするとよいかもしれない。

3 層アーキテクチャーでは、saveOrUpdate() を使うことを検討する。

サーブレット/セッション Bean アーキテクチャーを使う場合には、セッション Bean にロードされた永続オブジェクトを、サーブレット/JSP 層との間で受け渡しすることができる。新しいセッションを使って、個々のリクエストにサービスする。Session.update()Session.saveOrUpdate() を使って、オブジェクトの永続状態を更新する。

2 層アーキテクチャーでは、セッションの切断を使うことを検討する。

データベーストランザクションは、最善のスケーラビリティーのため、可能な限り短くする必要がある。しかしながら、ユーザーの観点から、単一の作業単位である時間のかかるアプリケーショントランザクションを実装する必要がある場合もしばしばある。このアプリケーショントランザクションは、幾つものクライアントリクエストとレスポンスサイクルをまたがる場合がある。そうした場合には、分離されたオブジェクトを使うか、2 層アーキテクチャーの場合には、引き続く個々のリクエストのため、Hibernate セッションを JDBC 接続から切断し、再接続するようにするとよい。決して、複数のアプリケーショントランザクションのユースケースのために、単一セッションを使ってはならない。そうしないと、無効になったデータに迷い込むことになる。

例外を回復可能なものとして扱ってはならない。

これは、"最善の" 実践というより、必要な実践というべきである。例外が発生したら、Transaction をロールバックし、Session をクローズする。そうしないと、Hibernate は、メモリー中の状態が、永続状態を正確に表現していることを保証できなくなる。この特殊ケースとして、指定した識別子をもつインスタンスがデータベースに存在するかどうかを判断するために、Session.load() を使ってはならない。その代わりに、find() を使うとよい。例外によっては、回復可能である。例えば、StaleObjectStateExceptionObjectNotFoundException がそれに当たる。

関連には、遅延フェッチを選択する。

eager (外部結合) フェッチを控えめに使用する。JVM レベルでキャッシュされたのではない、クラスに対する関連の多くに、プロキシーそして/あるいは遅延コレクションを使う。キャッシュがヒットする可能性が高い、キャッシュされたクラスに対する関連の場合、outer-join="false" を使って、明示的に eager フェッチを無効にする。外部結合によるフェッチが特定ユースケースにとって適切な場合には、left join をもつクエリーを使う。

ビジネスロジックを Hibernate から抽象化することを考慮する。

(Hibernate の) データアクセスコードを、インターフェースの背後に隠す。DAOスレッドローカルセッション (Thread Local Session) パターンを組み合わせる。手でコーディングした JDBC によって永続化され、UserType によって Hibernate に関連付けられたクラスをもつことすら可能である。(このアドバイスは、"十分な規模の" アプリケーションを意図している。これは、5 つ程度のテーブルしかないアプリケーションの場合には適切ではない。)

一意なビジネスキーを使って、equals()hashCode() を実装する。

オブジェクトをセッションスコープ外部で比較する場合、equals()hashCode() を実装しなければならない。セッションスコープの内部では、Java オブジェクトの同一性は、保証されている。こうしたメソッドを実装する場合、決してデータベース識別子を使ってはならない。一時的なオブジェクトは、識別子の値をもたないので、Hibernate は、オブジェクトを保存する際に、値を割り当てる。保存中にオブジェクトがセット内にある場合、ハッシュコードは変化し、契約は無効になる。equals()hashCode() を実装するには、一意なビジネスキーを使う。つまり、クラスのプロパティーの一意な組み合わせを比較する。このキーは、生存期間全体を通してではなく、オブジェクトがセット内にある場合にのみ、安定的で一意でなければならない (データベースの主キーほど安定的ではない) 点を思い出してもらいたい。決して、equals() 比較 (遅延ローディング) 内でコレクションを使ってはならない。プロキシーにされる場合もある他の関連付けられたクラスには注意が必要である。

風変わりな関連マッピングを使わない。

現実の多対多関連のための優れたユースケースは、稀である。ほとんどの場合、"リンクテーブル" 内に保存する追加情報が必要である。この場合、媒介的なリンククラスには、1 対多関連を使う方がはるかに望ましい。事実、ほとんどの関連は、1 対多と多対 1 であると考えられるので、その他の関連スタイルを使う場合には注意し、それが本当に必要なのかを自問すべきである。