2008-06-29

ファイル型データベースをリモートで使う

普段 Macや Linuxで仕事をしている人間にとっては、Windows機で作業をするのが苦痛だ。
しかし世の中には dBASEや Microsoft Accessで作られたデータベースというものがあって、時にはそういった「ファイル型データベース」を読み書きする仕事もあったりする。

Microsoft SQL Serverのようなクライアントサーバ型データベースであれば、それがたとえ Windowsでしか動作しなくとも JDBCを使ってリモートから利用できるため手元の Macで開発作業が出来るわけだが、ファイル型データベースの場合はそうもいかない。

最近 dBASEファイルをどうしても読み書きする必要があって、仕方ないので Virtual JDBCを使って Windows機上にあるデータベースファイルをリモートからアクセス出来るようにしてみたのが下記。
(なおWindowsには dBASEの ODBCドライバが標準で搭載されている)



Windowsで作業したくないばかりにこんなことまでやるかという気もしないでもないが、それはさておき。

(a) JDBC URLとして サーブレットのURLと vjdbcに設定したDB名を与える。
"jdbc:vjdbc:servlet:http://my-windows-host:8080/vjdbc/vjdbc,mydb"

(b) web.xmlにて vjdbcサーブレットをマッピングする。

<servlet>
<servlet-name>VJdbcServlet</servlet-name>
<servlet-class>
de.simplicit.vjdbc.server.servlet.ServletCommandSink
</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>VJdbcServlet</servlet-name>
<url-pattern>/vjdbc</url-pattern>
</servlet-mapping>

(c) vjdbcがリクエストを転送する先となるターゲットデータベースの設定を /WEB-INF/vjdbc-config.xmlに記述する

(d) (vjdbc-config.xml内) JDBC-ODBCブリッジのURLで ODBCデータソース名を指定
"jdbc:odbc:mydbase"

(e) Windowsの「管理ツール」→「ODBC」で dBASE用のデータソースを設定する(*.DBFファイルの置いてあるフォルダを指定)。データソース名は (d)で指定した物と合わせる。

この構成で JDBCを使って dBASEファイルへリモートアクセスが出来るようになった。
・・・なったのだが、トランザクション操作をしようとするとこの通り・・・
java.sql.SQLException: 
[Microsoft][ODBC dBase Driver]オプションの機能は実装されていません。

Windowsに標準でくっついてるようなドライバにそこまで期待すんなということですな。

ちなみに dBASEの JDBCドライバ(商用)もあって、5同時アクセスライセンスが 150ドルで買える。お金を取るくらいだからもちろんトランザクションもサポートしているに違いない。真面目に今後もdBASEと付き合っていく必要がある、かつ、ライセンス料を顧客に負担してもらえる場合はこれを使ったほうが無難だと思う(試してないけど)。

JDBC-ODBCブリッジを使ってファイル型データベースにアクセスする用途以外でも、何らかの理由で データベースアクセスをHTTPで中継したい場合に vjdbcは役に立つだろう。

Macと Xenと VNCクライアント

Xenで動作する仮想マシンの画面を表示するインターフェイスとしては、SDLとVNCの2種類がある。
SDLの場合、(特別な設定をしなければ)画面表示に X Window が用いられる。XなのでSSHを経由してリモートに画面を表示させることも出来るが、画面を閉じると仮想マシンも強制終了してしまうという凶悪な仕様なため主にリモートでヘッドレス運用をする場合は使えない。
VNCと比べた場合の利点は、(ローカルマシンで画面を表示するなら)高速だろうという点である。

いっぽう、VNCを使用する場合、仮想マシンごとに 5900番以降の空いているポートを用いて VNC接続の待ち受けが行われる(VMの設定ファイルでポート番号を固定することもできる)。
リモート運用にはこれが最も適しているのだが、ひとつ重大な問題がある。

Mac OS Xでまともに動く唯一のフリーな VNCクライアントである Chicken of the VNC (a.k.a. cotvnc) と Xenの VNCサーバの相性が悪く、使えないのだ(画面が全く表示できない又は乱れる、エラーで接続が切れる)。

