連載
» 2003年04月12日 00時00分 UPDATE

現場に活かすJakarta Project(5):Commonsでオブジェクトプーリングを実現

[横田健彦,(株)東芝]

 今回も、前回「XMLを簡単にJavaオブジェクトにマッピング」に引き続きCommonsプロジェクトの活用法を紹介します。今回からは2回連続でプーリングをテーマにします。今回は、オブジェクトプーリングを容易に実現するためのPoolコンポーネント。そして次回は、データベースコネクションを行うためのDBCPコンポーネントについて説明していきます。

Pool - オブジェクトのプーリングを実現する

 オブジェクトのプーリングは、パフォーマンス向上を目的に使用されます。プーリングとは、生成したオブジェクトをプール(pool)に蓄積しておき、必要になった際にプールからオブジェクトを取り出して利用し、不要になった際にはオブジェクトをプールに戻すことです。利用する度にオブジェクトを生成する方法に比べ、速度が速いという利点があります。

 小規模なプログラムではオブジェクトをプールする場面はあまりありませんが、例えばサーバサイドプログラミングでは、スレッドオブジェクトやデータベースコネクションオブジェクトなどの、生成にオーバーヘッドがかかるオブジェクトをプールして再利用することがよくあります。Jakarta CommonsのPoolコンポーネントは、オブジェクトをプールするためのメカニズムを提供します。この原稿執筆時点でのPoolコンポーネントの最新バージョンは1.0.1です。

ObjectPoolインターフェイスとPoolableObjectFactoryインターフェイス

 Poolの根幹をなすインターフェイスとして、ObjectPool(org.apache.commons.pool.ObjectPool)とPoolableObjectFactory(org.apache.commons.pool.PoolableObjectFactory)の2つのインターフェイスがあります。

 ObjectPoolはオブジェクトプールインスタンスを利用するためのインターフェイスで、PoolableObjectFactoryはオブジェクトプールに蓄積するオブジェクトのライフサイクルを管理するためのインターフェイスです。Poolを用いてオブジェクトプールを実現するには、通常まずプールするオブジェクトのライフサイクルを管理するPoolableObjectFactoryインターフェイスの実装クラスを準備し、次にPoolableObjectFactoryインターフェイスの実装クラスをコンストラクタに与えてObjectPoolインターフェイスの実装クラスのインスタンスを生成します。後は生成したObjectPoolインスタンスからオブジェクトを取り出して利用し、不要になった時点でObjectPoolインスタンスにオブジェクトを返すわけです。

 ObjectPoolは表1のようなメソッドを持つインターフェイスです。これらのメソッドのうち必須なのはborrowObject、returnObject、closeの3つです。

表1 ObjectPoolインターフェイスのメソッド
メソッド名 説明
borrowObject プールからオブジェクトを取り出す
clear プール中のアイドル状態のオブジェクトをクリアする(オプション)
close プールをクローズする
getNumActivate プールから取り出されて利用されているオブジェクトの個数を返す(オプション)
getNumIdle プール中のアイドル状態のオブジェクトの個数を返す(オプション)
returnObject プールから取り出したオブジェクトを返却する
setFactory オブジェクトを生成するためのPoolableObjectFactoryを設定する(オプション)

 ObjectPool#borrowObject()メソッドは引数なしでオブジェクトをプールから取り出します。単にプーリングするのではなく、あるキーとオブジェクトを結び付けて管理したい場合は、ObjectPoolの代わりにKeyedObjectPoolインターフェイス(org.apache.commons.pool.KeyedObjectPool)の実装クラスを用います。KeyedObjectPoolインターフェイスは引数なしのborrowObject()メソッドの代わりにjava.lang.Objectクラスのオブジェクトを引数に持つborrowObject()メソッドを持っており、このメソッドを用いることでキーを指定してオブジェクトをプールから取り出すことができるようになっています。

 PoolにはObjectPoolの実装クラスとしてGenericObjectPool、StackObjectPool、SoftReferenceObjectPoolの3つ(それぞれorg.apache.commons.pool.implパッケージ)が用意されています(これらに対応するKeyedObjectPoolの実装クラスもあります)。それぞれのクラスの説明を表2にまとめておきます。また、ObjectPoolの実装クラスを独自に作成することもできます。その場合、BaseObjectPoolクラス(org.apache.commons.pool.BaseObjectPool)のサブクラスとして作成することが推奨されています。

