Seasar DI Container with AOP

概要

Javaでデータベースにアクセスするには、データソースを経由したコネクションプールを使うのが一般的です。 コネクションプールは通常、アプリケーションサーバが実装しますが、 Tomcatのようなトランザクションと連動するコネクションプールの実装のないアプリケーションサーバ用に S2DBCPを用意しています。

データソースの設定は、jdbc.diconで行ないます。 jdbc.diconはクラスパスの通っているディレクトリ、 通常はWEB-INF/classesにおきます。

S2DBCP

XADataSource、ConnectionPoolの設定をおこないます。 JDBC DriverがXADataSourceの機能を提供している場合は、 それをそのまま使えますが、 提供されていない場合、S2で用意しているXADataSourceImplを使って、XAの機能をエミュレートします。

org.seasar.extension.dbcp.impl.XADataSourceImpl

プロパティ 説明
driverClassName JDBC Driverのクラス名 "oracle.jdbc.driver.OracleDriver"
URL RDBMS固有のURL "jdbc:oracle:thin:@xxx:1521:yyy"
user ユーザ名 "hoge"
password パスワード "password"
loginTimeout ログインタイムアウト時間 (秒単位) 2

org.seasar.extension.dbcp.impl.ConnectionPoolImpl

プロパティ 説明
XADataSource XADataSourceのインスタンスを設定します。S2Containerで設定する場合は、XADataSourceのコンポーネント名を設定します。
必須です。
xaDataSource
transactionManager TransactionManagerのインスタンスを設定します。S2Containerで設定する場合は、TransactionManagerのコンポーネント名を設定します。
必須です。
TransactionManager
timeout プールに戻されたコネクションがここで指定された秒数以上未使用だった場合、物理的にクローズされて破棄されます。
デフォルトは600(10分)です。
600
maxPoolSize 同時にアクティブになれる コネクションの数を指定します。 この数を超える要求があると、コネクションがプールに返されるまで その要求はブロックされます (maxWaitも参照)。
0を設定するとコネクションはプールされず、コネクションの取得要求はブロックされません。
デフォルトは10です。
10
minPoolSize コネクションプールに保持する最小のコネクション数を指定します。 プールに戻されたコネクションが timeoutで指定された時間を経過した場合でも、プールされたコネクションがこの数を下回る場合はクローズされません。
デフォルトは0です。
10
maxWait maxPoolSizeで指定された数のコネクションが既に使われている場合、コネクションが空くまでここで指定された時間 (ミリ秒単位) 待機します。 指定された時間待機してもコネクションが空かなかった場合はSQLExceptionがスローされます。 0 (ゼロ) が指定されると待機しません。-1が指定されると無制限に待機します。
デフォルトは-1 (無制限) です。
-1
allowLocalTx JTAによって制御されない、JDBCのローカルトランザクションを許可する (true) しない (false) を指定します。
falseを指定すると、JTAによるトランザクションが開始されていない状態でコネクションの取得が行われた場合に例外 (java.lang.IllegalStateException) がスローされます。
開発時にfalseを指定すると、S2Txが提供するトランザクション・インターセプタの設定漏れを確実に検出することができます。
デフォルトはtrueです。
true
validationQuery コネクションの死活を検証する際に使用するクエリを設定します。 nullもしくは空文字を指定した場合、検証は行われません。
デフォルトはnullです。
"select * from dual"
validationInterval プールから取得されるコネクションがここに指定した間隔(ミリ秒)よりも長い間プールされていた場合、コネクションの死活を検証します。 0以下の値を指定した場合、検証は行われません。
デフォルトは0です。
10000

コンポーネントの設定は以下のようになります。環境に合わせて書き換えてください。

jdbc.dicon

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN" 
	"http://www.seasar.org/dtd/components24.dtd">
<components namespace="jdbc">
    <include path="jta.dicon"/>
    <include path="jdbc-extension.dicon"/>
    
    <component class="org.seasar.extension.jdbc.impl.BasicResultSetFactory"/>
    <component class="org.seasar.extension.jdbc.impl.ConfigurableStatementFactory">
        <arg>
            <component class="org.seasar.extension.jdbc.impl.BasicStatementFactory"/>
        </arg>
        <property name="fetchSize">100</property>
        <!--
        <property name="maxRows">100</property>
        -->
    </component>

    <component name="xaDataSource"
            class="org.seasar.extension.dbcp.impl.XADataSourceImpl">
        <property name="driverClassName">
            "oracle.jdbc.driver.OracleDriver"
        </property>
        <property name="URL">
            "jdbc:oracle:thin:@xxx:1521:yyy"
        </property>
        <property name="user">"aaa"</property>
        <property name="password">"bbb"</property>
    </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>
        <property name="validationQuery">"select * from dual"</property>
        <property name="validationInterval">10000</property>
        <destroyMethod name="close"/>
    </component>
    <component name="dataSource"
       class="org.seasar.extension.dbcp.impl.DataSourceImpl"/>