cotvncが悪いのか Xenに内蔵されているVNCサーバが悪いのか(もしくは両方なのか)知らないが、Xenのメーリングリストで「cotvncでちゃんとVMの画面が表示できないよー」という投稿が過去に一度あったものの解決されずに今に至っていることや cotvncの開発が止まっていることから、もはや状況は絶望的かと思われた。
X over SSHを使って dom0上の vncviewerを動かすという手は使えるのだが、ものすごく遅いし受け付けてくれないキーがあったりするしたまにバグって切れたりするので絶望的な状況であることに変わりはなかったと言えよう。

そんな時に現れた救世主、
Redstone Software社の Vine Viewer
Vineといっても昔あった Linuxのそれとは関係ないし、Windows互換レイヤのソフトウェアでもない。

・XenのVNCサーバと接続できる
・VNC over SSHサポート(出先からのアクセスも安心)
・画面の録画ができる(QuickTime形式の動画ファイルになる)

これでバーチャルマシンの管理がしやすくなるなあ。
値段は 35ドル。

2008-06-25

SpringでJNDIを提供する 続き

以前のエントリで、XBeanの成果物を拝借して Springで JNDIを提供させる方法を示したのだが、この org.apache.xbean.spring.jndi.SpringInitialContextFactoryは Beanファクトリとして ApplicationContextのバリアントを使っていないためそのままだと PropertyPlaceholderConfigurerなどを使って Bean定義ファイル内の値をパラメタ化することが出来ない。
(ApplicationContextの子孫は、BeanFactoryPostProcessorを自動処理するのだが、それ以外の BeanFactoryではプログラムコードで明示的に postProcessBeanFactoryを呼んでやらなければ処理されない)
ApplicationContextはあくまで「アプリケーションの」コンテキストであるため JNDIの提供に適用しない事自体は正しいと思うが、せめてpostProcessBeanFactoryを呼んで欲しいところだ。
この問題(?)は2005年に報告されているが完全に無視という形のようだ。こんなのただの副産物だから本来の用途にさえ使えればよくてそれ以外興味ないねということだろうか。

仕方がないのでSpringInitialContextFactoryの派生クラスを作って BeanFactoryの生成を行っているメソッドをオーバーライドし、postProcessBeanFactoryの呼び出しを追加することにした。

package net.stbbs.xbean.spring.jndi;

import java.util.Map;

import org.apache.xbean.spring.context.impl.XBeanXmlBeanFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.core.io.Resource;

public class SpringInitialContextFactory extends
org.apache.xbean.spring.jndi.SpringInitialContextFactory {

@Override
protected BeanFactory createContext(Resource resource) {
BeanFactory factory = super.createContext(resource);
Map<String,Object> postProcessors =
((XBeanXmlBeanFactory)factory)
.getBeansOfType(BeanFactoryPostProcessor.class);
for (Map.Entry pp:postProcessors.entrySet()) {
((BeanFactoryPostProcessor)pp.getValue())
.postProcessBeanFactory((XBeanXmlBeanFactory)factory);
}
return factory;
}
}

jndi.propertiesは
java.naming.factory.initial=org.apache.xbean.spring.jndi.SpringInitialContextFactory

java.naming.factory.initial=net.stbbs.xbean.spring.jndi.SpringInitialContextFactory
に書き換えた。

ラベル:

DbUnitでテーブルをエクセル出力する

以前のエントリで、DbUnitを使ってフィクスチャによるデータベース事前条件の定義を含むユニットテストを行う方法を示した。

事後条件については、ターゲットとなるメソッドの実行が完了した後にSimpleJdbcTemplateなどでデータベースに対しクエリを行うことでチェックすることが出来る。理想としてはそのようにしてプログラムコードだけで事後条件を自動的に検査出来るほうが良い。よく練られた多角的なテストケースが存在することにより、プログラムは修正作業によるエンバグの発生から逃れやすくなるだろう。

しかし、テストケースに山のような数のselect文やループ構文を記述している時間が惜しい時があるのも事実だ。人間が表の状態を視覚的に確認することで、(ミスの検出確率は下がるものの)事後条件のチェックにかかる時間が節約出来ることも多々ある。

ところが、基本的にフィクスチャを用いるユニットテストは各テストメソッドの実行が1つのトランザクションとなっており、成功にせよ失敗にせよテストの完了時にトランザクションはロールバックされてしまいデータベース上のテーブルは空に(正確にはテスト実行前の状態に)戻ってしまうため、テストの実行後に GUIのフロントエンドなどを用いてデータベースの内容を確認しようとしても無駄である。

