JPA OneToOneのLAZYロード確認
先日JavaEE勉強会に初参加してきました。その中で「Hibernate EntityManagerでOneToOneのLAZYロードが有効にならない」という話をお聞きしました。この件は、自分が以前この日記の中で散々詰まっていた内容ですが、あの頃は最終的に「LAZYロードしたい関連オブジェクトが必ず存在するという制限をつけられるのであれば、LAZYロードは可能」という結論になっていた筈でした。でもあの当時のHibernate EntityManagerはまだベータ版でしたし、もしかしたら最終版とは事情が違ってきているのかもしれません。また、TopLinkとの動きの違いも再確認したいという気持ちもありましたので、ここでもう一度HibernateのOneToOneのLAZYロードの動きを再確認してみたいと思います。
環境は、Seasar2.4.4とSVNから落としたS2Hibernate-JPA・S2TopLink-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ロードを行っているからです。このことによって、TopLinkはHibernateのようなN:1のLAZYロードの問題は発生しません。更に、Hibernateの場合、ProxyクラスからLAZYロード後にクラスの型が変わることはありませんが、TopLinkの場合は、LAZYロードの結果によってフィールドにセットされるオブジェクトの型が決まります。JPAの継承戦略の結果も正しくLAZYロードに反映されます。
・・・まぁその代わり、javaagentという面倒な手続きが必要になるわけですが・・・
以上、手抜きなまとめ方なので分かりにくくてすみません。JPAのOneToOneLAZYロード問題に関して少しでもお役に立てれば幸いです。
(追記)Hibernateの場合、「相手側が必ず存在する」という前提をつける時点で、主キー同士の結合でしか使わない気がしてきた・・・自分自身、主キー同士で結合してる組み合わせの場面で利用していますし。この例だと、一応ユニーク制約ついているとはいえ、主キーではなく外部キーを使って検索しているのはなんか気持ち悪いですね・・・
(さらに追記)すみません、寝ぼけてました(汗)・・・「相手側が必ず存在する」という制限と、外部キーで検索するというのは無関係でした。