Atomikos TransactionsEssentials で JTA (XA with MySQL編その4)
前回(Atomikos TransactionsEssentials で JTA (XA with MySQL編その3)) では、
MySQL では XA START xid JOIN は未サポート
という事実が判明しました。
今回は、「なんかワークアラウンドないかな〜」的なコンセプトでぬるく攻めてみようと思います。
Atomikos の HP の情報
MySQL で XA START xid JOIN できない等の情報は載ってましたね。
今回の件には役に立ちそうにありませんが、一応対応しておきましょう。
jta.properties に、以下のように追加しておきます。
# workaround for MySQL(see http://www.atomikos.com/Documentation/KnownProblems#MySQL_XA_bug) com.atomikos.icatch.serial_jta_transactions=false
ちなみに、オプションの内容は以下のようなものです。
オプション | 内容 |
---|---|
com.atomikos.icatch.serial_jta_transactions | Specifies if subtransactions should be joined when possible. Defaults to true. When false, no attempt to call XAResource.start(TM_JOIN) will be made for different but related subtransctions. This setting has no effect on resource access within one and the same transaction. If you don't use subtransactions then this setting can be ignored. |
そもそも、persistの度にコネクション切らなきゃいいんじゃね?
そもそも、なんで persist のたびに毎回コネクション切断して、次の persist でトランザクションに JOIN してるんだ?という疑問が湧き出る今日この頃です。
XA END を呼んでいるのは、AbstractReturningDelegate#performInsert(String insertSQL, SessionImplementor session, Binder binder) の以下の箇所です。
// prepare and execute the insert PreparedStatement insert = prepare( insertSQL, session ); try { binder.bindValues( insert ); return executeAndExtract( insert ); } finally { releaseStatement( insert, session ); }
PreparedStatement insert = prepare( insertSQL, session );
にて XA START を投げ、
return executeAndExtract( insert );
にて insert into A (name) values ('Kuma-chan') を投げ、
releaseStatement( insert, session );
にて、XA END を投げています。
releaseStatement() 以下では、org.hibernate.jdbc.ConnectionManager#afterStatement() が呼ばれ、その中の isAggressiveRelease() メソッドで aggressiveRelease() メソッドを呼ぶかどうかの判断をしています。
となると、その aggressiveRelease とは何ぞやという話になりますね。
ConnectionManager#afterStatement() と aggressiveRelease
以下は、org.hibernate.jdbc.ConnectionManager#afterStatement() の javadoc です。
To be called after execution of each JDBC statement. Used to conditionally release the JDBC connection aggressively if the configured release mode indicates.
なるほど。release mode によって、JDBCコネクションをアグレッシブに開放するかどうかを決定するのですね。
現在の設定だと、
releaseMode == ConnectionReleaseMode.AFTER_STATEMENT
となってしまうので、問答無用で Statement 毎にコネクションをクローズしに行ってしまいます。
ということは、ConnectionReleaseMode を変更すればよいということですよね。
これって、Hibernate のドキュメントで見た記憶が、、、。
ありました。hibernate.connection.release_mode プロパティです。
ドキュメントには、以下のように書かれています。
Specify when Hibernate should release JDBC connections. By default, a JDBC connection is held until the session is explicitly closed or disconnected. For an application server JTA datasource, you should use after_statement to aggressively release connections after every JDBC call. For a non-JTA connection, it often makes sense to release the connection at the end of each transaction, by using after_transaction. auto will choose after_statement for the JTA and CMT transaction strategies and after_transaction for the JDBC transaction strategy.
なるほど。デフォルトは auto モードで、通常は after_transaction が選択され、JTA の場合は after_statement が選択されるのですね。たしかに、ステートメントのたびにコネクションが開放されれば、コネクションの利用効率は上がりますよね。グローバルトランザクションでは、同一トランザクションで複数のデータソースを扱うので、トランザクションが終了するまで複数のデータソースのコネクションをすべて掴みっぱなしにするというのは効率が悪そうです。
詳細は、Hibernate のマニュアル(11.5. Connection Release Modes)に書かれています。
しかし、今回の問題は、コネクションを開放してしまうと次のステートメントで XA START xid JOIN が発行されてしまい、MySQL ではそれをサポートしていないということでした。
腑に落ちました。
再挑戦
ということで、hibernate.connection.release_mode プロパティを明示的に after_transaction にして再挑戦。
hibernate.connection.release_mode プロパティ設定
<prop key="hibernate.connection.release_mode">after_transaction</prop>
ソース
A a1 = new A(); A a2 = new A(); A a3 = new A(); A a4 = new A(); a1.setName("Kuma-chan"); a2.setName("Fellow-kun"); a3.setName("Turgenev"); a4.setName("Saint-Exupery"); ut.begin(); em1.persist(a1); em1.persist(a2); em2.persist(a3); em2.persist(a4); ut.commit();
データソース1のクエリログ
2173 Query XA START 0x636f6d2e61746f6d696b6f732e737072696e672e6a6462632e746d30303030313030303732,0x636f6d2e61746f6d696b6f732e737072696e672e6a6462632e746d31,0x41544f4d 2173 Query insert into A (name) values ('Kuma-chan') 2173 Query insert into A (name) values ('Fellow-kun') 2173 Query XA END 0x636f6d2e61746f6d696b6f732e737072696e672e6a6462632e746d30303030313030303732,0x636f6d2e61746f6d696b6f732e737072696e672e6a6462632e746d31,0x41544f4d 2173 Query XA PREPARE 0x636f6d2e61746f6d696b6f732e737072696e672e6a6462632e746d30303030313030303732,0x636f6d2e61746f6d696b6f732e737072696e672e6a6462632e746d31,0x41544f4d 2173 Query XA COMMIT 0x636f6d2e61746f6d696b6f732e737072696e672e6a6462632e746d30303030313030303732,0x636f6d2e61746f6d696b6f732e737072696e672e6a6462632e746d31,0x41544f4d
データソース2のクエリログ
2135 Query XA START 0x636f6d2e61746f6d696b6f732e737072696e672e6a6462632e746d30303030313030303732,0x636f6d2e61746f6d696b6f732e737072696e672e6a6462632e746d32,0x41544f4d 2135 Query insert into A (name) values ('Turgenev') 2135 Query insert into A (name) values ('Saint-Exupery') 2135 Query XA END 0x636f6d2e61746f6d696b6f732e737072696e672e6a6462632e746d30303030313030303732,0x636f6d2e61746f6d696b6f732e737072696e672e6a6462632e746d32,0x41544f4d 2135 Query XA PREPARE 0x636f6d2e61746f6d696b6f732e737072696e672e6a6462632e746d30303030313030303732,0x636f6d2e61746f6d696b6f732e737072696e672e6a6462632e746d32,0x41544f4d 2135 Query XA COMMIT 0x636f6d2e61746f6d696b6f732e737072696e672e6a6462632e746d30303030313030303732,0x636f6d2e61746f6d696b6f732e737072696e672e6a6462632e746d32,0x41544f4d
データソース1へのクエリ結果
mysql> select * from a; +----+------------+ | id | name | +----+------------+ | 1 | Kuma-chan | | 2 | Fellow-kun | +----+------------+ 2 rows in set (0.00 sec)
データソース2へのクエリ結果
mysql> select * from a; +----+---------------+ | id | name | +----+---------------+ | 1 | Turgenev | | 2 | Saint-Exupery | +----+---------------+ 2 rows in set (0.00 sec)
Mission Complete!! (`・ω・´)
Atomikos のページによる hibernate.connection.release_mode プロパティの説明
ちなみに、Atomikos のページでは、hibernate.connection.release_mode プロパティを after_transaction にすることについて、以下のように書かれています。
Hibernate (re)uses the same connection throughout the transaction NOTE: this setting seems to be ignored by Hibernate
ちゃんと読んどけよって事ですね。(´・ω・`)
まとめ
ということで、Atomikos TransactionsEssentials + HibernateEntityManager + JTA + MySQL の動作確認完了です。
(まだ最低限の機能確認ですが、、、)
教訓
Hibernateのマニュアルや、AtomikosのWeb上の情報を一通り目を通しておけば、もっと楽な道でした o...rz
ということで、教訓。
- ちゃんとマニュアルを読め。
- 情報には一通り目を通せ。
ソース
基本的には、jndi.xml 経由で JTA系の bean を JNDI 経由で公開し、beans.xml ファイルでは普通に Spring の bean を設定しています。
JNDIでのbeanの公開方法は、xbean-springでSpringFrameworkのbeanを JNDIで公開するを参照のこと。
jndi.properties
java.naming.factory.initial=org.apache.xbean.spring.jndi.SpringInitialContextFactory java.naming.provider.url=jndi.xml
jndi.xml
<?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd" > <bean id="atomikosUserTransaction" class="com.atomikos.icatch.jta.UserTransactionImp"> <property name="transactionTimeout" value="300" /> </bean> <bean id="atomikosTransactionManager" class="com.atomikos.icatch.jta.UserTransactionManager" init-method="init" destroy-method="close"> <property name="forceShutdown" value="true" /> </bean> <bean id="jndi" class="org.apache.xbean.spring.jndi.SpringInitialContextFactory" factory-method="makeInitialContext"> <property name="entries"> <map> <entry key="java:comp/TransactionManager" value-ref="atomikosTransactionManager"/> <entry key="java:comp/UserTransaction" value-ref="atomikosUserTransaction"/> </map> </property> </bean> </beans>
jta.properties
# See http://www.atomikos.com/Documentation/JtaProperties com.atomikos.icatch.service=com.atomikos.icatch.standalone.UserTransactionServiceFactory com.atomikos.icatch.console_file_name = tm.out com.atomikos.icatch.log_base_name = tmlog com.atomikos.icatch.tm_unique_name = com.atomikos.spring.jdbc.tm com.atomikos.icatch.console_log_level = INFO com.atomikos.icatch.serial_jta_transactions=false com.atomikos.icatch.max_timeout=300000
beans.xml
<?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:jee="http://www.springframework.org/schema/jee" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-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/context http://www.springframework.org/schema/context/spring-context-2.5.xsd" > <context:annotation-config/> <tx:annotation-driven transaction-manager="springTransactionManager"/> <!-- ひとつめのデータソース --> <bean id="dataSource1" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close"> <property name="uniqueResourceName" value="dataSource1" /> <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" /> <property name="poolSize" value="10" /> <property name="xaProperties"> <props> <prop key="user">root</prop> <prop key="password"></prop> <prop key="url">jdbc:mysql://localhost:3307/test</prop> </props> </property> </bean> <!-- ふたつめのデータソース --> <bean id="dataSource2" class="com.atomikos.jdbc.AtomikosDataSourceBean" init-method="init" destroy-method="close"> <property name="uniqueResourceName" value="dataSource2" /> <property name="xaDataSourceClassName" value="com.mysql.jdbc.jdbc2.optional.MysqlXADataSource" /> <property name="poolSize" value="10" /> <property name="xaProperties"> <props> <prop key="user">root</prop> <prop key="password"></prop> <prop key="url">jdbc:mysql://localhost:3308/test</prop> </props> </property> </bean> <!-- Atomikos の TransactionManager 実装 --> <jee:jndi-lookup id="atomikosTransactionManager" jndi-name="java:comp/TransactionManager"/> <jee:jndi-lookup id="atomikosUserTransaction" jndi-name="java:comp/UserTransaction"/> <!-- Configure the Spring framework to use JTA transactions from Atomikos --> <bean id="springTransactionManager" class="org.springframework.transaction.jta.JtaTransactionManager"> <property name="transactionManager"><ref bean="atomikosTransactionManager" /></property> <property name="userTransaction"><ref bean="atomikosUserTransaction" /></property> </bean> <!-- JpaVendorAdapter --> <bean id="jpaVendorAdapter" class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" /> <!-- EntityManagerFactory --> <bean id="entityManagerFactory1" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="persistenceXmlLocation" value="persistence.xml" /> <property name="jpaVendorAdapter" ref="jpaVendorAdapter" /> <property name="dataSource" ref="dataSource1" /> <property name="jpaProperties"> <props> <prop key="javax.persistence.transactionType">jta</prop> <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop> <prop key="hibernate.hbm2ddl.auto">create-drop</prop> <prop key="hibernate.transaction.manager_lookup_class">com.atomikos.icatch.jta.hibernate3.TransactionManagerLookup</prop> <prop key="hibernate.current_session_context_class">jta</prop> <prop key="hibernate.connection.release_mode">after_transaction</prop> </props> </property> </bean> <bean id="entityManagerFactory2" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean"> <property name="persistenceXmlLocation" value="persistence.xml" /> <property name="jpaVendorAdapter" ref="jpaVendorAdapter" /> <property name="dataSource" ref="dataSource2" /> <property name="jpaProperties"> <props> <prop key="javax.persistence.transactionType">jta</prop> <prop key="hibernate.dialect">org.hibernate.dialect.MySQL5InnoDBDialect</prop> <prop key="hibernate.hbm2ddl.auto">create-drop</prop> <prop key="hibernate.transaction.manager_lookup_class">com.atomikos.icatch.jta.hibernate3.TransactionManagerLookup</prop> <prop key="hibernate.current_session_context_class">jta</prop> <prop key="hibernate.connection.release_mode">after_transaction</prop> </props> </property> </bean> <bean id="entityManager1" class="org.springframework.orm.jpa.support.SharedEntityManagerBean"> <property name="entityManagerFactory" ref="entityManagerFactory1" /> </bean> <bean id="entityManager2" class="org.springframework.orm.jpa.support.SharedEntityManagerBean"> <property name="entityManagerFactory" ref="entityManagerFactory2" /> </bean> <bean id="service" class="sample.JpaExampleServiceImpl"/> </beans>
persistence.xml
<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="manager1" /> </persistence>