</components>

JDBCドライバが提供するXADataSourceを使う場合はXADataSourceImplの代わりに次のように定義します。

    <component name="xaDataSource"
            class="XADataSource実装クラス名">
        実装固有のプロパティの設定
    </component>

Webコンテナ定義のDataSourceを使う

jdbc.dicon ファイルにデータベースの接続情報を記述したくない場合は、WebコンテナにXADataSourceまたはDataSourceを定義して使うことが出来ます。

Webコンテナで定義したXADataSourceを使う場合はXADataSourceImplの代わりに次のように定義します。

    <component name="xaDataSource"
            class="javax.sql.XADataSource">
        @org.seasar.extension.j2ee.JndiResourceLocator@lookup("JNDI名")
    </component>

Webコンテナで定義したDataSourceを使ってXAの機能をエミュレートする場合は、XADataSourceImplの代わりにDataSourceXADataSourceを使って次のように定義します。

    <component name="xaDataSource"
            class="org.seasar.extension.dbcp.impl.DataSourceXADataSource">
        <property name="dataSourceName">"JNDI名"</property>
    </component>

APサーバのコネクションプールを使う

一般的なアプリケーションサーバでは、JNDI経由(JndiResourceLocator)でデータソースを取得します。

jdbc.dicon

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE components PUBLIC "-//SEASAR//DTD S2Container 2.4//EN" 
	"http://www.seasar.org/dtd/components24.dtd">
<components namespace="jdbc">
    <include path="jta.dicon"/>
    <include path="jdbc-extension.dicon"/>
    
    <component class="org.seasar.extension.jdbc.impl.BasicResultSetFactory"/>
    <component class="org.seasar.extension.jdbc.impl.ConfigurableStatementFactory">
        <arg>
            <component class="org.seasar.extension.jdbc.impl.BasicStatementFactory"/>
        </arg>
        <property name="fetchSize">100</property>
        <!--
        <property name="maxRows">100</property>
        -->
    </component>

    <component name="dataSource"
            class="javax.sql.DataSource">
        @org.seasar.extension.j2ee.JndiResourceLocator@lookup("JNDI名")
    </component>
</components>

複数のデータソースを使用する

複数のデータソースを使う方法は、大別して次の二つの方法があります。

異なった種類のデータソースを複数使う
それぞれのデータソースで接続するDBに含まれるテーブルの名前や構造が異なっている場合です。 このケースでは、あるDaoやJdbcManagerは一つのデータソースを使用します。
例えば、FooDaoやBarDaoはDataSource1を使用し、HogeDaoやMogeDaoはDataSource2を使用します。 DataSource1が接続するDBにはFooテーブルやBarテーブルが、DataSource2が接続するDBにはHogeテーブルやMogeテーブルが存在します。
同じ種類のデータソースを複数使う
それぞれのデータソースが接続するDBに含まれるテーブルの名前や構造が同じ場合です。 このケースでは、あるDaoやJdbcManagerが複数のデータソースを使用します。
例えば、FooDaoやBarDaoがあるリクエストの場合はDataSource1を使用し、別のリクエストの場合はDataSource2を使用します。 DataSource1が接続するDBにも,DataSource2が接続するDBにも,FooテーブルやBarテーブルが存在します。

これら二つの方法を組み合わせることも可能です。 例えば、FooDaoやBarDaoはDataSource1とDataSource2を使用し、HogeDaoやMogeDaoはDataSourc2とDataSource3を使うような構成になります。 この場合、DataSource1とDataSource2は同じ種類のデータソース、DataSource3とDataSource4も同じ種類のデータソースとして構成します. その上で、DataSource1とDataSource2を使い分けるSelectableDataSourceProxyと、DataSource3とDataSource4を使い分けるSelectableDataSourceProxyは、異なった種類のデータソースとして構成します。

