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

PreparedStatement

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

6.1 PreparedStatement の概要

PreparedStatement インタフェースは、Statement から継承しますが、以下の 2 つの点で異なります。

  1. PreparedStatement のインスタンスには、コンパイル済みの SQL 文が含まれます。文が「prepared」(準備済み) と呼ばれるのはこのためです。
  2. PreparedStatement に含める SQL 文には、1 つ以上の IN パラメータを付けることができます。IN パラメータは、SQL 文が作られた時点ではその値が指定されていないパラメータです。その代わり、文には IN パラメータのプレースホルダとして疑問符 (?) を付けます。「?」は、パラメータマーカとしても知られています。アプリケーションは、PreparedStatement を実行する前に、PreparedStatement 内の各疑問符に対して値を設定する必要があります。

PreparedStatement オブジェクトはコンパイル済みなので、Statement オブジェクトより高速に実行することができます。したがって、何度も実行される SQL 文を PreparedStatement オブジェクトとして生成し、効率を高めることがよくあります。

PreparedStatementStatement のサブクラスであるため、Statement のすべての機能を継承します。さらに、IN パラメータのプレースホルダの代わりにデータベースへ送信する値を設定するのに必要なメソッドのセットが追加されています。また、3 つのメソッドの executeexecuteQuery、および executeUpdate は、変更されていて引数を取りません。これらのメソッドの Statement 形式 (SQL 文のパラメータを取る形式) を、PreparedStatement オブジェクトに対して決して使用しないでください。

6.1.1 PreparedStatement オブジェクトの生成

次のコード (con は Connection オブジェクト) は、IN パラメータのための 2 つのプレースホルダが含まれている SQL 更新文の PreparedStatement オブジェクトを生成します。

PreparedStatement pstmt = con.prepareStatement(
	"UPDATE table4 SET m = ? WHERE x = ?");

この文の実行後には、オブジェクト pstmt に "UPDATE table4 SET m = ?WHERE x = ?" という文が含まれ、さらに DBMS へ送信され、実行準備済みの状態になっています。

Statement オブジェクトの場合と同様に、更新文ではなくクエリーを含む PreparedStatement オブジェクトを作成することができます。 実際、頻繁に実行される SQL 文を効率化する目的で、この方法がよく用いられます。PreparedStatement オブジェクトは、JDBC 2.0 コア API 内に含まれるメソッド prepareStatement の新しいバージョンを使って、スクロールと更新が可能な ResultSet オブジェクトを生成できます。たとえば、次のコードは PreparedStatement オブジェクトを生成し、このオブジェクトは、コードが実行されるたびにスクロールと更新が可能な ResultSet オブジェクトを生成します。

PreparedStatement pstmt2 = con.prepareStatement(
            "SELECT a, b, c FROM Table1", ResultSet.TYPE_SCROLL_SENSITIVE,
            ResultSet.CONCUR_UPDATABLE);
ResultSet rs = pstmt2.executeQuery();

rs が表すオブジェクトは、Table1ab、および c 列にすべての値を格納する結果セットで、rs はスクロールおよび更新が可能です。pstmt2 が実行されるたびに、スクロールおよび更新が可能な結果セットが作成されます。

6.1.2 IN パラメータの引き渡し

PreparedStatement オブジェクトを実行する前に、各 ? パラメータに値を設定しておく必要があります。これは、setXXX メソッドを呼び出すことによって行います。 XXX は、パラメータの適切な型です。たとえば、パラメータが Java プログラミング言語の long という型の場合、使用するメソッドは setLong です。setXXX メソッドの最初の引数は、設定するパラメータの「番号付けされた位置」で、1 から始まる番号です。 2 番目の引数は、パラメータに設定する「値」です。たとえば、以下のコードは、最初のパラメータを 123456789 に、2 番目のパラメータを 100000000 に設定します。

pstmt.setLong(1, 123456789);
pstmt.setLong(2, 100000000);

一旦その文にパラメータの値が設定されると、メソッド clearParameters への呼び出しによってクリアされるか、または新しい値が設定されるまで、そのパラメータの値を複数回の実行において使用することができます。

接続の自動コミットモードが有効な場合は、各文は、完了すると自動的にコミットされます。データベースシステムによっては、コミットを跨がって PreparedStatement が保持されないものもあります。そのような場合は、各回のコミット後にドライバが PreparedStatement をコンパイルし直す必要があります。つまり、これらの DBMS に関して言えば、多くの回数実行する Statement オブジェクトを PreparedStatement オブジェクトに置き換えて使用すると、実際にはより効果的でなくなる場合があります。

以下のコードでは、上で生成した PreparedStatement オブジェクトである pstmt を使用して、2 つのパラメータプレースホルダに値を設定し、pstmt を 10 回実行する方法を示します。この例では、1 番目のパラメータに "Hi" が設定され、その値は変化しません。2 番目のパラメータは、0 で始まり、for ループを通るたびに異なる値が設定されて、最後は 9 で終わります。

