Seasar2のJTA実装を使って分散トランザクションを試す

ずいぶんとSeasar2JTA実装のコードを読んだりしてたんですが、その前に自分で使用した使い方を自身の頭の整理という面も含めましてまとめておこうと思います。

Seasar入門 はじめてのDI&AOP

Seasar入門 はじめてのDI&AOP

  • 作者: 須賀幸次,木村聡,西川麗,高安厚思,白井博章,椎野峻輔,岡薫,藤村浩士,ひがやすを
  • 出版社/メーカー: ソフトバンククリエイティブ
  • 発売日: 2006/02/25
  • メディア: 大型本
  • 購入: 7人 クリック: 51回
  • この商品を含むブログ (67件) を見る

ふたたびこの本の4章のトランザクションのサンプルコードを使って分散トランザクションを試してたいと思います。
Seasar2.4.34を使用しました。


ずいぶん前に2.3系でこのサンプルコードで分散トランザクションを試したときのj2ee.diconはこんな感じでした。

<?xml version="1.0" encoding="Shift_JIS"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components.dtd">
<components namespace="j2ee">
	<component name="transactionManager"
		class="org.seasar.extension.jta.TransactionManagerImpl"/>
	<component name="requiredTx"
		class="org.seasar.extension.tx.RequiredInterceptor"/>
	<component name="requiresNewTx"
		class="org.seasar.extension.tx.RequiresNewInterceptor"/>
	<component name="mandatoryTx"
		class="org.seasar.extension.tx.MandatoryInterceptor"/>

	<!--	XADataSource コンポーネント	-->
	<component name="xaDataSourcePos"
		class="org.seasar.extension.dbcp.impl.XADataSourceImpl">
		<property name="driverClassName">
			"org.postgresql.Driver"
		</property>
		<property name="URL">
			"jdbc:postgresql://localhost:5432/neko"
		</property>
		<property name="user">"neko"</property>
		<property name="password">"neko"</property>
	</component>
	<component name="xaDataSourceOra"
		class="org.seasar.extension.dbcp.impl.XADataSourceImpl">
		<property name="driverClassName">
			"oracle.jdbc.driver.OracleDriver"
		</property>
		<property name="URL">
			"jdbc:oracle:thin:@localhost:1521:neko"
		</property>
		<property name="user">"neko"</property>
		<property name="password">"neko"</property>
	</component>


	<!--	connectionPool コンポーネント	-->
	<component name="connectionPoolPos"
		class="org.seasar.extension.dbcp.impl.ConnectionPoolImpl">
		<property name="xADataSource">xaDataSourcePos</property>
		<property name="timeout">30</property>
		<property name="maxPoolSize">10</property>
		<destroyMethod name="close"/>
	</component>
	<component name="connectionPoolOra"
		class="org.seasar.extension.dbcp.impl.ConnectionPoolImpl">
		<property name="xADataSource">xaDataSourceOra</property>
		<property name="timeout">30</property>
		<property name="maxPoolSize">10</property>
		<destroyMethod name="close"/>
	</component>


	<!--	Datasouece コンポーネント	-->
	<component name="dataSourcePos" class="org.seasar.extension.dbcp.impl.DataSourceImpl">
		<arg>connectionPoolPos</arg>
	</component>

	<component name="dataSourceOra" class="org.seasar.extension.dbcp.impl.DataSourceImpl">
		<arg>connectionPoolOra</arg>
	</component>

</components>

ここでは、OraclePostgresqlという2つのXAなDataSourceを設定して、それを2つのConnectionPoolに明示的にDIしていて、そのConnectionPoolそれぞれを、これまた2つのdataSourceに明示的にDIしている。普通はDataSourceImplとConnectionPoolImplへのDIは、自動インジェクションされるので明示的に書く必要はないのですが、この場合はこうするのかなと思ってこうしたのでした。


そんで、このDataSourceImplを使っているコードはこんな感じにしていたみたいです(昔のことなので忘れてます)

public class EmployeeDaoImpl implements EmployeeDao {

	private DataSource dataSourceOra;
	private DataSource dataSourcePos;


