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>