この章では、Java Database Connectivity(JDBC)標準に対するOracleパフォーマンス拡張機能について説明します。
この章の内容は次のとおりです。
複数の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バッチ更新では、通常は暗黙的なバッチ処理となるバッチ値を使用します。バッチ値とは、データベースへのトリップごとにバッチに追加する操作の数です。この値まで操作がバッチに追加されると、バッチが処理されます。次のことに注意してください。
接続オブジェクトにデフォルト・バッチ値を設定できます。このバッチ値は、その接続で実行されるプリコンパイルされた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のバインド変数がストリーム型である場合、Oracle JDBCドライバによりバッチ値は1に設定され、処理待ちの要求はすべてデータベースに送信され、処理されます。
Oracle JDBCドライバは、次の場合に、Oracle PreparedStatementのsendBatch
メソッドを自動的に実行します。
接続が、commit
メソッドのコールまたは自動コミット・モードの結果として、COMMIT
要求を受信した場合。
文が、close
要求を受信した場合。
接続が、close
要求を受信した場合。
注意: 接続COMMIT 要求、文クローズまたは接続クローズは、Oracleバッチ更新を使用する場合のみ、保留バッチに影響を与えます。ただし、標準バッチ更新を使用する場合は、保留バッチに影響を与えません。 |
sendBatch
がコールされる前に接続がROLLBACK
要求を受信した場合は、保留バッチ処理が削除されません。削除するには、clearBatch
を明示的にコールする必要があります。
Oracle接続では、任意のOracle PreparedStatementにデフォルト・バッチ値を指定できます。この値を指定するには、OracleConnection
オブジェクトのsetDefaultExecuteBatch
メソッドを使用します。たとえば、次のコードにより、conn
接続オブジェクトに関連付けられているすべてのプリコンパイルされたSQL文に対して、デフォルト・バッチ値が20に設定されます。
((OracleConnection)conn).setDefaultExecuteBatch(20);
このメソッドにより、その接続のプリコンパイルされたSQL文すべてに対してデフォルト・バッチ値が設定されますが、Oracle PreparedStatementで個別にoracle.jdbc.OraclePreparedStatement
インタフェースのsetExecuteBatch
をコールすると、その設定値をオーバーライドできます。
接続バッチ値は、このバッチ値がセットされた後で作成された文オブジェクトに適用されます。
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 updated so far: " + ps.executeUpdate ());
第2行の入力値の集合および更新の実行を入力すると、executeUpdate
のバッチ・コール回数がバッチ値2と等しくなります。データはデータベースに送信され、1回のラウンドトリップで2つの行が挿入されます。
ps.setInt (1, 11); ps.setString (2, "Applications"); ps.setString (3, "Indonesia"); int rows = ps.executeUpdate (); System.out.println ("Number of rows updated now: " + rows); ps.close ();
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
メソッドからは、バッチに含まれるすべての操作によって影響を受ける行の合計数が戻されます。
例23-1は、Oracleバッチ更新の使用方法を示しています。
次の例では、Oracleバッチ更新機能の使用方法を示します。この例では、oracle.driver.*
インタフェースがインポート済であることを前提にしています。
... OracleDataSource ods = new OracleDataSource(); ods.setURL("jdbc:oracle:oci); ods.setUser("scott"); ods.setPassword("tiger"); 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リリース2(11.2)の場合、戻される整数配列にはStatement.EXECUTE_FAILED
エントリがn個(nはバッチのサイズ)含まれます。ただし、これは、バッチ内でエラーが発生した箇所を示しているわけではありません。この場合は、トランザクションをロールバックするしか有効な手段がありません。
Oracle Database 11gリリース2(11.2)の場合、戻される整数配列には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
メソッドは、入力を取りません。適切なset
XXX
メソッドによって最後に設定されたバインド・パラメータを使用して、操作をバッチに追加するだけです。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
インタフェースで拡張されています。
次の例では、前の例で示したプリコンパイルされた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 11gリリース1(11.1)以上では、executeBatch
メソッドが改善され、バッチ実行中にエラーが発生した場合、発生するBatchUpdateExecution
例外にバッチ内でのエラーの位置が含まれるようになりました。BatchUpdateExecution.getUpdateCounts
メソッドは、このエラーが発生する前に正常に実行された更新の更新回数を含むint
の配列を戻します。このため、エラーがバッチの5番目の要素で発生した場合に戻される配列のサイズは4で、それぞれの値がStatement.SUCCESS_NO_INFO
となります。
バッチを処理した後、変更をコミットする必要があります。自動コミットは使用禁止(推奨)にしてあるものとします。
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実装では、配列要素の値は次のようになります。
プリコンパイルされたSQL文のバッチの場合、バッチに含まれている個々の文によって影響を受けたデータベースの行数はわかりません。そのため、配列要素の値はすべて-2
になります。JDBC 2.0仕様によれば、値-2
は、操作は正常終了したが影響を受けた行数は不明であることを示します。
一般的な文のバッチの場合、配列には、各操作で影響を受けた行数を示す実際の更新件数を格納します。標準バッチのOracle実装では、一般的な文の場合のみ、実際の更新件数がわかります。
コール可能な文のバッチの場合、サーバーは、各操作で影響を受けた行数に関係なく、更新件数として常に値1
を戻します。
コードの側では、バッチの正常な処理に対して、配列要素に-2
、1
または実際の更新件数のいずれが設定されても処理できるように準備しておく必要があります。正常なバッチ処理では、配列にはすべて-2
が含まれるか、すべて1が含まれるか、またはすべて正の整数が含まれます。
例23-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文バッチの場合、どの操作が失敗したかはわかりません。配列には、バッチ内の各操作に対応して1つの要素があり、各要素は-3
の値を持っています。JDBC 2.0仕様では、-3
という値は、操作が正常に完了しなかったことを示します。この場合、実際に失敗したのはおそらくたった1つの操作ですが、それがどの操作であったかJDBCドライバにはわからないため、バッチ処理されたすべての操作に障害のラベル付けが行われます。
この場合、必ずROLLBACK
操作を実行する必要があります。
一般的な文のバッチまたはコール可能文のバッチの場合、更新件数配列は、エラーの時点までの実際の更新件数を格納する、部分的な配列になります。標準バッチ更新の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", "SCOTT"); info.setProperty("passwd", "TIGER"); // 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 に設定されます。 |
例23-3は、早期バッチ・フラッシュを示しています。
例23-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(11.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リリースg (11.2) JDBCドライバの場合、通常のフェッチ操作中にロケータとともにLOB
データの始めをプリフェッチするのと同様に、LOB
の長さやチャンク・サイズなどの使用頻度の高いメタデータをプリフェッチすることで、ラウンドトリップの回数を減らします。LOB
が小さい場合、ただ1回のラウンドトリップでデータをすべてプリフェッチできる場合があります。select
、パース、実行およびフェッチが1回のラウンドトリップで行われるため、パフォーマンスが大幅に向上します。LOB
がプリフェッチ・サイズの5倍より大きい場合、チャンク・サイズを取得するためのラウンドトリップが必要ないだけなので、パフォーマンスはそれほど向上しません。
defaultLobPrefetchSize接続プロパティ
Oracle Database 11gリリース2(11.2)から、接続のためのデフォルトLOB
プリフェッチ・サイズを設定するために使用できる新しい接続プロパティoracle.jdbc.defaultLobPrefetchSize
が導入されました。この接続プロパティは、定数OracleConnection.CONNECTION_PROPERTY_DEFAULT_LOB_PREFETCH_SIZE
として定義されています。このプロパティの値が、現行接続のデフォルトのLOB
プリフェッチ・サイズとして使用されます。この接続プロパティのデフォルト値は-1です。文レベルでこのデフォルト値を変更する場合は、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(11.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
メソッドを使用して問合せを実行します。
注意: 必要な結果セットのすべての列に対してデータ型を指定する必要はありません。 |
例23-4は、この機能の使用方法を示しています。この例では、oracle.jdbc.*
インタフェースがインポート済であることを前提にしています。
例23-4 列型の定義
OracleDataSource ods = new OracleDataSource(); ods.setURL("jdbc:oracle:thin:@localhost:1502:orcl"); ods.setUser("scott"); ods.setPassword("tiger"); 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
型コードに定義できます。
表23-1は、defineColumnType
メソッドで使用できる有効な列定義引数のリストです。
表23-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
にキャストする必要があります。
例23-5は、TABLE_REMARKS
レポートを使用可能にする方法を示しています。
例23-5 TABLE_REMARKSレポート
conn
は標準Connection
オブジェクト名を表しています。次の文を使用するとTABLE_REMARKS
レポートが使用可能になります。
( (oracle.jdbc.OracleConnection)conn ).setRemarksReporting(true);
getColumnsの考慮事項
シノニムが指定されている場合、デフォルトでは、getColumns
メソッドは列の情報を取り出しません。シノニムが指定されている場合に情報の取出しを可能にするには、次のように、接続でsetIncludeSynonyms
メソッドをコールする必要があります。
( (oracle.jdbc.driver.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データベース内のオブジェクトはすべてはスキーマを持つ必要があるため、スキーマを持たないオブジェクトの情報を戻すことには意味がありません。このため、構成"
"
(空の文字列)は、入力時に、現在のスキーマ、つまり現在接続しているスキーマを持つオブジェクトを意味するものと解釈されます。catalog
パラメータの動作との整合性を取るため、null
は選択基準からスキーマを除外するものと解釈されます。つまり、%
を渡すのと同じ効果です。SQLワイルドカードを使用したパターンとしても使用できます。
procedureNamePattern
およびcolumnNamePattern
プロシージャと引数はすべて名前を持っているため、空の文字列(" ")はいずれのパラメータについても意味を持ちません。このため、構成"
"
を使用すると例外が発生します。他のパラメータの動作との整合性を取るため、null
はパーセント記号(%
)を渡すのと同じ効果を持ちます。