この章では、Oracle Java Database Connectivity(JDBC)ドライバで様々なデータ型のJavaストリームを処理する方法を説明します。データ・ストリームを使用することにより、最大2GBのLONG型列データを読み取れます。
この章の内容は次のとおりです。
Oracle JDBCドライバは、サーバーとクライアント間の双方向のデータ・ストリーム操作をサポートします。ドライバは、すべてのストリーム変換、つまりバイナリ、ASCIIおよびUnicodeをサポートします。次に、ストリームの各タイプを簡単に説明します。
バイナリ
データのRAWバイトのために使用されます。getBinaryStreamメソッドに対応します。
ASCII
ISO-Latin-1エンコーディングでASCIIバイトのために使用されます。getAsciiStreamメソッドに対応します。
Unicode
UTF-16エンコーディングでUnicodeバイトのために使用されます。getUnicodeStreamメソッドに対応します。
getBinaryStream、getAsciiStreamおよびgetUnicodeStreamの各メソッドは、InputStreamオブジェクト内のデータ・バイトを戻します。
|
注意: Oracle Database 12cリリース1 (12.1)以降、CONNECTION_PROPERTY_STREAM_CHUNK_SIZEは非推奨になり、ドライバは内部でこれを使用してストリーム・チャンク・サイズを設定しません。 |
問合せがLONGまたはLONG RAW列を1つ以上選択すると、JDBCドライバは、ストリーム・モードでこれらの列をクライアントに転送します。ストリーム・モードでは、JDBCドライバは、必要になるまでLONGまたはLONG RAW列のためにネットワークから列データを読み取りません。コードでgetXXXメソッドを呼び出して列データを読み取るまで、列データはネットワーク通信チャネルに残っています。コールの後でも、列データは、getXXXコールから戻り値にデータを移入する場合にのみ読み取られます。列データが通信チャネルに残っているため、ストリーム・モードは他のすべての接続の使用を妨げます。列データの読取り以外で接続を使用すると、チャネルから列データが廃棄されます。ストリーム・モードはメモリーを効率よく使用し、ネットワークのラウンドトリップを最小限に抑えますが、他の多くのデータベース操作の妨げになります。
|
注意: LONGおよびLONG RAW列は使用しないことをお薦めします。かわりにLOBを使用してください。 |
LONG列のデータにアクセスするには、この列をJava InputStreamオブジェクトとして取得し、InputStreamオブジェクトのreadメソッドを使用します。また、データをStringまたはbyte配列として取得することもできます。この場合、ドライバによってストリーム処理が実行されます。
3種類のストリームのうち、任意のものを使用して、LONGまたはLONG RAWデータを取得できます。ドライバは、データベースおよびドライバのキャラクタ・セットに応じて、変換を行います。
|
注意: LONG列を使用した表を作成しないでください。かわりに、ラージ・オブジェクト(LOB)列CLOB、NCLOBおよびBLOBを使用してください。LONG列は、下位互換性のみのためにサポートされています。また、既存のLONG列をLOB列に変換することをお薦めします。LOB列に対する制約は、LONG列に比べてはるかに少なくなります。 |
この項の内容は次のとおりです。
getBinaryStreamへのコールにより、RAWデータが戻されます。getAsciiStreamへのコールにより、RAWデータが16進データに変換され、ASCIIコードが戻されます。getUnicodeStreamへのコールにより、RAWデータが16進データに変換され、Unicode文字が戻されます。
getAsciiStreamを使用してLONGデータを取得した場合、ドライバは、データベースに格納されている基礎となるデータにUS7ASCIIまたはWE8ISO8859P1キャラクタ・セットが使用されているものとみなします。実際にそうである場合、ドライバはASCII文字に対応するバイトを戻します。データベースでUS7ASCIIとWE8ISO8859P1のいずれのキャラクタ・セットも使用されていない場合、getAsciiStreamへのコールを実行すると、意味のないコードが戻されます。
getUnicodeStreamを使用してLONGデータを取得すると、Unicode文字のストリームがUTF-16エンコーディングで戻されます。これは、Oracleがサポートする、データベースの基礎となるキャラクタ・セットすべてに当てはまります。
getBinaryStreamを使用してLONGデータを取得する場合、次の2つのケースが考えられます。
ドライバがJDBC OCIで、クライアントのキャラクタ・セットがUS7ASCIIまたはWE8ISO8859P1でない場合、getBinaryStreamをコールするとUTF-8が戻されます。クライアントのキャラクタ・セットがUS7ASCIIまたはWE8ISO8859P1に設定されている場合、US7ASCIIバイト・ストリームが戻されます。
ドライバがJDBC Thinで、データベースのキャラクタ・セットがUS7ASCIIまたはWE8ISO8859P1でない場合、getBinaryStreamをコールするとUTF-8が戻されます。サーバー側キャラクタ・セットがUS7ASCIIまたはWE8ISO8859P1に設定されている場合、US7ASCIIバイト・ストリームが戻されます。
表12-1に、LONGおよびLONG RAWデータ変換の概要をストリーム・タイプ別に示します。
表12-1 LONGおよびLONG RAWデータの変換
| データ型 | BinaryStream | AsciiStream | UnicodeStream |
|---|---|---|---|
|
LONG |
Unicode |
ISO-Latin-1( |
Unicode |
|
LONG RAW |
データの変換なし。 |
16進バイトのASCII表現。 |
16進バイトのUnicode表現。 |
getXXXStreamメソッドには、データの増分的取得を可能にする機能があります。一方、getBytesは、すべてのデータを一度のコールでフェッチします。この項には、バイナリ・データ・ストリームの取得方法を示す2つの例が含まれています。LONG RAWデータを取得するのに、最初のバージョンではgetBinaryStreamメソッドを使用し、2番目のバージョンではgetBytesメソッドを使用します。
getBinaryStreamを使用したLONG RAWデータ列の取得
この例では、ローカル・ファイルシステム上のファイルにLONG RAW列の内容を書き込みます。この場合、ドライバはデータを増分的にフェッチします。
次のコードは、名前LESLIEに関連付けられたLONG RAWデータ列を格納する表を作成します。
-- SQL code:
create table streamexample (NAME varchar2 (256), GIFDATA long raw);
insert into streamexample values ('LESLIE', '00010203040506070809');
次のJavaコードの一部は、LONG RAW列のデータを、leslie.gifというファイルに書き込みます。
ResultSet rset = stmt.executeQuery
("select GIFDATA from streamexample where NAME='LESLIE'");
// get first row
if (rset.next())
{
// Get the GIF data as a stream from Oracle to the client
InputStream gif_data = rset.getBinaryStream (1);
try
{
FileOutputStream file = null;
file = new FileOutputStream ("leslie.gif");
int chunk;
while ((chunk = gif_data.read()) != -1)
file.write(chunk);
}
catch (Exception e)
{
String err = e.toString();
System.out.println(err);
}
finally
{
if file != null()
file.close();
}
}
この例では、getBinaryStreamへのコールにより戻されるInputStreamオブジェクトは、データベース接続から直接データを読み取ります。
getBytesを使用したLONG RAWデータ列の取得
この例では、getBinaryStreamのかわりにgetBytesを使用して、GIFDATA列の内容を取得します。この場合、ドライバは一度のコールですべてのデータをフェッチして、バイト配列に格納します。コードは次のようになります。
ResultSet rset2 = stmt.executeQuery
("select GIFDATA from streamexample where NAME='LESLIE'");
// get first row
if (rset2.next())
{
// Get the GIF data as a stream from Oracle to the client
byte[] bytes = rset2.getBytes(1);
try
{
FileOutputStream file = null;
file = new FileOutputStream ("leslie2.gif");
file.write(bytes);
}
catch (Exception e)
{
String err = e.toString();
System.out.println(err);
}
finally
{
if file != null()
file.close();
}
}
LONG RAW列では最大2GBのデータを格納できるため、getBytesの使用例では、getBinaryStreamの使用例に比べて多量のメモリーを使用できます。LONGまたはLONG RAW列のデータの最大サイズが不明な場合は、ストリームを使用してください。
JDBCドライバは、任意のLONGおよびLONG RAW列を自動的にストリーム処理します。ただし、データ・ストリームを避ける状況が生じる場合もあります。たとえば、LONG列のサイズが非常に小さい場合、データが増分的ではなく、一度のコールで戻される方が望ましい場合があります。
ストリームを回避するには、defineColumnTypeメソッドを使用してLONG列の型を再定義します。たとえば、LONGまたはLONG RAW列をVARCHAR型またはVARBINARY型として再定義すると、ドライバがデータを自動的にストリーム処理することはなくなります。
defineColumnTypeを使用して列の型を再定義する場合、問合せの中で列の型を宣言する必要があります。列の型を宣言しないと、executeQueryが失敗します。また、Statementオブジェクトをoracle.jdbc.OracleStatementオブジェクトにキャストする必要があります。
さらに、defineColumnTypeを使用すると、問合せの実行時にOCIドライバがデータベースへラウンドトリップを行わずにすみます。defineColumnTypeを使用しない場合、これらのJDBCドライバは列タイプのデータ型を要求する必要があります。JDBC Thinドライバでは、ラウンドトリップの回数が常に最小であるため、defineColumnTypeを使用する利点はありません。
前の項の例を使用して、StatementオブジェクトstmtはOracleStatementへキャストされ、LONG RAWデータを含む列はVARBINARAY型として再定義されます。このデータはストリーム化されません。かわりに、バイト配列で戻されます。コードは次のようになります。
//cast the statement stmt to an OracleStatement
oracle.jdbc.OracleStatement ostmt =
(oracle.jdbc.OracleStatement)stmt;
//redefine the LONG column at index position 1 to VARBINARY
ostmt.defineColumnType(1, Types.VARBINARY);
// Do a query to get the images named 'LESLIE'
ResultSet rset = ostmt.executeQuery
("select GIFDATA from streamexample where NAME='LESLIE'");
// The data is not streamed here
rset.next();
byte [] bytes = rset.getBytes(1);
defineColumnType Oracle拡張機能を使用してCHAR、VARCHARまたはRAW列をLONGVARCHARまたはLONGVARBINARYとして再定義する場合は、列をストリームとして取得できます。このプログラムは、列が実際にLONGまたはLONG RAW型であるかのように動作します。通常これらの列は短いため、これが問題になることはほとんどありません。
CHAR、VARCHARまたはRAW列を、列タイプを再定義することなくデータ・ストリームとして取得しようとすると、JDBCドライバはJava InputStreamを戻しますが、実際のストリームは発生しません。これらのデータ型の場合、JDBCドライバは、executeQueryメソッドまたはnextメソッドへのコール中に、データをインメモリーのバッファに完全にフェッチします。getXXXStreamエントリ・ポイントは、このバッファからデータを読み取るストリームを戻します。
ラージ・オブジェクト(LOB)という用語は、データベース表に直接格納するには大きすぎるデータ項目を指します。一方、ロケータはデータベース表に格納されて、実際のデータの場所を指します。外部ファイルも同様に管理されます。JDBCドライバは、ストリームの使用によって次の型をサポートできます。
バイナリ・ラージ・オブジェクト(BLOB)
非構造化バイナリ・データ用
キャラクタ・ラージ・オブジェクト(CLOB)
文字データ用
各国語キャラクタ・ラージ・オブジェクト(NCLOB)
各国語キャラクタ・データ用
バイナリ・ファイル(BFILE)
外部ファイル用
LOBとBFILEは、この章で説明した他のストリーム・データとは動作が異なります。表に実際のデータが格納されるかわりに、ロケータが格納されます。ストリームとしてのデータの読取りおよび書込みなど、実際のデータを操作するには、このロケータを使用します。ストリーム処理するときでも、一部のデータ(サイズで定義済)のみがネットワーク間を移動します。これに対して、LONGまたはLONG RAWをストリーム処理するときは、すべてのデータがネットワーク間を移動します。
BLOB、CLOBおよびNCLOBのストリーム
問合せにより、1つ以上のBLOB、CLOBまたはNCLOB列をフェッチすると、JDBCドライバはデータをクライアントに転送します。このデータはストリームとしてアクセスできます。JDBCより取得したBLOB、CLOBまたはNCLOBデータを操作する場合、Oracleの拡張機能クラスoracle.sql.BLOB、oracle.sql.CLOBおよびoracle.sql.NCLOB内のメソッドを使用します。これらのクラスは、BLOB、CLOBまたはNCLOBから入力ストリーム内への読取り、出力ストリームからBLOB、CLOBまたはNCLOB内への書込み、BLOB、CLOBまたはNCLOBの長さの決定およびBLOB、CLOBまたはNCLOBのクローズなど、固有の機能を提供します。
|
注意: Oracle Database 12cリリース1 (12.1)以降、oracle.sqlパッケージの具象クラスは非推奨となり、oracle.jdbcパッケージのインタフェースに置き換えられています。標準互換性には(可能であれば)java.sqlパッケージの使用可能なメソッドを使用し、Oracle固有の拡張機能にはoracle.jdbcパッケージの使用可能なメソッドを使用することをお薦めします。これらのインタフェースの詳細は、My Oracle Supportノート1364193.1を参照してください。 |
BFILEのストリーム
外部ファイルであるBFILEは、データベースの外部にあるファイルへのロケータを格納するために使用します。ファイルは、データ・サーバーのファイルシステム上のいずれかの場所に格納されます。ロケータは、ファイルが実際に格納された場所を指します。
問合せにより1つ以上のBFILE列をフェッチすると、JDBCドライバは必要に応じてファイルをクライアントに転送します。データにはストリームとしてアクセスできます。JDBCからBFILEデータを操作するには、Oracle拡張クラスoracle.sql.BFILEのメソッドを使用します。このクラスは、BFILEから入力ストリームへの読取り、出力ストリームからBFILEへの書込み、BFILEの長さの判定、BFILEのクローズなど、特定の機能を提供します。
1つの問合せが複数の列をフェッチする場合、その列の1つにデータ・ストリームが含まれていると、ストリーム列に後続する列の内容はストリームが読み取られるまで利用できず、後続する列が読み取られると、ストリーム列はそれ以降利用できません。ストリーミング列の先にある列を読み取ろうとすると、ストリーミング列はクローズされます。
複数列によるストリーミングの例
次のコードについて検討します。
ResultSet rset = stmt.executeQuery
("select DATECOL, LONGCOL, NUMBERCOL from TABLE");
while rset.next()
{
//get the date data
java.sql.Date date = rset.getDate(1);
// get the streaming data
InputStream is = rset.getAsciiStream(2);
// Open a file to store the gif data
FileOutputStream file = new FileOutputStream ("ascii.dat");
// Loop, reading from the ascii stream and
// write to the file
int chunk;
while ((chunk = is.read ()) != -1)
file.write(chunk);
// Close the file
file.close();
//get the number column data
int n = rset.getInt(3);
}
各行の受信データは、次の形式になります。
<a date><the characters of the long column><a number>
結果セットの各行を処理するときには、数値列を読み取る前にストリーム列の処理を完了する必要があります。
ストリーム・データ列のバイパス
ストリーム・データを含む列の読取りを回避することが望ましい場合があります。このようなデータの読取りを回避するには、ストリーム・オブジェクトのcloseメソッドをコールします。このメソッドを使用すると、ストリーム・データが廃棄され、ドライバが、ストリーム・データを含んだ列以降の非ストリーム・データを含んだ列すべてからデータを継続して読み取ることが可能になります。意図的にストリームを廃棄する場合でも、SELECT文で指定した順序で列を取り出すことは、よいプログラミング手法です。
次の例では、LONG列のストリーム・データを廃棄し、DATEおよびNUMBER列のデータのみをリカバリします。
ResultSet rset = stmt.executeQuery
("select DATECOL, LONGCOL, NUMBERCOL from TABLE");
while rset.next()
{
//get the date
java.sql.Date date = rset.getDate(1);
// access the stream data and discard it with close()
InputStream is = rset.getAsciiStream(2);
is.close();
// get the number column data
int n = rset.getInt(3);
}
ストリームから取得したデータは、closeメソッドをコールすることによりいつでも廃棄できます。不要になったストリームはクローズするのが、望ましいプログラミング手法です。たとえば、次のようになります。
... InputStream is = rset.getAsciiStream(2); is.close();
|
注意: ストリームのクローズは、LONGまたはLONG RAW列のパフォーマンスにはほとんど影響しません。データはすべて、そのままネットワーク経由で移動し、ドライバはネットワークからビットを読み取ることが必要です。 |
この項では、ストリームの使用に関して、次に示すいくつかの注意事項を説明します。
この項では、誤ってストリーム・データを廃棄または喪失することがないよう、事前に注意する必要がある点を説明します。現行ストリームの読取り以外の、データベースと通信する任意のJDBC操作を実行すると、ドライバは自動的にストリーム・データを廃棄します。2つの共通する注意事項について説明します。
ストリーム・データはアクセス後に使用します。
データ・ストリームを含む列からデータを取得するには、列のフェッチのみでは十分ではありません。列の内容をただちに処理する必要があります。そうしないと、次の列のフェッチ時にその内容は廃棄されます。
SELECT文で指定した順序でストリーム列をコールします。
問合せを使用して複数列をフェッチすると、データベースは各行を、列を表すバイト・セットとして、SELECTで指定された順序で送信します。列の1つにストリーム・データが含まれる場合は、データベースは、次の列を処理する前にデータ・ストリーム全体を送信します。
SELECT文で指定した順序を使用せずにデータへアクセスする場合は、ストリーム・データを失う可能性があります。つまり、ストリーム・データ列をバイパスして次の列のデータにアクセスすると、ストリーム・データは失われます。たとえば、ストリーム・データ列からデータを読み取る前にNUMBER列のデータにアクセスしようとすると、JDBCドライバは自動的に、まずストリーミング・データを読み取り、続いて廃棄します。LONG列に大量のデータが格納されている場合、これは非常に非効率的です。
LONG列に後でアクセスしようとしても、データは使用できず、ドライバは「ストリームがクローズされています。」エラーを戻します。
次の例では、2番目の点について例証します。
ResultSet rset = stmt.executeQuery
("select DATECOL, LONGCOL, NUMBERCOL from TABLE");
while rset.next()
{
int n = rset.getInt(3); // This discards the streaming data
InputStream is = rset.getAsciiStream(2);
// Raises an error: stream closed.
}
ストリームを取得して、それを使用する前にNUMBER列を取得した場合、ストリーム列はやはり自動的にクローズされます。
ResultSet rset = stmt.executeQuery
("select DATECOL, LONGCOL, NUMBERCOL from TABLE");
while rset.next()
{
InputStream is = rset.getAsciiStream(2); // Get the stream
int n = rset.getInt(3);
// Discards streaming data and closes the stream
}
int c = is.read(); // c is -1: no more characters to read-stream closed
Oracle Database 12c以降、setBytesメソッドおよびsetStringメソッドで使用されるデータ・サイズの上限は大幅に増加されました。どのJava byte配列もsetBytesに渡すことができ、どのJava StringもsetStringに渡すことができます。データのサイズ、SQL文またはPL/SQL文および使用するドライバによって、JDBCドライバは自動的に、setBinaryStreamかsetCharacterStreamの使用、またはsetBytesForBlobかsetStringForClobの使用に切り替わります。
以前のバージョンのOracle Database、およびサーバー側内部ドライバでは一部、制限があります。