新しい IBM Developer JP サイトへようこそ!サイトのデザインが一新され、旧 developerWorks のコンテンツも統合されました。 詳細はこちら

Archived | WAS虎の巻 第3回「JDBCとデータベース接続」

Archived content

Archive date: 2020-09-16

このコンテンツは作成された時点のものであり、それ以降の更新は適用されていません。テクノロジーの進歩により、コンテンツの一部、手順、説明図などが現在の状況と一致していない可能性があります。
1

データベースへのアクセス

前回は、Websphere Application Server(WAS)を構成する基本的なコンポーネントと、WASを利用したWebシステムの複数台構成についてご紹介しました。今回は、WASを経由してデータベースへアクセスする際の手法についてご説明します。

1.1. リソースアクセス

Webシステムに求められることは、アプリケーション・サーバーだけで解決できるとは限りません。アプリケーションからリレーショナル・データベースにアクセスしてデータを参照・更新したり、メッセージング製品を利用して他システムと連携したりすることも必要となる場合があります。データベースやメッセージング製品など、アプリケーション・サーバーの外部に存在するシステムをEIS(Enterprise Information Service)と呼び、アプリケーション・サーバーからはリソース・アダプターを経由して接続することができます。

alt

今回は、EISとしてデータベースを利用する場合の接続方法について紹介します。

1.2. データベースの接続に必要な要素

アプリケーションがデータベース接続を行うために物理的に必要な要素は以下の通りです。

  • データベースに接続するアプリケーション
  • アプリケーションを動作させるアプリケーション・サーバー
  • データベース

さらにそれらは以下のような要素技術を使い、アプリケーションからデータベース接続を行います。

  • アプリケーションを書く際に用いるJDBC API
  • データベースによって提供されるJDBCドライバー
  • アプリケーション・サーバー上での設定を定義したデータソース
  • アプリケーションとデータソースを結びつけるJNDI

1.3. JDBC

開発者は、JDBC APIを用いて、データベースに接続するアプリケーションをコーディングします。Java SEでは、リレーショナル・データベースへ接続を行うためのAPIとしてJDBC(Java Data Base Connectivity)が定義されています。データベースにはさまざまな製品がありますが、JDBCという共通のAPIを利用することにより、データベース製品に依存しない、可搬性の高いプログラムが作成できます。

1.4. JDBCドライバー

JDBCのAPIと各データベース製品を結びつけるクラス群がJDBCドライバーです。JDBCドライバーは、JarファイルやZipファイルといった形で、各データベース・ベンダーやサードパーティーによって提供されています。同じデータベース製品でも、接続の種類や2フェーズコミットのサポートの有無などにより、JDBCドライバーにもいくつかの種類がある場合もあります。JDBCドライバーは、JDBCプロバイダーとしてそのクラスパスなどをアプリケーション・サーバーに設定しておく必要があります。

alt

JDBCドライバーは、アクセスの方法によって以下の4つのタイプに分類されます。各データベース・ベンダーは1つまたは複数の種類のドライバーを提供していますが、必ずしも全ての種類のドライバーを提供しているわけではありません。

  • Type 1 : JDBCからの要求をODBC(Open Database Connectivity)と呼ばれるデータベースアクセス専用のAPIを介してアクセスを行います。
  • Type 2 : JDBCからの要求をNative-API ドライバーと呼ばれる専用ライブラリに受け渡し、アクセスを行います。
  • Type 3 : JDBCからの要求をJavaで記述されたドライバー内で独自のプロトコル変換を行い、アクセスを行います。
  • Type 4 : JDBCからの要求を全てJava上で処理し、アクセスを行います。

Type 4はJDBCを利用したアクセスを全てJava上で行うため、ガーベッジコレクションの対象となり、Javaにオブジェクトの管理を委ねられ、さらにハードウェアやOSに依存しないというメリットがあるため、アプリケーション・サーバーを用いたアクセスの主流となっています。

alt

1.5. データソース

アプリケーション・サーバー上で動作させる場合、アプリケーション開発者の作成したJavaプログラムは、データソースを参照してデータベースにアクセスすることができます。データソースを使用することにより、プログラムの中でJDBCドライバーのロードなどを行う必要はなくなります。さらに、データベースに依存する部分がデータソースに吸収されるため、プログラムの可搬性も向上します。

