[目次] [前の項目] [次の項目]

Statement

注: この章の内容は、Addison Wesley 社より Java シリーズの 1 巻として出版された『JDBCTM API Tutorial and Reference, Second Edition:Universal Data Access for the JavaTM 2 Platform』(ISBN 0-201-43328-1) に基づいて作成したものです。

4.1 Statement の概要

Statement オブジェクトは、SQL 文をデータベースへ送る場合に使用します。実際には StatementPreparedStatement、および CallableStatement の 3 種類の Statement オブジェクトがあり、このすべてのオブジェクトが与えられた接続で SQL 文の実行のためのコンテナの役目をします。PreparedStatementStatement から継承し、CallableStatementPreparedStatement から継承しています。これらは特定の種類の SQL 文を送信するための専用オブジェクトです。 具体的には、Statement オブジェクトはパラメータを持たない単純な SQL 文の実行に使用され、PreparedStatement オブジェクトは IN パラメータの有無に関係なくプリコンパイルされた SQL 文の実行に使用され、CallableStatement オブジェクトはデータベースに格納されたプロシージャーの呼び出しの実行に使用されます。

Statement インタフェースは、文の実行および結果の取り出しのための基本メソッドを提供します。PreparedStatement インタフェースは、IN パラメータの処理のためのメソッドを追加しています。 CallableStatement インタフェースは、OUT パラメータの処理のためのメソッドを追加しています。

JDBC 2.0 コア API の ResultSet インタフェースには、いくつかの新しい updateXXX メソッドおよびその他の新しい関連メソッドが追加されており、テーブルの列の値をプログラムで行うように更新することができます。また、この新しい API では、StatementPreparedStatement、および CallableStatement インタフェースにメソッドが追加されており、更新文を個別に実行しないでバッチ処理することもできます。

4.1.1 Statement オブジェクトの作成

特定のデータベースと接続が確立されていれば、その接続を SQL 文の送信に使用できます。Statement オブジェクトは、次のコード断片にあるような Connection メソッド createStatement を用いて作成されます。

Connection con = DriverManager.getConnection(url, "sunny", "");
Statement stmt = con.createStatement();

データベースに送信される SQL 文は、Statement オブジェクトの execute メソッドの 1 つに引数として渡されます。次のコードは、executeQuery メソッドを使用した場合の例です。

ResultSet rs = stmt.executeQuery("SELECT a, b, c FROM Table2");

変数 rs は、結果セットを参照しています。 デフォルトでは、ResultSet オブジェクトの結果セットは更新できず、結果セット内ではカーソルは前方にのみ移動できます。JDBC 2.0 コア API には、新しい Connection.createStatement メソッドが追加されています。 このメソッドを使用して生成した Statement オブジェクトでは、スクロールまたは更新、あるいはその両方を行うことのできる結果セットが生成されます。

4.1.2 Statement オブジェクトを使用した文の実行

Statement インタフェースは、SQL 文を実行する 3 つの異なるメソッド (executeQueryexecuteUpdate、および execute) を提供しています。使用するメソッドは、何を SQL 文が生成するかによって決まります。

メソッド executeQuery は、SELECT 文などの単一の結果セットを作成する文のために設計されています。

メソッド executeUpdate は、INSERTUPDATE、または DELETE の各文、および CREATE TABLEDROP TABLEALTER TABLE のような SQL DDL (Deta Definition Language) 文を実行するために使用します。INSERTUPDATE、または DELETE 文の結果は、テーブルの中の 0 行以上の行中の 1 列以上の列の修正です。executeUpdate の戻り値は、影響を受ける行の数を示す整数値 (更新カウント) となります。CREATE TABLE または DROP TABLE のような文では、行に対して動作しないため、executeUpdate の戻り値は常に 0 になります。

メソッド execute は、複数の結果セット、複数の更新カウント、あるいはその 2 つの組み合わせを返す文を実行するのに使用します。このメソッドは高度な機能であり、通常の場合、大多数のプログラマが使用することはまず考えられないので、この概要ではあとの項で説明します。

文を実行するすべてのメソッドは、呼び出している Statement オブジェクトの現在の結果のセットが開いていればこれを閉じます。したがって、現在の Statement オブジェクトを再実行する前に現在の ResultSet オブジェクトの処理をすべて完了する必要があります。

PreparedStatement インタフェースは Statement インタフェースの中のすべてのメソッドを継承していますが、PreparedStatement インタフェースは、メソッド executeQueryexecuteUpdate、および execute の独自のバージョンを持っていることに注意してください。Statement オブジェクト自体は SQL 文を含んでいません。 したがって、SQL 文を引数として Statement.execute メソッドに指定する必要があります。 PreparedStatement オブジェクトは、コンパイル済みの SQL 文を含んでいるため、これらのメソッドに SQL 文を引数として与えません。CallableStatement オブジェクトは、これらのメソッドの PreparedStatement 形式を継承しています。PreparedStatement または CallableStatement のこれらのメソッドにパラメータを渡すと、SQLException 例外がスローされます。