DbUnitには、テーブルの内容をファイルにダンプする機能があるので、それをテスト中の任意の時点で呼び出すことでこの問題を解決出来る。下記の例では、addCommentToArticle()メソッドの実行後に users, articles, commentsという3つのテーブルをエクセルファイルに出力している。エクセルファイル上では、1つのテーブルが1つのシートに割り当てられる。

public void testAddCommentToArticle()
{
target.addCommentToArticle(1, 2, "コメント");

// addCommentToArticle()実行後のデータベース内容をエクセルファイルにダンプ
IDatabaseConnection con = this.getConnection();
IDataSet dataset = con.createDataSet(
new String[]{"users","articles","comments"});
OutputStream os = new FileOutputStream("addCommentToArticle.xls");
XlsDataSet.write(dataset, os);
os.close();
con.close();
}

繰り返しになるが、ダンプされたエクセルデータを目視で確認し事後条件の検査をするという方法はユニットテストのあり方として理想的ではない。いうまでもなく、事後条件の検査はテストケースの中に assert文を使って記述されるべきだ。それをわかった上でどこを妥協するかは時間的制約との兼ね合いだろう。

事後条件の検査以外にもテーブルのダンプは有益な場合がある。たとえばロジックの実装者はデバッグのためにそれがあると便利なはずだ。事後条件の検査を自動で行う行わないに関わらず、テーブルのダンプを常に生成するようにしておいた方が実装者に優しいといえる。ただ、テストの最後でダンプを行うようにテストメソッドをコーディングした場合、途中のassertに引っかかるとダンプが行われないためデバッグ時には不便かもしれない。ダンプのタイミングは状況に応じて選ぶと良いだろう。

2008-06-22

Railsとsqlite3

RedmineをGentooで動かそうとしたら、"Unable to create the anonymous user"と言われて動かない。
Macだと(Railsを2.0.2に更新すれば)同じようにして動いたのにおかしいなと思ったら、どうやら RailsではSQLite 3.3.8以降をうまく扱えないらしい。
参考記事:Using SQLite3 with Rails

ところが Portageに入っているSQLite3は 一番古くても 3.4.1からだったりするもので。バカー!

仕方ないので MySQLで運用する事にした。めんどくせえなあ。

2008-06-15

スパースファイルの作り方

Portageに入っている iscsitargetがアップデートされ、今使っている Xenのカーネル(2.6.20)でもビルドできるようになったので MacProの TimeMachine用にiSCSIを立ち上げることにした。

iSCSIを使っていて思ったんだけど、LVMで作った論理ボリュームをSCSIディスクとして他のホストへ提供していると、そこに作られたパーティションを udevが勝手に認識してしまい面倒なことになったりする。これを避けるため、(少々のオーバーヘッドは覚悟の上で)ホスト側でいったんファイルシステムを作成しその中のイメージファイルをディスクとして提供することにした。

しかしながら、何百ギガバイトものファイルを ddで普通に作成するのにはかなり時間がかかる。なのでここはスパース(sparse, 疎な)ファイルを使うことにしたのでその方法をメモしておく。

私はこのようにして 400GBのスパースファイルを作成した。
dd if=/dev/zero of=timemachine-for-macpro.img bs=1M seek=409600 count=0

この操作は一瞬で終わるが、ls -l でファイルサイズを表示してみると確かに400GBの長さがあることになっている。
しかしこの段階では実際にはディスク容量を消費しておらず(それは dfで確認できる)、ファイルに対し書き込みが行われるにつれ順次ディスク容量を消費していくことになる。

巨大なファイルを作成するための時間と、初期のディスク容量を節約したい向きにはスパースファイルがお勧めである。
ただし運用中にフラグメントが発生しやすいであろうと想像できるため、パフォーマンスを稼ぎたい場合には向かないだろう。

2008-06-08

Springベースのユニットテストに DbUnitを組み合わせる方法

DbUnitは、JUnitにデータベース入出力のテストを行うための各種便利機能を提供するための拡張である。
ここでは、フィクスチャのロードを行う機能にスコープを絞って DbUnitを取り上げる。
フィクスチャとは、ユニットテストの事前条件となるテストデータである。