また、アプリケーションがデータベースへアクセスする際に必要な要素には、以下のようなものがありますが、これらの設定もデータソースで定義されるため、プログラムを変更せずに、接続の宛先を変更することができます。

  • 接続先のデータベース名
  • データベース・サーバーのホスト名およびポート番号
  • JDBCドライバー名

アプリケーション・サーバー上にデータソースを定義するためには、まずJDBCプロバイダーを定義します。そのJDBCプロバイダーに対して、データソースを定義します。これらをアプリケーション・サーバーに設定することで、アプリケーションはデータソースを通じて接続を行うことが可能になります。1つのデータソースに対し、1つのデータベースが結びつきますので、複数のデータベースにアクセスするアプリケーションでは複数のデータソースの設定が必要となります。

alt

1.6. JNDI

アプリケーションからデータベースへ接続を行う際、データベースへの接続情報をコーディングで直接指定することも可能です。しかし、この方法ではデータベースが変更になるたびに修正が必要となるため、可搬性に劣ります。可搬性を上げるために用いられるのがJNDIと呼ばれる仕組みです。

JNDIはJava Naming and Directory Interfaceの略で、ディレクトリー・サービスが提供するデータやオブジェクトを名前で検索し、参照するためにJava EEで定められた仕様です。データソースもアプリケーション・サーバーのディレクトリー・サービスで管理されており、JNDI名を使用して検索できます。データソースはJNDI名で管理されているため、アプリケーションは物理的な接続先データベースの変更の影響を受けません。そのため、データベースに変更が生じた際にも、アプリケーションはデータベースの変更を気にすることなく、常に同じJNDI名のデータソースを用いてアクセスを行うことが可能です。

JNDIを用いたデータソースへのアクセスには2種類の方法があります。ひとつはアプリケーション・サーバー上に定義されたデータソースを直接参照する方法です。しかし、この場合はデータベースを切り替える際に、それに結びつくデータソースも同様に変更しなくてはならず、コード上の変更を必要とするため、可搬性は下がります。そのため、Java EEでは非推奨となっています。

alt

そこでアプリケーションでは、次の方法が取られます。まずアプリケーションのコード上にはポインターとして、仮の名前でJNDI名を定義しておきます。この仮の定義は、アプリケーションではデプロイメント記述子に定義しておきます。この仮の定義の記述フォーマットは、[ java:comp/env/<参照名> ]を使用することが原則であり、Java EEとしても良い作法と言われています。また、アプリケーション・サーバー上では、データソースに対してJNDI名を定義しておきます。

そして、アプリケーションをアプリケーション・サーバーに導入する際に、アプリケーションで定義した仮のJNDIと、アプリケーション・サーバーで定義されたデータソースのJNDI名を結び付けます。これがリソース参照と呼ばれる参照の方法です。

alt

リソース参照の利点は、可搬性が上がることです。データソースを直接参照する方法では、接続するデータベースを変更する際は、データベースに結びつくデータソースを変更させるためにプログラム上の記載を変更させなくてはなりません。ところが、リソース参照であれば、アプリケーションでの仮の定義と、アプリケーション・サーバー上の定義の結びつきを変更するだけでよく、プログラムに手を加える必要はありません。複数のデータソースをアプリケーション・サーバー上に定義しておき、必要に応じてデータベースの接続先を変えるといったことも、リソース参照を利用すれば容易に可能です。

alt

2

データベース接続構成

先ほどの章では、データベース接続を行うために基本的な要素について説明しました。この章では、さらに効率的にデータベース接続を行うための機能について紹介します。

2.1. アプリケーションの流れ

機能について紹介する前に、データベース接続を行うアプリケーションを見ていきましょう。データベースに接続を行い、その結果を取得するアプリケーションの基本的な流れは以下のようになります。

  1. JNDI名を引数とし、DataSourceオブジェクトを取得
  2. DataSourceオブジェクトから接続オブジェクト(Connectionオブジェクト)を生成
  3. 実行したいSQL文を引数にし、接続オブジェクトからStatementオブジェクトを生成
  4. データベースに対しSQL文を実行し、結果(ResultSetオブジェクトなど)を取得
  5. 各オブジェクトをクローズ

アプリケーションがデータベースに接続するには、まずデータソースのオブジェクトを取得します。データソースは「JNDI」の章で紹介したように、仮のJNDI名の定義とアプリケーション・サーバー上で定義したデータソースのJNDIが結びつくことによって、目的のデータソースが取得できます。

