4.業務データとActiviti側のデータを跨いだトランザクション管理の実現
「3.アプリケーションの開発」で開発したアプリケーションは、トランザクション管理において問題が残っています。
その問題とは、「業務データ(交通費申請テーブル)とActiviti側で管理されているデータで、別々のトランザクション管理になっていること」です。
このため、交通費申請のデータ更新が成功した後でActiviti側のデータ更新が失敗した場合、データの整合性が失われてしまいます。
今回、上記問題の解決策として「業務側のトランザクションマネージャをActiviti側と共用する」という方法を紹介したいと思います。
1)データアクセス設定の確認
業務データ側
今回のアプリでは、Seasar2のJTA実装であるS2JTAを利用しています。
以下のファイルに、データアクセスに関する設定を記述します。
<!-- for H2 -->
<component name="xaDataSource"
class="org.seasar.extension.dbcp.impl.XADataSourceImpl">
<property name="driverClassName">
"org.h2.Driver"
</property>
<property name="URL">
"jdbc:h2:tcp://localhost:9092/demo;SCHEMA=ACTIVITI_TUTORIAL"
</property>
<property name="user">"sa"</property>
<property name="password">""</property>
<destroyMethod>
@org.seasar.framework.util.DriverManagerUtil@deregisterAllDrivers()
</destroyMethod>
</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" />
Activiti側
以下のファイルに設定を記述しています。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee"
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-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-3.0.xsd">
<!-- process engine configuration -->
<bean id="processEngineConfiguration"
class="org.activiti.engine.impl.cfg.StandaloneProcessEngineConfiguration">
<property name="jdbcUrl"
value="jdbc:h2:tcp:localhost/demo;SCHEMA=ACTIVITI_TUTORIAL;" />
<property name="jdbcDriver" value="org.h2.Driver" />
<property name="jdbcUsername" value="sa" />
<property name="jdbcPassword" value="" />
<property name="databaseSchemaUpdate" value="false" />
<property name="jobExecutorActivate" value="false" />
<property name="history" value="full" />
</bean>
</beans>
Activitiのプロセスエンジン生成時に参照される情報です。
org.activiti.engine.impl.cfg.StandaloneProcessEngineConfigurationを使用しているため、プロセスエンジンのトランザクション管理は、業務データ側から独立しています。
2)データアクセス設定の修正
- 修正対象ファイル:src/main/resources/activiti.cfg.xml
以下のように修正してください。
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:jee="http://www.springframework.org/schema/jee"
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-3.1.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee-3.0.xsd">
<!-- (1)jndi lookup S2JTA Transaction Manager -->
<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>
<!-- (2)jndi lookup S2 DataSource -->
<jee:jndi-lookup id="s2DataSource" jndi-name="jdbc/DataSource">
<jee:environment>
java.naming.factory.initial=org.seasar.extension.j2ee.JndiContextFactory
</jee:environment>
</jee:jndi-lookup>
<!-- (3)process engine configuration -->
<bean id="processEngineConfiguration"
class="org.activiti.cdi.CdiJtaProcessEngineConfiguration">
<property name="dataSource" ref="s2DataSource" />
<property name="transactionManager" ref="s2TxManager" />
<!-- using externally managed transactions -->
<property name="transactionsExternallyManaged" value="true" />
<property name="databaseSchemaUpdate" value="false" />
</bean>
</beans>
【解説】修正後のactiviti.cfg.xml
(1)jndi lookup S2JTA Transaction Manager
JNDIを利用して、業務データ側で使っているものと同じ「S2JTA TransactionManager」を取得しています。
(2)jndi lookup S2 DataSource
JNDIを利用して、業務データ側で使っているものと同じ「DataSource」を取得しています。
(3)process engine configuration
Activitiのプロセスエンジン生成時に参照される情報です。
org.activiti.cdi.CdiJtaProcessEngineConfigurationを使用するように変更しています。
これにより、JTAによるトランザクション管理をactiviti側で行うことができるようになります。また、dataSourceとtransactionManagerのプロパティに(1)(2)で取得したものを指定しています。
3)修正後の動作確認
申請時([申請]ボタン押下処理)に、Activiti側で例外が発生するようコードを変更してみましょう。
tutorial.action.paperwork.TransportationInputAction#doApplyメソッド内でruntimeService.startProcessInstanceByKeyの第1引数に存在しないプロセス定義ファイルのIDを指定してみます。
runtimeService.startProcessInstanceByKey(
"xxxxxxxxxxx", // 存在しないプロセス定義ファイルのIDを指定
Long.toString(transportation.transportationId),
variableMap);
上記コードをもとに交通費申請を行うと、Activiti側で例外が発生します。
申請処理は、「(1)交通費申請テーブルへのデータ登録」→「(2)Activiti側のプロセス開始」という順番で実行されますが、(2)で例外が発生しても、(1)のデータ登録がロールバックされていることを確認できます。
今回のアプリケーションは、Activiti APIの説明にフォーカスするため、例外処理は一部簡略化した実装となっています。ご了承ください。
5.おわりに
今回は、「交通費申請」という単純で分かりやすい業務を対象にワークフローアプリケーションを開発してみました。Activitiで用意されているAPIを利用すると、「プロセス定義ファイル」どおりにフローの制御が簡単に行えることがお分かりいただけたと思います。
ただし、今回開発したアプリケーションは、実運用を考えるとまだまだ解決しなければならない課題が残っています。例を挙げると、以下のようなものです。
- 自分に承認依頼が届いている申請データのみを表示させるなどの画面の実装
- 「差戻し」「取下げ」など、ワークフローアプリにおいて一般的に登場しそうな機能の実装
上記課題に対する具体的な解決策は、また別の機会に掲載したいと考えていますので、ご期待ください。
次回のテーマは、『ScalaのWebアプリケーションフレームワークである「Play2」』です。
まずは、開発環境の準備を中心に説明させていただき、以降の連載で「Play2を利用した簡単なアプリケーション」を開発したいと思います。

