21 パフォーマンス拡張機能

この章では、Java Database Connectivity(JDBC)標準に対するOracleパフォーマンス拡張機能について説明します。

この章の構成は、次のとおりです。

ノート:

Oracleバッチ更新は、Oracle Database 12c リリース1 (12.1)で非推奨となりました。Oracle Database 12cリリース2 (12.2)以降、Oracleバッチ更新はオペレーション・コードなし(no-op)になりました。つまり、Oracle Database 12cリリース2 (12.2)のJDBCドライバを使用してアプリケーションでOracleバッチ更新を実装すると、指定したバッチ・サイズが設定されず、バッチ・サイズが1になるということです。バッチがこの設定の場合、アプリケーションは一度に1行ずつを処理します。Oracle Database 12cリリース2 (12.2)のJDBCドライバを使用する場合は、標準のJDBCバッチを使用することを強くお薦めします。

21.1 バッチ更新

この項の内容は次のとおりです。

21.1.1 バッチ更新の概要

複数のUPDATE文、DELETE文またはINSERT文を単一のバッチにグループ化して、バッチ全体を一度にデータベースに送信して処理することによって、データベースへのラウンドトリップの回数を減らし、それによってアプリケーションのパフォーマンスを向上させることができます。これは、バッチ更新(update batching)と呼ばれます。これは、特にプリペアド文で、同じ文をバインド変数を変えて繰り返し使用する場合に効果的です。

ノート:

  • JDBC 2.0仕様では、これをバッチ更新(batch updates)と呼びます。

  • JDBC 2.0標準に準拠するために、標準バッチ更新のOracle実装では、プリペアド文と同じように、OUTパラメータなしのコール可能文および一般的な文もサポートされています。標準バッチ更新は、Oracle JDBCアプリケーションに簡単に移行できます。ただし、標準バッチ更新のOracle実装では、一般的な文およびコール可能文の実際のバッチ処理は実装されていないため、パフォーマンスの向上が確認されるのは、PreparedStatementオブジェクトの場合のみです。

21.1.2 標準バッチ更新

JDBC標準バッチ更新は、addBatchメソッドを使用して明示的に文をバッチに追加し、executeBatchメソッドを使用して明示的にバッチを処理します。

ノート:

バッチ更新を使用する場合、自動コミット・モードは無効化します。バッチの処理中にエラーが発生した場合、エラーの前に正常に実行された操作をコミットするかロールバックするかを選択できます。

21.1.2.1 標準バッチ処理のOracle実装の制限事項

この項では、標準バッチ更新のOracle実装に関する、制限事項と実装の詳細を説明します。

Oracle JDBCアプリケーションでバッチ更新を使用すると、バインド変数の設定を変えてプリペアド文を繰り返し処理できます。

標準バッチ更新のOracle実装では、一般的な文およびコール可能文の実際のバッチ処理は実装されていません。Oracle JDBCはStatementおよびCallableStatementオブジェクトに対する標準バッチ処理の使用をサポートしますが、パフォーマンスは向上しません。

21.1.2.2 バッチに対する操作の追加について

いかなる文オブジェクトも、最初に作成されたとき、その文バッチは空です。文バッチに操作を追加するには、標準の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つの操作が入っています。

プリペアド文の場合、バッチ更新は、異なる組合せのバインド・パラメータを使用して、同じ文を複数回実行するために使用されます。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つのバッチは単一のプリペアド文オブジェクトに関連付けられるので、バッチ処理できるのは、この例のような単一のプリペアド文の繰返し実行のみです。

21.1.2.3 バッチの処理について

現在の操作バッチを処理するには、文オブジェクトのexecuteBatchメソッドを使用します。このメソッドは標準のStatementインタフェースで指定されます。これは標準PreparedStatementインタフェースとCallableStatementインタフェースで拡張されています。

ノート:

addBatchメソッドを数回コールして非常に多くの操作をバッチに追加して、非常に大きなバッチ(100,000行以上のバッチなど)を作成した場合、バッチでexecuteBatchメソッドをコール中に、メモリーに関する深刻なパフォーマンスの問題が発生することがあります。このような問題を回避するために、JDBCドライバは透過的に大きなバッチを小さな内部バッチに分割し、各内部バッチに対してサーバーへのラウンドトリップを行います。各ラウンドトリップのオーバーヘッドのために、アプリケーションは若干遅くなりますが、メモリーはかなり最適化されます。ただし、各バインド行のサイズが非常に大きい場合(1MBを超える場合など)、このプロセスはパフォーマンス全体に悪影響を与えることがあります。これは、メモリーについて得られたパフォーマンスが時間について失われたパフォーマンスよりも少ないためです。