	public EmployeeDaoImpl(){
		dataSourcePos = (DataSource) SingletonS2ContainerFactory.getContainer().getComponent("dataSourcePos");
		dataSourceOra = (DataSource) SingletonS2ContainerFactory.getContainer().getComponent("dataSourceOra");
	}

	public void insertPos(Employee employee) throws SQLException {

		int result = 0;
		Connection con = dataSourcePos.getConnection();


		try{
			PreparedStatement ps = con.prepareStatement("insert into emp2 values(?,?)");
			try{
				ps.setInt(1, employee.getEmpno());
				ps.setString(2, employee.getEname());
				result = ps.executeUpdate();
			}finally{
				ps.close();
			}
		}finally{
			con.close();
		}

	}

	public void insertOracle(Employee employee) throws SQLException {

		int result = 0;
		Connection con = dataSourceOra.getConnection();


		try{
			PreparedStatement ps = con.prepareStatement("insert into emp2 values(?,?)");
			try{
				ps.setInt(1, employee.getEmpno());
				ps.setString(2, employee.getEname());
				result = ps.executeUpdate();
			}finally{
				ps.close();
			}
		}finally{
			con.close();
		}

	}


これで、ビジネスロジックのクラスのメソッドにS2TXのRequiredInterceptorでトランザクション境界を設定してあげると

    public void twoCommit(Employee employee) throws SQLException {
		dao_.insertPos(employee);
		dao_.insertOracle(employee);
	}

    public void twoCommitLogicError(Employee employee) throws SQLException {
		dao_.insertPos(employee);
		dao_.insertOracle(employee);
		throw new RuntimeException("ロジックエラー発生!");
	}

このメソッドの始まりと終わりが境界になり、ちゃんと2つのリソースにたいしてひとつのトランザクションを実現することができてます。もんだいは、どういう仕組みでそうなっているのかってことで、内部的にはもちろんJDBCのsetAutoCommit()とかは呼ばれていたりして、その辺の流れを僕みたいなもが読んでいくのは結構大変だったりしたのですが結構根気よくやりました。その辺の仕組みについてはまた別の機会に整理したいと考えてます。


しかし今回、これをdiconファイル等含めてコピーして、2.4.34で動かしてみたら、xaDatasourceが複数あるよってエラーで落ちてしまったので、明示的に指定してあげててもどっかでなんかが起こってしまっているようです。まだ調べ切れていません。


それでどうしようかなって考えてこちらをみて、これ使ってやってみようとしたらできました。


このサイトにあるように2つのdataSourceにたいして2つのdiconファイルを用意します。


oracle.dicon

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN"
	"http://www.seasar.org/dtd/components24.dtd">
<components>
	<include path="jta.dicon"/>
	<include path="jdbc-extension.dicon"/>

	<component name="xaDataSourceOra"
		class="org.seasar.extension.dbcp.impl.XADataSourceImpl">
		<property name="driverClassName">
			"oracle.jdbc.driver.OracleDriver"
		</property>
		<property name="URL">
			"jdbc:oracle:thin:@localhost:1521:neko"
		</property>
		<property name="user">"neko"</property>
		<property name="password">"neko"</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="oraDataSource" class="org.seasar.extension.dbcp.impl.DataSourceImpl"/>

</components>

postgres.dicon

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN"
	"http://www.seasar.org/dtd/components24.dtd">
<components>
	<include path="jta.dicon"/>
	<include path="jdbc-extension.dicon"/>

	<component name="xaDataSourcePos"
		class="org.seasar.extension.dbcp.impl.XADataSourceImpl">
		<property name="driverClassName">
			"org.postgresql.Driver"
		</property>
		<property name="URL">
			"jdbc:postgresql://localhost:5432/neko"
		</property>
		<property name="user">"neko"</property>
		<property name="password">"neko"</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="posDataSource" class="org.seasar.extension.dbcp.impl.DataSourceImpl"/>

</components>

それら二つをjdbc.diconにインクルードします。
jdbc.dicon

<?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="postgres.dicon"/>
    <include path="oracle.dicon"/>
	<component name="dataSource" class="org.seasar.extension.datasource.impl.SelectableDataSourceProxy"/>
</components>


dataSourceを入れ替える感じのinterceptorを作ってあげます。

package sample.transaction.interceptor;

import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.seasar.extension.datasource.DataSourceFactory;

public class InitializeDaoInterceptor implements MethodInterceptor {

