この章では、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
オブジェクト内のデータ・バイトを戻します。
問合せが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
バイト・ストリームが戻されます。
注意: LONG またはLONG RAW 列をストリームとして受信するには、データベースから取り出す列の順序に注意する必要があります。 |
表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表現。 |
get
XXX
Stream
メソッドには、データの増分的取得を可能にする機能があります。一方、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とKPRBの各ドライバがデータベースへ往復せずにすみます。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
メソッドへのコール中に、データをメモリー内のバッファに完全にフェッチします。get
XXX
Stream
エントリ・ポイントは、このバッファからデータを読み取るストリームを戻します。
ラージ・オブジェクト(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
のクローズなど、固有の機能を提供します。
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
メソッドをコールすることによりいつでも廃棄できます。不要になったストリームはクローズするのが、望ましいプログラミング手法です。
注意: ストリームのクローズは、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 10g以降、setBytes
メソッドおよびsetString
メソッドで使用されるデータ・サイズの上限は大幅に増加されました。どのJava byte
配列もsetBytes
に渡すことができ、どのJava String
もsetString
に渡すことができます。データのサイズ、SQL文またはPL/SQL文および使用するドライバによって、JDBCドライバは自動的に、setBinaryStream
かsetCharacterStream
の使用、またはsetBytesForBlob
かsetStringForClob
の使用に切り替わります。
以前のバージョンのOracle Database、およびサーバー側内部ドライバでは一部、制限があります。