pstmt.setString(1, "Hi"); 
for (int i = 0; i < 10; i++) {
	pstmt.setInt(2, i);
	int rowCount = pstmt.executeUpdate();
}

JDBC 2.0 API の新しい機能により、次の例に示すような SQL3 データ型のパラメータプレースホルダの設定が可能になりました。 ここで statistics は、SQL BLOB 値を表す Blob オブジェクト、departments は SQL ARRAY 値を表す Array オブジェクトです。

PreparedStatement pstmt = con.prepareStatement(
      "UPDATE Table3 SET Stats = ? WHERE Depts = ?");
pstmt.setBlob(1, statistics);
pstmt.setArray(2, departments);

6.1.3 IN パラメータにおけるデータ型の適合性

setXXX メソッドの XXX は Java プログラミング言語の型です。ドライバは、(「JDBC の型にマッピングされる Java の型」で指定されているマッピングに従って) Java の型を対応する JDBC の型にマップし、その JDBC の型をデータベースへ送信します。したがって、この型は、暗黙的に JDBC の型も指定しています。たとえば、以下の部分的なコードは、PreparedStatement オブジェクト pstmt の 2 番目のパラメータを short という Java の型で 44 に設定します。

pstmt.setShort(2, 44);

ドライバは 44 を、JDBC の SMALLINT としてデータベースへ送信します。 これは、Java の short からの標準マッピングです。

各 IN パラメータの Java プログラミング言語の型をデータベースが期待している JDBC のデータ型と互換性のある JDBC の型に確実にマッピングすることは、プログラマの責任です。データベースで JDBC の SMALLINT が求められている場合を想定してください。メソッド setByte が使用された場合、ドライバは JDBC の TINYINT をデータベースへ送信します。多くのデータベースシステムでは、1 つの関連する型から他の型に変換すること、また一般に TINYINT は、SMALLINT が使用されているところではどこでも使用できることから、通常これは動作します。しかし、アプリケーションをできるだけ多くのデータベースシステムで動作させるためには、データベースが期待している正しい JDBC の型に対応している Java プログラミング言語の型を使用するのが一番良い方法です。求められる JDBC の型が SMALLINT の場合には、setByte の代わりに setShort を使用することにより、アプリケーションの移植性を向上させることができます。「SQL と Java の型のマッピング」の章の表「JDBC の型にマッピングされる Java の型」を使って、使用する setXXX メソッドを決定できます。

6.1.4 setObject の使用

プログラマは、メソッド setObject を使用して、入力パラメータを特定の JDBC の型へ明示的に変換できます。このメソッドは、対象の JDBC の型を指定するための 3 番目の引数を取ることができます。ドライバは、Java プログラミング言語の Object を、指定された JDBC の型に変換してからデータベースへ送信します。

JDBC の型が指定されていない場合、ドライバは、単純に Java の Object をそのデフォルトの JDBC の型にマッピングして、データベースへ送信します。この動作は、通常の setXXX の処理と似ています。 どちらの場合でも、ドライバが Java の型を適切な JDBC の型に変換してから、データベースへ送信します。異なるのは、setXXX メソッドが標準マッピングを使うのに対し、setObject メソッドはオブジェクト型へのマッピングを使う点です。

メソッド setObject はあらゆる Java オブジェクトを受け付ける能力を持つため、アプリケーションを総称化して、入力パラメータを実行時に受け取ることができます。この場合、アプリケーションのコンパイル時点では入力の型はわかりません。setObject を使用することによって、アプリケーションは任意の Java のオブジェクト型を入力として受け取り、それをデータベースが求めている JDBC の型へ変換することができます。

JDBC 2.0 コア API には、 setObject メソッドの新しい実装が含まれています。 このメソッドは、 Java プログラミング言語のクラスにカスタムマッピングしたユーザー定義型 (UDT) に適用されます。SQL UDT のカスタムマッピングは、SQLData インタフェースを実装するクラスで指定されます。getObject メソッドによってデータベースから UDT インスタンスが取り出されると、そのインスタンスに対する SQLData を実装した Java クラスのインスタンスにマッピングされます。このカスタムマッピングされたインスタンスが setObject メソッドに渡されると、setObject は、適切な SQLData 実装内に定義された SQLOutput.writeObject メソッドを呼び出し、それによって Java クラスのインスタンスを元の SQL UDT に変換します。

カスタムマッピングの詳細については、ユーザーから隱されています。アプリケーションが setObject メソッドを呼び出すと、格納されようとしている値に対するカスタムマッピングが存在している場合は、自動的にカスタムマッピングが実行されます。このため、setObject メソッドがカスタムマッピングを行うコードは、setObject が標準マッピングを使うコードと同じように見えます。UDT は、setObject メソッドを使うことでしか格納できません。 これは、UDT がカスタムマッピングで適切にマッピングされることを保証するためです。

これまでの説明で、setObject メソッドに渡した値は、元々はテーブル列から取り出された SQL のデータ型でした。この値をデータベースに戻す前に、ドライバはこれを元の SQL のデータ型に変換する必要があります。データベースが Java リレーショナル DBMS と呼ばれる新型の Java 対応 DBMS の場合は、このデータベースは、SQL で定義された値と同様に Java プログラミング言語で定義されたクラスのインスタンスも格納できます。クラスインスタンスは、直列化された Java オブジェクトとして、または DBMS によって定義されているその他の形式で格納できます。