表2 ObjectPoolインターフェイスの実装クラス
クラス名 説明
GenericObjectPool 汎用的なオブジェクトプール
StackObjectPool 限定された個数のオブジェクトをプールするようなオブジェクトプール。ただしアイドル状態のオブジェクトがないときにオブジェクトの取り出し要求がきた場合には新規にオブジェクトを生成する
SoftReferenceObjectPool アイドル状態のインスタンスが適宜ガベージコレクタによって除去されるようなオブジェクトプール

 PoolableObjectFactoryは表3のようなメソッドを持つインターフェイスです。このインターフェイスの実装クラスはオブジェクトプールがオブジェクトのライフサイクルを管理するために用いられます。通常ObjectPoolの実装クラスはPoolableObjectFactoryオブジェクトを引数に取るコンストラクタを持っており、引数として指定されたPoolableObjectFactoryを用いてオブジェクトの管理を行うようになっています(なおKeyedObjectPoolの実装クラスのコンストラクタにはKeyedPoolableObjectFactoryクラス(org.apache.commons.pool.KeyedPoolableObjectFactory)のオブジェクトを指定します)。

表3 PoolableObjectFactoryインターフェイスのメソッド
メソッド名 説明
makeObject オブジェクトを新規に生成する
activateObject オブジェクトがプールから取り出される前に呼ばれる
passivateObject オブジェクトがプールに返却されたときに呼ばれる
validateObject プールから取り出されるオブジェクトが正当であるかどうかを判定する
destroyObject プールからオブジェクトが破棄されるときに呼ばれる

 PoolableObjectFactoryの実装クラスの作成を簡便化するための基底クラスとして、BasePoolableObjectFactoryクラス(org.apache.commons.pool.BasePoolableObjectFactory)が用意されています。

Poolの使用例

 それではサンプルプログラムを用いて具体的にPoolの使い方を説明していきたいと思います。サンプルプログラムはデータベースに対して指定した回数だけリクエストを発行するようなWebアプリケーションです。データベースへのアクセスはJDBCドライバが生成するConnectionオブジェクト(java.sql.Connection)を用いて行いますが、本サンプルプログラムではPoolを用いてこのConnectionオブジェクトをプーリングして再利用します。

 本稿のサンプルプログラムを動作させるにはTomcatとMySQLが必要です(動作確認はTomcat 4.1.18とMySQL 2.23.51の組み合わせで行いました)。プログラムのアーカイブはこちらからダウンロードできます。ダウンロードしたアーカイブを展開してできるsampleディレクトリをTomcatのwebappsディレクトリ以下にコピーしてWebブラウザからhttp://localhost:8080/sample/index.jspにアクセスしてください(TomcatのトップページのURLをhttp://localhost:8080/とします)。すると画面1のような画面が表示されます。

サンプルプログラムの実行画面 サンプルプログラムの実行画面

 「Push!」と書かれているボタンを押すと、テキストボックスで指定された回数だけ次の処理を繰り返して処理時間の合計(秒)を表示します。

  1. Connectionオブジェクトの取得
  2. SQL文の発行(SELECT 1)
  3. Connectionオブジェクトの解放

 「プールを使用しないデータベースアクセス」の項のボタンを押すと、毎回Connectionオブジェクトを生成してデータベースにアクセスします。これに対して「プールを使用するデータベースアクセス」の項のボタンを押すと、Connectionオブジェクトを生成するのではなくオブジェクトプールからConnectionオブジェクトを取り出してデータベースアクセスを行います。後者の方がConnectionオブジェクトを生成するコストがかからない分高速に処理を行うことができます。

