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ロードが効かないとなると、なかなか厳しいものがあります。でも、複合主キー結合の場合も単一方向であれば大丈夫ということになれば、まだ色々考えられる気もするし、もう少し調べてみようと思います。ひがさん、アドバイスありがとうございました。