DbUnitを使うには、クラスパスに下記のjarファイルを追加する。

dbunit-*.jar
slf4j-api-*.jar
slf4j-jcl-*.jar

フィクスチャを使うユニットテストは、各テストメソッド毎に下記のような流れで実行される。

a) トランザクション開始

b) フィクスチャをデータベースにINSERT

c) テスト対象メソッドの呼び出し

d) 実行結果のチェック

e) トランザクションをロールバック

a, e は Springの機能で自動的に行われる。
c, d はテストケースの実装者がコーディングするとして、残る b を DbUnitで行うということになる。

テストケースの各トランザクションを常にロールバックする設定にしておけば、データベースは各テストメソッドが終了するたびにテストを開始する前の状態へ戻るため、INSERTされたフィクスチャも取り消される。そのため、固定のテストデータを用いても2回目以降のテストで重複キーの問題が発生するということは無い。

前回のエントリSpringとユニットテストで解説したような、Springによって自動的にトランザクション管理が行われるように構成された、つまり

@RunWith(SpringJUnit4ClassRunner.class)
@TransactionConfiguration
@Transactional
@ContextConfiguration

といったアノテーションの付加されたテストケースをさらに DbUnitへ対応させるには、テストケースに下記のような追加を行う。

1) DataSourceBasedDBTestCaseを継承させる
2) DataSource型のプロパティを持たせ、Springでインジェクトさせる
3) protected DataSource getDataSource()メソッドを実装する (単に 2のデータソースオブジェクトを返すだけで良い)。このメソッドはDbUnitが利用する。
4) @Beforeアノテーションを付けて、public void setUp()を実装する。単に super.setUp()をコールするだけで良い。
5) protected IDataSet getDataSet()をオーバーライドする。ここには、フィクスチャとなるデータセットオブジェクトを返す処理を記述する。このメソッドはDbUnitが利用する。

注意しておきたいのは 2 のデータソースである。Springと DbUnitを一緒に使うためのデータソースは TransactionAwareDataSourceProxyでなくてはならない
DbUnitはデータソースから取得した物理コネクションをフィクスチャのINSERTが完了した後に一度クローズしてしまう。Springのトランザクション管理下ではこれは不正な操作であり(DbUnitは Springの事など知らないのでやむを得ないのだが)、このため素のデータソースを DbUnitへ渡すとテスト本体が正常に実行できなかったりする。
Springの TransactionAwareDataSourceProxyでデータソースを包んでおけば、物理コネクションが適切な Proxyオブジェクトによって保護されるため、この問題を防ぐことができる。

デフォルトでは、DbUnitはフィクスチャをデータベースに INSERTする際にテーブルを一旦クリアしてしまう。もし接続先のデータベースがユニットテスト専用ならばこの動作は妥当だが、そうでない場合(同じデータベースを結合テストにも使用するなど)はテーブルをクリアしてほしくないだろう。ユニットテスト実行時にテーブルをクリアせずフィクスチャの INSERTだけを行うようにするには、protected DatabaseOperation getSetUpOperation() メソッドをオーバーライドして DatabaseOperation.INSERT を返すようにすると良い。
(但し、できれば結合テスト用のデータベースとユニットテスト用のデータベースは別々に設けたいところである。例えば Ruby on Railsではそのような習わしになっている)

フィクスチャは Excelで記述することが出来る(私が DbUnitを使いたいと思った唯一の理由がこれだ)。
Excelの各シートがデータベースのテーブルに対応する。シート名としてテーブル名を用いることによって、シートがテーブルと対応づけられる。
各シートの最上行にはカラム名を記載し、2行目以降に INSERTしたいデータを並べる。このようにして作成した Excelファイルを fixtures.xls として保存したなら、先の 5番にある protected IDataSet getDataSet() の実装はこのようになるだろう。

@Override
protected IDataSet getDataSet() throws Exception {
return new XlsDataSet(new File("fixtures.xls"));
}

ラベル:

2008-06-06

HttpServletRequestについて思う事

