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が差し替わるというのは、いろんな意味でよほどのことだ)
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に登録してはどうかということで、やってみたところ一応期待通りの動作をするようだ。
Springのドキュメントによると、DataSourceTransactionManagerにセットする dataSourceは TransactionAwareDataSourceProxyでは駄目・・・なんだけど、そういう場合は勝手に TransactionAwareDataSourceProxyから中の本物データソースを取り出して使うよ。ということになっていて、実際ソースを確認したところそうなっていたので、JNDIからもってきたデータソースが既に TransactionAware...でラップされていても大丈夫みたい。
・・・だったらわざわざブログのエントリなぞ起こさんわい。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...でラップされていても大丈夫みたい。
ラベル: Spring

0 件のコメント:
コメントを投稿
<< ホーム