注: この章の内容は、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
クラスが分散トランザクションをサポートするように実装されていた場合、アプリケーションは自動的に分散トランザクションを使用できます。