Googleウェブマスターツールなるものを使ってこのブログへ到達する人々の検索ワードなんかを調べてみると、HttpServletRequestについて調べに来ている人がかなり多いことに驚いたのでエントリを起こしてみた。というか、うちのブログに書いてある HttpServletRequestの情報って Apache CXFのサービスからサーブレットリクエストを参照する方法のメモとかごく一部の人しかわからない超ニッチな奴なんで、ガチでHttpServletRequestについて知りたくて検索した人からすればかなりガッカリというか意味不明だと思う。それじゃ申し訳ないので Googleで HttpServletRequestについて検索したとき上位に出てくるコンテンツについて思った事とか述べてみることにした。これでどう申し訳が立つのか知らないけど。

Java 2 Platform EE 1.3.1: インタフェース HttpServletRequest sdc.sun.co.jp



さすが Google先生、トップには最も妥当な物を出してくれます。

英語でもいいから最新の情報が欲しいという人は HttpServletRequest (Java EE 5)をどうぞ。そういう人はここに来ないと思うけど・・・

リクエスト情報の取得(HttpServletRequest) www.javadrive.jp



いつの記事かわからなかったのだけど Tomcat6の話と並べてあるしスクリーンショットが IE7なので最近だと思う。比較的どうでもいいことだけど、

・日本語パラメータの対応(getBytes)
・日本語パラメータの対応(setCharacterEncoding)

後者を解説するなら前者は何のために必要だったんだろう。とはいえ HttpServletRequestを単一キーワードで検索した人にとっては望みのページだろう。

HttpServletRequest - Java 入門 JSP サーブレット etc. sengoku.ath.cx



この記事も Tomcat5.5 や IE7を使って解説しているのでそんなに古くないと思われる。というか実際古くない。これも多分 HttpServletRequestを単一キーワードで検索した人にとっては望みのページかと。

JavaA2Z【HttpServletRequestとは】 www.kab-studio.biz



2005年の記事?にしては古いかな?setAttribute()でセットした値を JSP側で使うためには jsp:useBeanはもう使わず JSTLを使われたし。

@IT:Javaプログラミング・ワンポイントレクチャー:HttpServletRequestオブジェクトの役割 www.atmarkit.co.jp



2003年の記事。記事は古いが内容が役に立たなくなっているというわけでもない。が、HttpServletRequest を単一キーワードで検索した人にとっては、直接欲しい情報ではないだろう。

J2EE講座 [ リクエストの取得 HttpServletRequestインタフェース] www.site-cooler.com



あくまで HttpServletRequestのページなので getParameter() とかの解説が無い。検索でダイレクトにこのページへ飛んでしまった人はガッカリかもしれない(このページの前の、スーパーインターフェイスたる ServletRequestの解説ページに書いてある)が、一連のページはとてもまっとうな内容。まっとうな内容だけに、「目の前にある仕事をやっつけるためだけに調べ物をしている人」向けではない。しかしそういう人でも J2EE講座 [ JSP、Servletの文字化け対策 ]はチェックしておいて損しないとおもう。

HttpServletRequest雑感


日本語文字列の入力エンコーディングを正す方法については setCharacterEncoding()を使いなさいということで正しいんだけど、仕事で作る Webアプリケーションだったら出来ればこういうサーブレットフィルタを使って解決したいところ(後でそのプログラムをメンテナンスする人のためにも)。

多くの入門サイトではコンテンツの文字コードを Shift_JISで扱ってるけど、マルつき数字とかキロとか昭和とかの字が化けるよーとか余計な悩みを抱えるのを避けたければ UTF-8を使うほうが良いかもしれない(データベースを使うならそれも合わせる)。周りがユニコードなんてしらねーよ、シフトJISでいいだろ!とか言う人ばかりで UTF-8なんて使わせてもらえない状況であればもう仕方ないので頑張って解決するしかないけど。ヒント:MS932、Windows-31J

しかしなんで HttpServletRequestについてみんなそんなに検索するんだろうなあ。もっと高位のフレームワークを使わないで仕事になるのかなあ(会社の方針でオープンソース禁止なのだろうか、だとしたら難儀なことだ)。むしろフレームワークを作ってる人なら HttpServletRequestについてすごくよく知りたいと思うかもしれないけど、そういう人そんなにたくさんいないだろうし。
Webアプリケーションの動作原理まで詳細に勉強したいとか、いやちょっと試してみるだけだからフレームワークみたいな大げさなのは・・・、とかいうのでなければ、サーブレット APIを直接呼び出すようなやりかたをしないで何かのフレームワークを使ったほうがいいかと。英語がだめなら Seasar2 あたりをあたってみるのがいいんじゃないかな。余計なお世話か。

