この章では、Java Database Connectivity(JDBC)標準に対するOracleパフォーマンス拡張機能について説明します。
この章の内容は次のとおりです。
|
注意: Oracle Database 12cリリース1 (12.1)以降、Oracleバッチ更新は非推奨です。Oracleバッチ更新のかわりに標準のJDBCバッチを使用することをお薦めします。 |
複数のUPDATE文、DELETE文またはINSERT文を単一のバッチにグループ化して、バッチ全体を一度にデータベースに送信して処理することによって、データベースへのラウンドトリップの回数を減らし、それによってアプリケーションのパフォーマンスを向上させることができます。これは、バッチ更新(update batching)と呼ばれます。
|
注意: JDBC 2.0仕様では、これをバッチ更新(batch updates)と呼びます。 |
これは、特にプリコンパイルされたSQL文で、同じ文をバインド変数を変えて繰り返し使用する場合に効果的です。
Oracle JDBCでは、2つの異なるバッチ更新のモデルがサポートされています。
標準モデル。JDBC 2.0仕様を実装します。これは、標準バッチ更新と呼ばれます。
Oracle固有モデル。JDBC 2.0仕様から独立しています。これは、Oracleバッチ更新と呼ばれます。
|
注意: これらのモデルは混在させることができません。単一アプリケーションで、どちらのモデルを使用してもかまいませんが、両方は使用できません。これらを混在させると、Oracle JDBCドライバで例外が発生します。 |
この項の内容は次のとおりです。
この項では、標準バッチ更新とOracleバッチ更新用にサポートされている、一般的なモデルと型を比較します。
|
注意: Oracle Database 12cリリース1 (12.1)以降、Oracleバッチ更新は非推奨です。Oracleバッチ更新のかわりに標準のJDBCバッチを使用することをお薦めします。 |
Oracleバッチ更新では、通常は暗黙的なバッチ処理となるバッチ値を使用します。バッチ値とは、データベースへのトリップごとにバッチに追加する操作の数です。この値まで操作がバッチに追加されると、バッチが処理されます。次のことに注意してください。
接続オブジェクトにデフォルト・バッチ値を設定できます。このバッチ値は、その接続で実行されるプリコンパイルされたSQL文すべてに適用されます。
プリコンパイルされたSQL文オブジェクトには、個別に文バッチ値を設定できます。この値は、接続バッチ値より優先します。
接続バッチ値と文バッチ値の両方を無視して、いつでも明示的にバッチを処理できます。
標準のバッチ更新は、手動の明示的なモデルです。バッチ値は設定されていません。手動で操作をバッチに追加し、明示的にバッチ処理のタイミングを選択します。
|
注意:
|
Oracleによる実装のとおり、バッチ更新は、同じ文をバインド変数を変えて繰り返し使用する場合に、プリコンパイルされたSQL文で使用するように用意されています。次のことに注意してください。
Oracleバッチ更新でサポートされるのは、プリコンパイルされたSQL文オブジェクトのみです。コール可能文では、接続デフォルト・バッチ値と文バッチ値の両方が値1にオーバーライドされます。Oracleの一般的な文では、文バッチ値はなく、接続デフォルト・バッチ値は値1にオーバーライドされます。
JDBC 2.0標準に準拠するために、標準バッチ更新のOracle実装では、プリコンパイルされたSQL文と同じように、OUTパラメータなしのコール可能文および一般的な文もサポートされています。標準バッチ更新は、Oracle JDBCアプリケーションに簡単に移行できます。
バッチできるのは、UPDATE、INSERTまたはDELETE操作のみです。結果セットを戻そうとする操作を含むバッチを処理すると、例外が発生します。
|
注意: 標準バッチ更新のOracle実装では、一般的な文およびコール可能文の実際のバッチ処理は実装されていません。Oracle JDBCはStatementおよびCallableStatementオブジェクトに対する標準バッチ処理構文の使用をサポートしますが、パフォーマンスが向上するのはPreparedStatementオブジェクトの場合のみです。 |
Oracleバッチ更新によって、バッチ値がプリコンパイルされたSQL文オブジェクトに関連付けられます。Oracleバッチ更新を使用すると、JDBCドライバはexecuteUpdateメソッドがコールされるたびにプリコンパイルされたSQL文操作を実行するのではなく、蓄積された処理要求のバッチに文を追加します。バッチ値に達すると、ドライバはすべての操作をデータベースに渡して一度に処理します。たとえば、バッチ値が10の場合、10の操作がバッチに蓄積されるたびにデータベースに送信され、1回のラウンドトリップで処理されます。
OracleConnectionクラスのメソッドを使用すると、Oracle接続全体に対するデフォルト・バッチ値を設定できます。このバッチ値は、接続内のすべてのプリコンパイルされたOracle SQL文に適用されます。任意の特定のOracleプリコンパイルされたSQL文に対して、OraclePreparedStatementクラスにおけるメソッドを使用して文バッチ値を設定できます。この値は、接続バッチ値より優先されます。保留中のバッチの手動処理を選択することで、両方のバッチ値より優先させることもできます。
Oracleバッチ更新に関する、次の制限事項と実装の詳細に注意してください。
バッチ値は、5から30にすると最も効率的です。非常に高い値を設定すると、逆効果になります。特定のアプリケーションでの有効性を検証するために、様々な値を試すことをお薦めします。
バッチ値が有効かどうかにかかわらず、Oracle PreparedStatementのバインド変数が、データのストリームが必要なデータ型(LOB、CLOBなど)である場合、Oracle JDBCドライバによりバッチ値は1に設定され、処理待ちの要求はすべてデータベースに送信され、処理されます。
Oracle JDBCドライバは、次の場合に、Oracle PreparedStatementのsendBatchメソッドを暗黙的に実行します。
接続が、commitメソッドのコールの結果として、COMMIT要求を受信した場合。
文が、close要求を受信した場合。
接続が、close要求を受信した場合。
|
注意: 接続のCOMMIT要求、文のclose要求または接続のclose要求は、Oracleバッチ更新を使用する場合のみ、保留バッチに影響を与えます。ただし、標準バッチ更新を使用する場合は、保留バッチに影響を与えません。 |
sendBatchがコールされる前に接続がROLLBACK要求を受信した場合は、保留バッチ処理が削除されません。削除するには、clearBatchを明示的にコールする必要があります。
Oracle接続では、任意のOracle PreparedStatementにデフォルト・バッチ値を指定できます。この値を指定するには、OracleConnectionオブジェクトのsetDefaultExecuteBatchメソッドを使用します。たとえば、次のコードにより、conn接続オブジェクトに関連付けられているすべてのプリコンパイルされたSQL文に対して、デフォルト・バッチ値が20に設定されます。
((OracleConnection)conn).setDefaultExecuteBatch(20);
接続バッチ値は、このバッチ値がセットされた後で作成された文オブジェクトに適用されます。
Java Propertiesオブジェクトを使用して接続を確立した場合、setDefaultExecuteBatchメソッドをコールするかわりに、defaultBatchValue Javaプロパティを設定することもできます。
特定のOracle PreparedStatementに文バッチ値を設定するには、次の手順を使用します。これにより、文を処理する接続のOracleConnectionインスタンスのsetDefaultExecuteBatchメソッドを使用して設定した接続バッチ値はすべて、オーバーライドされます。
次のように、プリコンパイルされたSQL文を記述し、第1行の入力値を指定します。
PreparedStatement ps = conn.prepareStatement("INSERT INTO dept VALUES (?,?,?)");
ps.setInt(1,12);
ps.setString(2,"Oracle");
ps.setString(3,"USA");
プリコンパイルされたSQL文をOraclePreparedStatementにキャストし、setExecuteBatchメソッドを適用します。この例では、文のバッチ・サイズは2に設定されます。
((OraclePreparedStatement)ps).setExecuteBatch(2);
必要に応じて、プログラムの任意の場所に、次のようにgetExecuteBatchメソッドを挿入すると、文のデフォルト・バッチ値を確認できます。
System.out.println(" Statement Execute Batch Value " +
((OraclePreparedStatement)ps).getExecuteBatch());
この時点で更新を実行するコールをデータベースに送信しても、データはデータベースに送信されず、0(ゼロ)が戻されます。
// No data is sent to the database by this call to executeUpdate
System.out.println("Number of rows inserted so far: " + ps.executeUpdate ());
第2行の入力値の集合および更新の実行を入力すると、executeUpdateのバッチ・コール回数がバッチ値2と等しくなります。データはデータベースに送信され、1回のラウンドトリップで2つの行が挿入されます。このexecuteUpdateコールはバッチ値の境界にあり、バッチ内のすべての操作はサーバーで実行されるため、バッチ全体で影響を受ける行の合計数が返されます。
ps.setInt(1, 11);
ps.setString(2, "Applications");
ps.setString(3, "Indonesia");
int rows = ps.executeUpdate();
System.out.println("Number of rows inserted now: " + rows);
ps.close();
|
注意: 列のデータ型がバッチ間で変更されると、バッチ値カウンタは内部的にリセットされます。データ型が変更される前に、すでにバッチ処理されたアイテムのバッチ全体によって影響を受けた行の合計数と、新しくバッチ処置されたアイテムが、新しいバッチ値の境界で返されます。たとえば、次のようになります。4の整数を |
Oracle接続インスタンスの全体的な接続バッチ値をチェックするには、OracleConnectionクラスのgetDefaultExecuteBatchメソッドを使用します。
Integer batch_val = ((OracleConnection)conn).getDefaultExecuteBatch();
Oracle PreparedStatementの特定の文バッチ値をチェックするには、OraclePreparedStatementクラスのgetExecuteBatchメソッドを使用します。
Integer batch_val = ((OraclePreparedStatement)ps).getExecuteBatch();
|
注意: 文バッチ値が設定されていない場合、getExecuteBatchは接続バッチ値を戻します。 |
有効なバッチ値に達する前に、蓄積された操作を処理するには、OraclePreparedStatementオブジェクトのsendBatchメソッドを使用します。
この例では、接続バッチ値を20に設定します。これにより、接続に関連付けられているすべてのプリコンパイルされたSQL文オブジェクトのデフォルト・バッチ値は、20に設定されます。次のように、接続をOracleConnectionにキャストし、接続にsetDefaultExecuteBatchメソッドを適用します。
((OracleConnection)conn).setDefaultExecuteBatch (20);
次のように、バッチ値をオーバーライドします。
次のように、プリコンパイルされたSQL文を記述し、第1行の入力値を指定して、この文を処理します。
PreparedStatement ps =
conn.prepareStatement("insert into dept values (?, ?, ?)");
ps.setInt(1, 32);
ps.setString(2, "Oracle");
ps.setString(3, "USA");
System.out.println (ps.executeUpdate ());
この時点では、バッチは処理されません。ps.executeUpdateメソッドにより0が戻されます。
2番目の操作の入力値の集合を入力し、もう一度executeUpdateをコールしても、データはデータベースに送信されません。文に対して有効なバッチ値は、接続のバッチ値である20です。
ps.setInt(1, 33);
ps.setString(2, "Applications");
ps.setString(3, "Indonesia");
// this batch is still not executed at this point
int rows = ps.executeUpdate();
System.out.println("Number of rows updated before calling sendBatch: " + rows);
println文のrowsの値が0である点に注意してください。
この時点でsendBatchを適用すると、これまでにバッチされていた2つの操作が、1回のラウンドトリップでデータベースに送信されます。sendBatchメソッドからは、更新された行の合計数も戻されます。sendBatchのプロパティをprintlnで使用すると、更新された行数が出力されます。
// Execution of both previously batched executes will happen
// at this point. The number of rows updated will be
// returned by sendBatch.
rows = ((OraclePreparedStatement)ps).sendBatch();
System.out.println("Number of rows updated by calling sendBatch: " + rows);
ps.close();
バッチを処理した後、変更をコミットする必要があります。自動コミットは使用禁止(推奨)にしてあるものとします。
Oracleバッチ処理で接続オブジェクトのcommitメソッドをコールすると、処理されたバッチの操作がコミットされるのみでなく、暗黙的なsendBatchコールが発行され、保留バッチがすべて処理されます。このように、commitメソッドによって、バッチに追加されたすべての操作による変更が効率的にコミットされます。
バッチを使用しない場合、OraclePreparedStatementオブジェクトのexecuteUpdateメソッドからは、操作の影響を受けたデータベース行の数が戻されます。
Oracleバッチ処理を使用する場合、このメソッドからは、次のように、メソッドが起動されたときに影響を受けた行の数が返されます。
executeUpdateのコールによって操作がバッチに追加される場合、値0(ゼロ)が戻されます。その時点では、データベースには何も書き込まれません。
executeUpdateのコールによってバッチ値に達し、バッチが処理される場合、バッチに含まれるすべての操作によって影響を受ける行の合計数が戻されます。
同様に、OraclePreparedStatementオブジェクトのsendBatchメソッドからは、バッチに含まれるすべての操作によって影響を受ける行の合計数が戻されます。
例21-1は、Oracleバッチ更新の使用方法を示しています。
次の例では、Oracleバッチ更新機能の使用方法を示します。この例では、oracle.driver.*インタフェースがインポート済であることを前提にしています。
...
OracleDataSource ods = new OracleDataSource();
ods.setURL("jdbc:oracle:oci);
ods.setUser("HR");
ods.setPassword("hr");
Connection conn = ods.getConnection();
conn.setAutoCommit(false);
PreparedStatement ps =
conn.prepareStatement("insert into dept values (?, ?, ?)");
//Change batch size for this statement to 3
((OraclePreparedStatement)ps).setExecuteBatch (3);
ps.setInt(1, 23);
ps.setString(2, "Sales");
ps.setString(3, "USA");
ps.executeUpdate(); //JDBC queues this for later execution
ps.setInt(1, 24);
ps.setString(2, "Blue Sky");
ps.setString(3, "Montana");
ps.executeUpdate(); //JDBC queues this for later execution
ps.setInt(1, 25);
ps.setString(2, "Applications");
ps.setString(3, "India");
ps.executeUpdate(); //The queue size equals the batch value of 3
//JDBC sends the requests to the database
ps.setInt(1, 26);
ps.setString(2, "HR");
ps.setString(3, "Mongolia");
ps.executeUpdate(); //JDBC queues this for later execution
((OraclePreparedStatement)ps).sendBatch(); // JDBC sends the queued request
conn.commit();
ps.close();
...
|
注意: バッチによって遅延した更新は、他の問合せの結果に影響を与えることがあります。次の例では、バッチにより最初の問合せが遅延した場合は、次の問合せから予期しない結果が戻されます。UPDATE emp SET name = "Sue" WHERE name = "Bob"; SELECT name FROM emp WHERE name = "Sue"; |
executeBatchがコールされたとき、バッチ処理された操作のうち1つでも失敗したり、結果セットを戻そうとしたりすると、処理は停止し、java.sql.BatchUpdateExceptionが生成されます。
例外が発生した場合は、BatchUpdateExceptionオブジェクトのgetUpdateCountsメソッドをコールすると、更新件数を取得できます。このメソッドからは、executeBatchメソッドと同じように、更新件数のint配列が戻されます。
Oracle Database 11gリリース1より前は、戻される整数配列にはStatement.EXECUTE_FAILEDエントリがn個(nはバッチのサイズ)含まれます。ただし、これは、バッチ内でエラーが発生した箇所を示しているわけではありません。選択できるのはトランザクションのロールバックのみです。
Oracle Database 11gリリース1以降は、戻される整数配列にはStatement.SUCCESS_NO_INFOエントリがn個(nはバッチ内で正常に実行された要素の数)含まれます。
|
注意: バッチの実行は常に、バッチ内でエラーが最初に生成された要素で停止します。 |
JDBC標準バッチ更新は、Oracleバッチ更新モデルと異なり、addBatchメソッドを使用して明示的に文をバッチに追加し、executeBatchメソッドを使用して明示的にバッチを処理します。Oracleモデルでは、バッチ処理を行わない場合と同様にexecuteUpdateをコールしますが、操作をバッチに追加するか、バッチ全体を処理するかは、通常、あらかじめ定義されているバッチ値に達したかどうかによって、暗黙的に判断されます。
|
注意:
|
この項では、標準バッチ更新のOracle実装に関する、制限事項と実装の詳細を説明します。
Oracle JDBCアプリケーションでバッチ更新を使用すると、バインド変数の設定を変えてプリコンパイルされたSQL文を繰り返し処理できます。
標準バッチ更新のOracle実装では、一般的な文およびコール可能文の実際のバッチ処理は実装されていません。Oracle JDBCはStatementおよびCallableStatementオブジェクトに対する標準バッチ処理の使用をサポートしますが、パフォーマンスは向上しません。
いかなる文オブジェクトも、最初に作成されたとき、その文バッチは空です。文バッチに操作を追加するには、標準のaddBatchメソッドを使用します。このメソッドは標準のjava.sql.Statementインタフェース、PreparedStatementインタフェースおよびCallableStatementインタフェースで指定され、それぞれ、oracle.jdbc.OracleStatementインタフェース、OraclePreparedStatementインタフェースおよびOracleCallableStatementインタフェースで実装されます。
Statementオブジェクトの場合、addBatchメソッドは、入力としてSQL操作のJava Stringを取ります。たとえば、次のようになります。
...
Statement stmt = conn.createStatement();
stmt.addBatch("INSERT INTO emp VALUES(1000, 'Joe Jones')");
stmt.addBatch("INSERT INTO dept VALUES(260, 'Sales')");
stmt.addBatch("INSERT INTO emp_dept VALUES(1000, 260)");
...
この時点で、バッチには3つの操作が入っています。
|
注意: ただし、標準バッチ更新のOracle実装では、一般的な文をバッチ処理してもパフォーマンスは向上しません。 |
プリコンパイルされたSQL文の場合、バッチ更新は、異なる組合せのバインド・パラメータを使用して、同じ文を複数回実行するために使用されます。PreparedStatementオブジェクトまたはOraclePreparedStatementオブジェクトの場合、addBatchメソッドは、入力を取りません。適切なsetXXXメソッドによって最後に設定されたバインド・パラメータを使用して、操作をバッチに追加するだけです。CallableStatementオブジェクトやOracleCallableStatementオブジェクトの場合も同様です。ただし、標準更新バッチのOracle実装では、コール可能文をバッチ処理してもパフォーマンスはおそらく向上しないことに注意してください。
たとえば、次のようになります。
...
PreparedStatement pstmt =
conn.prepareStatement("INSERT INTO employees VALUES(?, ?)");
pstmt.setInt(1, 2000);
pstmt.setString(2, "Milo Mumford");
pstmt.addBatch();
pstmt.setInt(1, 3000);
pstmt.setString(2, "Sulu Simpson");
pstmt.addBatch();
...
この時点で、バッチには2つの操作が入っています。
1つのバッチは単一のPreparedStatementオブジェクトに関連付けられるので、バッチ処理できるのは、この例のような単一のプリコンパイルされたSQL文の繰返し実行のみです。
現在の操作バッチを処理するには、文オブジェクトのexecuteBatchメソッドを使用します。このメソッドは標準のStatementインタフェースで指定されます。これは標準PreparedStatementインタフェースとCallableStatementインタフェースで拡張されています。
|
注意: addBatchメソッドを数回コールして非常に多くの操作をバッチに追加して、非常に大きなバッチ(100,000行以上のバッチなど)を作成した場合、バッチでexecuteBatchメソッドをコール中に、メモリーに関する深刻なパフォーマンスの問題が発生することがあります。このような問題を回避するために、JDBCドライバは透過的に大きなバッチを小さな内部バッチに分割し、各内部バッチに対してサーバーへのラウンドトリップを行います。各ラウンドトリップのオーバーヘッドのために、アプリケーションは若干遅くなりますが、メモリーはかなり最適化されます。ただし、各バインド行のサイズが非常に大きい場合(1MBを超える場合など)、このプロセスはパフォーマンス全体に悪影響を与えることがあります。これは、メモリーについて得られたパフォーマンスが時間について失われたパフォーマンスよりも少ないためです。 |
次の例では、前の例で示したプリコンパイルされたSQL文のaddBatchコールを繰り返した後、バッチを処理します。
...
PreparedStatement pstmt =
conn.prepareStatement("INSERT INTO employees VALUES(?, ?)");
pstmt.setInt(1, 2000);
pstmt.setString(2, "Milo Mumford");
pstmt.addBatch();
pstmt.setInt(1, 3000);
pstmt.setString(2, "Sulu Simpson");
pstmt.addBatch();
int[] updateCounts = pstmt.executeBatch();
...
Oracle Database 12cリリース1 (12.1)以降、executeBatchメソッドは改良され、バッチのレコード数と同じサイズのint配列を戻します。戻り配列の各項目は、バッチの対応するレコードの影響を受けたデータベース表の行数です。たとえば、バッチ・サイズが5の場合、executeBatchメソッドはサイズ5の配列を戻します。バッチの実行中にエラーが発生した場合、executeBatchメソッドは値を戻すことができず、かわりにBatchUpdateExceptionをスローします。この場合、例外自体がサイズnのint配列をそのデータとして保持します。nは、成功したレコード実行の数です。たとえば、バッチのサイズが5で、4番目のレコードでエラーが発生した場合、BatchUpdateExceptionはサイズ3の配列(3レコードは正常に実行)を保持し、配列内の各項目は各レコードの影響を受けた行数を表します。
バッチを処理した後、変更をコミットする必要があります。自動コミットは使用禁止(推奨)にしてあるものとします。
commitをコールすると、処理済の文バッチの場合、バッチ処理された操作とされなかった操作がコミットされますが、標準バッチのOracle実装では、未処理の保留文バッチには影響がありません。
現在の操作バッチを処理しないで消去するには、文オブジェクトのclearBatchメソッドを使用します。このメソッドは標準のStatementインタフェースで指定されます。これは標準PreparedStatementインタフェースとCallableStatementインタフェースで拡張されています。
次の点に注意してください。
バッチが処理されるとき、操作は、バッチに入れられた順に実行されます。
addBatchをコールした後、executeUpdateをコールする前に、executeBatchまたはclearBatchをコールする必要があります。コールしないと、SQL例外が発生します。
clearBatchまたはexecuteBatchをコールすると、文バッチは空にリセットされます。
接続がROLLBACK要求を受信すると、文のバッチが空にリセットされません。リセットするには、clearBatchを明示的にコールする必要があります。
|
注意:
|
ロールバック後のclearBatchメソッドのコールは、すべてのリリースで有効です。
文オブジェクトの現行結果セットがある場合、この結果セットはexecuteBatchコールによってクローズされます。
clearBatchメソッドの戻り値はありません。
次の例では、前の例で示したプリコンパイルされたSQL文のaddBatchコールを繰り返した後、特定の条件でバッチを消去します。
...
PreparedStatement pstmt =
conn.prepareStatement("INSERT INTO employees VALUES(?, ?)");
pstmt.setInt(1, 2000);
pstmt.setString(2, "Milo Mumford");
pstmt.addBatch();
pstmt.setInt(1, 3000);
pstmt.setString(2, "Sulu Simpson");
pstmt.addBatch();
if (...condition...)
{
int[] updateCounts = pstmt.executeBatch();
...
}
else
{
pstmt.clearBatch();
...
}
文バッチが正常に処理された場合、文のexecuteBatchコールから戻される整数配列、つまり、更新件数配列には、常にバッチ操作1つに対して1つの要素が含まれます。標準バッチ更新のOracle実装では、配列要素の値は次のようになります。
プリコンパイル文のバッチの場合、配列には、各操作で影響を受けた行数を示す実際の更新件数を格納します。
一般的な文のバッチの場合、配列には、各操作で影響を受けた行数を示す実際の更新件数を格納します。標準バッチのOracle実装では、一般的な文の場合のみ、実際の更新件数がわかります。
コール可能文のバッチの場合、配列には、各操作で影響を受けた行数を示す実際の更新件数を格納します。
コードの側では、バッチの正常な処理に対して、配列要素に-2、1または実際の更新件数のいずれが設定されても処理できるように準備しておく必要があります。正常なバッチ処理では、配列にはすべて-2が含まれるか、すべて1が含まれるか、またはすべて正の整数が含まれます。
例21-2は、標準バッチ更新の使用方法を示しています。
この例は、前の項のサンプル・コードを組み合せたもので、次の処理を行います。
自動コミット・モードの無効化。どちらかのバッチ更新モデルを使用する場合、無効にする必要があります。
PreparedStatementオブジェクトの作成。
PreparedStatementオブジェクトに関連付けられたバッチへの操作の追加。
バッチの処理。
バッチの操作のコミット。
conn.setAutoCommit(false);
PreparedStatement pstmt =
conn.prepareStatement("INSERT INTO employees VALUES(?, ?)");
pstmt.setInt(1, 2000);
pstmt.setString(2, "Milo Mumford");
pstmt.addBatch();
pstmt.setInt(1, 3000);
pstmt.setString(2, "Sulu Simpson");
pstmt.addBatch();
int[] updateCounts = pstmt.executeBatch();
conn.commit();
pstmt.close();
...
更新件数配列を処理して、バッチが正常に処理されたかどうかを判断できます。
executeBatchがコールされたとき、バッチ処理された操作のうち1つでも失敗したり、結果セットを戻そうとしたりすると、処理は停止し、java.sql.BatchUpdateExceptionが生成されます。
バッチ例外の後、BatchUpdateExceptionオブジェクトのgetUpdateCountsメソッドを使用して、更新件数配列を取り出せます。このメソッドからは、executeBatchメソッドと同じように、更新件数のint配列が戻されます。標準バッチ更新のOracle実装では、バッチが処理された後の更新件数配列の内容は次のようになります。
プリコンパイルされたSQL文バッチにおいて、バッチの実行中にエラーが発生した場合、executeBatchメソッドは値を戻すことができず、かわりにBatchUpdateExceptionをスローします。この場合、例外自体がサイズnのint配列をそのデータとして保持します。nは、成功したレコード実行の数です。たとえば、バッチのサイズが5で、4番目のレコードでエラーが発生した場合、BatchUpdateExceptionはサイズ3の配列(3レコードは正常に実行)を保持し、配列内の各項目は各レコードの影響を受けた行数を表します。
一般的な文のバッチまたはコール可能文のバッチの場合、更新件数配列は、エラーの時点までの実際の更新件数を格納する、部分的な配列になります。標準バッチ更新のOracle実装では、Oracle JDBCは一般の文およびコール可能文の本当の意味でのバッチ処理を使用できないので、実際の更新件数がわかります。
たとえば、バッチに20の操作が含まれているとき、最初の13は正常終了し、14番目で例外が生成された場合、更新件数配列には13の要素が含まれ、正常終了した操作の実際の更新件数が設定されます。
この場合、正常終了した操作をコミットすることも、ロールバックすることもできます。
コードの側では、例外が発生した場合、バッチの失敗した処理に対して、配列要素に-3または実際の更新件数のどちらが設定されても処理できるように準備しておく必要があります。失敗したバッチ処理では、すべてに-3が含まれる完全な配列か、正の整数が含まれる部分配列が作成されます。
文オブジェクトに操作の保留バッチがある場合、通常のバッチ処理されない操作の処理を行うためにexecuteUpdateをコールできません。
ただし、文バッチに操作を追加する前か、バッチを処理した後で、バッチ処理されない操作を処理する場合は、バッチ処理される操作とバッチ処理されない操作を単一文オブジェクトに混在させることができます。つまり、文オブジェクトのexecuteUpdateは、バッチ更新が空のときにのみコールできます。バッチが空でない場合、例外が生成されます。
たとえば、次のような順序は有効です。
...
PreparedStatement pstmt =
conn.prepareStatement("INSERT INTO employees VALUES(?, ?)");
pstmt.setInt(1, 2000);
pstmt.setString(2, "Milo Mumford");
int scount = pstmt.executeUpdate(); // OK; no operations in pstmt batch
pstmt.setInt(1, 3000);
pstmt.setString(2, "Sulu Simpson");
pstmt.addBatch(); // Now start a batch
pstmt.setInt(1, 4000);
pstmt.setString(2, "Stan Leland");
pstmt.addBatch();
int[] bcounts = pstmt.executeBatch();
pstmt.setInt(1, 5000);
pstmt.setString(2, "Amy Feiner");
int scount = pstmt.executeUpdate(); // OK; pstmt batch was executed
...
ある文オブジェクトのバッチ処理されない操作と、別の文オブジェクトのバッチ処理される操作を、コード上に混在させることはできます。異なる文オブジェクトは、バッチ更新操作に関して、互いに無関係です。COMMIT要求は、バッチ処理されない操作すべてと、処理済バッチの正常な操作すべてに影響を与えますが、保留バッチには影響を与えません。
早期バッチ・フラッシュは、キャッシュされたメタデータが変更されると発生します。キャッシュされたメタデータは、次のような様々な理由から変更されることがあります。
最初のバインドがNULLで後続のバインドが非NULLである場合。
最初は文字列としてスカラー型がバインドされ、後でスカラー型としてバインドされた場合。あるいはその逆の場合。
早期バッチ・フラッシュ・カウントは、次のexecuteUpdateメソッドまたはsendBatchメソッドの戻り値に追加されます。
以前の機能では、ここで取得できるすべてのバッチ・フラッシュ値が失われました。以前の機能に切り替えるには、次に示すようにAccumulateBatchResultプロパティをfalseに設定します。
java.util.Properties info = new java.util.Properties();
info.setProperty("user", "HR");
info.setProperty("passwd", "hr");
// other properties
...
// property: batch flush type
info.setProperty("AccumulateBatchResult", "false");
OracleDataSource ods = new OracleDataSource();
ods.setConnectionProperties(info);
ods.setURL("jdbc:oracle:oci:@"");
Connection conn = ods.getConnection();
|
注意: AccumulateBatchResultプロパティはデフォルトでtrueに設定されます。 |
例21-3は、早期バッチ・フラッシュを示しています。
例21-3 早期バッチ・フラッシュ
((OraclePreparedStatement)pstmt).setExecuteBatch (2); pstmt.setNull(1, OracleTypes.NUMBER); pstmt.setString(2, "test11"); int count = pstmt.executeUpdate(); // returns 0 /* * Premature batch flush happens here. */ pstmt.setInt(1, 22); pstmt.setString(2, "test22"); int count = pstmt.executeUpdate(); // returns 0 pstmt.setInt(1, 33); pstmt.setString(2, "test33"); /* * returns 3 with the new batching scheme where as, * returns 2 with the old batching scheme. */ int count = pstmt.executeUpdate();
Oracle JDBCドライバでは、バッチ更新に加えて、次の拡張機能がサポートされています。これにより、データベースへのラウンドトリップが減少し、パフォーマンスが向上します。
行をプリフェッチすることにより、データがフェッチされるたびに複数行がフェッチされるので、データベースへのラウンドトリップが減少します。余分にフェッチされたデータは、後で使用するためにクライアント側バッファに格納されます。プリフェッチの行数は、目的に応じて設定できます。
問合せの実行および問合せ結果の取出しを行うときに、標準のJDBCプロトコルで生じる非効率性を回避できます。
コストの高い外部結合操作を回避できます。
Oracleでは、これらのパフォーマンス拡張機能をサポートするために、接続プロパティ・オブジェクトにいくつかの拡張機能を提供します。これらの拡張機能により、remarksReportingフラグと、行プリフェッチとバッチ更新のデフォルト値を設定できます。
この項の内容は次のとおりです。
Oracle Database 11gリリース2 JDBCドライバより前のJDBCドライバの場合、1回のラウンドトリップでLOBデータを取得するには、データをVARCHAR2型としてフェッチする必要があります。つまり、OracleTypes.VARCHARまたはOracleTypes.LONGVARCHARを、JDBC defineColumnTypeメソッドとともに使用する必要があります。この手法の制限は、LOBデータをCHAR型としてフェッチする場合、ロケータをデータとともにフェッチすることができないということです。このため、アプリケーションがLOBデータを後から取得する場合、またはアプリケーションが他のLOB操作を実行する場合、アプリケーションがLOBロケータを取得できないため、LOBロケータを取得するためにラウンドトリップがもう1回必要になります。
|
注意: LOBロケータでの配列操作は、JDBC APIでサポートされていません。 |
Oracle Database 11gリリース2以降のJDBCドライバの場合、通常のフェッチ操作中にロケータとともにLOBデータの始めをプリフェッチするのと同様に、LOBの長さやチャンク・サイズなどの使用頻度の高いメタデータをプリフェッチすることで、ラウンドトリップの回数を減らします。LOBが小さい場合、ただ1回のラウンドトリップでデータをすべてプリフェッチできる場合があります。select、パース、実行およびフェッチが1回のラウンドトリップで行われるため、パフォーマンスが大幅に向上します。LOBがプリフェッチ・サイズの5倍より大きい場合、チャンク・サイズを取得するためのラウンドトリップが必要ないだけなので、パフォーマンスはそれほど向上しません。
defaultLobPrefetchSize接続プロパティ
Oracle Database 11gリリース2から、接続のためのデフォルトLOBプリフェッチ・サイズを設定するために使用できる新しい接続プロパティoracle.jdbc.defaultLobPrefetchSizeが導入されました。この接続プロパティは、定数OracleConnection.CONNECTION_PROPERTY_DEFAULT_LOB_PREFETCH_SIZEとして定義されています。このプロパティの値が、現行接続のデフォルトのLOBプリフェッチ・サイズとして使用されます。この接続プロパティのデフォルト値は4000です。文レベルでこのデフォルト値を変更する場合は、oracle.jdbc.OracleStatementインタフェースで定義されているsetLobPrefetchSizeメソッドを使用します。デフォルト値は、次の値に変更できます。
現行接続のLOBプリフェッチを無効にするには-1
メタデータ専用のLOBプリフェッチを有効にするには0
フェッチ操作中にロケータとともにプリフェッチするBLOBのバイト数およびCLOBの文字数を指定するには、0より大きな任意の値
LOBプリフェッチ・サイズを取り出すには、oracle.jdbc.OracleStatementインタフェースで定義されているgetLobPrefetchSizeメソッドを使用します。
defineColumnTypeメソッドを使用して列レベルでLOBプリフェッチ・サイズの値も設定できます。列レベルの値は、接続または文レベルで設定されているどの値よりも優先されます。
|
関連項目: 詳細は、Javadocを参照してください。 |
|
注意: LOBプリフェッチが接続レベルまたは文レベルで無効でない場合、列レベルでも無効にできません。 |
最大のプリフェッチ量は設定されていません。デフォルト値は10です。問合せから予想される行数および列数に基づいて、値を増減させた方が適している場合もあります。Propertiesオブジェクトを使用して、接続のデフォルト行プリフェッチ値を設定できます。
Statementオブジェクトは、作成されると、関連付けられた接続から行のプリフェッチ設定のデフォルト値を受け取ります。接続の行プリフェッチ設定のデフォルト値を後で変更しても、文の行プリフェッチ設定は変更されません。
結果セットの列のデータ型が、データ・インタフェースから戻されたLONG、LONG RAWまたはLOB、つまりストリーム型の場合は、いずれの型の値も実際に読み取らなくても、文の行プリフェッチ設定はJDBCによって1に変更されます。
プリフェッチ・サイズの設定は、アプリケーションのパフォーマンスに影響を与える場合があります。プリフェッチ・サイズを大きくすると、全データの取得に必要なラウンドトリップ数は減りますが、メモリーの使用量は増えます。これは、問合せ内の列の数とサイズ、および戻されることが予想される行数に依存します。また、JDBCクライアント・マシンのメモリーおよびCPU負荷にも依存します。スタンドアロン・クライアント・アプリケーションの最適値は、負荷の大きいアプリケーション・サーバーの場合と異なります。ネットワーク接続の速度および待機時間も考慮してください。
|
注意: Oracle Database 11gリリース1以降、Thinドライバは、最初のラウンドトリップで、最初のprefetch_size行をサーバーからフェッチすることができます。これにより、SELECT文でラウンドトリップを1回減らせます。 |
10gリリース1(10.1)以上のOracle JDBCドライバにそれより前のリリースのOracle JDBCドライバからアプリケーションを移行する場合は、以前検討した最適化を再検討する必要があります。メモリーの使用量およびパフォーマンス特性が大きく変化している場合があるからです。
一意なキーを選択する問合せなどは、よく実行する処理です。そのような問合せは、行をゼロ個または1つ戻します。プリフェッチ・サイズを1に設定すると、メモリーおよびCPUの使用量は減り、ラウンドトリップ数は増えません。if(rs.next())のかわりにwhile(rs.next())を記述して余分なフェッチを要求するというミスを避けるよう、注意する必要があります。
JDBC Thinドライバを使用する場合、useFetchSizeWithLongColumn接続プロパティを使用してください。PARSE、EXECUTEおよびFETCHが単一のラウンドトリップ内で実行されるからです。
プリフェッチ・サイズのチューニングは、実際のアプリケーションの現実的な負荷に基づき、JVM内でのメモリー管理のチューニングに合せて行う必要があります。
|
注意:
|
Oracle Database 10gでは、defineColumnTypeの実装が大幅に変更されました。これまでは、defineColumnTypeはパフォーマンスの最適化とデータ型変換の強制実行の両方に使用されていました。以前のリリースでは、すべてのドライバがdefineColumnTypeのコールによる利点を得ていました。Oracle Database 10gから、JDBC Thinドライバはこの情報の提供を必要としなくなりました。JDBC Thinドライバは、defineColumnTypeをコールせずに最大のパフォーマンスを実現します。アプリケーションでdefineColumnTypeを使用すると、JDBC Oracle Call Interface(OCI)およびサーバー側内部ドライバのパフォーマンスが向上します。
コードでJDBC ThinドライバおよびJDBC OCIドライバの両方を使用する場合は、Thinドライバの使用時に接続プロパティdisableDefineColumnTypeをtrueに設定することで、defineColumnTypeメソッドを無効にできます。その結果、defineColumnTypeは無視されます。JDBC OCIまたはサーバー側内部ドライバを使用する場合は、この接続プロパティをtrueに設定しないでください。
また、defineColumnTypeを使用して、クライアント側が割り当てるメモリー量を制御することや、可変長データのサイズを制限することもできます。
必要に応じて、StatementオブジェクトをOracleStatement、OraclePreparedStatementまたはOracleCallableStatementにキャストします。
必要に応じて、StatementオブジェクトのclearDefinesメソッドを使用して、このStatementオブジェクトの以前の列定義を消去します。
各列で、StatementオブジェクトのdefineColumnTypeメソッドをコールして次のパラメータを渡します。
列索引(整数)
型コード(整数)
java.sql.Typesクラスまたはoracle.jdbc.OracleTypesクラスのstatic定数を使用します(Types.INTEGER、Types.FLOAT、Types.VARCHAR、OracleTypes.VARCHAR、OracleTypes.ROWIDなど)。この2つのクラスで、標準型の型コードは同一です。
型名(文字列)
構造化オブジェクト、オブジェクト参照および配列の場合は、型名も指定する必要があります。たとえば、Employee、EmployeeRef、EmployeeArrayなどです。
最大フィールド・サイズ(整数)
オプションで、この列の最大データ長も指定できます。
構造化オブジェクト、オブジェクト参照または配列の列型を定義する場合、最大フィールド・サイズ・パラメータは指定できません。このパラメータを含めても、無視されます。
使用する形式(short)
オプションで、この列の使用する形式も指定できます。データベース・キャラクタ・セットを使用する場合はOraclePreparedStatement.FORM_CHARに、各国語キャラクタ・セットを使用する場合はOraclePreparedStatement.FORM_NCHARに設定します。このパラメータを省略した場合、デフォルトはFORM_CHARです。
たとえば、stmtをOracle文と仮定して、次のように使用します。
stmt.defineColumnType(column_index, typeCode);
列がVARCHARまたは等価な型で、長さの制限がわかっている場合は、次のようにします。
stmt.defineColumnType(column_index, typeCode, max_size);
列がNVARCHARで、元の最大長が必要、かつデータベース・キャラクタ・セットへの変換が要求される場合は、次のようにします。
stmt.defineColumnType(column_index, typeCode, 0, OraclePreparedStatement.FORM_CHAR );
列が構造化オブジェクト、オブジェクト参照および配列の場合は、次のようにします。
stmt.defineColumnType(column_index, typeCode, typeName);
デフォルトのデータ長をすべて受け取る必要がない場合は、最大フィールド・サイズを設定します。標準JDBC StatementクラスのsetMaxFieldSizeメソッドをコールして、戻されるデータ量の制限を設定します。つまり、戻されるデータのサイズは、次の値の最小値になります。
defineColumnTypeで設定された最大フィールド・サイズ
setMaxFieldSizeで設定された最大フィールド・サイズ
データ型固有の最大サイズ
この処理が終了した後に、文のexecuteQueryメソッドを使用して問合せを実行します。
|
注意: 必要な結果セットのすべての列に対してデータ型を指定する必要はありません。 |
例21-4は、この機能の使用方法を示しています。この例では、oracle.jdbc.*インタフェースがインポート済であることを前提にしています。
例21-4 列型の定義
OracleDataSource ods = new OracleDataSource();
ods.setURL("jdbc:oracle:thin:@localhost:5221:orcl");
ods.setUser("HR");
ods.setPassword("hr");
Connection conn = ods.getConnection();
Statement stmt = conn.createStatement();
// Allocate only 2 chars for this column (truncation will happen)
((OracleStatement)stmt).defineColumnType(1, Types.VARCHAR, 2);
ResultSet rset = stmt.executeQuery("select ename from emp");
while(rset.next() )
System.out.println(rset.getString(1));
stmt.close();
この例が示すように、defineColumnTypeメソッドの起動では、StatementオブジェクトstmtをOracleStatementにキャストする必要があります。接続のcreateStatementメソッドは、defineColumnTypeメソッドとclearDefinesを持たず、型がjava.sql.Statementであるオブジェクトを戻します。これらのメソッドは、OracleStatement実装でのみ提供されています。
定義の拡張要素では、JDBC型を使用して目的の型を指定します。使用可能な列型は、列のOracle内部型によって異なります。
すべての列は、本来のJDBC型に定義できます。多くの場合、Types.CHARまたはTypes.VARCHAR型コードに定義できます。
表21-1に、defineColumnTypeメソッドで使用できる有効な列定義引数を示します。
表21-1 有効な列型
| 列内のOracle SQL型 |
defineColumnTypeで 定義できる型 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
defineColumnTypeを列の元のデータ型で使用することは、常に可能です。
データベース・メタデータ・クラスのgetColumns、getProcedureColumns、getProceduresおよびgetTablesメソッドを使用してTABLE_REMARKS列をレポートすると、コストの高い外部結合を必要とするため処理が遅くなります。この理由でJDBCドライバは、デフォルトではTABLE_REMARKS列を報告しません。
OracleConnectionオブジェクトのsetRemarksReportingメソッドにtrue引数を渡すと、TABLE_REMARKSレポートが使用可能になります。
Java Propertiesオブジェクトを使用して接続を確立した場合、setRemarksReportingをコールするかわりに、remarksReporting Javaプロパティを設定することもできます。
標準java.sql.Connectionオブジェクトを使用している場合、setRemarksReportingを使用するには、オブジェクトをOracleConnectionにキャストする必要があります。
例21-5は、TABLE_REMARKSレポートを使用可能にする方法を示しています。
例21-5 TABLE_REMARKSレポート
connは標準Connectionオブジェクト名を表しています。次の文を使用するとTABLE_REMARKSレポートが使用可能になります。
((oracle.jdbc.OracleConnection)conn ).setRemarksReporting(true);
getColumnsの考慮事項
シノニムが指定されている場合、デフォルトでは、getColumnsメソッドは列の情報を取り出しません。シノニムが指定されている場合に情報の取出しを可能にするには、次のように、接続でsetIncludeSynonymsメソッドをコールする必要があります。
((oracle.jdbc.OracleConnection)conn ).setIncludeSynonyms(true)
これによって、その接続での後続のgetColumnsメソッドのコールすべてにシノニムが含まれます。これは、setRemarksReportingと同じです。あるいは、includeSynonyms接続プロパティを設定できます。これは、remarksReporting接続プロパティと同じです。
ただし、includeSynonymsがtrueに設定されている場合、シノニムが存在すると、table_name列に戻されるオブジェクト名は、シノニム名であることに留意してください。これは、表名をgetColumnsに渡す場合でも同じです。
getProceduresおよびgetProcedureColumnsメソッドの考慮事項
JDBCバージョン1.1および1.2では、getProceduresおよびgetProcedureColumnsメソッドは、catalog、schemaPattern、columnNamePatternおよびprocedureNamePatternパラメータを同じ方法で処理します。これらのメソッドに関するOracleの定義では、パラメータの処理方法は次のように異なります。
catalog
Oracleには複数カタログはありませんが、複数パッケージはあります。したがって、catalogパラメータは、パッケージ名として扱われます。これは入力(catalogパラメータ)と出力(戻されたResultSetのcatalog列)の両方に該当します。入力時に、構成" "(空の文字列)は、プロシージャと引数を、パッケージ(スタンドアロンのオブジェクト)なしで取得します。null値は、選択基準(スタンドアロン・オブジェクトとパッケージ化されたオブジェクトの両方に関する戻し情報)から除外されることを意味します。つまり、パーセント記号(%)を渡すのと同じ効果です。それ以外の場合は、catalogパラメータには、パッケージ名パターンを指定します。必要に応じてSQLワイルドカードを付けられます。
schemaPattern
Oracle Database内のオブジェクトはすべてはスキーマを持つ必要があるため、スキーマを持たないオブジェクトの情報を戻すことには意味がありません。このため、構成" "(空の文字列)は、入力時に、現在のスキーマ、つまり現在接続しているスキーマを持つオブジェクトを意味するものと解釈されます。catalogパラメータの動作との整合性を取るため、nullは選択基準からスキーマを除外するものと解釈されます。つまり、%を渡すのと同じ効果です。SQLワイルドカードを使用したパターンとしても使用できます。
procedureNamePatternおよびcolumnNamePattern
プロシージャと引数はすべて名前を持っているため、空の文字列(" ")はいずれのパラメータについても意味を持ちません。このため、構成" "を使用すると例外が発生します。他のパラメータの動作との整合性を取るため、nullはパーセント記号(%)を渡すのと同じ効果を持ちます。