Hibernate EntityManagerその3
もう毎度のことになりつつありますが、またまた自分は間違ってました(苦笑)
Hibernate EntityManagerのリファレンスに
The configuration for entity managers both inside an application server and in a standalone application reside in a persistence archive (.par). A persistence archive is a JAR file with the .par suffix instead of .jar. You must also define a persistence.xml file that resides in the META-INF folder of the .par file. All properly annotated classes included in the par archive (ie having an @Entity annotation), all annotated packages and all Hibernate hbm.xml files included in the par will be added to the persistence unit configuration, so by default, your persistence.xml will be quite minimalist:
こう書いてあったので、てっきりjarを作らないと動かないものだとばかり思っていたのですが・・・
試しに、Eclipseのソースフォルダ上にMETA-INFディレクトりを作成し、その中に以下のようなpersistence.xmlを作ってみました。
<entity-manager> <name>test</name> <jta-data-source>j2ee.dataSource</jta-data-source> <properties> <property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/> <property name="hibernate.jndi.class" value="org.seasar.extension.j2ee.JndiContextFactory"/> <property name="hibernate.transaction.manager_lookup_class" value="test.hibernate.S2TransactionManagerLookup"/> <property name="hibernate.show_sql" value="true"/> <property name="hibernate.format_sql" value="true"/> <property name="hibernate.use_sql_comments" value="true"/> </properties> </entity-manager>
S2のJndiContextによって、HibernateにDataSourceとTransactionManagerを渡すのは前回通りです。また、TransactionManagerLookupインターフェイスは、以下のように実装しました。
package test.hibernate; import org.hibernate.transaction.JNDITransactionManagerLookup; public class S2TransactionManagerLookup extends JNDITransactionManagerLookup { private static final String TRANSACTION_MANAGER_NAME = "j2ee.transactionManager"; @Override protected String getName() { return TRANSACTION_MANAGER_NAME; } public String getUserTransactionName() { return TRANSACTION_MANAGER_NAME; } }
TransactionManagerのComponent名を返すだけのクラスです。前回の日記では、ComponentAutoRegisterを使ってAnnotationConfigurationを作成していましたが、今回はEJB3正式のやり方でEntityManagerを作成してみます。
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.3//EN" "http://www.seasar.org/dtd/components23.dtd"> <components namespace="hibernate"> <component class="javax.persistence.EntityManager"> <aspect> <component class="org.seasar.framework.aop.interceptors.DelegateInterceptor"> <initMethod name="setTarget"> <arg> @javax.persistence.Persistence@createEntityManagerFactory("test").entityManager </arg> </initMethod> </component> </aspect> </component> </components>
テスト用クラスは、前回同様Hibernate Toolsプラグインの自動作成機能を使って作成した、HSQLDBデモテーブルのEntityBean。それを使うClientは以下のようになります。
package test.service; import java.math.BigDecimal; import java.util.List; import javax.persistence.EntityManager; import org.seasar.framework.container.S2Container; import org.seasar.framework.container.factory.SingletonS2ContainerFactory; import test.generate.entity.Invoice; public class TestServiceImpl implements TestService { private EntityManager em; public void setEm(EntityManager em) { this.em = em; } public static void main(String[] args) { SingletonS2ContainerFactory.init(); S2Container container = SingletonS2ContainerFactory.getContainer(); TestService service = (TestService) container.getComponent(TestService.class); service.execute(); } public void execute() { List<Invoice> list = (List<Invoice>) em.createQuery( "SELECT iv FROM Invoice iv INNER JOIN FETCH iv.customer WHERE iv.total >= :total") .setParameter("total", new BigDecimal("6000")) .getResultList(); for (Invoice iv : list) { System.out.println("INVOICE_ID:" + iv.getId()); System.out.println("FIRSTNAME:" + iv.getCustomer().getFirstname()); System.out.println("LASTNAME:" + iv.getCustomer().getLastname()); System.out.println("CITY:" + iv.getCustomer().getCity()); System.out.println("STREET:" + iv.getCustomer().getStreet()); System.out.println("TOTAL:" + iv.getTotal()); System.out.println(); iv.setTotal(iv.getTotal().add(new BigDecimal("100"))); } } }
↑のクラスには、ログとトランザクションのアスペクトをかけています。
さて、これらのクラスをJarに固めないまま実行してみると・・・
DEBUG 2005-12-17 00:02:40,953 [main] BEGIN test.service.TestServiceImpl#execute() DEBUG 2005-12-17 00:02:40,953 [main] トランザクションを開始しました DEBUG 2005-12-17 00:02:41,484 [main] 論理的なコネクションを取得しました Hibernate: /* SELECT iv FROM Invoice iv INNER JOIN FETCH iv.customer WHERE iv.total >= :total */ select invoice0_.ID as ID1_0_, customer1_.ID as ID0_1_, invoice0_.CUSTOMERID as CUSTOMERID1_0_, invoice0_.TOTAL as TOTAL1_0_, customer1_.FIRSTNAME as FIRSTNAME0_1_, customer1_.LASTNAME as LASTNAME0_1_, customer1_.CITY as CITY0_1_, customer1_.STREET as STREET0_1_ from PUBLIC.INVOICE invoice0_ inner join PUBLIC.CUSTOMER customer1_ on invoice0_.CUSTOMERID=customer1_.ID where invoice0_.TOTAL>=? DEBUG 2005-12-17 00:02:41,531 [main] 論理的なコネクションを閉じました INVOICE_ID:9 FIRSTNAME:Andrew LASTNAME:Heiniger CITY:Lyon STREET:347 College Av. TOTAL:6982.20 INVOICE_ID:13 FIRSTNAME:Mary LASTNAME:Karsen CITY:Chicago STREET:202 College Av. TOTAL:7001.70 INVOICE_ID:37 FIRSTNAME:Mary LASTNAME:Karsen CITY:Chicago STREET:202 College Av. TOTAL:7331.10 INVOICE_ID:38 FIRSTNAME:Andrew LASTNAME:May CITY:New York STREET:172 Seventh Av. TOTAL:6373.80 DEBUG 2005-12-17 00:02:41,609 [main] 論理的なコネクションを取得しました Hibernate: /* update test.generate.entity.Invoice */ update PUBLIC.INVOICE set CUSTOMERID=?, TOTAL=? where ID=? Hibernate: /* update test.generate.entity.Invoice */ update PUBLIC.INVOICE set CUSTOMERID=?, TOTAL=? where ID=? Hibernate: /* update test.generate.entity.Invoice */ update PUBLIC.INVOICE set CUSTOMERID=?, TOTAL=? where ID=? Hibernate: /* update test.generate.entity.Invoice */ update PUBLIC.INVOICE set CUSTOMERID=?, TOTAL=? where ID=? DEBUG 2005-12-17 00:02:41,625 [main] 論理的なコネクションを閉じました DEBUG 2005-12-17 00:02:41,625 [main] トランザクションをコミットしました DEBUG 2005-12-17 00:02:41,625 [main] END test.service.TestServiceImpl#execute() : null
・・・普通に実行出来てしまいました(汗)・・・
つまり、Hibernate EntityManagerはJar上でもファイル上でも、META-INF/persistence.xmlが存在する同一クラスパス上のEntityBeanを自動的に検索して登録してくれるってことか。何気に凄い・・・
Connectionについては、Hibernateの資料を読むと、JTAトランザクションの場合は、SQLが実行された直後にクローズしてくれるみたいですね。そして・・・flushについては、TransactionManagerと連携して、コミットのタイミングで行われているみたいです。試しにpersistence.xmlの「hibernate.transaction.manager_lookup_class」の記述をコメントアウトして実行してみると・・・
DEBUG 2005-12-17 00:16:08,640 [main] BEGIN test.service.TestServiceImpl#execute() DEBUG 2005-12-17 00:16:08,640 [main] トランザクションを開始しました DEBUG 2005-12-17 00:16:09,265 [main] 論理的なコネクションを取得しました Hibernate: /* SELECT iv FROM Invoice iv INNER JOIN FETCH iv.customer WHERE iv.total >= :total */ select invoice0_.ID as ID1_0_, customer1_.ID as ID0_1_, invoice0_.CUSTOMERID as CUSTOMERID1_0_, invoice0_.TOTAL as TOTAL1_0_, customer1_.FIRSTNAME as FIRSTNAME0_1_, customer1_.LASTNAME as LASTNAME0_1_, customer1_.CITY as CITY0_1_, customer1_.STREET as STREET0_1_ from PUBLIC.INVOICE invoice0_ inner join PUBLIC.CUSTOMER customer1_ on invoice0_.CUSTOMERID=customer1_.ID where invoice0_.TOTAL>=? DEBUG 2005-12-17 00:16:09,312 [main] 論理的なコネクションを閉じました INVOICE_ID:9 FIRSTNAME:Andrew LASTNAME:Heiniger CITY:Lyon STREET:347 College Av. TOTAL:7082.20 INVOICE_ID:13 FIRSTNAME:Mary LASTNAME:Karsen CITY:Chicago STREET:202 College Av. TOTAL:7101.70 INVOICE_ID:37 FIRSTNAME:Mary LASTNAME:Karsen CITY:Chicago STREET:202 College Av. TOTAL:7431.10 INVOICE_ID:38 FIRSTNAME:Andrew LASTNAME:May CITY:New York STREET:172 Seventh Av. TOTAL:6473.80 DEBUG 2005-12-17 00:16:09,343 [main] トランザクションをコミットしました DEBUG 2005-12-17 00:16:09,343 [main] END test.service.TestServiceImpl#execute() : null
flushが行われずにコミットされてしまってます。また、遅延ロード機能をトランザクションマネージャーが見えない環境で使おうとすると、
2005-12-17 00:18:21,312 [main] ERROR org.hibernate.LazyInitializationException - failed to lazily initialize a collection of role: test.generate.entity.Invoice.items, no session or session was closed org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: test.generate.entity.Invoice.items, no session or session was closed at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:358) at org.hibernate.collection.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:350) at org.hibernate.collection.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:343) at org.hibernate.collection.AbstractPersistentCollection.read(AbstractPersistentCollection.java:86) at org.hibernate.collection.PersistentSet.iterator(PersistentSet.java:138) at test.service.TestServiceImpl.execute(TestServiceImpl.java:43) at test.service.TestServiceImpl$$EnhancedByS2AOP$$2b7db1.execute$$invokeSuperMethod$$(TestServiceImpl$$EnhancedByS2AOP$$2b7db1.java) at test.service.TestServiceImpl$$EnhancedByS2AOP$$2b7db1$$MethodInvocation$$execute0.proceed(MethodInvocationClassGenerator.java) at org.seasar.extension.tx.RequiredInterceptor.invoke(RequiredInterceptor.java:40) at test.service.TestServiceImpl$$EnhancedByS2AOP$$2b7db1$$MethodInvocation$$execute0.proceed(MethodInvocationClassGenerator.java) at org.seasar.framework.aop.interceptors.TraceInterceptor.invoke(TraceInterceptor.java:50) at test.service.TestServiceImpl$$EnhancedByS2AOP$$2b7db1$$MethodInvocation$$execute0.proceed(MethodInvocationClassGenerator.java) at test.service.TestServiceImpl$$EnhancedByS2AOP$$2b7db1.execute(TestServiceImpl$$EnhancedByS2AOP$$2b7db1.java) at test.service.TestServiceImpl.main(TestServiceImpl.java:26) DEBUG 2005-12-17 00:18:21,328 [main] トランザクションをロールバックしました
LazyInitializationExceptionが吹っ飛んできます。JTA EntityManagerに関しては、TransactionManagerと連携して自動でflushを行ってくれているってことですね。
という訳で・・・昨日自分がゴニョゴニョ作っていたクラス関連は、殆ど必要なくなりました(汗)Hibernate EntityManagerさえあれば、persistence.xmlにDataSourceとTransactionManagerの設定をしてあげるだけで、簡単にコンテナ管理EntityManagerを作成することが出来るんですね。正直、標準の方法で、こんなに簡単に環境が構築出来るとは思ってませんでした。最初から本来のやり方で試すべきだった・・・
もうHibernateには、マッピング用ファイルも登録用ファイルも要らないんですね。しかもPersistence API標準の方法でEntityManagerを作成出来るから、もし将来、実装を他のフレームワークに変更したとしても、persistence.xmlの詳細設定を変えるだけでほぼ同じように利用することが出来る筈。たとえJTA環境を持ってなくても、ここまで設定が簡単なら、JDBCを使う感覚で積極的にEntityManagerを使っていっても問題無いのではないでしょうか。