注: この章の内容は、Addison Wesley 社より Java シリーズの 1 巻として出版された『JDBCTM API Tutorial and Reference, Second Edition: Universal Data Access for the JavaTM 2 Platform』(ISBN 0-201-43328-1) に基づいて作成したものです。
DataSource オブジェクトは、データソースを Java プログラミング言語で表現したものです。データソースとは、一言で言えば、データを格納するための機構です。データソースには、大企業向けの複雑なデータベースなど、高度なものもありますし、行と列を含むファイルなど、単純なものもあります。データソースは、リモートサーバ上、ローカルデスクトップマシン上のいずれに存在してもかまいません。アプリケーションはデータソースにアクセスする際に接続を使用しますが、DataSource オブジェクトは、そのインスタンスが表現するデータソースへの接続を作成するファクトリとみなすことができます。DataSource インタフェースには、データソースとの接続を確立するためのメソッドが 2 つ用意されています。
データソースへの接続を確立する場合、できれば DriverManager オブジェクトではなく、DataSource オブジェクトを使用することをお勧めします。DriverManager クラスと DataSource インタフェースの類似点としては、どちらも、接続作成用のメソッド、接続作成時のタイムアウト制限の取得/設定メソッド、およびログ用ストリームの取得/設定メソッドを備えている点が挙げられます。
ただし、両者の類似点よりも、相違点のほうが重要です。DataSource オブジェクトは DriverManager オブジェクトと異なり、表現しているデータソースを識別および記述するプロパティを備えています。また、DataSource オブジェクトは、JavaTM Naming and Directory Interfacetm (JNDI) ネーミングサービスと連携して動作し、DataSource オブジェクトを使用するアプリケーションとは別個に作成、配置、および管理されます。ドライバベンダーは、DataSource インタフェースの基本実装クラスを、その JDBC 2.0 または 3.0 ドライバ製品の一部として提供します。システム管理者が DataSource オブジェクトを JNDI ネーミングサービスに登録する手順と、アプリケーションが JNDI ネーミングサービスに登録された DataSource オブジェクトを使ってデータソースへの接続を取得する手順については、この章の後半で説明します。
DataSource オブジェクトを JNDI ネーミングサービスに登録する方法には、DriverManager を使用する方法と比べて、主に 2 つの利点があります。第 1 に、アプリケーション内にドライバ情報をハードコードする必要がありません (DriverManager ではそうする必要があります)。プログラマは、目的のデータソースに対する論理名を選択し、その論理名を JNDI ネーミングサービスに登録することができます。アプリケーションは論理名を使用し、JNDI ネーミングサービスはその論理名に関連付けられた DataSource オブジェクトを返します。その DataSource オブジェクトを使えば、それによって表現されているデータソースへの接続を作成できます。
2 つ目の主な利点は、DataSource 機構を使えば、開発者は接続プールや分散トランザクションなどの機能を活用した DataSource クラスを実装できる、という点です。接続プールを使えば、パフォーマンスが大幅に向上します。なぜなら、接続が要求されるたびに、新しい接続を物理的に作成する代わりに既存の接続を再利用するからです。分散トランザクション機能を使えば、アプリケーションは、大企業の負荷の高いデータベース処理を実行できるようになります。
アプリケーションで接続を取得する際、DriverManager、DataSource のいずれのオブジェクトを使用してもかまいませんが、はるかに利点の多い DataSource オブジェクトを使用することをお勧めします。
DataSource オブジェクトには、表現している実際のデータソースを識別および記述するための一連のプロパティが含まれます。そうしたプロパティとしては、データベースサーバの場所、データベースの名前、サーバとの通信時に使用するネットワークプロトコルなどの情報が挙げられます。DataSource のプロパティは JavaBeans の設計パターンに従い、その値は通常、DataSource オブジェクトの配置時に設定されます。
JDBC 2.0 API には、各種ベンダーの DataSource 実装間の統一を図る目的で、一連の標準プロパティとそれらの標準名が規定されています。次の表に、各標準プロパティの標準名、データ型、および説明を示します。ただし、これらのプロパティのすべてを DataSource 実装でサポートしなければならないわけではありません。この表の標準名は、実装でプロパティをサポートする際に使用すべき名前を示しているにすぎません。
内部的な | ||
もちろん、DataSource オブジェクトは、表現しているデータソースが接続作成時に必要とするプロパティのすべてをサポートしている必要がありますが、すべての DataSource 実装がサポートすべきプロパティは、description だけです。こうしたプロパティの標準化により、たとえば、使用可能なデータソースについて、説明とその他の使用可能なプロパティ情報を一覧表示するユーティリティを記述できるようになります。
DataSource オブジェクトでは、表 4.1 に記載されていないプロパティも使用できます。ベンダーは独自のプロパティを追加できますが、その場合、新しいプロパティのそれぞれにベンダー固有の名前を割り当てる必要があります。
ある DataSource オブジェクトでプロパティをサポートする場合、そのプロパティの getter メソッドと setter メソッドを、そのオブジェクトに用意する必要があります。次のコードは、DataSource オブジェクト ds でプロパティ serverName をサポートする際に必要となるメソッドを示したものです。
ds.setServerName("my_database_server");
String serverName = ds.getServerName();
プロパティの設定は通常、開発者またはシステム管理者が、データソースのインストール中に GUI ツールを使って行います。データソースに接続するユーザは、プロパティの取得/設定を行いません。これを強制する目的で、DataSource インタフェースには、プロパティの getter メソッドおよび setter メソッドが含まれていません。それらのメソッドは実装内にのみ含まれます。getter メソッドおよび setter メソッドを実装に含め、公開インタフェースには含めないことで、DataSource オブジェクトの管理 API とアプリケーションが使用する API との間で、ある種の分離が実現されます。管理ツールからプロパティにアクセスするには、イントロスペクションを使用します。
JNDI は、アプリケーション内からネットワーク経由でリモートサービスを検索しアクセスするための統一的な方法を提供します。リモートサービスとしては、メッセージングサービスやアプリケーション固有のサービスなど、さまざまな企業サービスが考えられますが、JDBC アプリケーションではもちろん、主にデータベースサービスを使用します。DataSource オブジェクトの作成と JNDI ネーミングサービスへの登録が完了すると、アプリケーションから JNDI API 経由でその DataSource オブジェクトにアクセスし、そのオブジェクトが表現するデータソースに接続できるようになります。
DataSource オブジェクトは通常、それを使用する Java アプリケーションとは別個に作成、配置、および管理されます。たとえば、次のコードでは、DataSource オブジェクトを作成し、そのプロパティを設定し、それを JNDI ネーミングサービスに登録しています。なお、データソースに対する DataSource オブジェクトを作成および配置するのは、ユーザではなく、開発者またはシステム管理者です。クラス VendorDataSource はほとんどの場合、ドライバベンダーによって提供されます (次のセクションでは、ユーザが接続を取得する際に記述するコードの例を示します)。また、DataSource オブジェクトの配置はおそらく GUI ツールを使って行われるため、主に説明目的で示した次のコードは、そうしたツールの内部で実行されることになります。
VendorDataSource vds = new VendorDataSource();ctx.bind("jdbc/AcmeDB", vds);vds.setServerName("my_database_server"); vds.setDatabaseName("my_database"); vds.setDescription("the data source for inventory and personnel"); Context ctx = new InitialContext();
最初の 4 行では、javax.sql.DataSource インタフェースを実装したベンダークラス VendorDataSource の API を使用しています。これらの行では、DataSource オブジェクト vds を作成し、そのプロパティ serverName、databaseName、description に値を設定しています。5 行目と 6 行目では、JNDI API を使って vds を JNDI ネーミングサービスに登録しています。5 行目では、デフォルトの InitialContext コンストラクタを呼び出して、初期 JNDI ネーミングコンテキストを参照する Java オブジェクトを作成しています。なお、ここでは紹介していませんが、システムプロパティによって、使用すべきネーミングサービスプロバイダが JNDI に通知されます。最後の行では、vds と、その vds が表現するデータソースの論理名とを、関連付けています。
JNDI の名前空間は、1 つの初期ネーミングコンテキストと、その下に配置される任意の個数のサブコンテキストから構成されます。その構造は、多くのファイルシステムに見られるディレクトリ/ファイル構造に類似した階層構造であり、初期コンテキストはファイルシステムのルートに、サブコンテキストはサブディレクトリに、それぞれ相当します。JNDI 階層のルートは初期コンテキストであり、ここでは変数 ctx として表現されています。初期コンテキストの下には任意の個数のサブコンテキストを定義できますが、その 1 つが JDBC データソース用の予約語である JNDI サブコンテキスト jdbc です (データソースの論理名は、サブコンテキスト jdbc 内か、jdbc 配下のサブコンテキスト内に定義します)。階層内の最後の要素は登録対象のオブジェクトです。このオブジェクトはファイルに相当し、ここではデータソースの論理名になります。上記コードの 6 行を実行すると、VendorDataSource オブジェクト vds が jdbc/AcmeDB に関連付けられます。次のセクションでは、これを使ってアプリケーション内からデータソースに接続する方法を示します。
1 つ前のセクションでは、DataSource オブジェクト vds のプロパティを設定し、そのオブジェクトを論理名 AcmeDB に関連付けました。次のコードには、この論理名を使って vds が表現しているデータベースに接続するアプリケーションコードが含まれています。続いてこのコードでは、取得した接続を使って、販売部門と顧客サービス部門の従業員の名前と役職を一覧表示しています。
Context ctx = new InitialContext();con.close();DataSource ds = (DataSource)ctx.lookup("jdbc/AcmeDB"); Connection con = ds.getConnection("genius", "abracadabra"); con.setAutoCommit(false); PreparedStatement pstmt = con.prepareStatement( "SELECT NAME, TITLE FROM PERSONNEL WHERE DEPT = ?"); pstmt.setString(1, "SALES"); ResultSet rs = pstmt.executeQuery(); System.out.println("Sales Department:"); while (rs.next()) { String name = rs.getString("NAME"); String title = rs.getString("TITLE"); System.out.println(name + " " + title); } pstmt.setString(1, "CUST_SERVICE"); ResultSet rs = pstmt.executeQuery(); System.out.println("Customer Service Department:"); while (rs.next()) { String name = rs.getString("NAME"); String title = rs.getString("TITLE"); System.out.println(name + " " + title); } rs.close(); pstmt.close();
最初の 2 行では JNDI API を、3 行目では DataSource API を、それぞれ使用しています。最初の行で初期ネーミングコンテキスト用の javax.naming.Context インスタンスを作成したあと、2 行目でそのインスタンスのメソッド lookup を呼び出すことで、jdbc/AcmeDB に関連付けられた DataSource オブジェクトを取得しています。ここで 1 つ前のコードを思い出してください。その最後の行で、jdbc/AcmeDB と vds とが関連付けられていました。したがって、lookup メソッドから返されるオブジェクトは、vds が表現していたのと同じ DataSource オブジェクトを参照しています。ただし、メソッド lookup の戻り値は、Java の最も汎用的なオブジェクトである Object への参照です。したがって、この戻り値は、より特化した DataSource にキャストしたあとで、DataSource 変数 ds に代入する必要があります。
この時点で、ds は、先に vds が参照していたのと同じデータソース (サーバ my_database_server 上のデータベース my_database) を参照しています。したがって、コードの 3 行目では、ユーザ名とパスワードを指定して ds のメソッド DataSource.getConnection を呼び出しています。これで、my_database への接続が作成されます。
残りのコードでは、単一のトランザクション内で、2 つのクエリーを実行し、その実行結果を出力しています。この場合の DataSource 実装は、JDBC ドライバに添付されている基本実装です。もしこの DataSource クラスが XADataSource クラスと連携動作するように実装されており、上記のサンプルコードが分散トランザクション内で実行されたとすると、そのコード内ではメソッド Connection.commit を呼び出せません。また、自動コミットモードも false に設定されません。というのも、そうする必要がないからです。分散トランザクションに参加可能な接続を新規作成した場合、その接続の自動コミットモードはデフォルトでオフになります。次のセクションでは、DataSource 実装の 3 つの大分類について説明します。
DataSource インタフェースには、ユーザ名とパスワードを指定するバージョンの getConnection に加え、パラメータを 1 つも指定しないバージョンのメソッド DataSource.getConnection も用意されています。このバージョンは、データソースに別のセキュリティ機構が備わっているためにユーザ名やパスワードが必要ない場合や、アクセス制限のないデータソースを使用する場合などに役立ちます。
DataSource インタフェースの実装は、3 種類の接続を提供できます。DataSource オブジェクトは JNDI サービスプロバイダと連携動作するため、DataSource オブジェクトによって生成されたすべての接続は、高い移植性と保守性という利点を備えています。これについては、この章の後半で説明します。より特殊化されたインタフェース ConnectionPoolDataSource、XADataSource と連携動作する DataSource 実装は、プール内に格納される接続や、分散トランザクション内で使用可能な接続を生成します。DataSource インタフェースを実装するクラスの 3 つの大分類の概要を、以下に列挙します。
DataSource クラス
DataSource クラス
ConnectionPoolDataSource クラス (常にドライバベンダーが提供)
DataSource クラス
XADataSource クラス (常にドライバベンダーが提供)
分散トランザクションをサポートする DataSource 実装は、接続プールもサポートするように実装されている場合がほとんどです。
DataSource インタフェースを実装したクラスのインスタンスは、単一のデータソースを表現します。そのインスタンスによって作成された接続はすべて、同じデータソースを参照します。基本 DataSource 実装を使用する場合、メソッド DataSource.getConnection の呼び出し時に返される Connection オブジェクトは、DriverManager 機構から返される Connection オブジェクトと同じく、データソースへの物理的な接続です。JDBC 2.0 標準拡張 API の仕様書 (http://java.sun.com/products/jdbc から入手可能) の付録 A に、基本 DataSource クラスのサンプル実装が記載されています。
接続プールを実装した DataSource オブジェクトも同じく、その DataSource クラスが表現するデータソースへの接続を生成します。ただし、メソッド DataSource.getConnection から返される Connection オブジェクトは、物理的な接続ではなく、PooledConnection オブジェクトへのハンドルです。アプリケーションは、この Connection オブジェクトを通常のオブジェクトと同様に使用でき、両者の違いを意識する必要は基本的にありません。接続プールがアプリケーションコードに影響を与えることはありません。ただし、プールされた接続もほかの接続と同様に、常に明示的に閉じる必要があります。アプリケーションがプールされた接続を閉じると、その接続は、再利用可能な接続のプール内に戻されます。DataSource.getConnection を次回呼び出すと、プール内に使用可能な接続が存在していた場合、そのいずれかへのハンドルが返されます。接続プールを使うと、接続が要求されるたびに物理的な接続が作成されることがなくなるため、アプリケーションが大幅に高速化される可能性があります。接続プールは通常、サーブレットや JavaServerTM ページをサポートする Web サーバなどで使用されます。
また、DataSource クラスを分散トランザクション環境と連携動作するように実装することも可能です。たとえば、EJB サーバは分散トランザクションをサポートしているため、EJB サーバを使用する場合は、分散トランザクションと連携動作するように実装された DataSource クラスが必要となります。その場合、DataSource.getConnection メソッドは、分散トランザクションで使用可能な Connection オブジェクトを返します。基本的に、EJB サーバが提供する DataSource クラスは、分散トランザクションだけでなく接続プールもサポートします。トランザクション管理は接続プールと同じく内部的に処理されるため、分散トランザクションの使い方は簡単です。ただし、1 つだけ制限があります。トランザクションを分散する場合 (トランザクションに複数のデータソースが関与する場合)、メソッド commit または rollback をアプリケーションから呼び出すことはできず、接続を自動コミットモードにすることもできません。このような制限が設けられているのは、トランザクションマネージャが内部的に分散トランザクションの開始と終了を管理するようになっているからです。したがって、アプリケーションは、トランザクションの開始や終了に影響を与える動作を実行できません。
DataSource インタフェースに含まれるメソッドを使えば、ユーザは、追跡情報およびエラーログ情報を書き込むための文字ストリームを取得/設定できます。ユーザは、単一データソースを単一ストリーム上で追跡することもできますし、複数のデータソースからのログメッセージを同一ストリームに書き込むこともできます。ただし、後者の場合、使用するストリームが各データソース用に設定されている必要があります。DataSource オブジェクトに固有のログストリームに書き込まれるログメッセージは、DriverManager が管理するログストリームには書き込まれません。
DriverManager 機構を使用する方法に比べ、JNDI ネーミングサービスに登録された DataSource オブジェクトを使ってデータソースに接続する方法の利点は、決して少なくありません。第 1 に、コードの移植性が高まります。DriverManager を使用する場合、ドライバベンダーを識別する JDBC ドライバクラス名を、アプリケーションコード内に記述します。このため、そのベンダーのドライバ製品にアプリケーションが依存することになり、アプリケーションの移植性が損なわれます。
もう 1 つの利点は、コードを保守しやすくなることです。データソースに関する必要情報が変更された場合、変更する必要があるのは関連する DataSource プロパティだけであり、そのデータソースに接続するアプリケーションを変更する必要はまったくありません。たとえば、あるデータベースを別のサーバに移動し、別のポート番号を使用するようにした場合、変更する必要があるのは、対応する DataSource オブジェクトの serverName プロパティと portNumber プロパティだけです。システム管理者は、次のコードを使うことで、既存コードのすべてを使用可能な状態に保つことができます。ただし実際には、システム管理者はおそらく、何らかの GUI ツールを使ってプロパティを設定するはずです。したがって、次のコードは、そうしたツールが内部的に実行すべきコードを示しています。
Context ctx = new InitialContext()ds.setPortNumber("940");DataSource ds = (DataSource)ctx.lookup("jdbc/AcmeDB"); ds.setServerName("my_new_database_server");
アプリケーションプログラマが何もしなくても、このデータソースを使用するすべてのアプリケーションが問題なく動作し続けます。
ほかにもまだ利点があります。使用する DataSource クラスが接続プールをサポートするように実装されていた場合、その DataSource オブジェクトを使って接続を取得するアプリケーションは、自動的に接続プールの恩恵を享受できます。同様に、使用する DataSource クラスが分散トランザクションをサポートするように実装されていた場合、アプリケーションは自動的に分散トランザクションを使用できます。