次の例では、前の例で示したプリペアド文の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();
...
21.1.2.4 配列DMLの反復ごとの行数

Oracle Database 12cリリース1 (12.1)以降、executeBatchメソッドは改良され、バッチのレコード数と同じサイズのint配列を戻します。戻り配列の各項目は、バッチの対応するレコードの影響を受けたデータベース表の行数です。たとえば、バッチ・サイズが5の場合、executeBatchメソッドはサイズ5の配列を戻します。バッチの実行中にエラーが発生した場合、executeBatchメソッドは値を戻すことができず、かわりにBatchUpdateExceptionをスローします。この場合、例外自体がサイズnのint配列をそのデータとして保持します。nは、成功したレコード実行の数です。たとえば、バッチのサイズが5で、4番目のレコードでエラーが発生した場合、BatchUpdateExceptionはサイズ3の配列(3レコードは正常に実行)を保持し、配列内の各項目は各レコードの影響を受けた行数を表します。

21.1.2.5 標準バッチ処理のOracle実装による変更のコミットについて

バッチを処理した後、変更をコミットする必要があります。自動コミットは使用禁止(推奨)にしてあるものとします。

commitをコールすると、処理済の文バッチの場合、バッチ処理された操作とされなかった操作がコミットされますが、標準バッチのOracle実装では、処理の保留文バッチには影響がありません。

21.1.2.6 バッチのクリアについて

現在の操作バッチを処理しないで消去するには、文オブジェクトのclearBatchメソッドを使用します。このメソッドは標準のStatementインタフェースで指定されます。これは標準PreparedStatementインタフェースとCallableStatementインタフェースで拡張されています。

次の点に注意してください。

  • バッチが処理されるとき、操作は、バッチに入れられた順に実行されます。

  • addBatchをコールした後、executeUpdateをコールする前に、executeBatchまたはclearBatchをコールする必要があります。コールしないと、SQL例外が発生します。

  • clearBatchまたはexecuteBatchをコールすると、文バッチは空にリセットされます。

  • 接続がROLLBACK要求を受信すると、文のバッチが空にリセットされません。リセットするには、clearBatchを明示的にコールする必要があります。

  • ロールバック後のclearBatchメソッドのコールは、すべてのリリースで有効です。

  • 文オブジェクトの現行結果セットがある場合、この結果セットはexecuteBatchコールによってクローズされます。

  • clearBatchメソッドの戻り値はありません。

次の例では、前の例で示したプリペアド文の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();
   ...
}
21.1.2.7 標準バッチ処理のOracle実装の更新件数

文バッチが正常に処理された場合、文のexecuteBatchコールから戻される整数配列、つまり、更新件数配列には、常にバッチ操作1つに対して1つの要素が含まれます。標準バッチ更新のOracle実装では、配列要素の値は次のようになります。

  • プリコンパイル文のバッチの場合、配列には、各操作で影響を受けた行数を示す実際の更新件数を格納します。

  • 一般的な文のバッチの場合、配列には、各操作で影響を受けた行数を示す実際の更新件数を格納します。標準バッチのOracle実装では、一般的な文の場合のみ、実際の更新件数がわかります。

  • コール可能文のバッチの場合、配列には、各操作で影響を受けた行数を示す実際の更新件数を格納します。

コードの側では、バッチの正常な処理に対して、配列要素に-21または実際の更新件数のいずれが設定されても処理できるように準備しておく必要があります。正常なバッチ処理では、配列にはすべて-2が含まれるか、すべて1が含まれるか、またはすべて正の整数が含まれます。

例21-1は、標準バッチ更新の使用方法を示しています。

例21-1 標準バッチ更新

この例は、前の項のサンプル・コードを組み合せたもので、次のステップを行います。

  1. 自動コミット・モードの無効化。どちらかのバッチ更新モデルを使用する場合、無効にする必要があります。

  2. プリペアド文オブジェクトの作成。

  3. プリペアド文オブジェクトに関連付けられたバッチへの操作の追加。

  4. バッチの処理

  5. バッチの操作のコミット。

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();
...

更新件数配列を処理して、バッチが正常に処理されたかどうかを判断できます。

21.1.2.8 標準バッチ処理のOracle実装におけるエラー処理