異なった種類のデータソースを使い分ける

異なった種類のDBに接続する複数のデータソースを使い分ける場合は、それぞれのデータソースごとにjdbc.diconをコピーして、customer-jdbc.diconproduct-jdbc.diconのように個別のdiconファイルを作成します。customer-jdbc.diconproduct-jdbc.diconのそれぞれのデータソースには、異なったコンポーネント名を設定します。そしてjdbc.diconでそれぞれのdiconをインクルードします。

customer-jdbc.dicon

<components>
    ...
    <component name="customerDataSource"
        class="org.seasar.extension.dbcp.impl.DataSourceImpl"/>
</components>

product-jdbc.dicon

<components>
    ...
    <component name="productDataSource"
        class="org.seasar.extension.dbcp.impl.DataSourceImpl"/>
</components>

jdbc.dicon

<components namespace="jdbc">
    <include path="customerJdbc.dicon"/>
    <include path="productJdbc.dicon"/>
</components>

それぞれのデータソースを利用するS2JDBCやS2Daoなどの設定ファイルでは、jdbc.diconではなく、それぞれが必要とするdiconファイルをインクルードします。

customer-s2jdbc.dicon

<components>
    <include path="customer-jdbc.dicon"/>
    ...
</components>

product-s2jdbc.dicon

<components>
    <include path="product-jdbc.dicon"/>
    ...
</components>

同じ種類のデータソースを動的に切り替える

同じ種類のDB (DBMSの種類やスキーマ内の構成が同じ) に接続するデータソースを、クライアントの属性などにより動的に切り替えたい場合は、SelectableDataSourceProxyDataSourceFactoryを利用します。 以下に、これらを利用するための設定とプログラムについて例を示します。

まず、データソースごとにjdbc.diconをコピーし、新しいdiconファイルを作成します。ここでは、customer1-jdbc.diconcustomer2-jdbc.diconという名前で作成します。

customer1-jdbc.dicon

<components>
    ...
    <component name="customer1DataSource"
       class="org.seasar.extension.dbcp.impl.DataSourceImpl"/>
</components>

customer2-jdbc.dicon

<components>
    ...
    <component name="customer2DataSource"
       class="org.seasar.extension.dbcp.impl.DataSourceImpl"/>
</components>

DataSourceのコンポーネントにはそれぞれ異なるコンポーネント名を設定してください。そのとき、名前は「任意のプレフィックス + DataSource」としてください。

次に、新規作成したdiconファイルをjdbc.diconでインクルードし、さらにjdbc.diconSelectableDataSourceProxyorg.seasar.extension.datasource.impl.DataSourceFactoryImplのコンポーネントを定義します。

jdbc.dicon

<components namespace="jdbc">
    <include path="customer1-jdbc.dicon"/>
    <include path="customer2-jdbc.dicon"/>

    <component name="dataSource"
        class="org.seasar.extension.datasource.impl.SelectableDataSourceProxy"/>
    <component  name="dataSourceFactory
        class="org.seasar.extension.datasource.impl.DataSourceFactoryImpl"/>
</components>

以上で、diconファイルの設定は完了です。

プログラムでは、org.seasar.extension.datasource.DataSourceFactoryを使ってデータソース名を設定します。 データソース名の設定は、データソースが使用される前に行います。 通常は、インターセプタやServlet Filterで行うのが良いでしょう。 ここでは、インターセプタを使った例を示します。

サンプルコード

public class SelectDataSourceInterceptor implements MethodInterceptor {
    public DataSourceFactory dataSourceFactory;

    public Object invoke(MethodInvocation invocation) throws Throwable {
        String currentName = dataSourceFactory.getSelectableDataSourceName();
        try {
            String dataSourceName = getDataSourceName();
            dataSourceFactory.setSelectableDataSourceName(dataSourceName);
            return invocation.proceed();
        } finally {
            dataSourceFactory.setSelectableDataSourceName(currentName);
        }
    }

    public String getDataSourceName() {
        //ユーザの種類に応じたデータソース名のプレフィックスを返す。
    }
}

DataSourceFactoryクラスのsetSelectableDataSourceNameメソッドにはデータソース名のプレフィックスを渡してください。 この例では、"customer1""customer2" がデータソース名のプレフィックスになります。

データソースを実際に利用する箇所では、単一のデータソースを扱う場合と変わらない方法でデータソースを利用できます。