注: この章の内容は、Addison Wesley 社より Java シリーズの 1 巻として出版された『JDBCTM API Tutorial and Reference, Second Edition: Universal Data Access for the JavaTM 2 Platform』(ISBN 0-201-43328-1) に基づいています。
Statement
オブジェクトは、SQL 文をデータベースへ送る場合に使用します。 実際には Statement
、PreparedStatement
、および CallableStatement
の 3 種類の Statement
オブジェクトがあり、このすべてのオブジェクトが与えられた接続で SQL 文の実行のためのコンテナの役目をします。 PreparedStatement
は Statement
から継承し、CallableStatement
は PreparedStatement
から継承しています。 これらのオブジェクトは、特定の型の SQL 文を送信するときに使用されます。Statement
オブジェクトは、パラメータを持たない単純な SQL 文の実行に使用されます。PreparedStatement
オブジェクトは、IN パラメータの有無に関係なくプリコンパイルされた SQL 文の実行に使用されます。 また、CallableStatement
オブジェクトは、データベースに格納されたプロシージャの呼び出しの実行に使用されます。
Statement
インタフェースは、文の実行および結果の取り出しのための基本メソッドを提供します。 PreparedStatement
インタフェースは、IN パラメータの処理のためのメソッドを追加しています。 CallableStatement
インタフェースは、OUT パラメータの処理のためのメソッドを追加しています。
特定のデータベースと接続が確立されていれば、その接続を 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
オブジェクトでは、スクロールまたは更新、あるいはその両方を行うことのできる結果セットが生成されます。
Statement
インタフェースには、SQL 文を実行するの 3 つの異なるメソッド (executeQuery
、executeUpdate
、および execute
) を提供しています。 使用するメソッドは、何を SQL 文が生成するかによって決まります。
メソッド executeQuery
は、SELECT
文などの単一の結果セットを作成する文のために設計されています。
メソッド executeUpdate
は、INSERT
、UPDATE
、または DELETE
の各文、および CREATE
TABLE
、DROP
TABLE
、ALTER TABLE
のような SQL DDL (Deta Definition Language) 文を実行するために使用します。 INSERT
、UPDATE
、または DELETE
文の結果は、テーブルの中の 0 行以上の行中の 1 列以上の列の修正です。 executeUpdate
の戻り値は、影響を受ける行の数を示す整数値 (更新カウント) となります。 CREATE
TABLE
または DROP
TABLE
のような文では、行に対して動作しないため、executeUpdate
の戻り値は常に 0 になります。
メソッド execute
は、複数の結果セット、複数の更新カウント、あるいはその 2 つの組み合わせを返す文を実行するのに使用します。 このメソッドは高度な機能であり、通常の場合、大多数のプログラマが使用することはまず考えられないので、この概要ではあとの項で説明します。
文を実行するすべてのメソッドは、呼び出している Statement
オブジェクトの現在の結果のセットが開いていればこれを閉じます。 したがって、現在の Statement
オブジェクトを再実行する前に現在の ResultSet
オブジェクトの処理をすべて完了する必要があります。
PreparedStatement
インタフェースは Statement
インタフェースの中のすべてのメソッドを継承していますが、PreparedStatement
インタフェースは、メソッド executeQuery
、executeUpdate
、および execute
の独自のバージョンを持っていることに注意してください。 Statement
オブジェクト自体は SQL 文を含んでいません。 したがって、SQL 文を引数として Statement.execute
メソッドに指定する必要があります。PreparedStatement
オブジェクトは、コンパイル済みの SQL 文を含んでいるため、これらのメソッドに SQL 文を引数として与えません。 CallableStatement
オブジェクトは、これらのメソッドの PreparedStatement
形式を継承しています。PreparedStatement
または CallableStatement
のこれらのメソッドにパラメータを渡すと、SQLException
例外がスローされます。
接続が自動コミットモードにある場合には、そのモードでの実行中の文は、完了時にコミットされるか、あるいはロールバックされます。 文は、その実行が終了し、かつその結果がすべて返されたときに完了したものと見なされます。 executeQuery
メソッドは 1 つの結果セットを返すので、文は、ResultSet
オブジェクトのすべての列の取り出しが終了したときに完了します。 executeUpdate
メソッドの場合は、文は、それが実行されたときに完了します。 ただし、execute
メソッドが呼ばれるようなまれな場合には、それが生成したすべての結果セットまたは更新カウントの取り出しが終了するまでは、文は完了しません。
DBMS の中には、ストアドプロシージャ中の各文を別個の文として取り扱うものもあります。 また、全体のプロシージャを 1 つの複合文として取り扱うものもあります。 この相違は、commit
メソッドが呼ばれるタイミングに影響するため、自動コミットが有効になっている場合に重要になります。 前者の場合には、各文は、個別にコミットされます。 後者の場合には、すべてが一緒にコミットされます。
Statement
オブジェクトは、Java ガベージコレクタによって自動的に閉じられます。 ただし、Statement
オブジェクトが必要なくなった場合には、明示的に閉じるようにプログラミングしておくことをお勧めします。 Statement
オブジェクトを閉じるとただちに、DBMS のリソースが解放され、潜在的なメモリでの問題が起こるのを防止できます。
Statement
オブジェクトは SQL エスケープ構文を使用する SQL 文を含むことができます。 エスケープ構文は、この文の中のコードを異なる方法で処理するようドライバに伝えます。 (Statement.setEscapeProcessing(true)
または RowSet.setEscapeProcessing(true)
を呼び出すことによって) エスケープ処理が有効になっている場合は、ドライバはエスケープ構文がないか走査し、個々のデータベースのコードへ変換します。 このため、エスケープ構文は DBMS から独立しており、プログラマは他の方法では使用できないような機能を使用できるようになります。
エスケープ句は、中括弧とキーワードによって区分けされます。キーワードによって、エスケープ句の種類を示します。
{keyword . . . parameters . . . }
エスケープ句を識別するために、次のキーワードが使用されます。
パーセント記号 (%
) および下線 (_
) 文字は、SQL の LIKE
句ではワイルドカードのように機能します (%
は 0 個以上の文字と一致し、_
は 1 つの文字と一致します)。 これらを文字どおりに解釈するには、文字列の特殊なエスケープ文字であるバックスラッシュ (¥
) を直前に置きます。 クエリーの終りに次の構文を組み込むと、どの文字をエスケープ文字として使用すべきかを指定することができます。
たとえば、次のクエリーでは、下線で始まる識別名を検索しています。ここでは、バックスラッシュ文字をエスケープ文字として使用しています。{escape 'escape-character'}
stmt.executeQuery("SELECT name FROM Identifiers WHERE Id LIKE'
¥_%'
{escape'
¥'
}");
ほとんどすべての DBMS は、スカラー値に関して、数字、文字列、時刻、日付、システム、および変換の関数を持っています。 希望の関数の名前およびその引数のあとに続くキーワード fn
を用いて、これらの関数の 1 つをエスケープ構文の中に入れると、その関数を使用することができます。 たとえば、次のコードは、2 つの引数を連結する関数 concat
を呼び出します。
次の構文で現在のデータベースのユーザ名を取得することができます。{fn concat("Hot", "Java")};
スカラー関数は、わずかに構文が異なっている異なるデータベースでサポートされているかもしれませんが、すべてのドライバがサポートしているわけではありません。{fn user()};
DatabaseMetaData
のさまざまなメソッドで、サポートされている関数のリストを取得できます。 たとえば、メソッド getNumericFunctions
は数値関数の名前 (Open Group CLI 名) のカンマで区切ったリストを返し、メソッド getStringFunctions
は文字列関数の名前のリストを返します。
ドライバは、エスケープされた関数呼び出しを適切な構文にマッピングするか、あるいはその関数をドライバ自体で実装します。ただし、ドライバは、その対象の DBMS がサポートしているこれらのスカラー関数を実装するよう要求されます。
日付、時刻、およびタイムスタンプのリテラルに使う構文は、DBMS ごとに異なります。 JDBC API は、これらのリテラルの構文については ISO の標準形式をサポートし、ドライバが DBMS 固有の表現に変換するエスケープ節を使っています。 たとえば、日付は次の構文を用いた JDBC SQL 文で指定されます。
この構文では、{d 'yyyy-mm-dd'}
yyyy
は年、mm
は月、そして dd
は日です。 ドライバはエスケープ句を等価の DBMS 固有の表現と置換します。 たとえば、ドライバは {d '1999-02-28'}
の基礎データベースの適切なフォーマットが '28-FEB-99'
であればこれに置換します。
TIME
と TIMESTAMP
にも同様のエスケープ節があります。
{t 'hh:mm:ss'} {ts 'yyyy-mm-dd hh:mm:ss.f . . .'}
TIMESTAMP
の小数秒 (.f . . .
) 部分は省略することもできます。
データベースがストアドプロシージャをサポートする場合、次のような構文でこれらを JDBC から呼び出すことができます。角括弧 ([ ]
) は、それに囲まれた部分がオプションであることを示します。 これらの括弧は、構文の一部ではありません。
{call procedure_name[(?, ?, . . .)]}
結果のパラメータを返すプロシージャの場合は、次の構文を使います。
入力引数には、リテラルまたはパラメータを指定できます。 詳細は、「パラメータの番号付け」 を参照してください。 メソッド{? = call procedure_name[(?, ?, . . .)]}
DatabaseMetaData.supportsStoredProcedures
を呼び出すことにより、データベースがストアドプロシージャをサポートするかどうかを調べることができます。
この構文では、{oj outer-join}
outer-join
は次の形式になります。
1 行目の中括弧 (table {LEFT|RIGHT|FULL} OUTER JOIN {table | outer-join} ON search-condition
{}
) は、括弧内の項目のいずれかを使用しなければならないことを示します。括弧は構文には含まれません。 次の SELECT
文では、外部結合のためのエスケープ構文が使用されています。
外部結合は高度な機能なので、一部の DBMS ではサポートされていません。詳細は SQL 文法を参照してください。 JDBC は、ドライバがサポートする外部結合の種類の決定のために、次の 3 つのStatement stmt = con.createStatement("SELECT * FROM {oj TABLE1 LEFT OUTER JOIN TABLE2 ON DEPT_NO = 003420930}");
DatabaseMetaData
メソッド、すなわち、supportsOuterJoins
、supportsFullOuterJoins
、および supportsLimitedOuterJoins
を提供しています。
Statement.setEscapeProcessing
メソッドを使用して、エスケープ処理のオンまたはオフを切り替えることができます。 デフォルトではオンになっています。 パフォーマンスを最高にし、処理時間を短縮するには、プログラマがこれをオフにする場合もありますが、エスケープ処理は、通常オンになっています。setEscapeProcessing
メソッドは、PreparedStatement
オブジェクトには機能しません。 呼び出すことができるようになる前に、すでにデータベースへ文が送信されていることがあるためです。 プリコンパイルについては、「PreparedStatement
の概要」を参照してください。
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
オブジェクトを生成したときに、コマンドの関連リストが作成されます。 このリストは、最初は空です。コマンドは、Statement
の addBatch
メソッドを使用してリストに追加します。 リストに追加するコマンドは、更新カウント以外の結果を返すことはできません。 たとえば、コマンドの 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 ドライバはバッチ更新のサポートを要求されていません。このため、ドライバによっては、addBatch
、clearBatch
、および executeBatch
メソッドが実装されていないことがあります。 通常は、使用しているドライバでバッチ更新がサポートされているかどうかについては、プログラマは把握していますが、アプリケーションから検査したい場合は、DatabaseMetaData
の supportsBatchUpdates
メソッドを呼び出して調べることができます。 次のコードでは、ドライバでバッチ更新がサポートされている場合にだけ、バッチ更新が使用されます。サポートされていない場合は、各更新文が個別に送信されます。 接続の自動コミットモードが無効になっているため、いずれの場合も、更新はすべて 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();
ドライバにパフォーマンスに関するヒントを提供するために、Statement
インタフェースには setFetchDirection
および setFetchSize
メソッドが組み込まれています。 これらのメソッドは、ResultSet
インタフェースにも組み込まれており、まったく同じ処理が行われます。 ただし、Statement
のメソッドでは、特定の Statement
によって生成されるすべての ResultSet
オブジェクトのデフォルト値を設定します。ResultSet
のメソッドは、フェッチ方向またはフェッチサイズを変更するときに、ResultSet
オブジェクトのライフタイム内の任意のときに呼び出すことができます。 これらのメソッドについての詳細は、「パフォーマンスヒントの提供」を参照してください。
Statement
インタフェースおよび ResultSet
インタフェースには、対応している getFetchDirection
および getFetchSize
という get
メソッドがあります。 Statement.getFetchDirection
が、フェッチ方向の設定前に呼び出された場合は、返される値は実装によって異なり、ドライバに依存します。 Statement.getFetchSize
メソッドの場合も同様です。
execute
メソッドは、文が複数の ResultSet
オブジェクト、複数の更新カウント、または ResultSet
オブジェクトと更新カウントの組み合せを返す可能性がある場合だけに使用してください。 この結果が複数になる可能性が、まれにですが、一部のストアドプロシージャを実行するとき、または (コンパイル時にアプリケーションプログラマに知られていない) 未知の SQL 文字列を動的に実行するときに起きることがあります。 たとえば、ユーザがストアドプロシージャを実行することがあり (CallableStatement
オブジェクトを使用)、ストアドプロシージャが更新、続いて選択、さらに更新、続いて選択と次々に実行することがある場合があります。 ストアドプロシージャを使用する場合には、それが何を返すかすでにわかっているのが普通です。