DBCP
DBCP(Database Connection Pool)コンポーネントは、JDBCリソースをプールする必要のあるアプリケーションで利用できます。このコンポーネントは、JDBC接続とは別にStatementインスタンスとPreparedStatementインスタンスのサポートも提供します。
JakartaのWebサイトにあるDBCPのドキュメントの説明は包括的で役に立ちます。特にユーザーガイドのDBCP Wikiサイトとシーケンス図は一見の価値があります。「org.apache.commons.dbcp」パッケージのjavadocには、APIに関する詳細な情報も含まれています。DBCPを使用するようにTomcatを設定する方法については、Jakarta-commons Wikiを参照してください。
このセクションのサンプルアプリケーションでは、軽量データベースエンジンHypersonic SQLを使ってDBCPの機能を例示しています。このアプリケーションの主な目的は、名前や電子メールアドレスなどの連絡先情報を格納して取り出すことにあります。このアプリケーションでは、DBCPフレームワークが提供するプリペアードステートメントとプールされたJDBC接続を使用します。ソースファイルはソースコードの「src」フォルダ内のパッケージ「in.co.narayanan.commons.dbcp」に収められています。
このアプリケーションでは以下のクラスを使用しています。
| ContactInfo | 連絡先の詳細を表現するドメインオブジェクト。 |
| ContactInfoRepository | データベースから連絡先レコードを作成するcreateContactInfoメソッドや、連絡先を検索するfindByNameメソッドなどが含まれているDAOクラス(このクラスに、プールされた接続およびステートメントの取得元となるIDatabaseContextの実装クラスへの参照が与えられます)。 |
| DatabaseContext | DBCPを使用してプールされた接続とステートメントをDAOクラスに供給します。 |
TestDBCP JUnitテストケースクラスを実行してアプリケーションを起動してください(アプリケーションの実行中は「hsqldb.jar」をクラスパスに入れておく必要があります)。このクラスはDatabaseContextを初期化し、ContactInfoRepositoryに参照を渡して、連絡先の詳細を格納します。リスト7はDBCPフレームワークを初期化するためのコードです。
// Load the HSQL Database Engine JDBC driver // hsqldb.jar should be in the class path try { Class.forName("org.hsqldb.jdbcDriver"); } catch (ClassNotFoundException e) { throw new DbException("Unable to load the db driver", e); } Properties db = getDBProperties(); ConnectionFactory connectionFactory = new DriverManagerConnectionFactory(db.getProperty("url"), db.getProperty("user"), db.getProperty("password")); connectionPool = new GenericObjectPool(); KeyedObjectPoolFactory stmtPool = new GenericKeyedObjectPoolFactory(null); new PoolableConnectionFactory( connectionFactory,connectionPool,stmtPool,null,false,true); dataSource = new PoolingDataSource(connectionPool);
DBCPは接続とステートメント参照をプールするためにCommons Poolフレームワークを使用します。このフレームワークは汎用性があって、任意のオブジェクトをプールできます。PoolableConnectionFactoryクラスはインスタンス化の際にコンストラクタに渡されたGenericObjectPoolインスタンスに自分自身を登録します。このファクトリクラスはJDBC接続の新しいインスタンスを作成するのに使われます。
接続プール参照をPoolingDataSourceに渡す必要があります。PoolingDataSourceクラスはjavax.sql.DataSourceインターフェイスを実装して、プールされた接続を提供します。DAOクラスはdataSource変数を使用して接続を取得することができます。
KeyedObjectPoolFactoryクラスのインスタンスをPoolableConnectionFactoryコンストラクタに渡すと、JDBC接続が作成するプリペアードステートメントオブジェクトのインスタンスをプールすることができます。KeyedObjectPoolFactoryはプールファクトリの変形で、リソースがキーごとにプールされます。プリペアードステートメントの場合、クエリがキーとして機能します。したがって、同じクエリの呼び出しに対してプールされたプリペアードステートメントインスタンスが返されます。
リスト8はDAOクラス内のごく一般的なメソッドです。7~9行目でDatabaseContextから接続を取得し、それを使ってデータベースにレコードを挿入します。
String query = "INSERT INTO contactinfo(name,email) VALUES(?,?)"; Connection con = null; PreparedStatement createContactPrStmt = null; try { try { con = context.getConnection(); createContactPrStmt = context.getPreparedStatement(con, query); createContactPrStmt.setString(1, contactInfo.getName()); createContactPrStmt.setString(2, contactInfo.getEmailAddress()); createContactPrStmt.execute(); } finally { if(createContactPrStmt != null) { createContactPrStmt.close(); } if(con != null) { con.close(); } } } catch(SQLException e) { throw new DbException("Unable to create ContactInfo record", e); } catch(DbException e) { throw new DbException("Unable to create ContactInfo record", e); }
DBCPで使われるプールはカスタマイズ性が高いので、エンタープライズソフトウェアに適しています。
DBUtils
DBUtilsフレームワークはJDBC APIを使いやすくする軽量ラッパーです。これにより、開発者はデータの照会と更新に専念し、リソースのクリーンアップをフレームワークに任せることができます。
このコンポーネントはデータアクセスオブジェクトでの使用にうってつけです。持続性のためにHibernateやJDOを使用する必要はないが、ダイレクトJDBC呼び出しを必要とするアプリケーションでは、DBUtilsを使用すると開発効率が上がります。Jakartaのサイト内の「Overview」と「Examples」は非常に包括的です。このフレームワークに含まれているクラスは多くないので、javadocも読みやすいです。
DBUtilsはどのような働きをするのでしょうか。SQLクエリのSELECT、INSERT、DELETE、またはUPDATEをQueryRunnerクラスのqueryメソッドに渡すことができます。クエリに加えて、ResultSetHandlerインターフェイスの実装も渡します。SELECTクエリが実行されると、取得したResultSetオブジェクトがハンドラに渡されます。ハンドラはレコードを読み取り、ドメインオブジェクトを作成し、呼び出し元に返すことができます。作成されたドメインオブジェクトはqueryメソッドの呼び出し元に返されます。
このフレームワークには、ArrayHandler、ArrayListHandler、BeanHandler、BeanListHandler、ColumnListHandler、KeyedHandler、MapHandler、MapListHandler、ScalarHandlerなどのハンドラがあります。しかし、ResultSetHandlerを実装することで、特定の状況に使用する独自のハンドラを書くこともできます。
以降では、このフレームワークの例を示すために、DBCPフレームワークのサンプルアプリケーションの修正版を紹介します。DatabaseContextクラスとContactInfoRepositoryクラスが、DBUtilsフレームワークを使用するように修正されています。リスト9はこれらの修正のコードです。このコードは、DBCPフレームワークによって返されるDataSourceインスタンスの参照をQueryRunnerコンストラクタに渡します。したがって、クエリーランナーのインスタンスをシステム内のすべてのクラスで利用できます(ソースファイルはソースコードの「src」フォルダ内のパッケージ「in.co.narayanan.commons.dbutils」に収められています)。
public class DatabaseContext implements IDatabaseContext { // Other declarations private QueryRunner qRunner; public DatabaseContext() throws DbException { init(); } private void init() throws DbException { // Initializing for DBCP dataSource = new PoolingDataSource(connectionPool); qRunner = new QueryRunner(dataSource); } // Other public and private methods public QueryRunner getQueryRunner() { return qRunner; } }
リスト10はContactInfoRepositoryクラスからのコードで、QueryRunnerを使ってデータベースにレコードを挿入する方法を示しています。updateメソッドはSQLクエリとそのパラメータを受け取り、それらをプリペアードステートメントに渡します。ここで注目すべきことは、リソースのクリーンアップをDBUtilsフレームワークがやってくれるので、GUIでユーザーにエラーを表示するためにSQLExceptionだけを処理すればよいという点です。
public void createContactInfo(ContactInfo contactInfo) throws DbException { String query = "INSERT INTO contactinfo(name,email) VALUES(?,?)"; try { context.getQueryRunner().update(query, new Object[] {contactInfo.getName(), contactInfo.getEmailAddress()}); } catch(SQLException e) { throw new DbException( "Unable to create ContactInfo record", e); } }
リスト11では、ResultSetHandlerインターフェイスの実装を作成し、QueryRunner内のqueryメソッドにインスタンスを渡します。データベースからレコードを選択するためにプリペアードステートメントのパラメータも渡します。このリストにはカスタムのResultSetHandlerも含まれており、その使用方法が示されています。より簡単なアプローチは、BeanHandlerを使用し、リフレクションを使ってContactInfoドメインオブジェクトに自動的に値を入れるというものです。
private static class ContactInfoRSHandlerByName implements ResultSetHandler { public Object handle(ResultSet results) throws SQLException { return getContactInfo(results); } private ContactInfo getContactInfo(ResultSet results) throws SQLException { if(results.next()) { ContactInfo contact = new ContactInfo(); contact.setName(results.getString("name")); contact.setEmailAddress(results.getString("email")); return contact; } else { return null; } } } public ContactInfo findByName(String name) throws DbException { String query = "select name, email from contactinfo where name=?"; try { return (ContactInfo)context.getQueryRunner() .query(query,new Object[] {name}, new ContactInfoRSHandlerByName()); } catch(SQLException e) { throw new DbException( "Unable to find ContactInfo record by name", e); } }
