seasar2にトランザクションをかける

トランザクションが必要ってことは、いまさら説明する必要もないので省略します。

そういう前提があるので最近のフレームワークは自動でトランザクションを開始して正常終了したらコミット、異常が発生したらロールバックするという仕組みを持っています。
Seasar2にもあるので、設定してみたいと思います。
前提としてこれまでの続きとなります。

AOPの設定

http://s2container.seasar.org/2.4/ja/aop.html#S2AOP

AOPとはAspect Oriented Programmingの略で、 複数のクラスに分散するロジックをモジュールとして抽出し、 外側から織り込む手法です。

Interceptorとかって呼ばれることもあります(厳密には違うモノを指すのかも知れませんが)。

これまでは、この設定をやってこなかったのでまずはこの設定の動作確認からしたいと思います。
まずは、std-customizer.dicon(s2-framework.jar)で使用されているInterceptorが定義されているdiconファイルを読み込みます。

app.diconを編集
<components>
  <include path="aop.dicon"/>
  <include path="j2ee.dicon"/>
  <include path="s2jdbc.dicon"/>
</components>

aop.dicon(s2-framework.jar)にはSeasar2で用意されているInterceptorが、j2ee.dicon(s2-extension.jar)にはJ2EEトランザクション関係のInterceptorが定義されています。

準備はこれだけです。
では、実際に試してみます。
今回は、アクションクラスのexecuteメソッドにTraceInterceptorを適用してみます。

customizer.dicon (componentsタグを置き換えてください)
<components>
    <include path="default-customizer.dicon"/>

  <component name="actionCustomizer" class="org.seasar.framework.container.customizer.CustomizerChain">
    <initMethod name="addAspectCustomizer">
      <arg>"aop.traceInterceptor"</arg>
      <arg>"execute"</arg>
    </initMethod>
  </component>
</components>

適用されているかどうかを確認するためにロギング設定を一時的に変えます。
例えばlog4Jを使っている場合は、既存の設定によりますが、対象のログレベルをDEBUGまで下げてください。

log4j.rootLogger=DEBUG, A1 # こう書き換えるか
log4j.logger.org.seasar=DEBUG # この1行を追加してもOK

適当なアクションクラスを作って実行してください。今回は以前作ったSeasarTestActionにリクエストしてみます。
その結果、次のようなログが表示されているはずです。

2013-00x-xx xx:xx:xx [http-8080-1] DEBUG org.seasar.framework.aop.interceptors.TraceInterceptor  - BEGIN kamegu.action.SeasarTestAction#execute()
~略~
2013-0x-xx xx:xx:xx [http-8080-1] DEBUG org.seasar.framework.aop.interceptors.TraceInterceptor  - END kamegu.action.SeasarTestAction#execute() : success

トランザクションの設定

まずは、トランザクション境界をどこに設定するかという話になります。
http://d.hatena.ne.jp/gekimu/20100211
今回はサービスクラスをまだ作ってないということもあるので、アクションクラスに対して設定することにします。
ユニットテスト等を考えたときにはサービスクラスに(もしくは両方)設定したほうがいいかもしれません。
http://s2container.seasar.org/2.4/ja/tx.html

customizer.dicon
<components>
    <include path="default-customizer.dicon"/>

  <component name="actionCustomizer" class="org.seasar.framework.container.customizer.CustomizerChain">
    <initMethod name="addAspectCustomizer">
      <arg>"j2ee.requiredTx"</arg>
      <arg>"execute"</arg>
    </initMethod>
  </component>
</components>

これで実行すると次のようなログが出力されているはずです

2013-0x-xx xx:xx:xx [http-8080-1] DEBUG org.seasar.extension.jta.TransactionImpl  - トランザクションを開始しました。tx=[FormatId=4360, GlobalId=1366967688642/0, BranchId=]
~略~
2013-0x-xx xx:xx:xx [http-8080-1] DEBUG org.seasar.extension.jta.TransactionImpl  - トランザクションをコミットしました。tx=[FormatId=4360, GlobalId=1366967688642/0, BranchId=]

では、ここでExceptionが発生するようにexecute()メソッドの中身を替えてみます。
すると次のようなログが出力されるはずです。