2008-06-04

MySQL: DDLとトランザクション

MySQLでは(他のDBMSでもおおよそ似たようなものだと思うが)、トランザクションの途中で CREATE TABLE文や DROP TABLE文のようなDDLを実行すると強制的にコミットが行われる。これに気付かないと、ロールバックしているはずなのになんかデータが残留してる、ということが起こる(そもそもトランザクションの中でDDLを呼ばなければいけない事態を何とかすべきだが)。DDL以外でも、TRUNCATE TABLE文などでもそうなる。でも、CREATE TEMPORARY TABLEは大丈夫のようだ。

参考 12.4.3. 暗黙のコミットを引き起こすステートメント

Springとユニットテスト

テストケースくらいはなるべく特定のフレームワークに依存させたくないという気持ちはあるものの、さすがに Hibernateやらといったヘヴィな奴をインスタンス化する長い道のりを手でコーディングしたり、単一責任の原則に基づいて真面目に分割されたコンポーネント群のワイヤリングを手でコーディングしたりするのはかなり厳しいものがあるので、そこは目をつぶってテストケースもやはり Springの助けを借りて実装するのが現実的だ。

なお JUnitと Springを連携させるには、spring.jarの他に spring-test.jar が必要となる。

Eclipseについてる JUnitを捨てる


Eclipseについている JUnitはちょっと古くて、Springに対応するために必要なインターフェイスJUnit4ClassRunnerが存在していない。なので Eclipseに標準で付属している JUnitをビルドパスから外し、最新の JUnitへビルドパスを通す必要がある。その場合でも "Run as"→"JUnit TestCase"はちゃんと動くので心配ない。

テストケースに Beanをインジェクトさせる


テストケースに Springサポートを適用する一番シンプルな方法は、下記のふたつのアノテーションをつけてやることである。

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration

これらのアノテーションがついたテストケースを走らせると、最初にテストケースと同じパッケージ階層とクラス名+"-context.xml"という名前を持つ Bean定義ファイルが読み込まれる。
テストケースに @Autowired (もしJava EEのアノテーションが使えるなら @Resource でも良い)の付いたプロパティがあれば、それぞれのプロパティと同じ型の Beanが自動的にインジェクトされる。

<!-- src/test/com/acme/DataImporterTest-context.xml -->
<beans>
<!-- 中略 -->
<bean id="dataImporter" class="com.acme.DataImporterImpl">
<property name="dataSource" ref="dataSource"/>
</bean>
</beans>

package com.acme;
// 中略
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class DataImporterTest {
@Autowired
DataImporter dataImporter;

public void testDoImport()
{
dataImporter.doImport();
}
}

これなら、テストケースの中に何らインスタンス化やワイヤリングの処理を記述しなくても良く非常に簡潔である。
読み込むBean定義ファイルを変更したり名称を指定してプロパティに Beanをインジェクトしたり(@Qualifierアノテーションを使う)も出来るので、詳しくは Spring 2.5のドキュメント 8.3.7.2. Context management and caching あたりを参照されたし。

トランザクション制御も自動化する


典型的には、データベースアクセスを行うコンポーネントに適用するテストケースの各メソッドはそれぞれがひとつのトランザクション単位として考えられるだろう。したがって、各テストメソッドの開始時点で自動的にトランザクションがBEGINされ、終了時には自動で COMMIT又は ROLLBACKが行われると便利である。
そうしたい場合は、先の @RunWith, @ContextConfigurationアノテーションに加えて下記のアノテーションをつけてやると良い。

@TransactionConfiguration
@Transactional

各テストメソッドの実行にあたり、Bean定義ファイル内で定義された transactionManagerという名前のトランザクションマネージャを勝手に使ってトランザクションの制御を行ってくれる。
(使用するトランザクションマネージャの名前は@TransactionConfigurationアノテーションのオプションで変更が可能)

