注: この章の内容は、Addison Wesley 社より Java シリーズの 1 巻として出版された『JDBCTM API Tutorial and Reference, Second Edition: Universal Data Access for the JavaTM 2 Platform』(ISBN 0-201-43328-1) に基づいて作成したものです。
PreparedStatement
インタフェースは、Statement
から継承していますが、以下の 2 つの点で異なります。
PreparedStatement
のインスタンスには、コンパイル済みの SQL 文が含まれます。文が「prepared」(準備済み) と呼ばれるのはこのためです。
PreparedStatement
に含める SQL 文には、1 つ以上の IN パラメータを付けることができます。IN パラメータは、SQL 文が作られた時点ではその値が指定されていないパラメータです。その代わり、文には IN パラメータのプレースホルダとして疑問符 (?
) を付けます。「?」は、パラメータマーカとしても知られています。アプリケーションは、PreparedStatement
を実行する前に、PreparedStatement
内の各疑問符に対して値を設定する必要があります。
PreparedStatement
オブジェクトはコンパイル済みなので、Statement
オブジェクトより高速に実行することができます。したがって、何度も実行される SQL 文を PreparedStatement
オブジェクトとして生成し、効率を高めることがよくあります。
PreparedStatement
は Statement
のサブクラスであるため、Statement
のすべての機能を継承します。さらに、IN パラメータのプレースホルダの代わりにデータベースへ送信する値を設定するのに必要なメソッドのセットが追加されています。また、3 つのメソッドの execute
、executeQuery
、および executeUpdate
は、変更されていて引数を取りません。これらのメソッドの Statement
形式 (SQL 文のパラメータを取る形式) を、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 が表すオブジェクトは、Table1
の a
、b
、および c
列にすべての値を格納する結果セットで、rs はスクロールおよび更新が可能です。pstmt2 が実行されるたびに、スクロールおよび更新が可能な結果セットが作成されます。
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);
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
メソッドを決定できます。
プログラマは、メソッド 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 のインスタンスの格納で使われる構文とも同じです。
setNull
メソッドにより、プログラマは、JDBC の NULL
(汎用 SQL NULL
) 値を IN パラメータとしてデータベースへ送信することができます。ただし、それでもなお、パラメータの DBC 型を指定する必要があることに注意してください。
Java の null
値が setXXX
メソッドに渡された場合、JDBC の NULL
もデータベースへ送信されます (Java オブジェクトを引数として取る場合)。ただし、メソッド setObject
が null
値を取れるのは、JDBC の型が指定されている場合に限られます。
メソッド 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 つの方法に、BLOB
、CLOB
のような SQL3 の型を使う方法があります。これは、BLOB
および CLOB
値が元々データベースから取り出された値であるという点で、ストリームを使用する場合と異なります。 これらはデータベースで SQL 型として作成されたものです。ストリームを使うと、Java プログラミング言語で書かれたファイルの内容をデータベースへ送信できます。
JDBC 2.0 コア API は、バッチとして実行するための複数の更新をデータベースへ送信する機能を提供しています。Statement
の addBatch
メソッドには、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
メソッドは例外をスローします。