このチュートリアルでは、Webベースのアプリケーションのための、 Apache Tomcatサーブレット・コンテナを使った、 Hibernate2.1のセットアップについて述べます。 主要なすべてのJ2EEアプリケーション・サーバや、 スタンドアローン・アプリケーションで管理された環境で、Hibernateはよく動作します。 この例で使うデータベース・システムはPostgreSQL7.3ですが、 HibernateのSQL方言を設定しなおすだけで、他のデータベースに変更できます。
最初のステップは、Tomcatインストール・ディレクトリに、 必要なライブラリのすべてをコピーすることです。 このチュートリアルでは、独立したウェブ・コンテキスト (webapps/quickstart)を使います。 そのためグローバル・ライブラリ・サーチパス(TOMCAT/common/lib)と webapps/quickstart/WEB-INF/lib における コンテキスト・レベルのクラスローダ(JARファイルのため)と webapps/quickstart/WEB-INF/classes を考慮しなければいけません。 追々グローバル・クラスパスとコンテキスト・クラスパスの、両方のクラスローダに言及します。
まず、ライブラリを2つのクラスパスにコピーしましょう:
初めに、グローバル・クラスパスに、データベースのJDBCドライバをコピーします。 これはTomcatにバンドルされる、DBCPコネクションプール・ソフトウェアに必要です。 HibernateはデータベースのSQLを実行するために、JDBCコネクションを使います。 そのため、プールされたJDBCコネクションを用意するか、 Hibernateが直接サポートしているプール(C3P0, Proxool)の1つを使うように、 設定しなければいけません。 このチュートリアルでは、グローバル・クラスローダパスに、 pg73jdbc3.jar ライブラリ (PostgreSQL7.3とJDK1.4用)をコピーしてください。 もし他のデータベースを使いたければ、適切なJDBCドライバをコピーするだけです。
Tomcatのグローバル・クラスローダパスには、他には決して何もコピーしないでください。 そうでなければ、例えばLog4j, commons-loggingなどのいろいろなツールに、 問題が出てきてしまいます。 必ず各アプリケーションごとに、コンテキストパスを使うようにしてください。 つまり WEB-INF/lib にライブラリをコピーして、 WEB-INF/classes に、 クラスと設定/プロパティ・ファイルを コピーしてください。 両ディレクトリともに、デフォルトでコンテキスト・レベルのクラスパスです。
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 Jakarta Commonsプロジェクトの、 いろいろなユーティリティ・ライブラリを使います。 |
ODMG4(必須) | Hibernateは、 オプションのODMG準拠の永続マネージャ・インターフェイスを用意しています。 ODMG APIを使うつもりがなくても、コレクションをマッピングするなら これは必要となります。 このチュートリアルではコレクションのマッピングは行いませんが、 いずれにせよこのJARをコピーしておくのは良い考えです。 |
EHCache(必須) | Hibernateは第2レベルキャッシュのために、 いろいろなキャッシュ・プロバイダを使うことができます。 設定を変更しなければ、EHCacheがデフォルトのキャッシュ・プロバイダです。 |
Log4j(オプション) | HibernateはCommons Logging APIを使いますが、代わりのロギング機構として、 Log4jを使うこともできます。 コンテキスト・ライブラリ・ディレクトリにLog4jライブラリが配置されると、 Commons LoggingはLog4jとコンテキスト・クラスパス中の log4j.properties を使うようになります。 Log4jのプロパティ・ファイルのサンプルが、 Hibernateディストリビューションにあります。 そのため、もし背後で何が行われているのか知りたければ、 コンテキスト・クラスパスにlog4j.jarと設定ファイル (src/ から)をコピーしてください。 |
その他 | Hibernateディストリビューションの lib/README.txt ファイルを見てください。 これはHibernateと一緒に配布されている、 サードパーティのライブラリの最新のリストです。 必須のライブラリとオプションのライブラリのすべてを、 ここで見つけることができるでしょう。 |
以上でデータベース・コネクション・プーリングのセットアップが終了し、 TomcatとHibernateの両方から共有できるようになりました。 これは(組み込みのDBCPプーリング機能を使い) Tomcatがプールされた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を設定することです。 この例題では、XMLベースでHibernateを設定します。 プロパティを使う基本的な方法では、機能は同じものの、利点がいくつか損なわれてしまいます。 普通は、より便利な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方言が使われていて、 どこからJDBCコネクションを手に入れれば良いかを (データソース・プールがバインドされているJNDIのアドレスを定義することで) Hibernateに伝えます。 データベースは、SQL「標準」の解釈がそれぞれ異なっているので、 方言を設定する必要があります。 Hibernateは、主要なすべての商用とオープンソースのデータベースに対して違いをケアし、 それぞれの方言に対応します。
SessionFactory は、データストア1つに対応するHibernateの概念です。 XML設定ファイルを複数用意し、アプリケーションで Configuration と SessionFactory オブジェクトを複数作成することで、 データベースを複数使うことができます。
hibernate.cfg.xml の最後の要素で、 永続クラス Cat のためのHibernate XMLマッピング・ファイルの名前として、 Cat.hbm.xml を定義しています。 このファイルは、POJOクラスのデータベース・テーブル(や複数のテーブル)へのマッピングに対する メタデータを含んでいます。 またすぐにこのファイルに戻ってくることになります。 それではPOJOクラスを書いて、メタデータのマッピングを定義しましょう。
永続クラスの、プレーンの古いJavaオブジェクト(POJO)プログラミング・モデルで、 Hibernateは最も良く動作します (POJOは、プレーンの普通のJavaオブジェクトと呼ばれることもあります)。 クラスのプロパティがgetter, setterメソッドを通してアクセス可能である点で、 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コレクション・フレームワークのクラスを含む、すべてのJava JDK型とプリミティブ (String, char, Date など)を マッピングできます。 それらをバリューや、バリューのコレクションや、他のエンティティへの関連として、 マッピングできます。 id は、クラスのデータベース識別子(主キー)を表す特別なプロパティで、 Cat のようなエンティティには非常におすすめです。 識別子は、Hibernateが内部的に使うだけでも構いませんが、 それではアプリケーション・アーキテクチャの持つ柔軟性をいくらか失うことになります。
永続クラスは、特別なインターフェイスを実装する必要も、 特別なルートの永続クラスからサブクラス化する必要もありません。 また、Hibernateはバイトコード操作のような、ビルド時のプロセスを使いません。 唯一Javaのリフレクションと、 (CGLIBを通した)実行時のクラス・エンハンスメントだけに依存しています。 そのためHibernateに全く依存させず、POJOクラスをデータベースのテーブルにマッピングできます。
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 hex文字は代理キーです。 これは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> <!-- catには名前がなければいけませんが、長すぎる名前はいけません。 --> <property name="name"> <column name="NAME" length="16" not-null="true"/> </property> <property name="sex"/> <property name="weight"/> </class> </hibernate-mapping>
すべての永続クラスが、識別子属性を持つべきです (実際には、エンティティのコンポーネントとしてマッピングされるのは、 依存するバリュー・オブジェクトではなく、エンティティを表すクラスだけです。)。 このプロパティは、永続オブジェクトを識別するために使われます: catA.getId().equals(catB.getId()) がtrueなら、 2つのcatを同じものとみなすこの概念は、 データベース・アイデンティティ と呼ばれています。 Hibernateには、多様なシナリオに対応する、いろいろな識別子ジェネレータがバンドルされています (データベース・シーケンスを使うnativeジェネレータや、hi/lo識別子テーブルや、 アプリケーション代入識別子などがあります)。 この例では、UUIDジェネレータ(データベースが生成する整数の代理キーの方が好ましいので、 これはテスト用としてだけおすすめします)を使い、 また(テーブルの主キーとして)Hibernateが生成する識別子の値のための、 CAT テーブルの CAT_ID カラムを指定します。
Cat の他のプロパティはすべて、同じテーブルにマッピングされます。 name プロパティについては、 明示的にデータベース・カラムを定義して、マッピングしています。 Hibernateの SchemaExport ツールを使い、 マッピング定義から(SQLのDDL文として)データベース・スキーマを自動生成する場合、 これは特に有用です。 他のプロパティはすべて、Hibernateのデフォルトの設定を使い、マッピングされます。 これは普通なら最も時間が必要になるところです。 データベースの CAT テーブルは、以下のようになっています:
カラム | 型 | 修飾子 --------+-----------------------+----------- cat_id | character(32) | not null name | character varying(16) | not null sex | character(1) | weight | real | Indexes: cat_pkey primary key btree (cat_id)
それでは、このテーブルを手作業で作成しましょう。 このステップをSchemaExportツールで自動化したければ、 後で 章 15. ツールセット・ガイド を読んでください。 このツールは、テーブル定義、カスタムカラム型制約、ユニーク制約、インデックスを含む、 完全なSQL DDLを作成することができます。
ついに、Hibernateの Session を始める準備ができました。 私たちは、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 にアクセスできるよう、 ある種の シングルトン が必要だということです。 設定の問題と、SessionFactory への容易なアクセスの問題の両方を、 次にお見せする方法で解決します。
HibernateUtil ヘルパ・クラスを実装しました:
import net.sf.hibernate.*; import net.sf.hibernate.cfg.*; public class HibernateUtil { private static Log log = LogFactory.getLog(HibernateUtil.class); private static final SessionFactory sessionFactory; static { try { // SessionFactoryを作成します sessionFactory = new Configuration().configure().buildSessionFactory(); } catch (Throwable ex) { log.error("Initial SessionFactory creation failed.", ex); throw new ExceptionInInitializerError(ex); } } public static final ThreadLocal session = new ThreadLocal(); public static Session currentSession() throws HibernateException { Session s = (Session) session.get(); // まだこのThreadに存在しなければ、新しくSessionをオープンします 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(); } }
このクラスは、static属性として 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();
ある1つの Session において、 すべてのデータベース操作が1つのトランザクション内で起こります (read-only操作でさえです)。 ベースとなるトランザクション戦略(私たちの場合は、JDBCトランザクション)から抽象化するために、 Hibernate Transaction APIを使っています。 このおかげで何も変更せずに、 コードを(JTAを使う)コンテナ管理トランザクションと一緒に配置できます。 上にあげた例で、全く例外処理をしていないことに注目してください。
また、好きなだけ HibernateUtil.currentSession(); をコールすることができます。 すると、常にこのスレッドの現時点の Session を取得します。 作業単位が完了した後、HTTPレスポンスが送られるまでに、サーブレットのコードかサーブレット・フィルタの中で、 確実に Session をクローズしなければなりません。 後者のすばらしい副作用は、簡単なlazy初期化です: ビューがレンダリングされるとき、Session はまだオープンしているので、 グラフのナビゲートの間、Hibernateは初期化されていないオブジェクトをロードすることができます。
Hibernateは、データベースからオブジェクトを復元するための方法をいろいろ用意しています。 最も柔軟な方法は、Hibernateクエリ言語(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は、オブジェクト指向の criteriaによるクエリ APIも 用意しています。これはタイプ・セーフなクエリを定式化するために使えます。 当然Hibernateは、データベースとのSQLのやりとりのすべてにおいて、 PreparedStatement とパラメータ・バインディングを使います。 余りないことですが、Hibernateで直接SQLクエリの機能を使うことや、 Session からプレーンのJDBCコネクションを取得することも可能です。