普通には lazy フェッチできないケース(LoadTimeWeaverで対応)
前回、以下のアノテーションにより、対1関連をフェッチできると書きました。
@OneToOne(fetch=FetchType.LAZY)
しかし、単純にそうもいかないケースもあるとも書きました。
これらのケースを、例を追いながら説明し、その後 LoadTimeWeaver を用いた解決方法を示します。
例1
概要
- クラス A と クラス B が互いに 1対1関連を持っている。
- DB のテーブル上、テーブル A はテーブル B の外部キーを持っているが、テーブル B はテーブル A への外部キーを持っていない。
ソース
クラス A
@Entity public class A extends EntityBase { @OneToOne(fetch=FetchType.LAZY) private B b; public B getB() { return b; } public void setB(B b) { this.b = b; } }
クラス B
@Entity public class B extends EntityBase { @OneToOne(mappedBy="b", fetch=FetchType.LAZY) private A a; public A getA() { return a; } public void setA(A a) { this.a = a; } }
EntityBase
@MappedSuperclass public class EntityBase { @Id @GeneratedValue private int id; }
うまくいくケース(A の取得)
A a = em.find(A.class, 1); // lazyロード成功! // select a0_.id as id0_0_, a0_.b_id as b2_0_0_ from A a0_ where a0_.id=1
うまくいかないケース(B の取得)
B b = em.find(B.class, 1); // lazyロード失敗 // select b0_.id as id1_0_ from B b0_ where b0_.id=1 // select a0_.id as id0_0_, a0_.b_id as b2_0_0_ from A a0_ where a0_.b_id=1
なぜうまくいかないケースが存在するのか?
このケースに関する基本的な考え方は、に Some explanations on lazy loading わかりやすく説明されています。
上記の例の場合は、取得した B のインスタンスの a フィールドに対して、
- 対応する a が存在するならばプロキシを配置
- 対応する a が存在しないならば null を配置
したいわけですが、その存在チェックのために a のテーブルの対象レコードを検索したら、それはすでに lazy ではないということになってしまいます。
SpringFramework を用いた Not EJB 環境の解決策
EntityManager の設定
EntityManager として、LocalContainerEntityManagerFactoryBean を使用します。
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" />
Spring2.0系の場合は、LoadTimeWeaver も明示的に指定します。
<property name="loadTimeWeaver"> <bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/> </property>
persistence.xml の設定
Enhancserを有効にします。
<property name="hibernate.ejb.use_class_enhancer" value="true" />
tomcat環境の場合
Context の Loader に、TomcatInstrumentableClassLoader を指定します。
<Context path="/myWebApp" docBase="/my/webApp/location"> <Loader loaderClass="org.springframework.instrument.classloading.tomcat.TomcatInstrumentableClassLoader"/> </Context>
B のソースに @LazyToOne アノテーションを追加
クラス B のソース上で、A への関連について @LazyToOne アノテーションを追加します。オプションを LazyToOneOption.NO_PROXY に設定することにより、プロキシではなくバイトコードの加工が行われます。
@Entity public class B extends EntityBase { @OneToOne(mappedBy="b", fetch=FetchType.LAZY) @LazyToOne(LazyToOneOption.NO_PROXY) private A a; public A getA() { return a; } public void setA(A a) { this.a = a; } }
SpringFramework を用いた Not EJB 環境の実行例
うまくいくケース(A の取得)
A a = em.find(A.class, 1); // lazyロード成功! // select a0_.id as id0_0_, a0_.b_id as b2_0_0_ from A a0_ where a0_.id=1 // もし A 側にも @LazyToOne アノテーションを追加した場合、下記のようなクエリが発行されます。 // select a0_.id as id0_0_ from A a0_ where a0_.id=1
うまくいくケース(B の取得)
B b = em.find(B.class, 1); // lazyロード成功 // select b0_.id as id1_0_ from B b0_ where b0_.id=1