デフォルトでは、各テストメソッドの終了時にはトランザクションがロールバックされるようになっている。つまりテストの実行によってデータベースの内容を変更しないということだ。ユニットテストは外部リソースの状態から独立しているべきなのでこの動作が理想的なのではあるが、どうしてもテストケースを回した後に Microsoft Accessやら Navicatやらといった GUIツールでテーブルの中身を確認してみたい場合もある。そういう時は @TransactionConfigurationアノテーションのオプションで defaultRollback=falseを与えてやるか、テストメソッドへ明示的に @Rollback(false) のようにアノテーションでロールバックを行わないことを記してやる必要がある。

<bean id="transactionManager"
class=
"org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@TransactionConfiguration
@Transactional
public class DataImporterTest {
@Autowired
DataImporter dataImporter;

@Rollback(false)
public void testDoImport()
{
dataImporter.doImport();
}
}

気になったのだが、テストメソッドがロールバックを行わない設定になっていると、テストが失敗した(キャッチされない例外が上がった、アサーションに引っかかった)場合でもトランザクションは容赦なくコミットされてしまうようだ。中途半端なデータをデータベースに残さないよう、実装の初期段階ではロールバックを行う設定でユニットテストを行う方が良さそうである。

Beforeと After


Springによるトランザクション管理が適用されたテストケースでは、JUnitの標準的な @Before, @Afterメソッドの他に @BeforeTransaction, @AfterTransactionメソッドを持つことが出来る。
@Before, @Afterはトランザクション内で実行されるが、@BeforeTransactionはトランザクションの開始前、@AfterTransactionはトランザクションの終了後に実行される。

少しだけ記述を減らす+α


もしテストケースが他のクラスを継承していないのであれば、@RunWith, @TransactionConfiguration, @Transactionalアノテーションを付ける代わりに AbstractTransactionalJUnit4SpringContextTests を継承させることもできる。

アノテーションの記述を省略できることに加え、このクラスにはデータベース操作のユニットテストを行う際に便利かもしれない下記のものが装備されている。
・Beanファクトリへのアクセス (applicationContext)
・ログAPIへのアクセス (logger)
・SimpleJdbcTemplateのインスタンス (simpleJdbcTemplate)
・データベース操作のテストに便利かもしれないいくつかのメソッド(countRowsInTable,deleteFromTables,executeSqlScript,setSqlScriptEncoding)

最後のはフィクスチャのロードなどに使うことを想定した機能かもしれないが、そのあたりを本格的にやるなら DbUnitを組み合わせて使うのが良いと思う。
言うまでもなく Javaは多重継承ができないので、DbUnitの DatabaseTestCaseを継承するかこのAbstractTransactionalJUnit4SpringContextTestsを継承するかの選択を迫られることになるのだが。

ラベル:

2008-06-03

SpringでJNDIを提供する

「え、それって<jee:jndi-lookup/> のこと?」
・・・だったらわざわざブログのエントリなぞ起こさんわい。JNDIを参照する側ではなく、提供する側の話。

Springは JNDIを参照することは出来るけど、JNDIサービス自体は提供していない。
ふつう JNDIはアプリケーションサーバが提供するものなので(ついでに言えば Java EEのサービスなので) Springの守備範囲ではないのだが、何らかの理由でユニットテストのターゲットが JNDIを参照しているとか(レガシーなSLSBを使い回してるとか、中のJRubyで activerecord-jdbc-adapterを使ってるとかね)、ユニットテスト用の Bean定義ファイル内でもデータソースの参照は JNDI経由で行いたいとか、Webアプリケーションではなくstatic public void main()から始まるスタンドアロンのプログラムなんだけどやっぱり Bean定義ファイル内でのデータソース設定は JNDIを参照する形の記述にしたいとか、そういう特殊な事情のある人またはマニアックな人は Springベースの JNDI実装が欲しくなったりもするのである。そして、やっぱりそういうものが存在する。

Springベースの JNDI実装 org.apache.xbean.spring.jndi.SpringInitialContextFactory は Apache Geronimoのサブプロジェクト XBeanの成果物である xbean-spring-*.jarに含まれている。これを拝借して自分のユニットテスト用クラスパスに追加してやろう。

JNDIの実装クラスを指定する方法には下記のようなものがある。
・システムプロパティjava.naming.factory.initial にクラス名をセットする
・クラスパスに jndi.propertiesというファイルを置き、java.naming.factory.initial=行にクラス名を記述する
どちらでも良いが Eclipseで Runする時に VM引数を設定するのは面倒なので私は後者にしている。