	private DataSourceFactory dataSourceFactory;

	public void setDataSourceFactory(DataSourceFactory dataSourceFactory) {
		this.dataSourceFactory = dataSourceFactory;
	}

	public Object invoke(MethodInvocation invocation) throws Throwable {
		String currentName = dataSourceFactory.getSelectableDataSourceName();
		try {
			String dataSourceName = getDataSourceName(invocation);
			dataSourceFactory.setSelectableDataSourceName(dataSourceName);
			return invocation.proceed();
		} finally {
			dataSourceFactory.setSelectableDataSourceName(currentName);
		}
	}

	public String getDataSourceName(MethodInvocation invocation) {
		if(invocation.getMethod().getName().endsWith("Oracle"))
			return "ora";
		else
			return "pos";
	}
}


このinterceptorをコンテナに登録します。ここでは自動登録機能は使っていません。
aopex.dicon

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container//EN"
"http://www.seasar.org/dtd/components.dtd">
<components namespace="aopex">
	<include path="jdbc-extension.dicon"/>

	<component name="initializeDaoInterceptor" class="sample.transaction.interceptor.InitializeDaoInterceptor"/>
</components>


customizer.diconでDaoをカスタマイズするよう設定します。
customizer.dicon

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN"
	"http://www.seasar.org/dtd/components24.dtd">
<components>
	<include path="default-customizer.dicon"/>

	<component name="txAttributeCustomizer" class="org.seasar.framework.container.customizer.TxAttributeCustomizer"/>

	<component name="initializeDaoCustomizer" class="org.seasar.framework.container.customizer.AspectCustomizer">
		<property name="interceptorName">"aopex.initializeDaoInterceptor"</property>
	</component>

	<component name="businessCustomizer" class="org.seasar.framework.container.customizer.CustomizerChain">
		<initMethod name="addCustomizer">
        	<arg>txAttributeCustomizer</arg>
		</initMethod>
	</component>

	<component name="daoCustomizer" class="org.seasar.framework.container.customizer.CustomizerChain">
		<initMethod name="addCustomizer">
            <arg>initializeDaoCustomizer</arg>
        </initMethod>
	</component>

</components>


Daoのコードはこんな感じに

public class EmployeeDaoImpl implements EmployeeDao {

	private DataSource dataSource;

	public EmployeeDaoImpl(DataSource dataSource){
		this.dataSource = dataSource;
	}

	public void insert(Employee employee) throws SQLException {

		int result = 0;
		Connection con = dataSource.getConnection();

		try{
			PreparedStatement ps = con.prepareStatement("insert into emp2 values(?,?)");
			try{
				ps.setInt(1, employee.getEmpno());
				ps.setString(2, employee.getEname());
				result = ps.executeUpdate();
			}finally{
				ps.close();
			}
		}finally{
			con.close();
		}

	}

        public void insertOracle(Employee employee) throws SQLException {
		int result = 0;
		Connection con = dataSource.getConnection();

		try{
			PreparedStatement ps = con.prepareStatement("insert into emp2 values(?,?)");
			try{
				ps.setInt(1, employee.getEmpno());
				ps.setString(2, employee.getEname());
				result = ps.executeUpdate();
			}finally{
				ps.close();
			}
		}finally{
			con.close();
		}

	}

この場合、insert()メソッドでは、postgresにinsertにいくし、insertOracle()メソッドではOracleにinsertにいきます。これでビジネスロジッククラスでは先ほどと同様にS2TXでInterceptorを設定してあげれば分散トランザクションが実現できます。


次回は分散トランザクションSeasar2がどのように実現しているのかについて整理できればしたいと思います。なかなか大変なんですが。