SQL Server 2000 Driver for JDBCのConnection Poolingについては、
http://support.microsoft.com/default.aspx?scid=kb;ja;313173
に実装方法のサンプルがありますが、これはあくまでサンプルであり、完全ではないようです。腕に自信のある方はいざ実装というところなのでしょうが、そんな自信も時間の余裕もない方はやはりJakarta CommonsのDBCPの利用でしょうか。これを使うととても簡単にコネクションプーリングを使用することができます。そこで、
http://www.jajakarta.org/commons/dbcp-1.0/ja/withoutPrimary/overview-summary.htmlを参考にして、色々試してみることにしました。
ダウンロードは以下のサイトからできます。
http://jakarta.apache.org/site/downloads/downloads_commons-dbcp.cgi
http://archive.apache.org/dist/xml/xerces-j/
これらのパッケージ中に含まれるjarファイル
commons-dbcp-1.2.1.jar
resolver.jar
xercesImpl.jar
xercesSamples.jar
xml-apis.jar
xmlParserAPIs.jar
をCLASSPATHに通し、
joclファイルをCLASSPATHの通ったところにおいておけばOK。これでserver.xmlなどの書きかえは不要になります。
joclファイルはPoolableConnectionFactoryについて作成します。
同梱されているjavadocを見るとPoolableConnectionFactoryのコンストラクタは、2つ用意されています。
(4つはdeprecateされたクラスAbandonedConfigを使用しているため、deprecateされています)。
主な引数は、
1.org.apache.commons.dbcp.ConnectionFactory connFactory(これはInterface)
2.org.apache.commons.pool.ObjectPool pool(これもInterface)
3.org.apache.commons.pool.KeyedObjectPoolFactory(これもInterface) stmtPoolFactory
4.String validationQuery(検証用SQL)
5.boolean defaultReadOnly(読取専用)
6.boolean defaultAutoCommit(オートコミットデフォルト設定)
7.int defaultTransactionIsolation(デフォルトのTransaction分離レベル)
ここでは、
PoolableConnectionFactory(org.apache.commons.dbcp.ConnectionFactory connFactory, ObjectPool pool, KeyedObjectPoolFactory stmtPoolFactory, String validationQuery, Boolean defaultReadOnly, boolean defaultAutoCommit, int defaultTransactionIsolation)
を使用することにします。Transaction分離レベルについてはローラー作戦で一通り試してみました。
(ConnectionクラスのsetTransactionIsolationを使用しプロファイラを観察)。
| setTransactionIsolationに与えた引数 |
プロファイラの結果 |
| TRANSACTION_READ_UNCOMMITTED |
SET TRANSACTION ISOLATION LEVEL READ UNCOMMITTED |
| TRANSACTION_READ_COMMITTED |
SET TRANSACTION ISOLATION LEVEL READ COMMITTED |
| TRANSACTION_REPEATABLE_READ |
SET TRANSACTION ISOLATION LEVEL REPEATABLE READ |
| TRANSACTION_SERIALIZABLE |
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE |
| TRANSACTION_NONE |
SET TRANSACTION ISOLATION LEVEL(['LEVEL' の近くに無効な構文があります]の例外発生。) |
そういうわけで、安心してdefaultTransactionIsolationには、上記のNONE以外のどれかが使えます。
早速joclファイル(PoolableConnectionFactory)を作成します。
<object class="org.apache.commons.dbcp.PoolableConnectionFactory"
xmlns="http://apache.org/xml/xmlns/jakarta/commons/jocl">
</object>
このタグの中に、コンストラクタの引数を埋め込んでいきます。
まず、1.のorg.apache.commons.dbcp.ConnectionFactory connFactoryから。
これはInterfaceなので、それを実装したDriverManagerConnectionFactoryを使用します。
コンストラクタは
DriverManagerConnectionFactory(String connectUri, String uname, String passwd)
を使用します。よって、この引数に相当するタグは以下のようになります。
<object class="org.apache.commons.dbcp.DriverManagerConnectionFactory">
<!-- URL:接続文字列 -->
<string value="jdbc:microsoft:sqlserver://サーバー名:1433;databaseName=Northwind;selectMethod=cursor"/>
<!-- ユーザー名 -->
<string value="sa"/>
<!-- パスワード -->
<string value="*******"/>
</object>
2.のorg.apache.commons.pool.ObjectPoolは、Interfaceneなのでそれを実装した
org.apache.commons.pool.impl.GenericObjectPoolを使用します。コンストラクタが11個もあります。
ここでは、
GenericObjectPool(PoolableObjectFactory factory, int maxActive, byte whenExhaustedAction, long maxWait, int maxIdle, int minIdle, boolean testOnBorrow, boolean testOnReturn, long timeBetweenEvictionRunsMillis, int numTestsPerEvictionRun, long minEvictableIdleTimeMillis, boolean testWhileIdle)
を使用することにします。
このコンストラクタ最初の引数、PoolableObjectFactoryもInterfaceですが、
<object class="org.apache.commons.pool.PoolableObjectFactory" null="true"/>
とできます。実際のConnection(poolされるobject)はPoolableConnectionFactoryが生成するそうなので、これでOK。
これは、
GenericObjectPool connectionPool = new GenericObjectPool(null, ......);
に相当します。
後の引数はプリミティブ型です。コンストラクタの引数並び順に並べると以下のようになります。
<!-- ObjectPool -->
<object class="org.apache.commons.pool.impl.GenericObjectPool">
<!-- PoolableObjectFactory -->
<object class="org.apache.commons.pool.PoolableObjectFactory" null="true"/>
<!-- 最大アクティブ接続数(maxActive) -->
<int value="50"/>
<!-- オブジェクトが尽きたときの処理(0:失敗,1:待つ,2:接続を増やす)(whenExhaustedAction)-->
<byte value="1"/>
<!-- whenExhaustedActionで1:待つを指定した場合の最大待機時間(maxWait) -->
<long value="15000"/>
<!-- アイドル状態のコネクションの最大数(maxIdle) -->
<int value="50"/>
<!-- アイドル状態のコネクションの最小数(minIdle) -->
<int value="10"/>
<!-- コネクション取得時のテストの実行(testOnBorrow) -->
<boolean value="true"/>
<!-- コネクション返却のテストの実行(testOnReturn) -->
<boolean value="false"/>
<!-- プール内オブジェクト排除スレッドの実行間隔(timeBetweenEvictionRunsMillis) -->
<long value="10000"/>
<!-- プール内オブジェクト排除スレッドが一度に処理するコネクションの数(numTestsPerEvictionRun) -->
<int value="20"/>
<!-- 排除対象となるまでの最短アイドル時間(minEvictableIdleTimeMillis) -->
<long value="380000"/>
<!-- アイドル状態のコネクションのテストの実行(testWhileIdle) -->
<boolean value="false"/>
</object>
3.のKeyedObjectPoolFactoryもInterfaceなので、これを実装したStackKeyedObjectPoolFactoryを使用します。
<object class="org.apache.commons.pool.StackKeyedObjectPoolFactory"/>
4.の検証用SQLですがなるべく軽いものがいいです。oracleの場合、
SELECT COUNT(*) FROM DUALが使われているようですが、
SQL Serverの場合は
SELECT null
で良いと思います。
<!-- 検証用クエリ -->
<string value="SELECT null"/>
5.の読取専用はもちろんfalseで
<!-- 読取専用 -->
<boolean value="false"/>
6.のAuto Commitは、trueで
<!-- Auto Commit -->
<boolean value="true"/>
7.最後のTransaction分離レベルですが、
java.sql.Connection
public static final int TRANSACTION_NONE 0
public static final int TRANSACTION_READ_COMMITTED 2
public static final int TRANSACTION_READ_UNCOMMITTED 1
public static final int TRANSACTION_REPEATABLE_READ 4
public static final int TRANSACTION_SERIALIZABLE 8
となっていますので、TRANSACTION_READ_COMMITTEDを明示的に指定するには
<!-- トランザクション分離レベル -->
<int value="2"/>
とします(デフォルトは、TRANSACTION_READ_COMMITTEDなので本来は不要)。
以下、joclファイルの最終形です。これにsqlserver.jocl(拡張子以外任意)という名前をつけて、CLASSPATHの通ったところに配置します。
<object class="org.apache.commons.dbcp.PoolableConnectionFactory"
xmlns="http://apache.org/xml/xmlns/jakarta/commons/jocl">
<!-- DriverManagerConnectionFactory -->
<object class="org.apache.commons.dbcp.DriverManagerConnectionFactory">
<!-- URL:接続文字列 -->
<string value="jdbc:microsoft:sqlserver://サーバー名:1433;databaseName=Northwind;selectMethod=cursor"/>
<!-- ユーザー名 -->
<string value="sa"/>
<!-- パスワード -->
<string value="**********"/>
</object>
<!-- ObjectPool -->
<object class="org.apache.commons.pool.impl.GenericObjectPool">
<!-- PoolableObjectFactory -->
<object class="org.apache.commons.pool.PoolableObjectFactory" null="true"/>
<!-- 最大アクティブ接続数(maxActive) -->
<int value="50"/>
<!-- オブジェクトが尽きたときの処理(0:失敗,1:待つ,2:接続を増やす)(whenExhaustedAction)-->
<byte value="1"/>
<!-- whenExhaustedActionで1:待つを指定した場合の最大待機時間(maxWait) -->
<long value="15000"/>
<!-- アイドル状態のコネクションの最大数:最大アイドル数(maxIdle) -->
<int value="50"/>
<!-- アイドル状態のコネクションの最小数:最小アイドル数(minIdle) -->
<int value="15"/>
<!-- コネクション取得時のテストの実行(testOnBorrow) -->
<boolean value="true"/>
<!-- コネクション返却のテストの実行(testOnReturn) -->
<boolean value="false"/>
<!-- 排除スレッドの実行間隔(timeBetweenEvictionRunsMillis) -->
<long value="10000"/>
<!-- 排除スレッドが一度に処理するコネクションの数(numTestsPerEvictionRun) -->
<int value="20"/>
<!-- 排除対象となるまでの最短アイドル時間(minEvictableIdleTimeMillis) -->
<long value="380000"/>
<!-- アイドル状態のコネクションのテストの実行(testWhileIdle) -->
<boolean value="false"/>
</object>
<!-- KeyedObjectPoolFactory -->
<object class="org.apache.commons.pool.StackKeyedObjectPoolFactory"/>
<!-- 検証用クエリ -->
<string value="SELECT null"/>
<!-- 読取専用(デフォルト) -->
<boolean value="false"/>
<!-- Auto Commit(デフォルト) -->
<boolean value="true"/>
<!-- デフォルトのトランザクション分離レベル -->
<!--
public static final int TRANSACTION_NONE 0
public static final int TRANSACTION_READ_COMMITTED 2
public static final int TRANSACTION_READ_UNCOMMITTED 1
public static final int TRANSACTION_REPEATABLE_READ 4
public static final int TRANSACTION_SERIALIZABLE 8
-->
<int value="2"/>
</object>
これを使用して色々実験してみます。
以下のように実験に使うServletを用意しました。
package org.passj.blogs.taniguchi.test_servlet;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
/**
* @author たにぐち
*
*/
public class TestServlet extends HttpServlet {
public void doGet(HttpServletRequest req, HttpServletResponse res)
throws ServletException, IOException {
res.setContentType("text/html;charset=Shift_JIS");
PrintWriter out = res.getWriter();
Connection con = null;
out.println("<html>\n");
out.println("<head><title>コネクションプーリングのテスト</title></head>\n");
out.println("<body>");
out.println("<P>コネクションプーリングをテストします。");
try {
// デフォルトのSAXドライバの指定
if (System.getProperty("org.xml.sax.driver") == null) {
System.setProperty("org.xml.sax.driver",
"org.apache.xerces.parsers.SAXParser");
}
// ドライバをロードします
Class.forName("org.apache.commons.dbcp.PoolingDriver");
Class.forName("com.microsoft.jdbc.sqlserver.SQLServerDriver");
// Connectionを取得します
// "/sqlserver"のsqlserverは、sqlservelr.joclのファイル名から拡張子を除いたもの
// スラッシュを忘れずに。
con = DriverManager.getConnection("jdbc:apache:commons:dbcp:/sqlserver");
out.println("<P>成功!");
// 必要に応じてsleepする
Thread.sleep(待ち時間ミリ秒);
} catch (Exception e) {
out.println("<P>ただいま接続が混雑しております");
e.printStackTrace();
} finally {
closeConnection(con);
}
out.println("</body>");
}
private void closeConnection(Connection con) {
try {
if (con != null) {
con.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
主役的な役割を果たすのは「排除スレッド」です。この排除スレッドは「排除スレッドの実行間隔」おきに実行され、アイドル状態の接続を「最小アイドル数」以上「最大アイドル数」以下に保ちます。
アイドル状態接続が追加されるのはアイドル状態接続数が「最小アイドル数」を下回った時、削除されるのはアイドル状態接続数が「最大アイドル数」を上回った時です。
またアイドル接続が「排除対象になるまでの最短時間」を上回っていた場合もアイドル接続を削除の対象にします。
ただし、削除されることにより「最小アイドル数」を下回ってしまった場合、直ちに不足した分を追加します。
また、検証用クエリを用いたコネクションのテスト(取得時、返却時、アイドル状態)も行います(指定されていた時のみ)。
設定を間違えるとかえってシステムに負担をかけてしまうので注意が必要です。