サンプルプログラムの動き

 ここから、本稿のサンプルプログラムにおけるPoolの利用方法について具体的に説明していきます。Poolを用いてオブジェクトのプーリングを実現するには、まずObjectPoolのインスタンスを生成します。これを行っているのがindex.jspのjspInit()メソッドです(リスト1)。

リスト1 index.jspのjspInit()メソッド
  /**
   * このJSPの初期化をします。
   */
  public void jspInit()
  {
    try {
      // MySQLのためのJDBCドライバをロードします。
      Class.forName("com.mysql.jdbc.Driver");

      // オブジェクトプールの管理クラスのインスタンスを生成します。(1)
      PoolableObjectFactory factory = new SimpleConnectionFactory(
        url_, user_, password_);

      // オブジェクトプールのインスタンスを生成します。(2)
      pool_ = new StackObjectPool(factory);
    } catch (Throwable t) {
      throw new RuntimeException(t);
    }
  }

 オブジェクトプールを利用するには、プーリングするオブジェクトのライフサイクルを管理するためのPoolableObjectFactoryインターフェイスの実装クラスを用意する必要があります。そのためまずConnectionオブジェクトのライフサイクルを管理するSimpleConnectionFactoryクラス(net.skirnir.sample.SimpleConnectionFactory)のインスタンスを生成します(1)。通常Poolを使ってオブジェクトプーリングを実現するには、このように独自のPoolableObjectFactoryの実装クラスを用意する必要があります。

 SimpleConnectionFactoryクラスはBasePoolableObjectFactoryのサブクラスとして作成してあります(リスト2)。ここではConnectionオブジェクトを生成するためのmakeObject()メソッドのみ実装していますが、プーリングするオブジェクトの性質によってはPoolableObjectFactoryインターフェイスのそのほかのメソッドを実装する必要があるかもしれません。

リスト2 SimpleConnectionFactory.java(抜粋)
public class SimpleConnectionFactory extends BasePoolableObjectFactory
{
    ... (略) ...

    /**
     * このクラスのインスタンスを生成します。
     *
     * @param url データベース接続のためのURL。
     * @param user データベース接続のためのユーザ名。
     * @param password データベース接続のためのパスワード。
     */
    public SimpleConnectionFactory
       (String url, String user, String password)
    {
        url_ = url;
        user_ = user;
        password_ = password;
    }

    /**
     * java.sql.Connectionオブジェクトを生成します。
     *
     * @return 生成したオブジェクト。
     */
    public Object makeObject()
        throws Exception
    {
        return DriverManager.getConnection(url_, user_, password_);
    }
}

 話をjspInit()に戻します。ConnectionFactoryクラスのインスタンスを生成した後、生成したConnectionFactoryインスタンスをコンストラクタの引数として与えてObjectPoolのインスタンスを生成します(2)。ここではStackObjectPoolクラスを用いています。なおオブジェクトプールのインスタンスの生成には、直接コンストラクタを呼び出す方法のほかにファクトリクラスを用いる方法もあります。StackObjectPoolクラスに対応するファクトリクラスとしてはStackObjectPoolFactoryクラス(org.apache.commons.pool.impl.StackObjectPoolFactory)が用意されています。

 以上で初期化処理は終わりです。この後Webブラウザ上で「Push!」ボタンを押すとデータベースアクセスを行う処理が実行されます。index.jspのうち、この処理に関する部分をリスト3に示します。

リスト3 ボタンを押したときの処理(抜粋)
   Connection con = null;
  for (int i = 0; i < count; i++) {
   try {
    // Connectionオブジェクトを取得します。
    con = getConnection(usePool);

    // SQL文を実行します。
    Statement stmt = con.createStatement();
    ResultSet rs = stmt.executeQuery("SELECT 1");

    rs.close();
    stmt.close();
   } finally {
    // Connectionオブジェクトを返却します。
    returnConnection(con, usePool);
   }
  }

 リスト3ではgetConnection()メソッドでConnectionオブジェクトを取得し、データベースにリクエストを送信した後returnConnection()メソッドでConnectionオブジェクトの使用を終了しています。getConnection()オブジェクトとreturnConnection()オブジェクトはそれぞれリスト4リスト5のように記述されています。

