JPA OneToOneのLAZYロード確認

先日JavaEE勉強会に初参加してきました。その中で「Hibernate EntityManagerでOneToOneのLAZYロードが有効にならない」という話をお聞きしました。この件は、自分が以前この日記の中で散々詰まっていた内容ですが、あの頃は最終的に「LAZYロードしたい関連オブジェクトが必ず存在するという制限をつけられるのであれば、LAZYロードは可能」という結論になっていた筈でした。でもあの当時のHibernate EntityManagerはまだベータ版でしたし、もしかしたら最終版とは事情が違ってきているのかもしれません。また、TopLinkとの動きの違いも再確認したいという気持ちもありましたので、ここでもう一度HibernateのOneToOneのLAZYロードの動きを再確認してみたいと思います。
環境は、Seasar2.4.4とSVNから落としたS2Hibernate-JPAS2TopLink-JPAを使います。DBは、個人的な都合でOracleを使いました。
まずはテーブル構成。MakerとMachineという2つのテーブルを作成しました。

CREATE TABLE MAKER (
	ID NUMBER(9, 0) PRIMARY KEY
	,NAME VARCHAR2(255)
	,VERSION NUMBER(9, 0) NOT NULL
)
/
CREATE TABLE MACHINE (
	ID NUMBER(9, 0) PRIMARY KEY
	,NAME VARCHAR2(255)
	,MAKER_ID NUMBER(9, 0)
	,VERSION NUMBER(9, 0) NOT NULL
	,CONSTRAINT FK_MAKER_MACHINE FOREIGN KEY(MAKER_ID) REFERENCES MAKER(ID) ON DELETE SET NULL
	,CONSTRAINT UK_MAKER_ID UNIQUE(MAKER_ID)
)
/
CREATE SEQUENCE SQ_MAKER
/
CREATE SEQUENCE SQ_MACHINE
/

MACHINEテーブルのMAKER_IDカラムが、MAKERテーブルの主キーとの外部キー制約を持ちます。つまり、外部キーを持つ側がMACHINEテーブルになります。この外部キーにはUNIQUE制約もついているので、関連は1対1となります。

MAKER

ID NAME VERSION
1 SCE 0
2 NINTENDO 0

MACHINE

ID NAME MAKER_ID VERSION
1 PS3 1 0
2 Wii 2 0

・・・1対1の例としてどうなのよ?・・・というのは置いといて(汗)・・・
このテーブルのEntityクラスを作成します。

@Entity
@SequenceGenerator(name = "SQ_MACHINE", sequenceName = "SQ_MACHINE", allocationSize = 1)
public class Machine {

	@Id
	@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SQ_MACHINE")
	private Integer id;
	
	private String name;
	
	@Version
	private Integer version;
	
	@OneToOne(fetch = FetchType.LAZY)
	private Maker maker;

(setter・getter略)
}
@Entity
@SequenceGenerator(name = "SQ_MAKER", sequenceName = "SQ_MAKER", allocationSize = 1)
public class Maker {

	@Id
	@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "SQ_MAKER")
	private Integer id;
	
	private String name;
	
	@Version
	private Integer version;

	@OneToOne(cascade = CascadeType.ALL, mappedBy = "maker", fetch = FetchType.LAZY)
	private Machine machine;

(setter・getter略)
}

IDはSEQUENCEで自動作成します。MachineクラスのmakerフィールドをOneToOneで設定することによって、外部キーが自動的に「MAKER_ID」と定義されます。これはJPAの規約ですが、知ってると非常に便利です。
そして、EntityにアクセスするDaoクラス。

public class MakerDaoImpl implements MakerDao {
	
	private EntityManager em;

	public void setEm(EntityManager em) {
		this.em = em;
	}

	public Maker getMaker(Integer id) {
		
		return em.find(Maker.class, id);
	}

	@SuppressWarnings("unchecked")
	public List<Maker> getMakerList() {
		return (List<Maker>) em.createQuery("SELECT m FROM Maker m").getResultList();
	}

	public Maker insertMaker(Maker maker) {
		em.persist(maker);
		return maker;
	}

}

そしてロジッククラス

public class MakerLogicImpl implements MakerLogic {
	
	private MakerDao makerDao;

	public void setMakerDao(MakerDao makerDao) {
		this.makerDao = makerDao;
	}