jndi.propertiesの内容は下記のようになる。
java.naming.factory.initial=org.apache.xbean.spring.jndi.SpringInitialContextFactory

SpringInitialContextFactoryは jndi.xmlという名前の Bean定義ファイルをクラスパス上で探しだし、それを JNDIの設定ファイルとして用いる(jndi.propertiesと同じ場所にでも置いておけば良いだろう)。この Bean定義ファイルには少なくとも、jndi という名称で org.apache.xbean.spring.jndi.DefaultContext クラスの Beanを定義しておく必要がある。空の JNDIツリーを提供するだけならばこの Beanに何らプロパティをセットする必要はないが、典型的にはここにデータソースをバインドすることになるだろう。データソースをJNDIにバインドしたければ、jndi.xmlは下記のような記述になる。例はMySQLの場合だが、DBベンダの独自な DataSource実装を使うのが嫌であれば Springの DriverManagerDataSourceなどといった汎用のDataSource実装を使えば良いと思う。
(個人的には、ことユニットテストに用いる場合などコネクションプーリングの必要ないケースに限って言うならば DataSourceの実装が DBベンダのものか Springのものかは大した問題ではないと考えている。アプリケーションの利用するDBMSが差し替わるというのは、いろんな意味でよほどのことだ)

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans">
<bean id="dataSource"
class=
"com.mysql.jdbc.jdbc2.optional.MysqlDataSource">
<property name="url" value="jdbc:mysql://localhost/mydb"/>
<property name="user" value="username"/>
<property name="password" value="secret"/>
<property name="zeroDateTimeBehavior" value="convertToNull"/>
<property name="useUnicode" value="true"/>
<property name="characterEncoding" value="UTF8"/>
</bean>

<bean id="jndi"
class=
"org.apache.xbean.spring.jndi.DefaultContext">
<property name="entries">
<map>
<entry key="java:/comp/env/jdbc/dataSource">
<ref bean="dataSource"/>
</entry>
</map>
</property>
</bean>
</beans>

ずるをする


Springのトランザクション管理下で JDBCの呼び出しをしたい場合、下記のいずれかの条件を満たしている必要がある。

1)常に Springの JdbcTemplateを経由してJDBCの呼び出しを行う
2)常に SpringのDataSourceUtilsを経由してデータソースからコネクションを取得する
3)DataSourceを Springの TransactionAwareDataSourceProxyでラップし、常にそれを使う
4)JtaTransactionManagerを使う

JNDIから直接データソースを取得するように実装されているレガシーなJEEコードも Springのトランザクション管理下で動作させたい場合、Java EEコンテナ上で動作させる時は 4の方法でコンテナ管理のトランザクションマネージャとくっつけてやれば裏でうまく処理してくれそうな気がするが、Java SEベースの開発環境でユニットテストをするときはその方法が使えない。

では、TransactionAwareDataSourceProxyでラップ済みのデータソースを JNDIに登録してはどうかということで、やってみたところ一応期待通りの動作をするようだ。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans">
<bean id="dataSource"
class=
"org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy">
<constructor-arg>
<bean class="com.mysql.jdbc.jdbc2.optional.MysqlDataSource">
<property name="url" value="jdbc:mysql://localhost/mydb"/>
<property name="user" value="username"/>
<property name="password" value="secret"/>
<property name="zeroDateTimeBehavior" value="convertToNull"/>
<property name="useUnicode" value="true"/>
<property name="characterEncoding" value="UTF8"/>
</bean>
</constructor-arg>
</bean>

<bean id="jndi" class="org.apache.xbean.spring.jndi.DefaultContext">
<property name="entries">
<map>
<entry key="java:/comp/env/jdbc/dataSource">
<ref bean="dataSource"/>
</entry>
</map>
</property>
</bean>
</beans>

Springのドキュメントによると、DataSourceTransactionManagerにセットする dataSourceは TransactionAwareDataSourceProxyでは駄目・・・なんだけど、そういう場合は勝手に TransactionAwareDataSourceProxyから中の本物データソースを取り出して使うよ。ということになっていて、実際ソースを確認したところそうなっていたので、JNDIからもってきたデータソースが既に TransactionAware...でラップされていても大丈夫みたい。

ラベル: