Commons DBCPを使ってみる
Commons DBCPについて調べてみました。
ちょっと前置きからスタートします。
Connectionオブジェクトの生成について
Javaは、DBとの接続をjava.sql.Connection型のオブジェクトとして取り扱います。
JDBCドライバを使った一般的なプログラムは以下のようになります。
Class.forName("oracle.jdbc.driver.OracleDriver"); Connection con = DriverManager.getConnection( "jdbc:oracle:thin:@localhost:1521/ORCL", "scott", "tiger");
Connectionオブジェクトの生成にかかる負荷
上記のプログラムの場合、
Connection con = DriverManager.getConnection( "jdbc:oracle:thin:@localhost:1521/ORCL", "scott", "tiger");
の部分でConnectionオブジェクトを生成していますが、
これは一般的なオブジェクトの生成(例えばStringオブジェクトなど)と比べて、
非常に時間のかかる作業になります。
仮に上記のコードの実行に0.1秒かかる場合、
1回だけ実行するのであれば、それほど意識しなくてもよいかもしれませんが、
1000回繰り返し実行するとなると、100秒時間がかかることになります。
DBとの物理的なコネクション
DBとの物理的なコネクション(接続)について調べてみます。
Connection con = DriverManager.getConnection( "jdbc:oracle:thin:@localhost:1521/ORCL", "scott", "tiger");
上記のコードを実行すると、
DBとの間に物理的なコネクション(接続)が生まれます。
Oracleで接続数(セッション数)を調べるには以下のようにコマンドを実行します。
select machine, program, count(*) from v$session where username != 'SYS' group by machine, program;
実行結果(セッション数)
MACHINE PROGRAM COUNT(*) -------------------- ------------- -------- e9583f70 1 <-- これがJDBCの接続 WORKGROUP1\E9583F70 sqlplus.exe 1 <-- こっちはsqlplus
上記の実行結果から、プログラムからDBへ接続している様子が確認できます。
コネクションのクローズ
Javaでは、Connectionオブジェクトが不要になった場合、
プログラマーがcloseメソッドを呼び出すことで、をクローズするのが一般的です。
con.close();
上記のプログラムを実行すると、コネクションがクローズされます。
と、ここがちょっとしたポイントです。
「コネクションのクローズ」とは一体どういうことでしょうか。
con.close();
メソッドを実行すると、物理的なコネクション(OracleのV$SESSIONで確認したもの)は切断されるのでしょうか。
con.close()を実行すると、
物理的な接続が切れる、つまり、V$SESSIONから見えなくなる、と思うかもしれませんが、
厳密な意味では、これは正しくありません。
正確には、Connectionの実体(実装オブジェクト)に依存します。
ちなみに、ここまでのプログラム、
Class.forName("oracle.jdbc.driver.OracleDriver"); Connection con = DriverManager.getConnection( "jdbc:oracle:thin:@localhost:1521/ORCL", "scott", "tiger"); // 検索とか更新とか con.close();
の場合は、closeメソッドの呼び出しにより、物理的なコネクションも切断されます。
一方で、これから紹介するDBCPでは、closeメソッドを呼び出したとしても、
必ず物理的なコネクションが切断される、というわけではなくなります。
コネクションプールとは
JavaのConnectionオブジェクトの生成は負荷のかかる作業です。
そのため、何度も繰り返し不必要にConnectionオブジェクトを生成することは好ましくありません。
とくに、Webアプリケーションを開発する場合は、ユーザからのリクエストに応じて、DBへ接続する必要があります。
ユーザからのリクエストごとにConnectionオブジェクトの生成を繰り返すことは好ましくありません。
そこで、一般的なAPサーバでは起動時にDBとの物理的な接続を予め複数用意しておき、
それをユーザのリクエストに対して使い回すようにすることで、上記の問題を解決します。
DBとの物理的な接続を予め複数用意することをコネクションプール(コネクションプーリング)と呼びます。
コネクションプールの仕組みは、APサーバによって提供されるものというわけではないので、
スタンドアロンのJavaアプリケーションでも利用(あるいは実装)することができます。
コネクションプールを独自に実装することもできますが、
Commons DBCPを使うこともできます。*1
Commons DBCPとは
Jakartaプロジェクトで開発されているコネクションプーリングのライブラリです。
実装サンプル(呼び出しもと)は以下のようになります。
Properties properties = new Properties(); InputStream is = ClassLoader.getSystemResourceAsStream("dbcp.properties"); properties.load(is); DataSource ds = BasicDataSourceFactory.createDataSource(properties); Connection con = ds.getConnection();
用意したdbcp.propertiesファイルは以下のとおり。
driverClassName=oracle.jdbc.driver.OracleDriver url=jdbc:oracle:thin:@localhost:1521/ORCL username=scott password=tiger initialSize=30 maxActive=100 maxIdle=30 maxWait=5000 validationQuery=select count(*) from dual
上記の設定だと、
- initialSize・・・コネクションプールの初期コネクションの数は30件である。
- maxActive・・・最大で100件までコネクションを作ることができる。
- maxIdle・・・アイドル状態(未使用状態)のコネクションは30件保持される。
- アイドル状態のコネクションが30件を超えている場合は、余計な件数分の物理コネクションが切断される。
- maxWait・・・コネクション取得の待ち時間は5000msである。
- validationQuery・・・コネクションが利用可能か検証するためのSQL
また、コネクションをクローズすると、
con.close();
設定ファイルの閾値により、
コネクションプールに戻るか、物理的に切断されるかが決まります。
コネクションプールの効果測定
以下のプログラムで、コネクションプールの効果を測定してみました。
package sample; import java.sql.Connection; import java.sql.SQLException; import java.util.ArrayList; import java.util.List; public class Main { private static final int CONNECTION_COUNT = 30; public static void main(String[] args) throws SQLException { System.out.println("start"); ConnectionFactory factory = ConnectionFactory.getInstance(); List<Connection> connections = createConnections(factory); releaseConnections(connections); List<Connection> connections2 = createConnections(factory); releaseConnections(connections2); List<Connection> connections3 = createConnections(factory); releaseConnections(connections3); System.out.println("end"); } private static List<Connection> createConnections(ConnectionFactory factory) throws SQLException { List<Connection> conList = new ArrayList<Connection>(); long start = System.currentTimeMillis(); for(int i = 0; i < CONNECTION_COUNT; i++){ Connection con = factory.getConnection(); // なんらかの処理 // PreparedStatement ps = con.prepareCall("select * from emp where empno = ?"); // ps.setInt(1, 7369); // ResultSet rs = ps.executeQuery(); // while(rs.next()){ // System.out.println(rs.getString("ename")); // } // ps.close(); conList.add(con); } long end = System.currentTimeMillis(); System.out.println("createConnections 所要時間:" + (end - start)); return conList; } private static void releaseConnections(List<Connection> conList) throws SQLException { long start = System.currentTimeMillis(); for(int i = 0; i < CONNECTION_COUNT; i++){ Connection con = conList.get(i); con.close(); } conList.clear(); long end = System.currentTimeMillis(); System.out.println("releaseConnections 所要時間:" + (end - start)); } }
package sample; import java.sql.Connection; public abstract class ConnectionFactory { protected ConnectionFactory(){} public static ConnectionFactory getInstance(){ return new DBCPConnectionFactory(); } public abstract Connection getConnection(); }
package sample; import java.io.IOException; import java.io.InputStream; import java.sql.Connection; import java.sql.SQLException; import java.util.Properties; import javax.sql.DataSource; import org.apache.commons.dbcp.BasicDataSourceFactory; public class DBCPConnectionFactory extends ConnectionFactory { private DataSource ds; protected DBCPConnectionFactory(){ Properties properties = new Properties(); try { InputStream is = ClassLoader.getSystemResourceAsStream("dbcp.properties"); properties.load(is); this.ds = BasicDataSourceFactory.createDataSource(properties); } catch (IOException e) { throw new RuntimeException(e); } catch (Exception e) { throw new RuntimeException(e); } } @Override public Connection getConnection() { try { return ds.getConnection(); } catch (SQLException e) { throw new RuntimeException(e); } } }
dbcp.propertiesは先のものと同じです。
実行結果は以下のとおり。
start createConnections 所要時間:1437 releaseConnections 所要時間:0 createConnections 所要時間:31 releaseConnections 所要時間:0 createConnections 所要時間:16 releaseConnections 所要時間:0 end
createConnectionsメソッドの2回目以降の所要時間が大きく短縮されました。