4.1.3 文の完了

接続が自動コミットモードにある場合には、そのモードでの実行中の文は、完了時にコミットされるか、あるいはロールバックされます。文は、その実行が終了し、かつその結果がすべて返されたときに完了したものと見なされます。executeQuery メソッドは 1 つの結果セットを返すので、文は、ResultSet オブジェクトのすべての列の取り出しが終了したときに完了します。executeUpdate メソッドの場合は、文は、それが実行されたときに完了します。ただし、execute メソッドが呼ばれるようなまれな場合には、それが生成したすべての結果セットまたは更新カウントの取り出しが終了するまでは、文は完了しません。

DBMS の中には、ストアドプロシージャー中の各文を別個の文として取り扱うものもあります。 また、全体のプロシージャーを 1 つの複合文として取り扱うものもあります。この相違は、commit メソッドが呼ばれるタイミングに影響するため、自動コミットが有効になっている場合に重要になります。前者の場合には、各文は、個別にコミットされます。 後者の場合には、すべてが一緒にコミットされます。

4.1.4 Statement を閉じる

Statement オブジェクトは、Java ガベージコレクタによって自動的に閉じられます。ただし、Statement オブジェクトが必要なくなった場合には、明示的に閉じるようにプログラミングしておくことをお勧めします。Statement オブジェクトを閉じるとただちに、DBMS のリソースが解放され、潜在的なメモリでの問題が起こるのを防止できます。

4.1.5 Statement 内の SQL エスケープ構文

Statement オブジェクトは SQL エスケープ構文を使用する SQL 文を含むことができます。エスケープ構文は、この文の中のコードを異なる方法で処理するようドライバに伝えます。Statement.setEscapeProcessing(true) または RowSet.setEscapeProcessing(true) を呼び出すことによって) エスケープ処理が有効になっている場合は、ドライバはエスケープ構文がないか走査し、個々のデータベースのコードへ変換します。このため、エスケープ構文は DBMS から独立しており、プログラマは他の方法では使用できないような機能を使用できるようになります。

エスケープ句は、中括弧とキーワードによって区分けされます。 キーワードによって、エスケープ句の種類を示します。

	{keyword . . . parameters . . . }

エスケープ句を識別するために、次のキーワードが使用されます。

Statement.setEscapeProcessing メソッドを使用して、エスケープ処理のオンまたはオフを切り替えることができます。デフォルトではオンになっています。パフォーマンスを最高にし、処理時間を短縮するには、プログラマがこれをオフにする場合もありますが、エスケープ処理は、通常オンになっています。setEscapeProcessing メソッドは、PreparedStatement オブジェクトには機能しません。 呼び出すことができるようになる前に、すでにデータベースへ文が送信されていることがあるためです。プリコンパイルについては、PreparedStatement の概要」を参照してください。

4.1.6 バッチ更新の送信

JDBC 2.0 コア API で提供されているバッチ更新機能を使用すると、1 つの Statement オブジェクトで、基盤となっている DBMS に対して、複数の更新コマンドを一括して送信することができます。更新を個別に送信しないで、複数の更新をバッチとして送信できるので、状況によってはパフォーマンスを大きく向上させることができます。

次のコードは、データベースにバッチ更新を送信する方法を示しています。この例では、会社のデータベースに新入社員を追加するために、新しい行が 3 つの異なるテーブルに挿入されます。複数の文をトランザクションとして一括して送信できるように、最初に Connection オブジェクト con の自動コミットモードをオフに設定します。Statement オブジェクト stmt を生成してから、addBatch メソッドを使用して 3 つの SQL INSERT INTO コマンドをバッチに追加し、次に executeBatch メソッドを使用してデータベースにバッチを送信します。コード例を示します。

Statement stmt = con.createStatement();
con.setAutoCommit(false);

stmt.addBatch("INSERT INTO employees VALUES (1000, 'Joe Jones')");
stmt.addBatch("INSERT INTO departments VALUES (260, 'Shoe')");
stmt.addBatch("INSERT INTO emp_dept VALUES (1000, '260')");

int [] updateCounts = stmt.executeBatch();

接続の自動コミットモードを無効にしているので、エラーが発生した場合、またはバッチ処理の一部のコマンドの実行に失敗した場合に、トランザクションをコミットするかどうかをアプリケーションが自由に決めることができます。たとえば、挿入に失敗した場合は、アプリケーションで変更をコミットしないことによって、社員情報が一部のテーブルにだけ存在する状態を回避することができます。

JDBC 2.0 コア API では、Statement オブジェクトを生成したときに、コマンドの関連リストが作成されます。このリストは、最初は空です。コマンドは、StatementaddBatch メソッドを使用してリストに追加します。リストに追加するコマンドは、更新カウント以外の結果を返すことはできません。たとえば、コマンドの 1 つが結果セットを返すクエリー (SELECT 文) の場合は、executeBatch メソッドは BatchUpdateException をスローします。Statement オブジェクトのコマンドのリストは、clearBatch メソッドを呼び出すことによって空にできます。

