Hiernateの双方向one-to-oneの遅延ロードについて
前回Hibernate EntityManagerの@OneToOneのLAZYロードの動きで詰まっていたのですが、この件についてひがさんからアドバイス(http://d.hatena.ne.jp/da-yoshi/20060129/1138551132#c)を頂きました。ありがとうございます。早速、Hibernate3.1.1で試してみました。
・・・実はHibernateはバージョン2の頃に触った程度だったので、タグについては不勉強だったりします(汗)。Hibernate in Actionと3.1.1のリファレンスを読みながら設定してみました。
まずは、単一のユニークな外部キーで1対1になる場合。テーブルは以下の通り。
CREATE TABLE TEST1( ID INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, NAME VARCHAR, VERSION TIMESTAMP ) CREATE TABLE TEST2( ID INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, NAME VARCHAR, TEST1_ID INTEGER, VERSION TIMESTAMP, UNIQUE(TEST1_ID), FOREIGN KEY(TEST1_ID) REFERENCES TEST1(ID) )
クラスは以下の通り
public class Test1 implements Serializable { private static final long serialVersionUID = 7556192090172970471L; private Integer id; private Timestamp version; private String name; private Test2 test2; (getter、setter略) } public class Test2 implements Serializable { private static final long serialVersionUID = 79887318817618572L; private Integer id; private Timestamp version; private Test1 test1; private String name; (getter、setter略) }
そしてマッピングファイル
<hibernate-mapping package="test.generate.entity"> <class name="Test1"> <id name="id"> <generator class="identity" /> </id> <timestamp name="version"/> <property name="name"/> <one-to-one name="test2" property-ref="test1" cascade="all" lazy="proxy"/> </class> </hibernate-mapping> <hibernate-mapping package="test.generate.entity"> <class name="Test2"> <id name="id"> <generator class="identity" /> </id> <timestamp name="version"/> <many-to-one name="test1" column="TEST1_ID" unique="true" lazy="proxy"/> <property name="name"/> </class> </hibernate-mapping>
最後に実行クラス
public class Main1 { /** * @param args */ public static void main(String[] args) { SessionFactory factory = null; Session session = null; Transaction transaction = null; try { factory = new Configuration().configure().buildSessionFactory(); session = factory.openSession(); transaction = session.beginTransaction(); Test1 test1 = new Test1(); test1.setName("TEST1"); Test2 test2 = new Test2(); test2.setName("TEST2"); test2.setTest1(test1); test1.setTest2(test2); session.save(test1); session.flush(); session.clear(); test1 = (Test1) session.get(Test1.class, (Serializable) test1.getId()); System.out.println("TEST1.ID:" + test1.getId()); System.out.println("TEST1.NAME:" + test1.getName()); System.out.println("TEST1.VERSION:" + test1.getVersion()); System.out.println(); test2 = test1.getTest2(); System.out.println("TEST2.ID:" + test2.getId()); System.out.println("TEST2.NAME:" + test2.getName()); System.out.println("TEST2.VERSION:" + test2.getVersion()); System.out.println(); session.clear(); test2 = (Test2) session.get(Test2.class, (Serializable) test2.getId()); System.out.println("TEST2.ID:" + test2.getId()); System.out.println("TEST2.NAME:" + test2.getName()); System.out.println("TEST2.VERSION:" + test2.getVersion()); System.out.println(); test1 = test2.getTest1(); System.out.println("TEST1.ID:" + test1.getId()); System.out.println("TEST1.NAME:" + test1.getName()); System.out.println("TEST1.VERSION:" + test1.getVersion()); System.out.println(); transaction.commit(); } finally { if (session != null) { try { session.close(); } catch (HibernateException e) { } } if (factory != null) { factory.close(); } } } }
この実行結果は・・・
Hibernate: /* insert test.generate.entity.Test1 */ insert into Test1 (version, name, id) values (?, ?, null) Hibernate: call identity() Hibernate: /* insert test.generate.entity.Test2 */ insert into Test2 (version, TEST1_ID, name, id) values (?, ?, ?, null) Hibernate: call identity() Hibernate: /* load test.generate.entity.Test1 */ select test1x0_.id as id0_1_, test1x0_.version as version0_1_, test1x0_.name as name0_1_, test2x1_.id as id1_0_, test2x1_.version as version1_0_, test2x1_.TEST1_ID as TEST3_1_0_, test2x1_.name as name1_0_ from Test1 test1x0_ left outer join Test2 test2x1_ on test1x0_.id=test2x1_.TEST1_ID where test1x0_.id=? TEST1.ID:11 TEST1.NAME:TEST1 TEST1.VERSION:2006-01-31 08:16:22.796 TEST2.ID:11 TEST2.NAME:TEST2 TEST2.VERSION:2006-01-31 08:16:22.843 Hibernate: /* load test.generate.entity.Test2 */ select test2x0_.id as id1_0_, test2x0_.version as version1_0_, test2x0_.TEST1_ID as TEST3_1_0_, test2x0_.name as name1_0_ from Test2 test2x0_ where test2x0_.id=? TEST2.ID:11 TEST2.NAME:TEST2 TEST2.VERSION:2006-01-31 08:16:22.843 TEST1.ID:11 Hibernate: /* load test.generate.entity.Test1 */ select test1x0_.id as id0_1_, test1x0_.version as version0_1_, test1x0_.name as name0_1_, test2x1_.id as id1_0_, test2x1_.version as version1_0_, test2x1_.TEST1_ID as TEST3_1_0_, test2x1_.name as name1_0_ from Test1 test1x0_ left outer join Test2 test2x1_ on test1x0_.id=test2x1_.TEST1_ID where test1x0_.id=? Hibernate: /* load test.generate.entity.Test2 */ select test2x0_.id as id1_0_, test2x0_.version as version1_0_, test2x0_.TEST1_ID as TEST3_1_0_, test2x0_.name as name1_0_ from Test2 test2x0_ where test2x0_.TEST1_ID=? TEST1.NAME:TEST1 TEST1.VERSION:2006-01-31 08:16:22.796 INFO 2006-01-31 08:16:23,015 [main] closing INFO 2006-01-31 08:16:23,015 [main] cleaning up connection pool: jdbc:hsqldb:hsql://localhost
Hibernateの場合、LAZYロードが効かない場合OUTER JOINになるのかな?
何はともあれ、EntityManagerのときとほぼ同じ動きになりました。外部キーを持つ側だけを呼び出した場合はLAZYロードが効いてますね。しかしそこから双方向の1対1のクラスを呼び出したときは、OUTER JOINした上にもう一度SELECT文を発行してる・・・うーん、なんか自分の設定に問題あるのか??
ちなみに、lazy=”no_proxy”では同じ結果に、lazy=”false”の場合は当然LAZYロードは効きませんでした。
続いて、主キー同士で結合している場合
CREATE TABLE TEST3( ID INTEGER GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, NAME VARCHAR, VERSION TIMESTAMP ) CREATE TABLE TEST4( ID INTEGER PRIMARY KEY, NAME VARCHAR, VERSION TIMESTAMP, FOREIGN KEY(ID) REFERENCES TEST3(ID) )
クラスは前回とほぼ同じなので省略。
マッピングファイルは・・・
<hibernate-mapping package="test.generate.entity"> <class name="Test3"> <id name="id"> <generator class="identity" /> </id> <timestamp name="version"/> <property name="name"/> <one-to-one name="test4" cascade="all" lazy="proxy"/> </class> </hibernate-mapping> <hibernate-mapping package="test.generate.entity"> <class name="Test4"> <id name="id"> <generator class="foreign"> <param name="property">test3</param> </generator> </id> <timestamp name="version"/> <one-to-one name="test3" constrained="true" lazy="proxy"/> <property name="name"/> </class> </hibernate-mapping>
foreignというgeneratorがHibernateには存在するんですね。主キーに外部キー制約がある場合、外部キーとして設定されているカラムの値を自動的にセットしてくれるみたいです。HibernateのJIRAに書いてあったのはこのことだったのかな?・・・
実行クラスもほぼ同じなので省略。この実行結果は・・・
Hibernate: /* insert test.generate.entity.Test3 */ insert into Test3 (version, name, id) values (?, ?, null) Hibernate: call identity() Hibernate: /* insert test.generate.entity.Test4 */ insert into Test4 (version, name, id) values (?, ?, ?) Hibernate: /* load test.generate.entity.Test3 */ select test3x0_.id as id2_1_, test3x0_.version as version2_1_, test3x0_.name as name2_1_, test4x1_.id as id3_0_, test4x1_.version as version3_0_, test4x1_.name as name3_0_ from Test3 test3x0_ left outer join Test4 test4x1_ on test3x0_.id=test4x1_.id where test3x0_.id=? TEST3.ID:7 TEST3.NAME:TEST3 TEST3.VERSION:2006-01-31 08:22:25.734 TEST4.ID:7 TEST4.NAME:TEST4 TEST4.VERSION:2006-01-31 08:22:25.781 Hibernate: /* load test.generate.entity.Test4 */ select test4x0_.id as id3_0_, test4x0_.version as version3_0_, test4x0_.name as name3_0_ from Test4 test4x0_ where test4x0_.id=? TEST4.ID:7 TEST4.NAME:TEST4 TEST4.VERSION:2006-01-31 08:22:25.781 TEST3.ID:7 Hibernate: /* load test.generate.entity.Test3 */ select test3x0_.id as id2_1_, test3x0_.version as version2_1_, test3x0_.name as name2_1_, test4x1_.id as id3_0_, test4x1_.version as version3_0_, test4x1_.name as name3_0_ from Test3 test3x0_ left outer join Test4 test4x1_ on test3x0_.id=test4x1_.id where test3x0_.id=? TEST3.NAME:TEST3 TEST3.VERSION:2006-01-31 08:22:25.734 INFO 2006-01-31 08:22:25,921 [main] closing INFO 2006-01-31 08:22:25,921 [main] cleaning up connection pool: jdbc:hsqldb:hsql://localhost
最後にTest4クラスが呼び出される際に、SELECT文がもう一回呼び出されることが無くなった以外は、同じ動きでした。
そして最後に、複合主キーを使って結合した場合。
CREATE TABLE TEST5( ID INTEGER, NAME VARCHAR, VERSION TIMESTAMP, PRIMARY KEY(ID,NAME) ) CREATE TABLE TEST6( ID INTEGER, NAME VARCHAR, VERSION TIMESTAMP, PRIMARY KEY(ID,NAME), FOREIGN KEY(ID,NAME) REFERENCES TEST5(ID,NAME) )
クラスは・・・
public class Test5 implements Serializable { private static final long serialVersionUID = -4004185336469821692L; private Test5Id test5Id; private Timestamp version; private Test6 test6; (setter、getter略) } public class Test6 implements Serializable { private static final long serialVersionUID = 5904589021008814390L; private Test5Id test6Id; private Timestamp version; private Test5 test5; (setter、getter略) } public class Test5Id implements Serializable { private static final long serialVersionUID = 8864935166511950063L; private int id; private String name; // Constructors /** default constructor */ public Test5Id() { } /** full constructor */ public Test5Id(int id, String name) { this.id = id; this.name = name; } // Property accessors public int getId() { return this.id; } public void setId(int id) { this.id = id; } public String getName() { return this.name; } public void setName(String name) { this.name = name; } public boolean equals(Object other) { if ((this == other)) return true; if ((other == null)) return false; if (!(other instanceof Test5Id)) return false; Test5Id castOther = (Test5Id) other; return (this.getId() == castOther.getId()) && ((this.getName() == castOther.getName()) || (this.getName() != null && castOther.getName() != null && this.getName() .equals(castOther.getName()))); } public int hashCode() { int result = 17; result = 37 * result + this.getId(); result = 37 * result + (getName() == null ? 0 : this.getName().hashCode()); return result; } }
複合主キークラスはHibernate Tools beta4に自動作成させました。
マッピングファイルは
<hibernate-mapping package="test.generate.entity"> <class name="Test5"> <composite-id name="test5Id" class="test.generate.entity.Test5Id"> <key-property name="id"/> <key-property name="name"/> </composite-id> <timestamp name="version"/> <one-to-one name="test6" cascade="all" lazy="proxy"/> </class> </hibernate-mapping> <hibernate-mapping package="test.generate.entity"> <class name="Test6"> <composite-id name="test6Id" class="test.generate.entity.Test5Id"> <key-property name="id"/> <key-property name="name"/> </composite-id> <timestamp name="version"/> <one-to-one name="test5" constrained="true" lazy="proxy"/> </class> </hibernate-mapping>
実行クラスは省略。実行結果は・・・
Hibernate: /* insert test.generate.entity.Test5 */ insert into Test5 (version, id, name) values (?, ?, ?) Hibernate: /* insert test.generate.entity.Test6 */ insert into Test6 (version, id, name) values (?, ?, ?) Hibernate: /* load test.generate.entity.Test5 */ select test5x0_.id as id4_1_, test5x0_.name as name4_1_, test5x0_.version as version4_1_, test6x1_.id as id5_0_, test6x1_.name as name5_0_, test6x1_.version as version5_0_ from Test5 test5x0_ left outer join Test6 test6x1_ on test5x0_.id=test6x1_.id and test5x0_.name=test6x1_.name where test5x0_.id=? and test5x0_.name=? TEST5.ID:4 TEST5.NAME:TEST5 TEST5.VERSION:2006-01-31 08:31:22.734 TEST6.ID:4 TEST6.NAME:TEST5 TEST6.VERSION:2006-01-31 08:31:22.75 Hibernate: /* load test.generate.entity.Test6 */ select test6x0_.id as id5_0_, test6x0_.name as name5_0_, test6x0_.version as version5_0_ from Test6 test6x0_ where test6x0_.id=? and test6x0_.name=? TEST6.ID:4 TEST6.NAME:TEST5 TEST6.VERSION:2006-01-31 08:31:22.75 TEST5.ID:4 TEST5.NAME:TEST5 Hibernate: /* load test.generate.entity.Test5 */ select test5x0_.id as id4_1_, test5x0_.name as name4_1_, test5x0_.version as version4_1_, test6x1_.id as id5_0_, test6x1_.name as name5_0_, test6x1_.version as version5_0_ from Test5 test5x0_ left outer join Test6 test6x1_ on test5x0_.id=test6x1_.id and test5x0_.name=test6x1_.name where test5x0_.id=? and test5x0_.name=? TEST5.VERSION:2006-01-31 08:31:22.734 INFO 2006-01-31 08:31:22,968 [main] closing INFO 2006-01-31 08:31:22,968 [main] cleaning up connection pool: jdbc:hsqldb:hsql://localhost
複合主キー結合の場合も同じ結果でした。EntityManagerを使ったときに単一方向でもLAZYロードが効かなかったのとは、動きが異なりますね。EntityManagerで試したときの設定に、何か間違いがあるのだろうか?・・・
ちなみに、3回とも、proxy、no_proxyの設定では同じ結果に、lazy=falseでは遅延ロードは効きませんでした。
今回のHibernateの設定が間違ってなければ、双方向の1対1で、外部キー制約を持たない方の関連定義はLAZYロードが効かないというのは、もしかしたら仕様なのかもしれません。ただ、自分の設定が間違ってる可能性もあるので、もうちょっと調べる必要がありそうです。
複合主キーで設定した場合、Hibernateの場合は単一主キーのときと同じ動きをしてました。この件は、自分がEntityManagerを使ってOneToOneの定義をしたときに、何か間違ってる部分があるのかもしれません。
うーん・・・双方向の1対1でLAZYロードが効かないとなると、なかなか厳しいものがあります。でも、複合主キー結合の場合も単一方向であれば大丈夫ということになれば、まだ色々考えられる気もするし、もう少し調べてみようと思います。ひがさん、アドバイスありがとうございました。