次の例では、setObject メソッドを使って Employee クラスのインスタンスである emp を格納する方法について示します。emp の salary フィールドを 50 パーセント増加させたあと、emp を元のデータベースへ送信します。PERSONNEL テーブルの EMPLOYEE 列は、Employee のインスタンスを格納しています。

emp.salary = emp.salary * 1.5;
PreparedStatement pstmt = con.prepareStatement(
	"UPDATE PERSONNEL SET EMPLOYEE = ? WHERE EMPLOYEE_NO = 300485");
pstmt.setObject(1, emp);
pstmt.executeUpdate();

この例の構文は、JDBC 1.0 API の構文と同じで、しかも、カスタムマッピングされた UDT のインスタンスの格納で使われる構文とも同じです。

6.1.5 JDBC NULL の IN パラメータとしての送信

setNull メソッドにより、プログラマは、JDBC の NULL (総称 SQL NULL) 値を IN パラメータとしてデータベースへ送信することができます。ただし、それでもなお、パラメータの DBC 型を指定する必要があることに注意してください。

Java の null 値が setXXX メソッドに渡された場合、JDBC の NULL もデータベースへ送信されます (Java オブジェクトを引数として取る場合)。ただし、メソッド setObjectnull 値を取れるのは、JDBC の型が指定されている場合に限られます。

6.1.6 きわめてサイズの大きな IN パラメータの送信

メソッド setBytes および setString は、無制限の量のデータを送信できます。ただし、時々、プログラマが大量のデータを小さな塊で渡したいと考えることがあります。これは、IN パラメータを Java 入力ストリームに設定することによって達成できます。文の実行時に、JDBC ドライバがこの入力ストリームを繰り返し呼び出し、その内容を読みとって、それを実際のパラメータデータとして伝送します。

JDBC 1.0 API には、IN パラメータを入力ストリームに設定するためのメソッドが 2 つあります。解釈されないバイトが入ったストリームのための setBinaryStream と、ASCII 文字の入ったストリームのための setAsciiStream です。3 番目のメソッドとして Unicode 文字が入ったストリームのための set-UnicodeStream がありますが、これは推奨されません。その代わりに、新しい JDBC 2.0 コア API メソッドの setCharacterStream を使います。これらのストリームメソッドは、ストリーム全体の長さを指定する必要があるので、他の setXXX メソッドよりも引数を 1 つ多く取ります。データベースシステムによっては、データが送られる前に全転送サイズを知る必要があるので、この引数が必要になります。

以下のコードは、ストリームを使用し、ファイルの内容を IN パラメータとして送信する方法を示します。

java.io.File file = new java.io.File("/tmp/data");
int fileLength = file.length();
java.io.InputStream fin = new java.io.FileInputStream(file);
java.sql.PreparedStatement pstmt = con.prepareStatement(
	"UPDATE Table5 SET stuff = ? WHERE index = 4");
pstmt.setBinaryStream (1, fin, fileLength);
pstmt.executeUpdate();

文が実行されると、そのデータを配信するために、入力ストリーム fin が繰り返し呼び出されます。

大きな IN パラメータをデータベースへ送信するもう 1 つの方法に、BLOBCLOB のような SQL3 の型を使う方法があります。これは、BLOB および CLOB 値が元々データベースから取り出された値であるという点で、ストリームを使用する場合と異なります。 これらはデータベースで SQL 型として作成されたものです。ストリームを使うと、Java プログラミング言語で書かれたファイルの内容をデータベースへ送信できます。

6.1.7 バッチ更新での PreparedStatement オブジェクトの使用

JDBC 2.0 コア API は、バッチとして実行するための複数の更新をデータベースへ送信する機能を提供しています。StatementaddBatch メソッドには、1 つの SQL 更新文がパラメータとして渡されていて、次のバッチ更新で実行される Statement オブジェクトのコマンドのリストに、その SQL 文が追加されます。インタフェース PreparedStatement は、独自のバージョンの addBatch メソッドを持っています。次のコードに示すように、このメソッドはバッチにパラメータのセットを追加します。

PreparedStatement pstmt = con.prepareStatement(
         "UPDATE Table4 SET History = ? WHERE ID = ?");
pstmt.setClob(1, clob1);
pstmt.setLong(2, 350985839);
pstmt.addBatch();

pstmt.setClob(1, clob2);
pstmt.setLong(2, 350985840);
pstmt.addBatch();

int [] updateCounts = pstmt.executeBatch();

pstmt 内の PreparedStatement オブジェクトの実行時には、1 回目はパラメータ clob1 および 350985839、2 回目はパラメータ clob2 および 350985840 を使って合計 2 回実行されます。 どちらかの更新コマンドで、更新カウント以外のものが返された場合は、executeBatch メソッドは例外をスローします。



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

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