2013-0x-xx xx:xx:xx [http-8080-1] DEBUG org.seasar.extension.jta.TransactionImpl  - トランザクションを開始しました。tx=[FormatId=4360, GlobalId=1366968137464/0, BranchId=]
~略~
2013-0x-xx xx:xx:xx [http-8080-1] DEBUG org.seasar.extension.jta.TransactionImpl  - トランザクションロールバックしました。tx=[FormatId=4360, GlobalId=1366968137464/0, BranchId=]

詳細なトランザクション設定

上の例だとexecuteメソッドにのみトランザクションを設定していました。
ただ、実際には他のメソッドにも設定する必要があるはずです。
一番安全なのは、次のようにして全メソッドに対してトランザクションを設定するようにすることですが、

    <initMethod name="addAspectCustomizer">
      <arg>"j2ee.requiredTx"</arg>
      <arg>".*"</arg>
    </initMethod>

これだと、余計なものにまでInterceptorが適用されるため、あまり効率がよくありません。
(prepare()とか、validate()とかtoString()とか)
本来はデータの更新が生じるようなメソッドに対してのみ行えば十分です。
あとは、命名規則だと思います。
命名規則を作って、最低限のルールを守れるならば対象のメソッドを指定すればよいと思います。
今回は、更新処理の発生するメソッド名は、「update~~」「create~~」「insert~~」「save~~」「register~~」「delete~~」のみとします。

    <initMethod name="addAspectCustomizer">
      <arg>"j2ee.requiredTx"</arg>
      <arg>"update.*, create.*, insert.*, save.*, register.*, delete.*"</arg>
    </initMethod>

トランザクション外で更新処理が実行された場合

本来はこのような処理は行われるべきではありません。(例外的にログデータの保存等の目的でオートコミットを許す場合もあると思います)

ただし、上の設定だと何かのミスで更新処理にトランザクションがかかっていないという状態が起こりえます。
その場合は例外を発生させて更新のSQLを発行しないようにします。

考え方としては、jdbcManagerの更新系メソッドを実行する場合はトランザクションが開始されている必要があるということです。

s2jdbc.dicon

こんな感じになります。

    <include path="j2ee.dicon"/>
    <include path="jdbc.dicon"/>
    <include path="s2jdbc-internal.dicon"/>
    <component name="jdbcManager" class="org.seasar.extension.jdbc.manager.JdbcManagerImpl">
        <property name="maxRows">0</property>
        <property name="fetchSize">0</property>
        <property name="queryTimeout">0</property>
        <property name="dialect">postgre81Dialect</property>
        <property name="allowVariableSqlForBatchUpdate">true</property>
        
        <aspect pointcut="insert.*, update.*, delete.*">j2ee.mandatoryTx</aspect>
    </component>

変えたのは、j2ee.diconのインクルードと、aspectの設定です。
j2ee.mandatoryTx」というのは、トランザクションが既に開始されてなければエラーにするコンポーネントです。

ためしにSeasarTestActionのexecuteメソッド(トランザクション外)で更新処理を書いて見ます。

	public String execute() throws Exception {
		employees = jdbcManager.from(Employee.class).orderBy("id != 2, id").getResultList();
		Employee employee = employees.get(0);
		employee.name = "test";
		jdbcManager.update(employee).execute();
		return SUCCESS;
	}

結果は、、、例外が発生しました。

2013-04-26 18:54:21,434 [http-8080-4] ERROR org.apache.struts2.dispatcher.Dispatcher  - Exception occurred during processing request: [ESSR0311]トランザクションが開始されていません
org.seasar.framework.exception.SIllegalStateException: [ESSR0311]トランザクションが開始されていません
	at org.seasar.extension.tx.adapter.JTATransactionManagerAdapter.mandatory(JTATransactionManagerAdapter.java:93)
	at org.seasar.extension.tx.MandatoryInterceptor.invoke(MandatoryInterceptor.java:44)
	at kamegu.common.jdbc.MyJdbcManagerImpl$$EnhancedByS2AOP$$74ee7e66$$MethodInvocation$$update2.proceed(MethodInvocationClassGenerator.java)
~以下略~

この設定が理由でこの例外が発生するのはプログラムの不備だと言っていいと思いますので、特にこのハンドリングは不要かなと思います。