Hibernate Annotationsの@LazyToOne

現在Hibernateを仕事で使っているのですが、またまたレガシースキーマに対応しなければいけなくなってしまいました。複合主キーは今回@IdClassを使って定義しました(JPQLの記述を少しでもSQLに近づける為)。DBA担当にお願いしてバージョンカラムを定義してもらい、楽観的排他制御とmerge処理での登録・更新自動制御の挙動を確保。これで何とかなるかな・・・と思っていたのですが・・・
複合主キーなだけあって、主キー同士の結合が大量に存在していることが判明。双方向1対1だけでなく、多対1の結合でもLAZYロードが効かないパターンが頻発してしまいました。このままではまずい・・・というわけで、再度HibernateのLAZYロード機能について調査してみました。
HibernateのLAZYロードには2パターンあります。デフォルトのモードはPROXYモード。これはLAZYロード設定時に関連EntityにProxyを設定し、主キー以外のProxyのプロパティにアクセスされたときに初めてSQLを発行するモードです。外部キーを持つテーブルならこれでOKなんですが、外部キーを持たない側の1対1関連や、主キーで結合する多対1関連の場合、その関連Entityが存在するかどうかを判別することが出来ない為、HibernateはLAZY設定を無視してSELECT文を発行してしまいます。この挙動がHibernateで最もハマる問題点でした。
もう一つのモードはNO-PROXYモード。このモードはTopLinkやOpenJPAと同じく、Entityに対してバイトコードエンハンスを行い、フィールドアクセス時にSELECT文を発行します。なので、外部キーを持たなかったり主キーで結合するようなN対1の関連EntityでもLAZYロードが有効になります。以前はこのNO-PROXYモードまで調査しきれていませんでした・・・
このNO-PROXYモードを有効にするには、あらかじめEntityをantタスクによってinstrumentする必要があります。
http://www.hibernate.org/hib_docs/v3/reference/en/html_single/#performance-fetching-lazyproperties
↑の設定によってHibernateのproperty LAZY LOADが有効になるので、N対1の関連LAZYロードも有効になります。
ただし、JPAの@ManyToOne、@OneToOneアノテーションでFetchType.LAZYを設定した場合、HibernateはbytecodeエンハンスをしてもPROXYモードにしか設定してくれません。この挙動を変更するには、Hibernateの独自アノテーションである@LazyToOneを定義する必要があります。

	@OneToOne(mappedBy="map_content", fetch = FetchType.LAZY)
	@LazyToOne(LazyToOneOption.NO_PROXY)
	private Map_content_images map_content_image;

これでようやく、HibernateのN対1関連でLAZYロードを有効にすることが出来ました。このLazyToOneアノテーション、以前調べたときには存在していなかったので、調査しても解らなかったみたいです・・・
仕事の環境はとりあえずこれで済ませることにしました。・・・ただ、やっぱりコンパイルとは別にantタスクを動かさないとEntityが使えないのは何かと不便です・・・そこで思い出したのが、JPAで定義されているClassTransformerです。TopLinkやOpenJPAはこのクラスを使って、動的なEntityエンハンスを実現しています。HibernateもこのClassTransformerには対応済です。ということは、JavaEE5サーバ環境なら、このClassTransformerを使ってHibernateでも事前処理無しにバイトコードエンハンスが可能なのでは?
というわけで、早速実験。まずはHibernateでClassTransformerを有効にする為に、persistence.xmlのプロパティに以下の設定を追加します。

<property name="hibernate.ejb.use_class_enhancer" value="true"/>

後は先ほどの@LazyToOneアノテーションを対象となる関連プロパティに定義すればOK。GlassFish上で動かしたところ、双方向1対1でLAZYロードが有効になりました! これでようやくHibernateでもLAZYロードが意図した通りに使えるようになりますね。1対1の関連が大量に存在したり、レガシースキーマHibernateを使わなければいけない場合は、この設定は必須ではないかと思います。
さて、この動的バイトコードエンハンス機能を使うには、JPAを動かす環境がClassTransformerに対応してないといけません。JavaEE5サーバなら問題無しに使えます。EJB3対応コンテナでも大丈夫の筈。問題はWithout JavaEE環境ですね。まずSpringの場合は、Tomcatのserver.xmlに独自のClassLoaderを定義してあげる必要があります。自分は試してませんが、多分これで動くと思います(汗)
そしてSeasar2ですが、こちらはDIコンテナレベルでClassTransformerに対応しています。S2Hibernate-JPAを普通にセッティングして、persistence.xmlと@LazyToOneの定義を同じように指定してやれば、JavaEE5サーバ環境と同様に動的なバイトコードエンハンスが可能です。久々にTomcatをセッティングして動かしてみたところ、問題なくLAZYロードできました。注意点としては、S2Hibernate-JPAに添付されてあるjpa.diconを読み込ませること。javaee5.diconでもEntityManagerは使えますが、その場合バイトコードエンハンスは有効にならないみたいです。