Spring+S2JTA+Hibernate EntityManager
前回に引き続き、今度はTomcat上でSpring+Hibernateの連携を調べていた・・・のですが、どーも、Springのトランザクション機能が気に入らない・・・Seasar2によるいつでもどこでもJTAトランザクション環境に身も心も染まってしまった今、今更JDBCトランザクションとかやりたくないです。Springは自身が提供するTransactionインターフェイスによってその違いを吸収するって謳ってるけど、標準APIを独自フレームワークがラップして隠してしまうのは、やっぱり根本的におかしいと思うし、JPAやHibernateは自身でJTAと連携するのでこういう隠蔽方法とは相性が悪いです。
というわけで、結構有名な方法だと思うのですが、S2JTAを利用してSpringのJtaTransactionManagerとHibernateEntityManagerをそれぞれ連携させ、JavaEE5サーバと同等環境を構築してみたいと思います。
まずは、pom.xml
<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>springexample</groupId> <artifactId>springexample</artifactId> <packaging>war</packaging> <version>1.0-SNAPSHOT</version> <name>springexample Maven Webapp</name> <url>http://maven.apache.org</url> <dependencies> <dependency> <groupId>struts</groupId> <artifactId>struts</artifactId> <version>1.2.9</version> <exclusions> <exclusion> <groupId>xml-apis</groupId> <artifactId>xml-apis</artifactId> </exclusion> <exclusion> <groupId>xalan</groupId> <artifactId>xalan</artifactId> </exclusion> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> <version>3.3.2.GA</version> <exclusions> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> <exclusion> <groupId>javax.transaction</groupId> <artifactId>jta</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>javax.transaction</groupId> <artifactId>transaction-api</artifactId> <version>1.1</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-webmvc-struts</artifactId> <version>2.5.2</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-orm</artifactId> <version>2.5.2</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-aop</artifactId> <version>2.5.2</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>2.5.2</version> </dependency> <dependency> <artifactId>s2-extension</artifactId> <groupId>org.seasar.container</groupId> <version>2.4.23</version> <exclusions> <exclusion> <groupId>jboss</groupId> <artifactId>javassist</artifactId> </exclusion> <exclusion> <groupId>commons-logging</groupId> <artifactId>commons-logging</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>org.seasar.hibernate</groupId> <artifactId>s2hibernate-jpa</artifactId> <version>1.0.1</version> <exclusions> <exclusion> <groupId>org.seasar.container</groupId> <artifactId>s2-tiger</artifactId> </exclusion> <exclusion> <groupId>jboss</groupId> <artifactId>javassist</artifactId> </exclusion> <exclusion> <groupId>org.hibernate</groupId> <artifactId>hibernate</artifactId> </exclusion> <exclusion> <groupId>org.hibernate</groupId> <artifactId>hibernate-entitymanager</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency> <dependency> <groupId>jstl</groupId> <artifactId>jstl</artifactId> <version>1.2</version> </dependency> <dependency> <groupId>log4j</groupId> <artifactId>log4j</artifactId> <version>1.2.15</version> <exclusions> <exclusion> <groupId>javax.mail</groupId> <artifactId>mail</artifactId> </exclusion> <exclusion> <groupId>javax.jms</groupId> <artifactId>jms</artifactId> </exclusion> <exclusion> <groupId>com.sun.jdmk</groupId> <artifactId>jmxtools</artifactId> </exclusion> <exclusion> <groupId>com.sun.jmx</groupId> <artifactId>jmxri</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>javax.servlet</groupId> <artifactId>servlet-api</artifactId> <version>2.5</version> <scope>provided</scope> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.4</version> <scope>test</scope> </dependency> </dependencies> <build> <finalName>springexample</finalName> <plugins> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <configuration> <source>1.5</source> <target>1.5</target> </configuration> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-eclipse-plugin</artifactId> <configuration> <downloadSources>true</downloadSources> <downloadJavadocs>true</downloadJavadocs> <wtpversion>2.0</wtpversion> </configuration> </plugin> </plugins> </build> <repositories> <repository> <id>java.net</id> <url>http://download.java.net/maven/1</url> <layout>legacy</layout> </repository> <repository> <id>jboss-maven2</id> <url>http://repository.jboss.org/maven2</url> </repository> <repository> <id>maven.seasar.org</id> <name>The Seasar Foundation Maven2 Repository</name> <url>http://maven.seasar.org/maven2</url> </repository> </repositories> </project>
HibernateにS2のトランザクションマネージャーを認識させるためにS2Hibernate-JPAを導入しています。依存ライブラリは極力排除しているし、Springと被る依存ライブラリが多いので、実際にS2関連で追加されるjarは僅かです。
続いて、web.xml
<?xml version="1.0" encoding="UTF-8"?> <web-app version="2.5" xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"> <display-name>springexamples</display-name> <context-param> <param-name>javax.servlet.jsp.jstl.fmt.locale</param-name> <param-value>ja_JP</param-value> </context-param> <context-param> <param-name> javax.servlet.jsp.jstl.fmt.fallbackLocale </param-name> <param-value>ja_JP</param-value> </context-param> <context-param> <param-name> javax.servlet.jsp.jstl.fmt.localizationContext </param-name> <param-value>messages</param-value> </context-param> <servlet> <servlet-name>s2servlet</servlet-name> <servlet-class> org.seasar.framework.container.servlet.S2ContainerServlet </servlet-class> <init-param> <param-name>configPath</param-name> <param-value>jdbc.dicon</param-value> </init-param> <init-param> <param-name>debug</param-name> <param-value>false</param-value> </init-param> <load-on-startup>1</load-on-startup> </servlet> <servlet-mapping> <servlet-name>s2servlet</servlet-name> <url-pattern>/s2servlet</url-pattern> </servlet-mapping> <!-- Standard Action Servlet Configuration (with debugging) --> <servlet> <servlet-name>action</servlet-name> <servlet-class> org.apache.struts.action.ActionServlet </servlet-class> <init-param> <param-name>config</param-name> <param-value>/WEB-INF/struts-config.xml</param-value> </init-param> <init-param> <param-name>debug</param-name> <param-value>2</param-value> </init-param> <init-param> <param-name>detail</param-name> <param-value>2</param-value> </init-param> <load-on-startup>2</load-on-startup> </servlet> <!-- Standard Action Servlet Mapping --> <servlet-mapping> <servlet-name>action</servlet-name> <url-pattern>*.do</url-pattern> </servlet-mapping> <!-- The Usual Welcome File List --> <welcome-file-list> <welcome-file>index.jsp</welcome-file> </welcome-file-list> <jsp-config> <jsp-property-group> <url-pattern>*.jsp</url-pattern> <page-encoding>UTF-8</page-encoding> <include-prelude>/WEB-INF/jsp/header.jsp</include-prelude> <trim-directive-whitespaces> true </trim-directive-whitespaces> </jsp-property-group> </jsp-config> </web-app>
ActionServletの前にS2ContainerServletの初期化を行うように定義するのがポイントです。S2で作成したTransactionManagerをSpringがJNDI経由で受け取るからです。
続いてSeasar2の設定ファイル
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN" "http://www.seasar.org/dtd/components24.dtd"> <components namespace="jdbc"> <include path="jta.dicon"/> <component class="org.seasar.extension.jdbc.impl.BasicResultSetFactory"/> <component class="org.seasar.extension.jdbc.impl.ConfigurableStatementFactory"> <arg> <component class="org.seasar.extension.jdbc.impl.BasicStatementFactory"/> </arg> <property name="fetchSize">100</property> <!-- <property name="maxRows">100</property> --> </component> <component name="xaDataSource" class="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource"> <property name="serverName">"localhost"</property> <property name="databaseName">"springtest"</property> <property name="user">"root"</property> <property name="password">"root"</property> </component> <component name="connectionPool" class="org.seasar.extension.dbcp.impl.ConnectionPoolImpl"> <property name="timeout">600</property> <property name="maxPoolSize">10</property> <property name="allowLocalTx">true</property> <destroyMethod name="close"/> </component> <component name="dataSource" class="org.seasar.extension.dbcp.impl.DataSourceImpl"/> </components>
トランザクション、コネクションプール、DataSourceのみを管理する最小限の構成です。Springで直接定義しようとも思ったのですが、HibernateにTransactionManagerを渡す方法が解らなかったのでS2側で初期化することにしました。
続いてpersistence.xml
<?xml version="1.0" encoding="UTF-8"?> <persistence version="1.0" 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"> <persistence-unit name="springexample" 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.MySQL5InnoDBDialect" /> <property name="hibernate.jndi.class" value="org.seasar.extension.j2ee.JndiContextFactory" /> <property name="hibernate.transaction.manager_lookup_class" value="org.seasar.hibernate.jpa.transaction.SingletonTransactionManagerProxyLookup" /> <property name="hibernate.show_sql" value="true" /> <property name="hibernate.format_sql" value="true" /> <property name="hibernate.use_sql_comments" value="false" /> </properties> </persistence-unit> </persistence>
データソースはSpringの方から渡してやるのでここでは定義しません。Hibernate.transaction.manager_lookup_classにS2Hibernateで提供されるLookupクラスを定義するのがポイントです。
(2008/03/29追記)transaction-type="JTA"の設定を明記しておかないと、SpringのPersistenceUnitInfoはRESOURCE_LOCALで定義してしまうみたいです。せっかくS2JTAを導入した意味が無くなってしまうので、ここは明記しておく必要があります。
そしてSpringの設定ファイル
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jee="http://www.springframework.org/schema/jee" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-2.5.xsd"> <jee:jndi-lookup id="s2TxManager" jndi-name="jta/TransactionManager"> <jee:environment> java.naming.factory.initial=org.seasar.extension.j2ee.JndiContextFactory </jee:environment> </jee:jndi-lookup> <jee:jndi-lookup id="dataSource" jndi-name="jdbc/dataSource"> <jee:environment> java.naming.factory.initial=org.seasar.extension.j2ee.JndiContextFactory </jee:environment> </jee:jndi-lookup> <bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"> <constructor-arg ref="s2TxManager" /> </bean> <tx:annotation-driven /> <bean id="pum" class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager"> <property name="defaultDataSource" ref="dataSource" /> </bean> <bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="persistenceUnitManager" ref="pum" /> </bean> <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" /> <bean name="/login" class="springexample.action.LoginAction" scope="prototype"/> </beans>
S2JTAで作成したTransactionManagerは、jee:jndi-lookupタグを利用して取得します。jee:environmentにS2用のJNDI定義を書くのがポイントです。
トランザクションマネージャはSpringのJtaTransactionManagerに渡し、DataSourceはDefaultPersistenceUnitManager経由でLocalContainerEntityManagerFactoryBeanに渡します。これで、JavaEEサーバ上で動かしたときと全く同じ感覚で、Spring上からHibernate EntityManagerを利用出来るようになりました。
JTA実装と割り切ってS2を使えば、Springとの組み合わせは意外に簡単で、しかもSpringのトランザクション定義が劇的に簡単になります。今回はTomcat上で動かしましたが、この構成なら当然ローカルアプリでも実現可能な筈です。ポイントは、Springの初期化前にS2を初期化するという一点のみ。
HibernateのようにJNDI経由でTransactionManagerを渡す必要が無い場合は、S2JTAの全てのクラスをSpring側で設定することも出来ます。でも、今回のようにS2からJNDI経由で取得する構成にしておけば、テスト時はこの構成を使い、JNDIの取得先を変更するだけですぐにアプリケーションサーバ環境に切り替えることが出来ます。
実はSpringユーザにこそ、S2JTAはお勧めなんじゃないか・・・と、今回試してみて感じました。みんなでJTAを使って楽にトランザクションを管理しましょう。