データソースにはgetConnectionメソッドがあり、このメソッドを実行すると、接続オブジェクトが生成されます。接続オブジェクトとは、データベースへの接続を抽象化したオブジェクトのことで、このオブジェクトを生成することで、データベースへの接続が可能になります。

次に、生成した接続オブジェクトからStatementオブジェクトを生成します。Statementオブジェクトは、データベースに対する処理命令となるSQLを実行させるために必要となるオブジェクトです。生成する際に実行したいSQL文を引数として入力させることで、SQL文を実行させることができます。

最後に、StatementオブジェクトのSQL実行メソッドを実行させ、データベースに対して処理を実行します。その結果は、例えばSELECTであればResultSetというオブジェクトで返されます。

2.2. 接続プール

データベースは1つのデータソースに結びついているため、同じデータベースに接続する限り、①の作業は一度で構いません。しかし、②以降の作業は接続を行うたびに毎回行う必要があります。②の接続オブジェクトの生成と消滅は、データベースへの接続と切断を意味し、アプリケーション・サーバーにとってもデータベースにとっても負担となる作業です。一度の生成と消滅にかかる負荷は僅かでも、この処理が何回も繰り返し行われることによってパフォーマンスの低下に繋がる可能性もあります。従って、一度作成した接続オブジェクトを次回の接続の際に再利用することができれば、アプリケーション・サーバーの負荷が軽減され、アプリケーションもより効率的に動作させることができます。そのための仕組みが接続プールです。

alt

接続プールは、アプリケーションで作成される接続オブジェクトを再利用する仕組みです。接続プールを利用しない場合、生成された接続オブジェクトはデータベースの処理を終えると削除されますが、接続プールを利用した場合は、データベースの処理を終えた接続オブジェクトは接続プールに一定期間保管されます。一定期間内であれば、次に接続オブジェクトが必要となった時にプール内にある接続オブジェクトを再利用することができます。接続プールはデータソースを設定する時の設定項目の一つであるため、アプリケーション・サーバー上でデータソースを設定した際に同時に設定可能です。

接続プールは、接続オブジェクトをプーリングできるだけでなく、プーリングする接続オブジェクトの最大数やタイムアウトを指定することが可能です。これらの値を適切に設定することにより、データベースに接続を行う接続数を必要に応じて調整し、アプリケーションのパフォーマンスを調整することが可能です。

2.3. 接続プールで設定できるパラメーター

接続プールで設定できるパラメーターには以下のようなものがあります。

  • 最大接続数
  • 最小接続数
  • 接続タイムアウト
  • リープ時間
  • 未使用タイムアウト
  • 経過タイムアウト

最大接続数/ 最小接続数

最大接続数、最小接続数は接続プールにプーリングできる接続オブジェクトの数を調整するものです。最大接続数はその名の通り、プーリングできる接続オブジェクトの最大数を示していますが、最小接続数の設定には注意が必要です。

最小接続数は、増えすぎた接続オブジェクトを破棄する目的で設定するパラメーターです。プーリングされた接続オブジェクトは、一定期間未使用の状態が続くと、接続プールから破棄されますが、この際に接続プール内に残しておく最小の接続数が、この最小接続数になります。接続オブジェクトは可能な限りプーリングされたものを使用し、不必要なオブジェクトは生成しません。そのため、必要な接続オブジェクトが最小接続数に満たない場合、接続プール内の接続オブジェクト数が、最小接続数より少なくなる可能性があります。

alt

接続タイムアウト

接続オブジェクトは最大接続数を越えて生成されることはありません。そのため、最大数以上の接続オブジェクトが必要な事態になった場合、接続オブジェクトを生成することができず、リクエストは使用中の接続オブジェクトが空くのを待ちます。この待ち時間が接続タイムアウトです。

接続オブジェクトの空きを待ったものの、接続タイムアウトを迎えた場合は、アプリケーションにエラーを返します。頻繁にこの例外が返される場合には、接続プールの最大接続数を上げて、接続できるリクエストの数を増やす必要があります。

リープ時間 / 未使用タイムアウト / 経過タイムアウト

接続プール内に保管された接続オブジェクトは、接続プール内に残り続けるのではなく、適切なタイミングで破棄されます。この破棄に関わるパラメーターがリープ時間、未使用タイムアウト、経過タイムアウトです。