	public Maker getMaker(Integer id) {
		Maker maker = makerDao.getMaker(id);
		maker.getMachine().getName();
		return maker;
	}

	public List<Maker> getMakerList() {
		return makerDao.getMakerList();
	}

	public Maker insertMaker(Maker maker) {
		return makerDao.insertMaker(maker);
	}

}

これらを設定するdiconファイル

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN" 
	"http://www.seasar.org/dtd/components24.dtd">
<components>
	<include path="jpa.dicon"/>
	<include path="aop.dicon"/>
	
	<component class="example.dao.impl.MakerDaoImpl">
		<aspect>aop.traceInterceptor</aspect>
	</component>
	
	<component class="example.logic.impl.MakerLogicImpl">
		<aspect>aop.traceInterceptor</aspect>
		<aspect>j2ee.requiredTx</aspect>
	</component>
</components>

・・・そしてpersistence.xml

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://java.sun.com/xml/ns/persistence"
	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://java.sun.com/xml/ns/persistence
	http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd"
	version="1.0">
	<persistence-unit name="hibernate" transaction-type="JTA">
		<provider>org.hibernate.ejb.HibernatePersistence</provider>
		<jta-data-source>jdbc/dataSource</jta-data-source>
		<properties>
			<property name="hibernate.dialect" value="org.hibernate.dialect.Oracle9Dialect"/>
			<property name="hibernate.jndi.class" value="org.seasar.extension.j2ee.JndiContextFactory"/>
			<property name="hibernate.transaction.manager_lookup_class" value="org.seasar.hibernate.jpa.transaction.S2TransactionManagerLookup"/>
			<property name="hibernate.show_sql" value="true"/>
			<property name="hibernate.format_sql" value="true"/>
			<property name="hibernate.use_sql_comments" value="false"/>
			<!-- 
			<property name="hibernate.archive.autodetection" value=""/>
			<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
			 -->
		</properties>
	</persistence-unit>
	<persistence-unit name="toplink" transaction-type="JTA">
		<provider>oracle.toplink.essentials.ejb.cmp3.EntityManagerFactoryProvider</provider>
		<jta-data-source>java:comp/env/jdbc/dataSource</jta-data-source>
		<exclude-unlisted-classes>false</exclude-unlisted-classes>
		<properties>
        	<property name="toplink.logging.level" value="FINE"/>
			<property name="toplink.target-server" value="org.seasar.toplink.jpa.platform.server.S2ServerPlatform"/>
			<!--
			<property name="toplink.ddl-generation" value="create-tables"/>
			<property name="toplink.ddl-generation.output-mode" value="both"/>
			 -->
			<property name="toplink.weaving" value="true"/>
 		</properties>
	</persistence-unit>
</persistence>

