TopLink JPAでN対1マッピングをLAZYロードで定義する

http://www.oracle.com/technology/products/ias/toplink/JPA/index.html
上記サイトでTopLink JPATopLink Essentials・・・どっちが正式名称?)をダウンロードして色々触っていたのですが、Java SE環境だとJTAはおろかDataSourceすら使えませんでした。どうやら、GlassFish上で動くことを最優先で製造していた感がアリアリで、Java SE対応は後回しになっていたっぽい。うーん、リファレンスインプリメンテーションなのに、単体製品としての完成度はHibernateに負けてる気が・・・
がしかし、GlassFishCVSをチェックしてみると、最新のソースではJava SE環境でもDataSourceが使えるように修正されていることを発見。単体でのビルド方法がよくわからなかったので、GlassFishの最新Nightly binary buildをダウンロードして、中に入っていたTopLinkのJarを利用することにしました。
最早JTA無しの環境で動かす気にはなれないので、Seasar2を使って動作を試してみることにしました。S2TigerのSVNには、JPA対応のモジュールがアップされてありますので、早速利用してみます。Seasar2とS2Tigerのプロジェクトをチェックアウトして、Maven2を使ってビルド(やり方がよくわからなかったので、とにかくmvn:installで済ませました)。
色々触ってみたのですが、どうやらTopLinkに対してTransactionManagerを認識させる必要があるっぽい。Hibernateと同じですね。TransactionManagerをキーに色々クラスを検索していたところ、どうやら
oracle.toplink.essentials.platform.server.ServerPlatform
インターフェイスを実装したクラスが必要らしい。このインターフェイスを実装したクラスの中に、ServerPlatformBaseクラスというものがありました。これがどうやら、個別のサーバ環境用のServerPlatformクラスの基底クラスとなるものらしい。Oc4jPlatform(Oracleサーバ用)やSunAS9ServerPlatform(GlassFish用)などが子クラスとして存在していました。このクラスを調べてみると・・・getExternalTransactionControllerClassメソッドで何やらClassオブジェクトを返してます。これを調べると・・・JTATransactionControllerクラスの子クラスを返してました。このクラスがどうやらTransactionManagerを返すみたいですね。
ここまできたら、とりあえず適当にクラスを作れそうです。まずはJTATransactionControllerクラスの子クラス。

package test.ejb3;

import javax.transaction.TransactionManager;

import org.seasar.framework.container.S2Container;
import org.seasar.framework.container.factory.SingletonS2ContainerFactory;

import oracle.toplink.essentials.transaction.JTATransactionController;

public class S2TransactionController extends JTATransactionController {

	protected TransactionManager acquireTransactionManager() throws Exception {
		S2Container container = SingletonS2ContainerFactory.getContainer();
		return (TransactionManager) container.getComponent(TransactionManager.class);
    }
}

TransactionManagerを扱うツールはどれもこういうことやることになるのか・・・
さて、このクラスオブジェクトを返すServerPlatformクラスは以下の通り

package test.ejb3;

import oracle.toplink.essentials.internal.sessions.DatabaseSessionImpl;
import oracle.toplink.essentials.platform.server.ServerPlatformBase;

public class S2ServerPlatform extends ServerPlatformBase {

	public S2ServerPlatform(DatabaseSessionImpl newDatabaseSession) {
		super(newDatabaseSession);
	}

	@Override
	public Class getExternalTransactionControllerClass() {
		return S2TransactionController.class;
	}

}

そして、このクラスを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="em" transaction-type="JTA">
		<jta-data-source>jdbc.DataSource</jta-data-source>
		<exclude-unlisted-classes>false</exclude-unlisted-classes>
		<properties>
			<property name="toplink.target-server" value="test.ejb3.S2ServerPlatform"/>
			<property name="toplink.logging.level" value="FINE"/>
		</properties>
	</persistence-unit>
</persistence>

あと、DataSourceを取得する際の、JNDIの設定(jndi.properties)も必要です。

java.naming.factory.initial=org.seasar.extension.j2ee.JndiContextFactory