保管された接続オブジェクトは、一定のタイミングで、未使用である時間をチェックされています。このチェック間隔がリープ時間です。リープ時間を短くすると、チェックがより頻繁に行われるようになり、タイムアウトの判定がより正確になりますが、その分パフォーマンスに影響する可能性も考えられます。

接続プール内で保管された接続オブジェクトは、一定の期間が経つと接続プールから破棄されます。破棄されるまでの時間は、未使用タイムアウトと経過タイムアウトの設定により決まります。未使用タイムアウトは、使用されていない接続オブジェクトが接続プールに残ることを防ぐ目的で使用されます。タイムアウトになった接続オブジェクトは、最小接続数で指定された数だけ接続プール内に残り、それ以上の接続オブジェクトは接続プールから破棄されます。一方、経過タイムアウトは接続自体を破棄する目的で使用されます。接続を開始してから時間が経ち、経過タイムアウトを迎えた接続オブジェクトは、最小接続数で指定された数とは関係なく、接続プールから全て破棄されます。

最小接続数だけ接続オブジェクトを残しておくべきかどうかは、データベースに接続する頻度が一つの基準です。常にある程度のデータベース接続が見込まれるシステムでは、接続プールに一定数の接続オブジェクトを残しておくほうが良いですし、データベース接続を全く行わない時間が存在するなど、頻度にムラがある場合には、使用されない時間に接続オブジェクトを破棄してしまったほうが有効と言えるでしょう。

2.4. ステートメントのキャッシュ

パフォーマンスの向上を目的とした機能は接続プールだけではありません。「アプリケーションの流れ」の章でも紹介したように、生成した接続オブジェクトは、SQL文を実行するために、Statementオブジェクトを作成します。しかし実際には、StatementではなくPreparedStatementを使うケースが多いです。

PreparedStatementは、JDBCのステートメントを事前に準備しておくことによって、性能を上げる機能です。Statementは完全なSQL文を取り込みますが、PreparedStatementでは、SQL文の中に?記号を含んでおり、実行する際にsetメソッドを使って値を渡します。StatementとPreparedStatementを用いた際のアプリケーション・サーバーとデータベースの処理を下図に示します。

alt

Statementクラスを使用すると、アプリケーション側でStatementが実行された際に、データベース側でPrepareと呼ばれる解析処理が始まります。Prepareは構文をチェックし、そのSQL文をどのように実行するかを決定する作業です。Prepareが終わるとSQL文がデータベース上で実行され(Execute)、アプリケーション側でのデータ取得依頼に対応して、データをアプリケーションに返します(Fetch)。しかし、Prepare処理はデータベースにとっては時間のかかる処理であるため、SQLを実行させる際に毎回Prepare処理を行うのは効率的ではありません。

PreparedStatementクラスを使用した際は、Prepare処理はPreparedStatementオブジェクトを生成した際に実行されます。値がセットされ、アプリケーション側でPreparedStatementが実行されると、データベース側でExecute処理が実行されます。パラメーター(PreparedStatementの?記号部分)の値のみが変化するSQL文であれば、データベース側では以降、Prepare処理を行わなくなるため、そのぶんパフォーマンスの向上が見込めます。実際のデータベースの処理は、パラメーターだけ違うSQL文を使うことが多いため、PreparedStatementを使い、パラメーターをその都度セットしたほうが効率的です。

アプリケーション・サーバーには、さらにそれを一歩進めた「ステートメント・キャッシュ」と呼ばれる機能があります。これは作成されたPreparedStatementオブジェクトを、破棄するのではなく、アプリケーション・サーバーのメモリ上にキャッシュしておく機能です。ステートメント・キャッシュを使用した際のアプリケーション・サーバーとデータベースの処理を下図に示します。

alt

同じSQLがリクエストされると、再度PreparedStatementを生成するのではなく、キャッシュ上のPreparedStatementが再利用されます。それにより、データベース側にはPrepare処理の依頼自体がなくなり、さらなるパフォーマンスの向上が見込めます。

2.5. 失効した接続オブジェクトへの対応

