とらんざくしょん
じゃ次は「簡単なサービスクラスを書いてみてトランザクションをかけてみよう」というお題。
その前にトランザクションってナニ?e-wordで検索してみると・・・
「関連する複数の処理を一つの処理単位としてまとめたもの。」
と書いてある。ふ〜ん。更にトランザクション処理で検索すると
「関連する複数の処理を一つの処理単位にまとめて管理する処理方式。トランザクションとして管理された処理は「すべて成功」か「すべて失敗」のいずれかであることが保証される。」
と書いてある。なるほど。
つまり、トランザクションとしてまとめられた処理を実行中にコケたらトランザクション開始前の状態に戻さなければならないのか。
JDBCを使っている場合はJTA(Java Transaction API)を使うことでトランザクションを制御できる・・・らしい。。。
んで、JTAにはトランザクションマネージャーたるものがあって、それでトランザクションを制御できる・・・らしい。。。
Seasarでは予めJTAを組み込んであるらしく、しかもAOPでトランザクションをかけることができる・・・らしい。。。
j2ee.diconを見ると
<component name="transactionManager" class="org.seasar.extension.jta.TransactionManagerImpl"/>
という記述がある。なるほど、これでJTAのトランザクションマネージャの実装をコンテナから取得できるな。でもSeasarのドキュメントを見ると、
- S2Txの機能を使って、POJO(普通のJavaのクラス)に対して、Aspectでトランザクションの自動管理機能を組み込むことができます。EJBコンテナが提供するようなトランザクション管理機能をPOJOに対して透明に組み込むことができるのです。
なんて書いてある。確かにj2ee.diconには
<component name="requiredTx" class="org.seasar.extension.tx.RequiredInterceptor"/> ・・・
なんてモノが一通り用意されている。ここのソースを見ると引数のメソッドに対してJTAのトランザクション処理を実現している・・・ように見える。
んで、この場合はどっちを試せばいいんだ?
よくわからんので、とりあえずSeasarで実装されているAOPを試してみよう。
まずは簡単なサービスということで、ユニットデータの範囲を指定すると範囲内のデータを削除するメソッドを書く。
redrisefirm.seasar.s2dao.service.UnitDataDeleteProxy
public class UnitDataDeleteProxy { private UnitDataDao m_unitDataDao = null; public UnitDataDeleteProxy(UnitDataDao unitDataDao) { this.m_unitDataDao = unitDataDao; } public void rangeDelete(int startId, int endId) throws Exception { UnitDataEntity deleteEntity = null; for (int count = startId; count <= endId; count++) { deleteEntity = m_unitDataDao.getUnitDataEntity(count); if (deleteEntity != null) { m_unitDataDao.delete(deleteEntity); } else { throw new Exception("指定idのデータが取得できませんでした"); } } } }
つっこみどころ満載なのは気にしない、ということで・・・(汗)
んで、こいつにアスペクト+コンストラクタインジェクションをかけるためにdao.diconに以下の記述を追記。
<component name="rangeDelete" class="redrisefirm.seasar.s2dao.service.UnitDataDeleteProxy"> <arg>dao.UnitData</arg> <aspect>j2ee.requiresNewTx</aspect> </component>
んで、このサービスのテストを書く。
public class UnitDataDeleteProxyTest extends S2TestCase { protected void setUp() throws Exception { include("redrisefirm/seasar/s2dao/dao.dicon"); } public void testRangeDelete() { readXlsReplaceDb("redrisefirm/seasar/s2dao/util/getUnitDataResult.xls"); UnitDataDeleteProxy dataDeleteProxy = (UnitDataDeleteProxy) getComponent("rangeDelete"); try { //正常に削除できる範囲を指定する dataDeleteProxy.rangeDelete(3, 4); } catch (Exception e) { e.printStackTrace(); fail("削除中に予期せぬエラーが発生しました"); } UnitDataDao unitDataDao = (UnitDataDao) getComponent(UnitDataDao.class); List answerList = unitDataDao.getAllEntity(); assertEquals("正常に削除されていません", 3, answerList.size()); } public void testRangeDeleteIllegalRange() { readXlsReplaceDb("redrisefirm/seasar/s2dao/util/getUnitDataResult.xls"); UnitDataDeleteProxy dataDeleteProxy = (UnitDataDeleteProxy) getComponent("rangeDelete"); try { //わざとこける範囲を指定する dataDeleteProxy.rangeDelete(3, 7); } catch (Exception e) { UnitDataDao unitDataDao = (UnitDataDao) getComponent(UnitDataDao.class); List answerList = unitDataDao.getAllEntity(); assertEquals("トランザクションがかかっていません", 5, answerList.size()); } catch (Throwable e) { e.printStackTrace(); fail(); } } }
んで、書けたところで実行。正常系は通るが異常系が通らない・・・。
リストの件数を見ると2件ということでトランザクションがロールバックされていない。う〜ん、と悩んでいるところで師匠登場。
「ライブラリのorg.seasar.extension.tx.RequiresNewInterceptorクラスにブレークをはってみ〜」
ということで張ってテストを実行。すると華麗にスル〜〜。
ってことでdiconの書き方が悪いのではということでS2AOPのドキュメントを引く。すると↓のような記述が
- pointcut属性を指定しない場合、すべてのメソッドが対象になるわけではありません。実装しているインターフェースのすべてのメソッドが対象になります。すべてのメソッドを対象にするには、pointcut属性に".*"と指定します。
なるほど。確かにdao.diconにはpointcutの指定をしてないね。ということで↓のように書き換える。
<component name="rangeDelete" class="redrisefirm.seasar.s2dao.service.UnitDataDeleteProxy"> <arg>dao.UnitData</arg> <aspect pointcut="rangeDelete">j2ee.requiresNewTx</aspect> </component>
これで実行したら通った。よしよし。
さて、これでS2AOPを使ってトランザクションをかけるのは出来た。
もう一つはコンテナからトランザクションマネージャーを取得して手動でトランザクション制御を試すのだが、よく考えたらそれは今回の主旨に外れる気がしてきた。
なぜって、今回はSeasarの勉強が主旨だからだ。(勿論、JTAによるトランザクション制御を自分で実装してみることは意味はあると思うが。)
自分で実装したものよりかはライブラリの実装の方が遥かに確実だし、実際にS2使う時も在りモノを使うわけだしね。(本音は面倒くさいというウワサが・・)
というわけでこんなもんかな。結局今日はS2DAOで終わってしまった。
今日まででS2DAOはだいたい試したので明日からはUIコンポーネントでS2Strutsかなぁ。