これで下準備は出来ました。早速テストしてみようと思うのですが・・・以前、id:da-yoshi:20060506:1146912430で書いたように(id:taedium:20060503#p1さんの記事が元ネタ)、TopLinkはどうやらHibernateとはN対1のLAZYロードの振る舞いが異なるらしいのです。N対1のLAZYロードといえば、Hibernateで最も自分が苦労させられた部分です。是非この部分を試してみたいと思います。
記事の目的が異なるのですが、id:taedium:20060522#p1さんの記事のサンプルを使って色々調べていた関係で、このサンプルを少しいじってテストを行ってみようと思います。
まずは、EMPLOYEEテーブルを少しいじった、EMPLOYEE2テーブル。DEPARTMENTテーブルと主キー同士で結合します。

CREATE TABLE EMPLOYEE2 (
	ID NUMBER(15,0) PRIMARY KEY
	,EMPLOYEENAME VARCHAR2(255) NOT NULL
	,SALARY NUMBER(20,0)
	,ADDRESS_ID NUMBER(15,0)
	,VERSION NUMBER(15,0) NOT NULL
	,CONSTRAINT FK2_ADDRESS_ID FOREIGN KEY(ADDRESS_ID) REFERENCES ADDRESS(ID) ON DELETE SET NULL
)

HSQLDBに対応するTopLink内のモジュールが古すぎてちょっと使い辛かったので、今回はOracleで試しています。DEPARTMENTとは主キーで結合するので外部キーはありません。対応するDEPARTMENTがいない状態も試したかったので、IDに外部キー制約はつけてません。
準備値として入れるデータは・・・

ADDRESS

ID CITY
1 東京
2 大阪
3 京都

DEPARTMENT

ID DEPARTMENTNAME DTYPE
2 経理 A
1 営業 B
4 製造 B

SINGLE_TABLEの継承戦略を使いたかったので、DTYPEカラムを追加しています。

EMPLOYEE2

ID EMPLOYEENAME SALARY ADDRESS_ID VERSION
1 A 1000 1 1
2 B 2000 2 1
3 C 3000 3 1
4 D 4000 2 1

これを入れるクラスは以下の通り。Addressには変更なし。DEPARTMENTは・・・

package test.ejb3;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Inheritance;

@Entity
@Inheritance
public abstract class Department {
	
	@Id
	private Long id;
	
	private String departmentName;

	public String getDepartmentName() {
		return departmentName;
	}

	public void setDepartmentName(String departmentName) {
		this.departmentName = departmentName;
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

}

package test.ejb3;

import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;

@Entity
@DiscriminatorValue("A")
public class DepartmentA extends Department {

}

package test.ejb3;

import javax.persistence.DiscriminatorValue;
import javax.persistence.Entity;

@Entity
@DiscriminatorValue("B")
public class DepartmentB extends Department {


}

SINGLE_TABLEの継承戦略を使ってます。
そしてEMPLOYEE2用のEntityクラス。

package test.ejb3;

import java.math.BigDecimal;

import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Version;

@Entity
public class Employee2 {

	@Id
	private Long id;
	
	private String employeeName;
	
	private BigDecimal salary;
	
	@OneToOne(fetch = FetchType.LAZY)
	@PrimaryKeyJoinColumn
	private Department department;
	
	@ManyToOne(fetch = FetchType.LAZY)
	private Address address;
	
	@Version
	private Long version;

	public Employee2() {
	}

	public Employee2(String employeeName, BigDecimal salary, Address address, Department department) {
		this.id = department.getId();
		this.employeeName = employeeName;
		this.salary = salary;
		this.department = department;
		this.address = address;
	}

	public Address getAddress() {
		return address;
	}

	public void setAddress(Address address) {
		this.address = address;
	}

	public Department getDepartment() {
		return department;
	}

	public void setDepartment(Department department) {
		this.department = department;
	}

	public String getEmployeeName() {
		return employeeName;
	}

	public void setEmployeeName(String employeeName) {
		this.employeeName = employeeName;
	}

	public Long getId() {
		return id;
	}

	public void setId(Long id) {
		this.id = id;
	}

	public BigDecimal getSalary() {
		return salary;
	}

	public void setSalary(BigDecimal salary) {
		this.salary = salary;
	}

	public Long getVersion() {
		return version;
	}

	public void setVersion(Long version) {
		this.version = version;
	}


}

DEPARTMENTとの関係が、主キー同士で結合する1対1の関係となっています。LAZYロードを定義しています。Hibernateの場合は、optional = false の定義を追加しないと、この場合LAZYロードは有効になりませんでした。今回のテストデータは、わざとEMPLOYEE2がDEPARTMENTよりも多くなるように登録しているので、この場合Hibernateは optional = false が定義できない即ちLAZYロードできないことになります。
さて、TopLinkの場合はどうなるでしょうか?
実行用クラスをS2EJB3TestCaseで書いてみました。

	@Rollback
	public void testSelectEmployee() {

		readXlsAllReplaceDb("selectEmployeePrepare.xls");

		List<Employee2> list = (List<Employee2>) getEntityManager().createQuery(
				"SELECT e FROM Employee2 e ORDER BY e.id").getResultList();

		for (Employee2 e : list) {
			System.out.println(e.getEmployeeName());
			Department d = e.getDepartment();
			if (d != null) {
				System.out.println(d.getClass());
				System.out.println(d.getDepartmentName());
			}
		}

	}

テストケースなのにassert書いてませんが(汗)・・・
これで実行してみます。但し、TopLink JPAをJavaSE環境で実行する場合、javaagentオプションをつけないとN対1のLAZYロードは有効にならない・・・とOracleのサイトに書いてありました。これがあまりよくわからなかったのですが・・・どうやら絶対パスでjarを定義してやるといいみたい。
それで、TestCaseを実行する際に

と書いて実行してみました。すると・・・

2006-05-28 21:23:34.453 [DEBUG] main org.seasar.extension.jdbc.impl.BasicUpdateHandler
DELETE FROM EMPLOYEE2
2006-05-28 21:23:36.140 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionPoolImpl
物理的なコネクションを取得しました
2006-05-28 21:23:36.140 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
論理的なコネクションを取得しました
2006-05-28 21:23:37.125 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
論理的なコネクションを閉じました
2006-05-28 21:23:37.125 [DEBUG] main org.seasar.extension.jdbc.impl.BasicUpdateHandler
DELETE FROM DEPARTMENT
2006-05-28 21:23:37.125 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
論理的なコネクションを取得しました
2006-05-28 21:23:37.156 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
論理的なコネクションを閉じました
2006-05-28 21:23:37.156 [DEBUG] main org.seasar.extension.jdbc.impl.BasicUpdateHandler
DELETE FROM ADDRESS
2006-05-28 21:23:37.156 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
論理的なコネクションを取得しました
2006-05-28 21:23:37.171 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
論理的なコネクションを閉じました
2006-05-28 21:23:37.250 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
論理的なコネクションを取得しました
2006-05-28 21:23:37.531 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
論理的なコネクションを閉じました
2006-05-28 21:23:37.531 [DEBUG] main org.seasar.extension.jdbc.impl.BasicUpdateHandler
INSERT INTO ADDRESS (ID, CITY) VALUES (1, '東京')
2006-05-28 21:23:37.531 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
論理的なコネクションを取得しました
2006-05-28 21:23:37.578 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
論理的なコネクションを閉じました
2006-05-28 21:23:37.578 [DEBUG] main org.seasar.extension.jdbc.impl.BasicUpdateHandler
INSERT INTO ADDRESS (ID, CITY) VALUES (2, '大阪')
2006-05-28 21:23:37.578 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
論理的なコネクションを取得しました
2006-05-28 21:23:37.578 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
論理的なコネクションを閉じました
2006-05-28 21:23:37.578 [DEBUG] main org.seasar.extension.jdbc.impl.BasicUpdateHandler
INSERT INTO ADDRESS (ID, CITY) VALUES (3, '京都')
2006-05-28 21:23:37.578 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
論理的なコネクションを取得しました
2006-05-28 21:23:37.578 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
論理的なコネクションを閉じました
2006-05-28 21:23:37.578 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
論理的なコネクションを取得しました
2006-05-28 21:23:37.609 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
論理的なコネクションを閉じました
2006-05-28 21:23:37.609 [DEBUG] main org.seasar.extension.jdbc.impl.BasicUpdateHandler
INSERT INTO DEPARTMENT (ID, DEPARTMENTNAME, DTYPE) VALUES (2, '経理', 'A')
2006-05-28 21:23:37.609 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
論理的なコネクションを取得しました
2006-05-28 21:23:37.609 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
論理的なコネクションを閉じました
2006-05-28 21:23:37.609 [DEBUG] main org.seasar.extension.jdbc.impl.BasicUpdateHandler
INSERT INTO DEPARTMENT (ID, DEPARTMENTNAME, DTYPE) VALUES (1, '営業', 'B')
2006-05-28 21:23:37.609 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
論理的なコネクションを取得しました
2006-05-28 21:23:37.609 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
論理的なコネクションを閉じました
2006-05-28 21:23:37.609 [DEBUG] main org.seasar.extension.jdbc.impl.BasicUpdateHandler
INSERT INTO DEPARTMENT (ID, DEPARTMENTNAME, DTYPE) VALUES (4, '製造', 'B')
2006-05-28 21:23:37.609 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
論理的なコネクションを取得しました
2006-05-28 21:23:37.609 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
論理的なコネクションを閉じました
2006-05-28 21:23:37.609 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
論理的なコネクションを取得しました
2006-05-28 21:23:37.625 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
論理的なコネクションを閉じました
2006-05-28 21:23:37.625 [DEBUG] main org.seasar.extension.jdbc.impl.BasicUpdateHandler
INSERT INTO EMPLOYEE2 (ID, EMPLOYEENAME, SALARY, ADDRESS_ID, VERSION) VALUES (1, 'A', 1000, 1, 1)
2006-05-28 21:23:37.625 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
論理的なコネクションを取得しました
2006-05-28 21:23:37.640 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
論理的なコネクションを閉じました
2006-05-28 21:23:37.640 [DEBUG] main org.seasar.extension.jdbc.impl.BasicUpdateHandler
INSERT INTO EMPLOYEE2 (ID, EMPLOYEENAME, SALARY, ADDRESS_ID, VERSION) VALUES (2, 'B', 2000, 2, 1)
2006-05-28 21:23:37.640 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
論理的なコネクションを取得しました
2006-05-28 21:23:37.640 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
論理的なコネクションを閉じました
2006-05-28 21:23:37.640 [DEBUG] main org.seasar.extension.jdbc.impl.BasicUpdateHandler
INSERT INTO EMPLOYEE2 (ID, EMPLOYEENAME, SALARY, ADDRESS_ID, VERSION) VALUES (3, 'C', 3000, 3, 1)
2006-05-28 21:23:37.640 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
論理的なコネクションを取得しました
2006-05-28 21:23:37.640 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
論理的なコネクションを閉じました
2006-05-28 21:23:37.640 [DEBUG] main org.seasar.extension.jdbc.impl.BasicUpdateHandler
INSERT INTO EMPLOYEE2 (ID, EMPLOYEENAME, SALARY, ADDRESS_ID, VERSION) VALUES (4, 'D', 4000, 2, 1)
2006-05-28 21:23:37.640 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
論理的なコネクションを取得しました
2006-05-28 21:23:37.656 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
論理的なコネクションを閉じました
[TopLink Info]: 2006.05.28 09:23:37.843--ServerSession(21480956)--Thread(Thread[main,5,main])--TopLink, version: Oracle TopLink Essentials - 2006.5 (Build 060511)
[TopLink Info]: 2006.05.28 09:23:37.859--ServerSession(21480956)--Thread(Thread[main,5,main])--Server: unknown
2006-05-28 21:23:37.890 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
論理的なコネクションを取得しました
[TopLink Fine]: 2006.05.28 09:23:37.906--Thread(Thread[main,5,main])--Detected Vendor platform: oracle.toplink.essentials.platform.database.oracle.OraclePlatform
2006-05-28 21:23:37.921 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
論理的なコネクションを閉じました
[TopLink Config]: 2006.05.28 09:23:37.937--ServerSession(21480956)--Connection(18788761)--Thread(Thread[main,5,main])--connecting(DatabaseLogin(
platform=>OraclePlatform
user name=> ""
connector=>JNDIConnector datasource name=>jdbc.DataSource
))
2006-05-28 21:23:37.953 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
論理的なコネクションを取得しました
[TopLink Config]: 2006.05.28 09:23:37.953--ServerSession(21480956)--Connection(1959064)--Thread(Thread[main,5,main])--Connected: jdbc:oracle:thin:@localhost:1521:orcl
User: YOSHIDA
Database: Oracle Version: Personal Oracle Database 10g 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-05-28 21:23:37.984 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
論理的なコネクションを閉じました
[TopLink Config]: 2006.05.28 09:23:37.984--ServerSession(21480956)--Connection(9102426)--Thread(Thread[main,5,main])--connecting(DatabaseLogin(
platform=>OraclePlatform
user name=> ""
connector=>JNDIConnector datasource name=>jdbc.DataSource
))
2006-05-28 21:23:37.984 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
論理的なコネクションを取得しました
[TopLink Config]: 2006.05.28 09:23:37.984--ServerSession(21480956)--Connection(1959064)--Thread(Thread[main,5,main])--Connected: jdbc:oracle:thin:@localhost:1521:orcl
User: YOSHIDA
Database: Oracle Version: Personal Oracle Database 10g 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-05-28 21:23:37.984 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
論理的なコネクションを閉じました
[TopLink Info]: 2006.05.28 09:23:38.093--ServerSession(21480956)--Thread(Thread[main,5,main])--file:/I:/workspace/TopLink/bin-em login successful
2006-05-28 21:23:39.109 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
論理的なコネクションを取得しました
[TopLink Fine]: 2006.05.28 09:23:39.109--UnitOfWork(3223920)--Connection(1959064)--Thread(Thread[main,5,main])--SELECT ID, SALARY, VERSION, EMPLOYEENAME, ADDRESS_ID FROM EMPLOYEE2 ORDER BY ID ASC
A
[TopLink Fine]: 2006.05.28 09:23:39.156--UnitOfWork(3223920)--Connection(1959064)--Thread(Thread[main,5,main])--SELECT ID, DTYPE, DEPARTMENTNAME FROM DEPARTMENT WHERE (ID = ?)
bind => [1]
class test.ejb3.DepartmentB
営業
B
[TopLink Fine]: 2006.05.28 09:23:39.156--UnitOfWork(3223920)--Connection(1959064)--Thread(Thread[main,5,main])--SELECT ID, DTYPE, DEPARTMENTNAME FROM DEPARTMENT WHERE (ID = ?)
bind => [2]
class test.ejb3.DepartmentA
経理
C
[TopLink Fine]: 2006.05.28 09:23:39.171--UnitOfWork(3223920)--Connection(1959064)--Thread(Thread[main,5,main])--SELECT ID, DTYPE, DEPARTMENTNAME FROM DEPARTMENT WHERE (ID = ?)
bind => [3]
D
[TopLink Fine]: 2006.05.28 09:23:39.203--UnitOfWork(3223920)--Connection(1959064)--Thread(Thread[main,5,main])--SELECT ID, DTYPE, DEPARTMENTNAME FROM DEPARTMENT WHERE (ID = ?)
bind => [4]
class test.ejb3.DepartmentB
製造
2006-05-28 21:23:39.218 [DEBUG] main org.seasar.extension.jta.TransactionImpl
トランザクションロールバックしました
2006-05-28 21:23:39.218 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
論理的なコネクションを閉じました
[TopLink Warning]: 2006.05.28 09:23:39.234--UnitOfWork(3223920)--Thread(Thread[main,5,main])--Exception [TOPLINK-23010] (Oracle TopLink Essentials - 2006.5 (Build 060511)): oracle.toplink.essentials.exceptions.TransactionException
Exception Description: No externally managed transaction is currently active for this thread
2006-05-28 21:23:39.296 [ERROR] main org.seasar.extension.jta.TransactionImpl

Exception Description: No externally managed transaction is currently active for this thread
Local Exception Stack:


Exception [TOPLINK-23010] (Oracle TopLink Essentials - 2006.5 (Build 060511)): oracle.toplink.essentials.exceptions.TransactionException
Exception Description: No externally managed transaction is currently active for this thread
at oracle.toplink.essentials.exceptions.TransactionException.inactiveUnitOfWork(TransactionException.java:126)
at oracle.toplink.essentials.transaction.AbstractSynchronizationListener.afterCompletion(AbstractSynchronizationListener.java:150)
at oracle.toplink.essentials.transaction.JTASynchronizationListener.afterCompletion(JTASynchronizationListener.java:87)
at org.seasar.extension.jta.TransactionImpl.afterCompletion(TransactionImpl.java:296)
at org.seasar.extension.jta.TransactionImpl.rollback(TransactionImpl.java:322)
at org.seasar.extension.jta.TransactionManagerImpl.rollback(TransactionManagerImpl.java:103)
at org.seasar.extension.unit.S2TestCase.doRunTest(S2TestCase.java:92)
at org.seasar.framework.unit.S2FrameworkTestCase.runBare(S2FrameworkTestCase.java:145)
at junit.framework.TestResult$1.protect(TestResult.java:106)
at junit.framework.TestResult.runProtected(TestResult.java:124)
at junit.framework.TestResult.run(TestResult.java:109)
at junit.framework.TestCase.run(TestCase.java:118)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.runTests(RemoteTestRunner.java:478)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.run(RemoteTestRunner.java:344)
at org.eclipse.jdt.internal.junit.runner.RemoteTestRunner.main(RemoteTestRunner.java:196)
2006-05-28 21:23:39.296 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
物理的なコネクションを閉じました
[TopLink Config]: 2006.05.28 09:23:39.296--ServerSession(21480956)--Connection(27929635)--Thread(Thread[main,5,main])--disconnect
[TopLink Info]: 2006.05.28 09:23:39.296--ServerSession(21480956)--Thread(Thread[main,5,main])--file:/I:/workspace/TopLink/bin-em logout successful

・・・ん?・・・メソッドは実行できたものの、ロールバック中に何かエラーが起こってる・・・
調べてみると、どうやらTopLinkがTransactionに対してregisterSynchronizationしていた、JTASynchronizationListenerのafterCompletion内でエラーが起こってるらしい。TopLinkのUnitOfWorkオブジェクトが既に死んでいる・・・とかいうエラーでした。TransactionのafterCompletionメソッド内で、先にEntityManagerが閉じられているのが原因みたいですね。Hibernateの場合は、こういうエラーは起こらないのだが・・・
とりあえず、EntityManagerを作成した後registerSynchronizationする前に、joinTransactionメソッドを呼ぶようにいじってみました。TopLinkが登録するJTASynchronizationListenerのafterCompletionメソッドを、EntityManagerがcloseされる前に呼び出させるのが目的です。こういうやり方でいいのかあまりよくわかってませんが、とりあえず・・・
修正後に実行してみると・・・

(中略)
[TopLink Info]: 2006.05.28 09:36:13.406--ServerSession(21480956)--Thread(Thread[main,5,main])--file:/I:/workspace/TopLink/bin-em login successful
2006-05-28 21:36:14.046 [DEBUG] main org.seasar.extension.dbcp.impl.DataSourceImpl
論理的なコネクションを取得しました
[TopLink Fine]: 2006.05.28 09:36:14.046--UnitOfWork(7435043)--Connection(934469)--Thread(Thread[main,5,main])--SELECT ID, SALARY, VERSION, EMPLOYEENAME, ADDRESS_ID FROM EMPLOYEE2 ORDER BY ID ASC
A
[TopLink Fine]: 2006.05.28 09:36:14.093--UnitOfWork(7435043)--Connection(934469)--Thread(Thread[main,5,main])--SELECT ID, DTYPE, DEPARTMENTNAME FROM DEPARTMENT WHERE (ID = ?)
bind => [1]
class test.ejb3.DepartmentB
営業
B
[TopLink Fine]: 2006.05.28 09:36:14.109--UnitOfWork(7435043)--Connection(934469)--Thread(Thread[main,5,main])--SELECT ID, DTYPE, DEPARTMENTNAME FROM DEPARTMENT WHERE (ID = ?)
bind => [2]
class test.ejb3.DepartmentA
経理
C
[TopLink Fine]: 2006.05.28 09:36:14.109--UnitOfWork(7435043)--Connection(934469)--Thread(Thread[main,5,main])--SELECT ID, DTYPE, DEPARTMENTNAME FROM DEPARTMENT WHERE (ID = ?)
bind => [3]
D
[TopLink Fine]: 2006.05.28 09:36:14.140--UnitOfWork(7435043)--Connection(934469)--Thread(Thread[main,5,main])--SELECT ID, DTYPE, DEPARTMENTNAME FROM DEPARTMENT WHERE (ID = ?)
bind => [4]
class test.ejb3.DepartmentB
製造
2006-05-28 21:36:14.156 [DEBUG] main org.seasar.extension.jta.TransactionImpl
トランザクションロールバックしました
2006-05-28 21:36:14.171 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
論理的なコネクションを閉じました
2006-05-28 21:36:14.171 [DEBUG] main org.seasar.extension.dbcp.impl.ConnectionWrapperImpl
物理的なコネクションを閉じました
[TopLink Config]: 2006.05.28 09:36:14.187--ServerSession(21480956)--Connection(3841429)--Thread(Thread[main,5,main])--disconnect
[TopLink Info]: 2006.05.28 09:36:14.187--ServerSession(21480956)--Thread(Thread[main,5,main])--file:/I:/workspace/TopLink/bin-em logout successful

どうやら上手く動いたっぽい。そして、N対1のLAZYロードも文句なしに動くことが確認できました。ポイントはEMPLOYEE2テーブルのID=3の行(name = C)。同一主キーのDEPARTMENTが存在しない為、LAZYロードでSELECT文を発行した後、Objectが存在しないということでnullを返しています。これがHibernateの場合、そもそもLAZYロードできないし、optional = false と嘘をついて無理やりLAZYロードを行った場合、偽者のPROXYをそのまま返してしまうので、nullチェックが誤りとなり、更にその後LAZYロードでエラーになるという最悪コンボに突入するわけですが(苦笑)
継承戦略も上手く動くので、instanceof による場合分けも可能です。N対1のLAZYロードに関する限り、TopLinkHibernateより数段上を行ってるということが明らかになりました。
ただ・・・触ってみて思ったのですが、やはりTopLinkは情報が少ないですね・・・JPAAPIに限定する処理の範囲では最強だと思うのですが、例えばSQLを直接書いて処理したい場合・・・何度も言ってる様にJPASQLサポートは使い物にならないので(orm.xmlを使って設定ファイルにSQLを書く方法を一度試してみようとは思っているのですが、仕様書のサンプル読む限り、これもあまり使えなさそうな予感・・・)、実装ツールの機能が無いと正直いって使えません。HibernateならSessionを使えばいいわけですが、TopLinkはどうすればいいのか、自分はよくわかりません(単にTopLinkを知らないだけですけど、そもそも知る為の情報に圧倒的な差がある・・・)。また、HSQLDBのサポートが弱かったように、多種多様なDBへの対応は、やはりオープンソース歴の長いHibernateに分があるようです。更に更に、今回使ったTopLinkのjarは、まだバイナリでリリースもされておらず、Java SE環境で使うにはまだまだ完成度が低すぎます。Hibernateはもうリリース前状態に入ってるのに・・・
とはいえ、Hibernateの最大の弱点(だと自分は思っている)N対1のLAZYロードに対しては、TopLinkの対応は完璧とも思える動作をしています。うーむ、比較するとなかなか面白いと言うか複雑というか・・・両方を足して完璧なJPA実装を作ってくれればよかったのに(汗)
(追記)余談ですが、TopLinkのPersistenceProvider実装は、読み込んだpersistence.xmlが別のProvider実装を指定していても、EntityManagerFactoryを無理やり返そうとしてエラーになります(苦笑)なんちゅうか・・・本当にJava SE環境がアウトオブ眼中だったとしか思えない・・・