executeBatchがコールされたとき、バッチ処理された操作のうち1つでも失敗したり、結果セットを戻そうとしたりすると、処理は停止し、java.sql.BatchUpdateExceptionが生成されます。

バッチ例外の後、BatchUpdateExceptionオブジェクトのgetUpdateCountsメソッドを使用して、更新件数配列を取り出せます。このメソッドからは、executeBatchメソッドと同じように、更新件数のint配列が戻されます。標準バッチ更新のOracle実装では、バッチが処理された後の更新件数配列の内容は次のようになります。

  • プリペアド文バッチにおいて、バッチの実行中にエラーが発生した場合、executeBatchメソッドは値を戻すことができず、かわりにBatchUpdateExceptionをスローします。この場合、例外自体がサイズnのint配列をそのデータとして保持します。nは、成功したレコード実行の数です。たとえば、バッチのサイズが5で、4番目のレコードでエラーが発生した場合、BatchUpdateExceptionはサイズ3の配列(3レコードは正常に実行)を保持し、配列内の各項目は各レコードの影響を受けた行数を表します。

  • 一般的な文のバッチまたはコール可能文のバッチの場合、更新件数配列は、エラーの時点までの実際の更新件数を格納する、部分的な配列になります。標準バッチ更新のOracle実装では、Oracle JDBCは一般の文およびコール可能文の本当の意味でのバッチ処理を使用できないので、実際の更新件数がわかります。

    たとえば、バッチに20の操作が含まれているとき、最初の13は正常終了し、14番目で例外が生成された場合、更新件数配列には13の要素が含まれ、正常終了した操作の実際の更新件数が設定されます。

    この場合、正常終了した操作をコミットすることも、ロールバックすることもできます。

コードの側では、例外が発生した場合、バッチの失敗した処理に対して、配列要素に-3または実際の更新件数のどちらが設定されても処理できるように準備しておく必要があります。失敗したバッチ処理では、すべてに-3が含まれる完全な配列か、正の整数が含まれる部分配列が作成されます。

21.1.2.9 バッチ処理される文とバッチ処理されない文の混在について

文オブジェクトに操作の保留バッチがある場合、通常のバッチ処理されない操作の処理を行うために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要求は、バッチ処理されない操作すべてと、処理済バッチの正常な操作すべてに影響を与えますが、保留バッチには影響を与えません。

21.1.3 早期バッチ・フラッシュ

早期バッチ・フラッシュは、キャッシュされたメタデータが変更されると発生します。キャッシュされたメタデータは、次のような様々な理由から変更されることがあります。

  • 最初のバインドが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-2は、早期バッチ・フラッシュを示しています。

例21-2 早期バッチ・フラッシュ