S2.4の自動登録機能は使ってません(まだ自分がよくわかってないので・・・)。ただし、convention.diconがないとエラーになるみたいなので、これは追加しました。後、TopLink用にjndi.propertiesも追加しています。
最後にMainメソッドを実行するクラス

	public static void main(String[] args) {

		SingletonS2ContainerFactory.init();
		S2Container container = SingletonS2ContainerFactory.getContainer();
		
		try {
			MakerLogic makerLogic = MakerLogic.class.cast(container.getComponent(MakerLogic.class));
			
			Maker maker = makerLogic.getMaker(1);
			System.out.println(maker.getId());
			System.out.println(maker.getName());
			System.out.println(maker.getMachine().getId());
			System.out.println(maker.getMachine().getName());
		} finally {
			SingletonS2ContainerFactory.destroy();
		}

それでは、まずはHibernateから。S2Hibernate-JPAのプロジェクトにあるjpa.diconを使って動かしてみます。

2006-11-20 03:22:22.703 [WARN] main org.seasar.framework.container.assembler.BindingTypeShouldDef
    org.seasar.hibernate.jpa.impl.S2HibernatePersistenceUnitProviderのプロパティ(s2HibernateConfiguration)が見つからないので設定をスキップします
2006-11-20 03:22:23.968 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionPoolImpl
    物理的なコネクションを取得しました
2006-11-20 03:22:23.968 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
    論理的なコネクションを取得しました
2006-11-20 03:22:24.015 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
    論理的なコネクションを閉じました
2006-11-20 03:22:24.906 [DEBUG] main org.seasar.framework.aop.interceptors.TraceInterceptor
    BEGIN example.logic.impl.MakerLogicImpl#getMaker(1)
2006-11-20 03:22:24.906 [DEBUG] main org.seasar.extension.jta.TransactionImpl
    トランザクションを開始しました
2006-11-20 03:22:24.906 [DEBUG] main org.seasar.framework.aop.interceptors.TraceInterceptor
    BEGIN example.dao.impl.MakerDaoImpl#getMaker(1)
2006-11-20 03:22:24.921 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
    論理的なコネクションを取得しました
Hibernate: 
    select
        maker0_.id as id1_0_,
        maker0_.name as name1_0_,
        maker0_.version as version1_0_ 
    from
        Maker maker0_ 
    where
        maker0_.id=?
2006-11-20 03:22:25.125 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
    論理的なコネクションを閉じました
2006-11-20 03:22:25.156 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
    論理的なコネクションを取得しました
Hibernate: 
    select
        machine0_.id as id0_0_,
        machine0_.name as name0_0_,
        machine0_.version as version0_0_,
        machine0_.maker_id as maker4_0_0_ 
    from
        Machine machine0_ 
    where
        machine0_.maker_id=?
2006-11-20 03:22:25.156 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
    論理的なコネクションを閉じました
2006-11-20 03:22:25.156 [DEBUG] main org.seasar.framework.aop.interceptors.TraceInterceptor
    END example.dao.impl.MakerDaoImpl#getMaker(1) : example.entity.Maker@14e0e90
2006-11-20 03:22:25.156 [DEBUG] main org.seasar.extension.jta.TransactionImpl
    トランザクションをコミットしました
2006-11-20 03:22:25.156 [DEBUG] main org.seasar.framework.aop.interceptors.TraceInterceptor
    END example.logic.impl.MakerLogicImpl#getMaker(1) : example.entity.Maker@14e0e90
1
SCE
1
PS3
2006-11-20 03:22:25.156 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
    物理的なコネクションを閉じました

ログをそのままコピペしたので見難いと思いますが・・・
ポイントはDaoのメソッド終了のログが、Machineオブジェクトのデータを取得するSELECT文の後に発行されているということです。つまり・・・DaoでSELECT文を発行したときに、同時に発行されているということになり・・・LAZYロードは無視されてしまっています。
これがHibernateのやっかいな制限・・・「OneToOneの関連で、外部キーを持たない側のオブジェクトからは関連オブジェクトをLAZYロードできない」という症状です。外部キーではなくて主キー同士で関連を定義した場合も、同じ症状が発生します。その理由は、HibernateがN:1のLazyロードにProxyクラスを利用しているからです。LAZYロードがオブジェクトに設定されている場合、Hibernateは外部キーを主キーに設定したProxyクラスをLAZY設定したフィールドにセットしておきます。プログラム側が主キー以外の情報にアクセスしようとした場合にLAZYロードが行われ、Proxyクラスにデータがセットされます。・・・つまり、関連元がLAZYロード対象データの主キー情報を特定できないと、この仕組みは利用不可能ということになります。なので、外部キーを持たないオブジェクトからのLAZYロードが無効になってしまうのです。これは結構やっかいで、自分もかなりハマりました・・・
しかし、制限つきですが、この現象に対する解決方法があります。主キーを外部キーとして設定し、相手側が必ず存在するという「optional = false」オプションをつけてやれば、LAZYロードは有効となります。具体的には、以下の内容でOneToOneの設定を書き換えます。

	@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
	@PrimaryKeyJoinColumn(referencedColumnName = "MAKER_ID")
	private Machine machine;

mappedByを削除してしまうので、同じ意味の関連定義を両方に書いてしまうことになります。更に、optional = falseをつけるので、nullになる可能性がある関連には使えません。そういう前提でもよければ、上記の設定で再実行してみると・・・

2006-11-20 03:37:02.468 [WARN] main org.seasar.framework.container.assembler.BindingTypeShouldDef
    org.seasar.hibernate.jpa.impl.S2HibernatePersistenceUnitProviderのプロパティ(s2HibernateConfiguration)が見つからないので設定をスキップします
2006-11-20 03:37:03.687 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionPoolImpl
    物理的なコネクションを取得しました
2006-11-20 03:37:03.687 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
    論理的なコネクションを取得しました
2006-11-20 03:37:03.687 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
    論理的なコネクションを閉じました
2006-11-20 03:37:04.390 [DEBUG] main org.seasar.framework.aop.interceptors.TraceInterceptor
    BEGIN example.logic.impl.MakerLogicImpl#getMaker(1)
2006-11-20 03:37:04.390 [DEBUG] main org.seasar.extension.jta.TransactionImpl
    トランザクションを開始しました
2006-11-20 03:37:04.390 [DEBUG] main org.seasar.framework.aop.interceptors.TraceInterceptor
    BEGIN example.dao.impl.MakerDaoImpl#getMaker(1)
2006-11-20 03:37:04.406 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
    論理的なコネクションを取得しました
Hibernate: 
    select
        maker0_.id as id1_0_,
        maker0_.name as name1_0_,
        maker0_.version as version1_0_ 
    from
        Maker maker0_ 
    where
        maker0_.id=?
2006-11-20 03:37:04.609 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
    論理的なコネクションを閉じました
2006-11-20 03:37:04.625 [DEBUG] main org.seasar.framework.aop.interceptors.TraceInterceptor
    END example.dao.impl.MakerDaoImpl#getMaker(1) : example.entity.Maker@979f67
2006-11-20 03:37:04.625 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
    論理的なコネクションを取得しました
Hibernate: 
    select
        machine0_.id as id0_0_,
        machine0_.name as name0_0_,
        machine0_.version as version0_0_,
        machine0_.maker_id as maker4_0_0_ 
    from
        Machine machine0_ 
    where
        machine0_.id=?
2006-11-20 03:37:04.625 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
    論理的なコネクションを閉じました
2006-11-20 03:37:04.640 [DEBUG] main org.seasar.extension.jta.TransactionImpl
    トランザクションをコミットしました
2006-11-20 03:37:04.640 [DEBUG] main org.seasar.framework.aop.interceptors.TraceInterceptor
    END example.logic.impl.MakerLogicImpl#getMaker(1) : example.entity.Maker@979f67
1
SCE
1
PS3
2006-11-20 03:37:04.640 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
    物理的なコネクションを閉じました

Daoのメソッドが終了した後に、Machineデータを取得するSELECT文が発行されていることがわかります。LAZYロードが成功しています。
ちなみに、TopLinkではどうなるかというと・・・
まずはEntityを元の形に戻します。そして、jpa.diconをS2TopLinkのものに置き換えます。toplink.weavingを有効にし、javaagentをvmのオプションに定義します。
以上の内容で実行すると・・・

[TopLink Config]: 2006.11.20 03:42:35.250--ServerSession(16842840)--Thread(Thread[main,5,main])--The alias name for the entity class [class example.entity.Machine] is being defaulted to: Machine.
[TopLink Config]: 2006.11.20 03:42:35.296--ServerSession(16842840)--Thread(Thread[main,5,main])--The table name for entity [class example.entity.Machine] is being defaulted to: MACHINE.
[TopLink Config]: 2006.11.20 03:42:35.312--ServerSession(16842840)--Thread(Thread[main,5,main])--The column name for element [private java.lang.Integer example.entity.Machine.id] is being defaulted to: ID.
[TopLink Config]: 2006.11.20 03:42:35.328--ServerSession(16842840)--Thread(Thread[main,5,main])--The column name for element [private java.lang.String example.entity.Machine.name] is being defaulted to: NAME.
[TopLink Config]: 2006.11.20 03:42:35.343--ServerSession(16842840)--Thread(Thread[main,5,main])--The column name for element [private java.lang.Integer example.entity.Machine.version] is being defaulted to: VERSION.
[TopLink Config]: 2006.11.20 03:42:35.375--ServerSession(16842840)--Thread(Thread[main,5,main])--The alias name for the entity class [class example.entity.Maker] is being defaulted to: Maker.
[TopLink Config]: 2006.11.20 03:42:35.375--ServerSession(16842840)--Thread(Thread[main,5,main])--The table name for entity [class example.entity.Maker] is being defaulted to: MAKER.
[TopLink Config]: 2006.11.20 03:42:35.375--ServerSession(16842840)--Thread(Thread[main,5,main])--The column name for element [private java.lang.Integer example.entity.Maker.id] is being defaulted to: ID.
[TopLink Config]: 2006.11.20 03:42:35.375--ServerSession(16842840)--Thread(Thread[main,5,main])--The column name for element [private java.lang.String example.entity.Maker.name] is being defaulted to: NAME.
[TopLink Config]: 2006.11.20 03:42:35.375--ServerSession(16842840)--Thread(Thread[main,5,main])--The column name for element [private java.lang.Integer example.entity.Maker.version] is being defaulted to: VERSION.
[TopLink Config]: 2006.11.20 03:42:35.375--ServerSession(16842840)--Thread(Thread[main,5,main])--The target entity (reference) class for the one to one mapping element [private example.entity.Maker example.entity.Machine.maker] is being defaulted to: class example.entity.Maker.
[TopLink Config]: 2006.11.20 03:42:35.484--ServerSession(16842840)--Thread(Thread[main,5,main])--The primary key column name for the mapping element [private example.entity.Maker example.entity.Machine.maker] is being defaulted to: ID.
[TopLink Config]: 2006.11.20 03:42:35.484--ServerSession(16842840)--Thread(Thread[main,5,main])--The foreign key column name for the mapping element [private example.entity.Maker example.entity.Machine.maker] is being defaulted to: MAKER_ID.
[TopLink Config]: 2006.11.20 03:42:35.484--ServerSession(16842840)--Thread(Thread[main,5,main])--The target entity (reference) class for the one to one mapping element [private example.entity.Machine example.entity.Maker.machine] is being defaulted to: class example.entity.Machine.
2006-11-20 03:42:36.453 [DEBUG] main org.seasar.framework.aop.interceptors.TraceInterceptor
    BEGIN example.logic.impl.MakerLogicImpl#getMaker(1)
2006-11-20 03:42:36.484 [DEBUG] main org.seasar.extension.jta.TransactionImpl
    トランザクションを開始しました
2006-11-20 03:42:36.484 [DEBUG] main org.seasar.framework.aop.interceptors.TraceInterceptor
    BEGIN example.dao.impl.MakerDaoImpl#getMaker(1)
[TopLink Info]: 2006.11.20 03:42:36.515--ServerSession(16842840)--Thread(Thread[main,5,main])--TopLink, version: Oracle TopLink Essentials - 9.1 (Build b22)
[TopLink Info]: 2006.11.20 03:42:36.531--ServerSession(16842840)--Thread(Thread[main,5,main])--Server: unknown
2006-11-20 03:42:37.078 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionPoolImpl
    物理的なコネクションを取得しました
2006-11-20 03:42:37.078 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
    論理的なコネクションを取得しました
[TopLink Fine]: 2006.11.20 03:42:37.093--Thread(Thread[main,5,main])--Detected Vendor platform: oracle.toplink.essentials.platform.database.oracle.OraclePlatform
2006-11-20 03:42:37.109 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
    論理的なコネクションを閉じました
[TopLink Config]: 2006.11.20 03:42:37.109--ServerSession(16842840)--Connection(25252664)--Thread(Thread[main,5,main])--connecting(DatabaseLogin(
	platform=>OraclePlatform
	user name=> ""
	connector=>JNDIConnector datasource name=>java:comp/env/jdbc/dataSource
))
2006-11-20 03:42:37.125 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
    論理的なコネクションを取得しました
[TopLink Config]: 2006.11.20 03:42:37.125--ServerSession(16842840)--Connection(25068634)--Thread(Thread[main,5,main])--Connected: jdbc:oracle:thin:@fmvlt90d:1521:orcl
	User: YOSHIDA
	Database: Oracle  Version: Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Production
With the Partitioning, OLAP and Data Mining options
	Driver: Oracle JDBC driver  Version: 10.2.0.1.0
2006-11-20 03:42:37.125 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
    論理的なコネクションを閉じました
[TopLink Config]: 2006.11.20 03:42:37.125--ServerSession(16842840)--Connection(19097823)--Thread(Thread[main,5,main])--connecting(DatabaseLogin(
	platform=>OraclePlatform
	user name=> ""
	connector=>JNDIConnector datasource name=>java:comp/env/jdbc/dataSource
))
2006-11-20 03:42:37.125 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
    論理的なコネクションを取得しました
[TopLink Config]: 2006.11.20 03:42:37.125--ServerSession(16842840)--Connection(25068634)--Thread(Thread[main,5,main])--Connected: jdbc:oracle:thin:@fmvlt90d:1521:orcl
	User: YOSHIDA
	Database: Oracle  Version: Oracle Database 10g Enterprise Edition Release 10.2.0.1.0 - Production
With the Partitioning, OLAP and Data Mining options
	Driver: Oracle JDBC driver  Version: 10.2.0.1.0
2006-11-20 03:42:37.125 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
    論理的なコネクションを閉じました
[TopLink Info]: 2006.11.20 03:42:37.265--ServerSession(16842840)--Thread(Thread[main,5,main])--file:/C:/workspace/test/bin/-toplink login successful
2006-11-20 03:42:37.328 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
    論理的なコネクションを取得しました
[TopLink Fine]: 2006.11.20 03:42:37.328--ServerSession(16842840)--Connection(25068634)--Thread(Thread[main,5,main])--SELECT ID, NAME, VERSION FROM MAKER WHERE (ID = ?)
	bind => [1]
2006-11-20 03:42:37.656 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
    論理的なコネクションを閉じました
2006-11-20 03:42:37.656 [DEBUG] main org.seasar.framework.aop.interceptors.TraceInterceptor
    END example.dao.impl.MakerDaoImpl#getMaker(1) : example.entity.Maker@35bb0f
2006-11-20 03:42:37.687 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
    論理的なコネクションを取得しました
[TopLink Fine]: 2006.11.20 03:42:37.687--ServerSession(16842840)--Connection(25068634)--Thread(Thread[main,5,main])--SELECT ID, NAME, VERSION, MAKER_ID FROM MACHINE WHERE (MAKER_ID = ?)
	bind => [1]
2006-11-20 03:42:37.718 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
    論理的なコネクションを閉じました
2006-11-20 03:42:37.718 [DEBUG] main org.seasar.extension.jta.TransactionImpl
    トランザクションをコミットしました
2006-11-20 03:42:37.734 [DEBUG] main org.seasar.framework.aop.interceptors.TraceInterceptor
    END example.logic.impl.MakerLogicImpl#getMaker(1) : example.entity.Maker@35bb0f
1
SCE
1
PS3
2006-11-20 03:42:37.734 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
    物理的なコネクションを閉じました
[TopLink Config]: 2006.11.20 03:42:37.734--ServerSession(16842840)--Connection(5569009)--Thread(Thread[main,5,main])--disconnect
[TopLink Info]: 2006.11.20 03:42:37.734--ServerSession(16842840)--Thread(Thread[main,5,main])--file:/C:/workspace/test/bin/-toplink logout successful

TopLinkの場合、通常通りのマッピングでもOneToOneのLAZYロードは有効となります。これは、TopLinkはLAZYロード対象フィールドをProxyクラスにするのではなく、LAZYロードで呼び出す側のクラスをエンハンスして、プログラムがgetterメソッドを呼んだときにLAZYロードを行っているからです。このことによって、TopLinkHibernateのようなN:1のLAZYロードの問題は発生しません。更に、Hibernateの場合、ProxyクラスからLAZYロード後にクラスの型が変わることはありませんが、TopLinkの場合は、LAZYロードの結果によってフィールドにセットされるオブジェクトの型が決まります。JPAの継承戦略の結果も正しくLAZYロードに反映されます。
・・・まぁその代わり、javaagentという面倒な手続きが必要になるわけですが・・・
以上、手抜きなまとめ方なので分かりにくくてすみません。JPAのOneToOneLAZYロード問題に関して少しでもお役に立てれば幸いです。
(追記)Hibernateの場合、「相手側が必ず存在する」という前提をつける時点で、主キー同士の結合でしか使わない気がしてきた・・・自分自身、主キー同士で結合してる組み合わせの場面で利用していますし。この例だと、一応ユニーク制約ついているとはいえ、主キーではなく外部キーを使って検索しているのはなんか気持ち悪いですね・・・
(さらに追記)すみません、寝ぼけてました(汗)・・・「相手側が必ず存在する」という制限と、外部キーで検索するというのは無関係でした。