Hibernateを使ったラウンドトリップ・エンジニアリングはHibernateプロジェクトの一部として保守されているコマンドライン・ツールのセットを使うことで可能で、XDoclet、Middlegen、AndroMDAがHibernateサポートに組み込まれています。
Hibernateメインパッケージは最も重要なツールになっています(実行時にHibernateの「中」からも使われます)。:
マッピングファイルからDDLスキーマを生成SchemaExport, hbm2ddl)
Hibernateプロジェクトにより直接提供された他のツール群は、Hibernate Extensionsという別のパッケージになっています。このパッケージは以下のようなタスクのためのツールを含んでいます。:
マッピングファイルからJavaソースファイルを生成(CodeGenerator, hbm2java)
コンパイル済みJavaクラス、またはXDocletのマークアップを施されたJavaソースからマッピングファイルを生成(MapGenerator, class2hbm)
実はHibernate Extentionsにはもうひとつ、実用的なツールddl2hbmがありますが、非推奨であり、もはや保守されていません。Middlegenの方が同じタスクに対してより良い仕事を行ないます。
以下にHibernateをサポートするサード・パーティ・ツールを挙げます。:
Middlegen (既存のデータベース・スキーマからマッピング・ファイルを生成)
AndroMDA(UML図とそのXML/XMI表現から永続クラスのコードを生成するMDA(モデル駆動アーキテクチャ)アプローチ)
これらサード・パーティ・ツールはこのリファレンスには文書化されていません。最新の情報はHibernateの ウェブサイトを参照してください。(サイトのスナップショットはHibernateメイン・パッケージに含まれています。
DDLはコマンドライン・ユーティリティによりマッピング・ファイルから生成することができます。バッチファイルはHibernateのコア・パッケージのhibernate-x.x.x/binディレクトリにあります。
生成されるスキーマはエンティティとコレクション・テーブルへの参照完全性制約(主キーと外部キー)を含みます。また、マッピングする識別子ジェネレータに対しテーブルとシーケンスが作成されます。
このツールを使うときは、hibernate.dialectプロパティでSQLの方言を指定しなければなりません。
多くのHibernateのマッピング要素ではオプションのlengthという名の属性を定義しています。この属性でカラム長を設定することができます(または数値/小数型のデータの精度を設定できます)。
not-null属性(テーブルのカラムへのNOT NULL制約を生成する)とunique属性(テーブルのカラムへのUNIQUE制約を生成する)が設定できるタグもあります。
カラムのインデックスの名前を特定するためのindex属性を指定できるタグもあります。unique-key属性はカラムをシングル・ユニットのキー制約でグループ化するために使われます。現在、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属性で、デフォルトのHibernate型のマッピングをSQLのデータ型にすることができます。
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 | index_name | (マルチ・カラム)インデックスの名前を指定する |
| unique-key | unique_key_name | マルチ・カラムのユニーク制約の名前を指定する |
| foreign-key | foreign_key_name | <one-to-one>、<many-to-one>、<many-to-many>マッピング要素を使って、関連に対し生成された外部キー制約の名前を指定する。inverse="true"側はSchemaExportにより考慮されないことに注意してください。 |
| sql-type | column_type | デフォルトのカラム型をオーバーライドする(<column>要素の属性のみ) |
| check | SQL expression | カラムやテーブルにSQLのチェック制約を作成する |
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);
データベースのプロパティを指定することができます。
システム・プロパティとして-D<property>
hibernate.properties内で指定する
--propertiesで指定されたファイル内で指定する
必要なプロパティは以下のようになります。:
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>もしプロパティファイルやコンフィグファイルを指定しなければ、SchemaExportTaskは通常のなAntプロジェクトのプロパティを代わりに使用します。言いかえれば、外部の設定ファイルやプロパティ・ファイルを望まないし必要としなければ、build.xmlあるいはbuild.propertiesの設定プロパティをhibernate.*としてもよい。
SchemaUpdateツールは既存のスキーマをインクリメンタルに更新します。SchemaUpdateはJDBCメタデータAPIに強く依存します。そのためすべての、JDBCドライバでうまくいくとは限らないことに注意してください。
java -cp hibernate_classpaths net.sf.hibernate.tool.hbm2ddl.SchemaUpdate options mapping_files
表 15.4. SchemaUpdateのコマンドライン・オプション
| オプション | 説明 |
|---|---|
| --quiet | 標準出力にスクリプトを出力しません |
| --properties=hibernate.properties | ファイルからデータベース・プロパティを読み込みます |
アプリケーションにSchemaUpdateを組み込むことができます。:
Configuration cfg = ....; new SchemaUpdate(cfg).execute(false);
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>Hibernateのコードジェネレータを使い、HibernateのマッピングファイルからJavaでの実装クラスのスケルトンを生成することができます。このツールはHibernate Extensionパッケージに含まれています。(別途ダウンロード)
hbm2javaはマッピング・ファイルを構文解析し、十分に機能するJavaソースファイルを生成します。このように hbm2javaを使い、単に.hbmファイルを作成するだけで、Javaファイルをハンドコードする煩わしさから解放されます。
java -cp hibernate_classpaths net.sf.hibernate.tool.hbm2java.CodeGenerator options mapping_files
表 15.5. コードジェネレータのコマンドライン・オプション
| オプション | 説明 |
|---|---|
| --output=output_dir | 生成されるコードのルートディレクトリ |
| --config=config_file | hbm2javaを設定するオプションのファイル |
設定ファイルはソースコードの複数の「レンダラ」を指定する方法と、「グローバル」スコープの<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)と ファインダを生成するレンダラの二つを指定しています(詳しくは次の「BasicFinder生成」をご覧ください)。
2番目のレンダラはパッケージ属性とサフィックス属性とともに提供されます。
パッケージ属性では、.hbmファイル内で指定されたパッケージスコープの代わりに、このレンダラから生成されたソースファイルの配置場所を指定します。
サフィックス属性では、生成されるファイルのサフィックスを指定します。例えばFoo.javaというファイルを、代わりにFooFinder.javaとすることができます。
<generate>要素に<param>属性を付け加えることで、任意のパラメータを(注:)投げることも可能です。
hbm2javaは現在generate-concrete-empty-classesパラメータをサポートします。これはBasicRendererに対し、ベースクラスを拡張する、空の具象クラスだけを生成するという情報を与えます。以下のonfig.xmlの例はこの特徴を示します。
<codegen>
<generate prefix="Base" renderer="net.sf.hibernate.tool.hbm2java.BasicRenderer"/>
<generate renderer="net.sf.hibernate.tool.hbm2java.BasicRenderer">
<param name="generate-concrete-empty-classes">true</param>
<param name="baseclass-prefix">Base</param>
</generate>
</codegen>このconfig.xmlファイルは2つのレンダラを設定することに注意してください。一つは基底クラスを生成するもので、もう一つは空の具象クラスを生成するものです。
<meta>タグはhbm.xmlに情報を付加する簡単な方法です。これによりツールがHibernateのコアに直接関係しない情報を保存し、読みこむための自然な場所を保持します。
"protected"なセッタだけを生成する、あるインタフェースの集合を常に実装する、またはある基底クラスを常に拡張するといった事をhbm2javaに知らせるために<meta>タグを使用することができます。:
下の例は以下のようなコードを生成します(わかりやすいようコードは短くしてあります)。
<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なセット・メソッドに注目してください。:
// default package
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;
/**
* Javadoc for the Person class
* @author Frodo
*
*/
public class Person implements Serializable, IAuditable {
/** identifier field */
public Long id;
/** nullable persistent field */
public String name;
/** full constructor */
public Person(java.lang.String name) {
this.name = name;
}
/** default constructor */
public Person() {
}
public java.lang.Long getId() {
return this.id;
}
protected void setId(java.lang.Long id) {
this.id = id;
}
/**
* The name of the person
*/
public java.lang.String getName() {
return this.name;
}
public void setName(java.lang.String name) {
this.name = name;
}
}表 15.6. サポートしているメタ・タグ
| 属性 | 説明 |
|---|---|
| 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ファイル内のデフォルトの"inherited"ごとにあります。
これはどういうことでしょうか?これは例えば、もしすべてのクラスでIAuditableを実装したいならばhbm.xmlファイルの先頭で、<hibernate-mapping>のすぐ後に、<meta attribute="imp lements">IAuditable</meta>を追加するだけでいいということです。これで、そのhbm.xmlファイルで定義されているすべてのクラスはIAuditableを実装することになります!(ただし、そのクラスが"implements"メタ属性を持っているときは除きます。というのはローカルで指定されたメタタグは継承されたメタタグを無効にし、置き換えるからです。)
注意:これはすべての <meta>タグに当てはまります。そのため例えば、デフォルトであるprivateの代わりにすべてのフィールドをprotectedと指定するなどに使うことができます。このことは、たとえば<class>タグのすぐ後に<meta attribute="scope-field">protected</meta>を追加することで、クラスのすべてのフィールドがプロテクティドになります。
継承された<meta>タグを無効にするには、単に属性をinherit="false"と指定するればよく、例えば<meta attribute="scope-class" inherit="false">public abstract</meta>は"クラススコープ"を現在のクラスに制限し、サブクラスには適用されません。
今ではHibernateプロパティに対しhbm2javaを使って、BasicFinderを生成させることが可能になりました。これによりhbm.xmlファイルに二つのことが必要になります。
一つめは、どのフィールドに対してファインダを生成したいのか指定する必要があります。以下のようにプロパティ・タグ中のメタ・ブロックを使って指定します。:
<property name="name" column="name" type="string">
<meta attribute="finder-method">findByName</meta>
</property>ファインダ・メソッドの名前はメタ・タグで囲まれたテキストになります。
二つめは、以下のフォーマットのような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 attribute="session-method">
com.whatever.SessionTable.getSessionTable().getSession();
</meta>もしThread Local Sessionパターンを使うなら、Sessionを得る方法を指定します。(Hibernateのウェブサイトのデザインパターン・エリアに文書化されています)。
今では、代替のレンダリング・メカニズムとして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がJavaBeansの最も基本的な部分しか生成しないことに注意してください。Velocityベースのレンダラ/ジェネレータはデフォルトのレンダラほど完全でも特徴豊かではありません。基本的に多くのメタタグはサポートされていません。
マッピング・ファイルのスケルトンはMapGeneratorというコマンドライン・ユーティリティを使ってコンパイルされた永続クラスから生成できます。このユーティリティはHibernate Extensionパッケージの一部です。
Hibernateのマッピング・ジェネレータはコンパイルされたクラスからマッピングを生成するメカニズムを提供します。これにはプロパティを見つけるのにJavaのリフレクションを使い、プロパティの型から適切なマッピングを推測するために経験則を用いています。生成されたマッピングは開始点としてのみ意図されています。ユーザからの追加入力なしでは、完全なHibernateマッピングを生成することはできません。しかしながら、このツールはマッピングを生成する"退屈な"繰り返しをいくらか減らします。
クラスは一度に1つのマッピングに追加されます。このツールはHibernate persistableではないと判断したクラスを無視します。
クラスをHibernate persistableにするには、以下のようにします。
プリミティブ型ではいけません
配列であってはいけません
インターフェイスであってはいけません
入れ子クラスではいけません
デフォルトコンストラクタ(引数なし)を持たなければなりません。
実際にはインタフェースと入れ子クラスはHibernateで永続化できることに注意してください。しかしこれは普通ユーザが永続化したいとは思わないものです。
MapGeneratorは、同じデータベースのテーブルにできるだけ多くのHiberante Persistableなスーパークラスを追加するよう意図された、すべてのクラス階層をたどっていきます。検索はプロパティがcandidate UID namesのリストに名前が見つかるとすぐに終了します。
デフォルトのcandidate UID プロパティ名のリストは: uid, UID, id, ID, key, KEY, pk, PKです。
クラス内の2つのメソッド、セッタとゲッタが、セッタの第一引数が引数なしのゲッタの戻り値と同じであり、セッタの戻り値がvoidである場合、プロパティは発見されます。さらには、セッタの名前はsetで始まらなければならず、ゲッタの名前はgetか、プロパティの型がbooleanのときはisで始まらなければなりません。どちらの場合も、名前の残りの部分は一致しなければなりません。もし二文字めが小文字なら、プロパティ名の頭文字が小文字になることを除けば、この一致する部分はプロパティ名になります。
それぞれのプロパティのデータベースの型を決定する規則はこのようになります:
もしJavaの型がHibernate.basic()ならば、プロパティはその型のカラムです。
hibernate.type.Type カスタム型と PersistentEnum型についても同様に、単純なカラムが使われます。
プロパティの型が配列なら、Hibernateの配列が使われMapGeneratorは配列の要素の型をリフレクトするよう試みます。
もしプロパティがjava.util.List, java.util.Map, or java.util.Setの型を持つならば、対応するHibernate型が使われますが、MapGeneratorは決してこれらの型の内部は処理しません。
もしプロパティの型が他のクラスなら、MapGeneratorはすべてのクラスが実行されるまでデータベースの表現の決定保留します。この時点で、もし上述のようなスーパークラスの検索を通してクラスが発見されるとすれば、プロパティはmany-to-one関連です。もしクラスがどれかプロパティを持つなら、それはcomponentとしてマッピングします。さもなければ、それはシリアライズ可能でも永続化可能でもありません。
ツールはXMLマッピングを標準出力と/またはファイルに書き込みます。
ツールを起動するとき、コンパイルされたクラスをクラスパスに通しておかなければなりません。
java -cp hibernate_and_your_class_classpaths net.sf.hibernate.tool.class2hbm.MapGenerator options and classnames
コマンドラインまたはインタラクティブの二つの操作モードがあります。
インタラクティブモードはコマンドラインの引数--interactを一つ指定することで選択されます。このモードはプロンプトの応答をコンソールに出力します。それを使って、XXXがUIDプロパティのときuid=XXXコマンドを使ってそれぞれのクラスにUIDのプロパティ名を設定することができます。他の代替コマンドは単に完全限定クラス名で、XMLを生成するコマンドや終了するコマンドがあります。
コマンドライン・モードにおいて、引き数は処理されるクラスの完全限定クラス名が組み込まれているオプションです。オプションのほとんどは複数回使われるものと意図されていて、どの使い方も続くクラスに影響を及ぼします。
表 15.7. MapGeneratorのコマンドライン・オプション
| オプション | 説明 |
|---|---|
| --quiet | 標準出力にO-Rマッピングを出力しません |
| --setUID=uid | UIDの候補とするリストをひとつのUIDに設定します |
| --addUID=uid | 候補のUIDのリストの先頭にUIDを追加します |
| --select=mode | 続いて追加されたクラスに対して、選択モードmode(例えば, distinct または all)を使うモード |
| --depth=<small-int> | 続いて追加されたクラス対して、コンポーネント・データの再帰の深さを制限します |
| --output=my_mapping.xml | O-R Mappingをファイルに出力します |
| full.class.Name | クラスをマッピングに追加します |
| --abstract=full.class.Name | 以下を見てください |
抽象スイッチはマップ・ジェネレータツールが特定のスーパークラスを無視するようにします。 それは共通の継承を持つクラスが一つの大きなテーブルにマッピングされないようにするためです。 例えば、これらのクラスの階層構造を考えてみてください:
Animal-->Mammal-->Human
Animal-->Mammal-->Marsupial-->Kangaroo
もし--abstractスイッチが使われなければ、すべてのクラスはAnimalのサブクラスとしてマッピングされます。 その結果、どのサブクラスが実際に格納されたのかを示すディスクリミネータ・カラムと、すべてのクラスのすべてのプロパティを含んだ一つの大きなテーブルが付け加えることになります。もし MammalがabstractとマークされるならHuman と Marsupialは別の<class>定義にマッピングされ、別のテーブルに格納されます。Marsupialがaabstracttとしてマークされなければ、KangarooはMarsupialのサブクラスになってしまいます。