アプリケーション・サーバーとデータベースの間でのネットワーク障害、データベースの障害、データベースの再起動などが発生すると、接続プールにプールされている接続オブジェクトは無効となり、その接続オブジェクトを用いたデータベース操作は行えなくなります。そのような失効した接続オブジェクトは接続プール内に残り続け、アプリケーションが接続プール内の接続オブジェクトを使用しようとした場合、StaleConnectionExceptionが発生します。失効した接続オブジェクトの対応として、失効した接続オブジェクトによるエラーを検知すると、接続プール内の接続オブジェクトを全てプールから破棄するといった設定を行うこともできます。しかし、この機能を用いても、最初のアクセスの際にはエラーが返されてしまいます。

alt

エラーを発生させない対応としては、従来はStaleConnectionExceptionをコード内でcatchしリトライする方法が取られていましたが、JDBC4.0以降ではisValidメソッドを用いて検証することが可能です。これは接続オブジェクトを実際に使用する前に、事前に接続オブジェクトの有効性を確認することでエラーの発生を防ぐ機能です。

isValidメソッドが導入される前は、データベースに対して確認用のSQLを実行し、その結果で接続オブジェクトの有効性を検証していました。しかし、isValidメソッドの登場により、実際にSQLを発行しなくても、接続オブジェクトの有効性の確認が可能になりました。事前検証の設定は、アプリケーションのコーディングで行うことも可能ですが、アプリケーションは変更せず、データソースの1つのプロパティーとしてアプリケーション・サーバーで設定することも可能です。具体的には、再試行回数と再試行間隔の2つのパラメーターを設定します。

isValidメソッドによる事前確認を有効にしておくと、有効な接続オブジェクトが取得できた際に始めて接続オブジェクトがアプリケーションに返されます。有効な接続オブジェクトが取得できない場合は、再試行回数と再試行間隔に伴い、リトライします。再試行回数を超えると、同様にアプリケーションに例外が返ります。

alt

3

データベースとトポロジー

この章では、データベースを用いたWebシステムのトポロジーと、データソースの設定範囲についてご説明します。

3.1. データベース接続トポロジー

WASとデータベースを使用したシステムの代表的なトポロジーは、ローカル構成とリモート構成に分けることができます。ローカル構成は、最もシンプルな構成であり、WASのシングルサーバー構成とデータベースを同一筐体に配置します。JVM、データベースがそれぞれ一つずつしか存在しないため、高可用性や高拡張性は実現できません。この構成は、これらの要件が必要でないテスト環境や開発環境で主に採用されます。

alt

リモート構成は、WAS のNetwork Development構成とデータベースを別筐体に配置します。アプリケーション・サーバーはクラスタリング機能を、DBサーバーはPowerHA 等のクラスタリングソフトウェアを使用することにより、高可用性と拡張性を実現します。この構成は、主に本番環境で採用されます。

alt

3.2. データソースの設定範囲

WAS虎の巻第二回で紹介しましたように、WASのNetwork Developmentを使用した構成では、セル、クラスター、ノード、アプリケーション・サーバーといった設定値の有効範囲が存在します。WASでは、データソースなどのリソースをクラスター、ノード、アプリケーション・サーバーといった範囲で定義することができます。

あるデータソースをサーバー・レベルで定義した場合には、そのデータソースは定義したサーバーでのみ使用可能です。ノード・レベルで定義した場合は、そのノード上で稼働する全てのサーバーでその設定が有効になります。同様にクラスター・レベルも、そのクラスター・メンバーがデータソースの有効範囲となります。複数のレベルで定義がされている場合には、広い範囲のレベル定義は、より限定的なレベルの範囲の定義により上書きされます。有効範囲の設定は、そのリソースを使用しないメンバーからは参照できないように限定的なレベルの範囲で設定する事が望ましいですが、限定的なレベルの範囲で複数のリソースを作成すると、定義に変更があった場合に修正箇所が増えるという面もあります。

alt

注意すべき点は、有効範囲は設定できますが、そのプロパティーは個々のサーバー単位で適用されるということです。例えば、あるデータソースをクラスター・レベルで定義した場合、そのデータソースは、クラスター内の全てのサーバー上で稼働するアプリケーションから使用することができます。しかし、そのデータソースの最大接続数が10だった場合、クラスター全体で10の接続を使用するのではなく、クラスター内の各サーバーが10の接続を使用できることになります。

WAS虎の巻第三回では、WASからのリソースアクセスとして、JDBCとデータベース接続についてご紹介しました。

次回は、JMSとメッセージングサービス接続についてご紹介します。