上記の例では、executeBatch メソッドは、基盤となっている DBMS に stmt のコマンドリストを送信します。DBMS では、各コマンドがバッチに追加された順番に実行され、バッチの各コマンドに対して更新カウントが同じ順番で返されます。コマンドから更新カウントが返されなかった場合は、そのコマンドの戻り値は executeBatch メソッドが返す更新カウントの配列には追加されません。この場合、executeBatch メソッドから BatchUpdateException がスローされます。この例外によって、エラーが発生する前に、正常に実行されたコマンドの更新カウントを追跡することができます。 また、更新カウントの順番は、バッチ処理内のコマンドの順番と一致しています。

次のコードでは、アプリケーションは、try ブロックと catch ブロックを使用しています。 BatchUpdateException がスローされた場合は、例外の更新カウントの配列を取り出し、BatchUpdateException がスローされる前に正常に実行されたバッチ更新のコマンドを検出します。

try {
    stmt.addBatch("INSERT INTO employees VALUES (" +
				"1000, 'Joe Jones')");
	stmt.addBatch("INSERT INTO departments VALUES (260, 'Shoe')");
	stmt.addBatch("INSERT INTO emp_dept VALUES (1000, '260')");

	int [] updateCounts = stmt.executeBatch();

} catch(BatchUpdateException b) {
	System.err.println("Update counts of successful commands: ");
	int [] updateCounts = b.getUpdateCounts();
	for (int  i = 0; i < updateCounts.length; i ++) {
		System.err.print(updateCounts[i] + "  ");
	}
	System.err.println("");
}

次のような出力が生成された場合は、最初の 2 つのコマンドは正常に実行され、3 番目のコマンドは失敗しています。

Update counts of successful commands:
1   1

JDBC ドライバはバッチ更新のサポートを要求されていません。 このため、ドライバによっては、addBatchclearBatch、および executeBatch メソッドが実装されていないことがあります。通常は、使用しているドライバでバッチ更新がサポートされているかどうかについては、プログラマは把握していますが、アプリケーションから検査したい場合は、DatabaseMetaDatasupportsBatchUpdates メソッドを呼び出して調べることができます。次のコードでは、ドライバでバッチ更新がサポートされている場合にだけ、バッチ更新が使用されます。 サポートされていない場合は、各更新文が個別に送信されます。接続の自動コミットモードが無効になっているため、いずれの場合も、更新はすべて 1 つのトランザクションに含まれます。

con.setAutoCommit(false);
    if(dbmd.supportsBatchUpdates) {
	stmt.addBatch("INSERT INTO . . .");
	stmt.addBatch("DELETE . . .");
	stmt.addBatch("INSERT INTO . . .");
	. . .
	stmt.executeBatch();
} else {
	System.err.print("Driver does not support batch updates; ");
	System.err.println("sending updates in separate statements.");
	stmt.executeUpdate("INSERT INTO . . .");
	stmt.executeUpdate("DELETE . . .");
	stmt.executeUpdate("INSERT INTO . . .");
	. . .
con.commit();

4.1.7 パフォーマンスヒントの提供

ドライバにパフォーマンスに関するヒントを提供するために、Statement インタフェースには setFetchDirection および setFetchSize メソッドが組み込まれています。これらのメソッドは、ResultSet インタフェースにも組み込まれており、まったく同じ処理が行われます。ただし、Statement のメソッドでは、特定の Statement によって生成されるすべての ResultSet オブジェクトのデフォルト値を設定します。 ResultSet のメソッドは、フェッチ方向またはフェッチサイズを変更するときに、ResultSet オブジェクトの有効期間内の任意のときに呼び出すことができます。これらのメソッドについての詳細は、「パフォーマンスヒントの提供」を参照してください。

Statement インタフェースおよび ResultSet インタフェースには、対応している getFetchDirection および getFetchSize という get メソッドがあります。Statement.getFetchDirection が、フェッチ方向の設定前に呼び出された場合は、返される値は実装によって異なり、ドライバに依存します。Statement.getFetchSize メソッドの場合も同様です。

4.1.8 特別な種類の Statement の実行

execute メソッドは、文が複数の ResultSet オブジェクト、複数の更新カウント、または ResultSet オブジェクトと更新カウントの組み合せを返す可能性がある場合だけに使用してください。この結果が複数になる可能性が、まれにですが、一部のストアドプロシージャーを実行するとき、または(コンパイル時にアプリケーションプログラマに知られていない) 未知の SQL 文字列を動的に実行するときに起きることがあります。たとえば、ユーザーがストアドプロシージャーを実行することがあり (CallableStatement オブジェクトを使用)、ストアドプロシージャーが更新、続いて選択、さらに更新、続いて選択と次々に実行することがある場合があります。ストアドプロシージャーを使用する場合には、それが何を返すかすでにわかっているのが普通です。



[目次] [前の項目] [次の項目]

Copyright © 1999, Sun Microsystems, Inc. All rights reserved.