リスト4 index.jspのgetConnection()メソッド
  /**
   * java.sql.Connectionオブジェクトを返します。
   *
   * @param usePool trueの時プールからオブジェクトを取得して返します。
   *        falseの時オブジェクトを毎回生成して返します。
   * @return java.sql.Connectionオブジェクト。
   */
  private Connection getConnection(boolean usePool)
    throws Exception
  {
    if (usePool) {
      // オブジェクトプールからオブジェクトを取り出します。(3)
      return (Connection)pool_.borrowObject();
    } else {
      return DriverManager.getConnection(url_, user_, password_);
    }
  }

リスト5 index.jspのreturnConnection()メソッド
  /**
   * java.sql.Connectionオブジェクトの使用を終了します。
   *
   * @param con java.sql.Connectionオブジェクト。
   * @param usePool trueの時オブジェクトをプールに返却します。
   *        falseの時オブジェクトをクローズします。
   */
  private void returnConnection(Connection con, boolean usePool)
    throws Exception
  {
    if (con != null) {
      if (usePool) {
        // オブジェクトプールにオブジェクトを返却します。(4)
        pool_.returnObject(con);
      } else {
        con.close();
      }
    }
  }

 リスト4では先ほど生成したオブジェクトプールのインスタンスについてObjectPool#borrowObject()メソッドを呼び出すことによってConnectionオブジェクトをプールから取り出しています(3)。一方、リスト5ではObjectPool#returnObject()メソッドを呼び出してConnectionオブジェクトをプールに返却しています(4)。なお、必要な初期化処理や終了処理は通常PoolableObjectFactoryクラスが行ってくれますので、プールからオブジェクトを取り出したときにConnectionオブジェクトを初期化したり、プールにオブジェクトを返却するときにConnectionオブジェクトをクローズしたりする必要はありません。

 まとめると、Poolを用いたオブジェクトプールの利用の流れは以下のようになります。

(1) プールするオブジェクトのためのPoolableObjectFactoryインターフェイスの実装クラスのインスタンス(以下pof)を生成する
(2) 生成したインスタンスpofをコンストラクタの引数に与えてObjectPoolインスタンス(以下op)を生成する
(3)(4) 必要に応じて以下の処理を繰り返す

・op.borrowObject()メソッドを用いてオブジェクトをプールから取り出す(以下o)
・借り出したオブジェクトoを使用する
・op.returnObject()メソッドを用いて取り出したオブジェクトoをプールに返却する

 次回はPoolよりも便利にデータベースのコネクションをプーリングできるDBCPを紹介します。

筆者プロフィール

横田健彦(よこた たけひこ)

東京工業大学卒業後、(株)東芝に入社。現在、知識メディアラボラトリーにてコミュニティベース情報共有システムの研究に従事。小学校のころからコンピュータに触れ、主にゲームプログラミングを通してBASIC、アセンブラをはじめとする多数の言語を学ぶ。JavaではJakartaプロジェクトの成果物を利用していく中で主にWebアプリケーションプログラミングの面白さに引かれ、Ja-Jakartaプロジェクトの活動に貢献する一方でオープンソースのJavaベースのWebコンテンツ管理システムであるKvasir/Soraの開発を行っている。



Copyright© 2017 ITmedia, Inc. All Rights Reserved.

@IT Special

- PR -

TechTargetジャパン

この記事に関連するホワイトペーパー

RSSについて

アイティメディアIDについて

メールマガジン登録

@ITのメールマガジンは、 もちろん、すべて無料です。ぜひメールマガジンをご購読ください。