((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();

21.2 その他のOracleパフォーマンス拡張機能

Oracle JDBCドライバでは、バッチ更新に加えて、次の拡張機能がサポートされています。これにより、データベースへのラウンドトリップが減少し、パフォーマンスが向上します。

  • 行のプリフェッチ

    行をプリフェッチすることにより、データがフェッチされるたびに複数行がフェッチされるので、データベースへのラウンドトリップが減少します。余分にフェッチされたデータは、後で使用するためにクライアント側バッファに格納されます。プリフェッチの行数は、目的に応じて設定できます。

  • 列型の指定

    問合せの実行および問合せ結果の取出しを行うときに、標準のJDBCプロトコルで生じる非効率性を回避できます。

  • データベース・メタデータTABLE_REMARKS列の抑止

    コストの高い外部結合操作を回避できます。

Oracleでは、これらのパフォーマンス拡張機能をサポートするために、接続プロパティ・オブジェクトにいくつかの拡張機能を提供します。これらの拡張機能により、remarksReportingフラグと、行プリフェッチとバッチ更新のデフォルト値を設定できます。

この項の内容は次のとおりです。

21.2.1 LOBデータのプリフェッチについて

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プリフェッチ・サイズの値も設定できます。列レベルの値は、接続または文レベルで設定されているどの値よりも優先されます。

ノート:

LOBプリフェッチが接続レベルまたは文レベルで無効でない場合、列レベルでも無効にできません。

21.2.2 Oracle行プリフェッチの制限事項

最大のプリフェッチ量は設定されていません。デフォルト値は10です。問合せから予想される行数および列数に基づいて、値を増減させた方が適している場合もあります。Propertiesオブジェクトを使用して、接続のデフォルト行プリフェッチ値を設定できます。

Statementオブジェクトは、作成されると、関連付けられた接続から行のプリフェッチ設定のデフォルト値を受け取ります。接続の行プリフェッチ設定のデフォルト値を後で変更しても、文の行プリフェッチ設定は変更されません。

結果セットの列のデータ型が、データ・インタフェースから戻されたLONGLONG 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接続プロパティを使用してください。PARSEEXECUTEおよびFETCHが単一のラウンドトリップ内で実行されるからです。

プリフェッチ・サイズのチューニングは、実際のアプリケーションの現実的な負荷に基づき、JVM内でのメモリー管理のチューニングに合せて行う必要があります。

ノート:

  • JDBC 2.0フェッチ・サイズApplication Program Interface(API)とOracle行プリフェッチAPIをアプリケーションで混在させることはできません。どちらも使用できますが、両方は使用できません。

  • Oracle行フェッチ・サイズ値を設定すると、問合せ以外に、結果セットのrefreshRowメソッドによる結果セットの行の明示的な再フェッチ(scroll-sensitive/読取り専用、scroll-sensitive/更新可能およびscroll-insensitive/更新可能結果セットに関係します)行数およびscroll-sensitive結果セットのウィンドウ・サイズ(自動再フェッチの実行頻度に影響します)にも影響を与えることがあります。ただし、Oracle行フェッチ・サイズ値は、フェッチ・サイズの設定によってオーバーライドされます。

21.2.3 列型の定義について

ノート:

Oracle Database 12cリリース1 (12.1)以降、defineColumnTypeメソッドは非推奨です。

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ドライバの使用時に接続プロパティdisableDefineColumnTypetrueに設定することで、defineColumnTypeメソッドを無効にできます。その結果、defineColumnTypeは無視されます。JDBC OCIまたはサーバー側内部ドライバを使用する場合は、この接続プロパティをtrueに設定しないでください。

また、defineColumnTypeを使用して、クライアント側が割り当てるメモリー量を制御することや、可変長データのサイズを制限することもできます。

問合せの列型を定義するには、次のステップを実行します。

  1. 必要に応じて、StatementオブジェクトをOracleStatementOraclePreparedStatementまたはOracleCallableStatementにキャストします。

  2. 必要に応じて、StatementオブジェクトのclearDefinesメソッドを使用して、このStatementオブジェクトの以前の列定義を消去します。

  3. 各列で、StatementオブジェクトのdefineColumnTypeメソッドをコールして次のパラメータを渡します。

    • 列索引(整数)

    • 型コード(整数)

      java.sql.Typesクラスまたはoracle.jdbc.OracleTypesクラスのstatic定数を使用します(Types.INTEGERTypes.FLOATTypes.VARCHAROracleTypes.VARCHAROracleTypes.ROWIDなど)。この2つのクラスで、標準型の型コードは同一です。

    • 型名(文字列)

      構造化オブジェクト、オブジェクト参照および配列の場合は、型名も指定する必要があります。たとえば、EmployeeEmployeeRefEmployeeArrayなどです。

    • 最大フィールド・サイズ(整数)

      オプションで、この列の最大データ長も指定できます。

      構造化オブジェクト、オブジェクト参照または配列の列型を定義する場合、最大フィールド・サイズ・パラメータは指定できません。このパラメータを含めても、無視されます。

    • 使用する形式(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メソッドを使用して問合せを実行します。

ノート:

必要な結果セットのすべての列に対してデータ型を指定する必要はありません。

この機能の使用例を次に示します。この例では、oracle.jdbc.*インタフェースがインポート済であることを前提にしています。

例21-3 列型の定義

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オブジェクトstmtOracleStatementにキャストする必要があります。接続のcreateStatementメソッドは、defineColumnTypeメソッドとclearDefinesを持たず、型がjava.sql.Statementであるオブジェクトを戻します。これらのメソッドは、OracleStatement実装でのみ提供されています。

定義の拡張要素では、JDBC型を使用して目的の型を指定します。使用可能な列型は、列のOracle内部型によって異なります。

すべての列は、本来のJDBC型に定義できます。多くの場合、Types.CHARまたはTypes.VARCHAR型コードに定義できます。

次の表に、defineColumnTypeメソッドで使用できる有効な列定義引数を示します。

表21-1 有効な列型

列内のOracle SQL型 defineColumnTypeで定義できる型

NUMBERVARNUM

BIGINTTINYINTSMALLINTINTEGERFLOATREALDOUBLENUMERICDECIMALCHARVARCHAR

CHARVARCHAR2

CHARVARCHAR

LONG

CHARVARCHARLONGVARCHAR

LONGRAW

LONGVARBINARYVARBINARYBINARY

RAW

VARBINARYBINARY

DATE

DATETIMETIMESTAMPCHARVARCHAR

ROWID

ROWID

BLOB

VARBINARYBINARY

CLOB

LONGCHARVARCHAR

defineColumnTypeを列の元のデータ型で使用することは、常に可能です。

21.2.4 DatabaseMetaData TABLE_REMARKSのレポートについて

データベース・メタデータ・クラスのgetColumnsgetProcedureColumnsgetProceduresおよびgetTablesメソッドを使用してTABLE_REMARKS列をレポートすると、コストの高い外部結合を必要とするため処理が遅くなります。この理由でJDBCドライバは、デフォルトではTABLE_REMARKS列を報告しません

OracleConnectionオブジェクトのsetRemarksReportingメソッドにtrue引数を渡すと、TABLE_REMARKSレポートが使用可能になります。

Java Propertiesオブジェクトを使用して接続を確立した場合、setRemarksReportingをコールするかわりに、remarksReporting Javaプロパティを設定することもできます。

標準java.sql.Connectionオブジェクトを使用している場合、setRemarksReportingを使用するには、オブジェクトをOracleConnectionにキャストする必要があります。

次のコードでは、TABLE_REMARKSレポートを使用可能にする方法を示します。

((oracle.jdbc.OracleConnection)conn ).setRemarksReporting(true);

ここで、connは標準Connectionオブジェクト名を表しています。次の文を使用するとTABLE_REMARKSレポートが使用可能になります。

getColumnsの考慮事項

シノニムが指定されている場合、デフォルトでは、getColumnsメソッドは列の情報を取り出しません。シノニムが指定されている場合に情報の取出しを可能にするには、次のように、接続でsetIncludeSynonymsメソッドをコールする必要があります。

((oracle.jdbc.OracleConnection)conn ).setIncludeSynonyms(true)

これによって、その接続での後続のgetColumnsメソッドのコールすべてにシノニムが含まれます。これは、setRemarksReportingと同じです。あるいは、includeSynonyms接続プロパティを設定できます。これは、remarksReporting接続プロパティと同じです。

ただし、includeSynonymstrueに設定されている場合、シノニムが存在すると、table_name列に戻されるオブジェクト名は、シノニム名であることに留意してください。これは、表名をgetColumnsに渡す場合でも同じです。

getProceduresおよびgetProcedureColumnsメソッドの考慮事項

JDBCバージョン1.1および1.2では、getProceduresおよびgetProcedureColumnsメソッドは、catalogschemaPatterncolumnNamePatternおよびprocedureNamePatternパラメータを同じ方法で処理します。これらのメソッドに関するOracleの定義では、パラメータの処理方法は次のように異なります。

  • catalog

    Oracleには複数カタログはありませんが、複数パッケージはあります。したがって、catalogパラメータは、パッケージ名として扱われます。これは入力(catalogパラメータ)と出力(戻されたResultSetcatalog列)の両方に該当します。入力時に、構成" "(空の文字列)は、プロシージャと引数を、パッケージ(スタンドアロンのオブジェクト)なしで取得します。null値は、選択基準(スタンドアロン・オブジェクトとパッケージ化されたオブジェクトの両方に関する戻し情報)から除外されることを意味します。つまり、パーセント記号(%)を渡すのと同じ効果です。それ以外の場合は、catalogパラメータには、パッケージ名パターンを指定します。必要に応じてSQLワイルドカードを付けられます。

  • schemaPattern

    Oracle Database内のオブジェクトはすべてはスキーマを持つ必要があるため、スキーマを持たないオブジェクトの情報を戻すことには意味がありません。このため、構成" "(空の文字列)は、入力時に、現在のスキーマ、つまり現在接続しているスキーマを持つオブジェクトを意味するものと解釈されます。catalogパラメータの動作との整合性を取るため、nullは選択基準からスキーマを除外するものと解釈されます。つまり、%を渡すのと同じ効果です。SQLワイルドカードを使用したパターンとしても使用できます。

  • procedureNamePatternおよびcolumnNamePattern

    プロシージャと引数はすべて名前を持っているため、空の文字列(" ")はいずれのパラメータについても意味を持ちません。このため、構成" "を使用すると例外が発生します。他のパラメータの動作との整合性を取るため、nullはパーセント記号(%)を渡すのと同じ効果を持ちます。