6 型のサポート

この章では、Oracle SQLJ実装でサポートされているデータ型について説明し、サポートされているSQL型とそれに対応するJava型を示します。さらに、ストリームとOracle拡張型のサポートについても詳しく説明します。SQLJでサポートされるJava型とは、ホスト式で使用できる型のことです。

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

ホスト式での型サポート

ここでは、Oracle SQLJ実装でサポートされている型について概説します。具体的には、Java Database Connectivity(JDBC)2.0の型に関して新規にサポートされた点について説明します。

関連項目:

各Oracle SQL型ごとの正当なJavaマッピングの詳細リストは、『Oracle Database JDBC開発者ガイド』を参照してください。

注意:

SQLJでは、SQL型とJava型との変換が暗黙的に実行されるようになっています。この型変換は、一般的には便利ですが、予想と異なる結果を招く場合もあります。コードが正確かどうかの確認は、変換時の型チェックのみに頼らないでください。

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

サポートされている型の概要

表6-1に、Oracle JDBCドライバを使用した場合にホスト式で使用できるJava型を示します。また、Java型、SQL型(oracle.jdbc.OracleTypesクラスに型コードが定義されている)およびOracle Database 12c リリース2 (12.2)のデータ型との相関関係もこの表からわかります。

注意:

OracleTypesクラスは、各Oracleデータ型の型コード(整数の定数)のみを定義します。標準JDBC型のOracleTypesの値は、標準java.sql.Typesの値と同じです。

Java変数に出力されるSQLデータは、該当するJava型に変換されます。SQLに入力されるJava変数は、該当するOracleデータ型に変換されます。

表6-1 ホスト式でサポートされている型の対応

Java型 Oracle型定義 Oracle SQLデータ型

標準JDBC 1.x型

 

 

boolean

BIT

NUMBER

byte

TINYINT

NUMBER

short

SMALLINT

NUMBER

int

INTEGER

NUMBER

long

BIGINT

NUMBER

float

REAL

NUMBER

double

FLOATDOUBLE

NUMBER

java.lang.String

CHAR VARCHAR LONGVARCHAR

CHAR VARCHAR2 LONG

byte[]

BINARY VARBINARY LONGVARBINARY

RAW RAW LONGRAW

java.sql.Date

DATE

DATE

java.sql.Time

TIME

DATE

java.sql.Timestamp

TIMESTAMP TIMESTAMP

DATE TIMESTAMP

java.math.BigDecimal

NUMERIC DECIMAL

NUMBER NUMBER

標準JDBC 2.0型

 

 

java.sql.Blob

BLOB

BLOB

java.sql.Clob

CLOB

CLOB

java.sql.Struct

STRUCT

オブジェクト型

java.sql.Ref

REF

参照型

java.sql.Array

ARRAY

コレクション型

java.sql.SQLDataを実装したカスタム・オブジェクト・クラス

STRUCT

オブジェクト型

Javaラッパー・クラス

 

 

java.lang.Boolean

BIT

NUMBER

java.lang.Byte

TINYINT

NUMBER

java.lang.Short

SMALLINT

NUMBER

java.lang.Integer

INTEGER

NUMBER

java.lang.Long

BIGINT

NUMBER

java.lang.Float

REAL

NUMBER

java.lang.Double

FLOATDOUBLE

NUMBER

SQLJストリーム・クラス

 

 

sqlj.runtime.BinaryStream

LONGVARBINARY

LONG RAW

sqlj.runtime.CharacterStream

LONGVARCHAR

LONG

sqlj.runtime.AsciiStream(非推奨。CharacterStreamを使用)

LONGVARCHAR

LONG

sqlj.runtime.UnicodeStream(非推奨。CharacterStreamを使用)

LONGVARCHAR

LONG

Oracle拡張型

 

 

oracle.sql.NUMBER

NUMBER

NUMBER

oracle.sql.CHAR

CHAR

CHAR

oracle.sql.RAW

RAW

RAW

oracle.sql.DATE

DATE

DATE

oracle.sql.TIMESTAMP

TIMESTAMP

TIMESTAMP

oracle.sql.TIMESTAMPTZ

TIMESTAMPTZ

TIMESTAMP-WITH- TIMEZONE

oracle.sql.TIMESTAMPLTZ

TIMESTAMPLTZ

TIMESTAMP-WITH- LOCAL-TIMEZONE

oracle.sql.ROWID

ROWID

ROWID

oracle.sql.BLOB

BLOB

BLOB

oracle.sql.CLOB

CLOB

CLOB

oracle.sql.BFILE

BFILE

BFILE

oracle.sql.STRUCT

STRUCT

オブジェクト型

oracle.sql.REF

REF

参照型

oracle.sql.ARRAY

ARRAY

コレクション型

oracle.sql.OPAQUE

OPAQUE

OPAQUE

oracle.sql.ORADataを実装したカスタム・オブジェクト・クラス

STRUCT

オブジェクト型

oracle.sql.ORADataを実装したカスタム参照クラス

REF

参照型

oracle.sql.ORADataを実装したカスタム・コレクション・クラス

ARRAY

コレクション型

OPAQUE型のoracle.sql.ORADataを実装したカスタム・クラス(例: oracle.xdb.XMLType)

OPAQUE

OPAQUE

oracle.sql.ORADataを実装したカスタムJavaクラス(任意のoracle.sql型をラッピングするクラス)

任意

任意

SQLJオブジェクトJava型(SQLDataまたはORADataを実装可能)

JAVA_STRUCT

SQLJオブジェクトSQL型(背後で使用されるJAVA_STRUCT。適切なJavaクラスに自動変換される)

Java型(PL/SQL型用)

 

 

スカラーの索引付き表で、Java数値配列またはStringoracle.sql.CHARまたはoracle.sql.NUMBERの配列表現

該当なし

該当なし

注意: PLSQL_INDEX_TABLE型がありますが、外部的には使用されません。

グローバリゼーション・サポート

 

 

oracle.sql.NCHAR

CHAR

CHAR

oracle.sql.NString

CHAR VARCHAR LONGVARCHAR

CHAR VARCHAR2 LONG

oracle.sql.NCLOB

CLOB

CLOB

oracle.sqlj.runtime.NcharCharacterStream

LONGVARCHAR

LONG

oracle.sqlj.runtime. NcharAsciiStream (非推奨。NcharCharacterStreamを使用)

LONGVARCHAR

LONG

oracle.sqlj.runtime. NcharUnicodeStream (非推奨。NcharCharacterStreamを使用)

LONGVARCHAR

LONG

問合せ結果オブジェクト

 

 

java.sql.ResultSet

CURSOR

CURSOR

SQLJイテレータ・オブジェクト

CURSOR

CURSOR

関連項目:

Oracle型のサポートの詳細は、『Oracle Database JDBC開発者ガイド』を参照してください。

標準機能での型のサポートについては、次に要点を示します。

  • Java char型とCharacter型は、JDBCおよびSQLJではサポートされていません。文字データを表現するときは、かわりにJavaのString型を使用してください。

  • サポートされているjava.sql.Date型と、直接にはサポートされていないjava.util.Date型とを混同しないようにしてください。java.sql.Dateクラス(java.util.Dateのラッパー)を使用すると、JDBCの日付値に関して、SQLのDATEデータを識別したり、JDBCエスケープ構文をサポートする書式設定操作および解析操作を加えたりできるようになります。

  • Oracle Database 12c リリース2 (12.2)では、すべての数値型がNUMBERとして格納されます。表作成時にNUMBERを宣言すると、さらに詳細に精度を指定できますが、Oracle JDBCドライバを介してデータを取得する場合、データの受取りに使用するJava型によっては、この精度が失われることがあります。oracle.sql.NUMBERインスタンスであれば、すべての情報が保持されます。

  • Javaのラッパー・クラス(IntegerFloatなど)を使用すると、SQL文からNULLが戻された場合に役立ちます。基本型(intfloatなど)には、NULL値を格納できないためです。

    関連項目:

    「NULLの処理」

  • ストリームをホスト変数として使用するときは、SQLJストリーム・クラスが必要です。

  • 弱い型指定は、OUTパラメータまたはINOUTパラメータには使用できません。対応するOracle拡張型と同様に、これはStructRefおよびArrayの標準的なJDBC 2.0型に適用されます。

  • 一連の新しいインタフェースは、oracle.jdbcパッケージにあり、oracle.jdbc.driverパッケージに代わり、Oracle9i JDBC実装で最初に追加されました。これらのインタフェースには、ユーザーがOracle JDBCドライバによってOracle固有機能を利用するためのより汎用的な方法が用意されています。特に、中間層のプログラムを作成する場合は、oracle.jdbc Application Program Interface(API)を使用してください。ただし、SQLJプログラマは通常これらのインタフェースを直接使用しません。これらのインタフェースは、SQLJランタイムまたはOracle固有生成コードで透過的に使用されます。

  • 結果セットおよびイテレータのホスト変数に対するSQLJのサポートの詳細は、「ホスト変数としてのイテレータおよび結果セットの使用」および「ストアド・ファンクションの戻り値としてのイテレータおよび結果セットの使用」を参照してください。

次に、Oracleの拡張型について要点を示します。

  • Oracle SQLJ実装でOracleTypesクラスに定義されている値に応じて静的_SQL_TYPECODEパラメータを設定するには、oracle.sql.ORADataを実装するクラスが必要です。場合によっては、これ以外にも、オブジェクト用の_SQL_NAMEやオブジェクト参照用の_SQL_BASETYPEなどのパラメータ設定が必要になることがあります。

  • oracle.sqlクラスはSQLデータのラッパーであり、各Oracleデータ型に対応しています。ARRAYSTRUCTREFBLOBおよびCLOBクラスは、標準JDBC 2.0インタフェースに対応します。

    関連項目:

    これらのクラスとOracle拡張型の詳細は、『Oracle Database JDBC開発者ガイド』を参照してください。

  • カスタムJavaクラスでマッピングの対象となるのは、Oracleオブジェクト(ORADataまたはSQLDataを実装)、参照(ORADataのみを実装)、コレクション(ORADataのみを実装)、OPAQUE型(ORADataのみを実装)またはその他のカスタマイズ処理に使用するSQL型(ORADataのみを実装)です。

  • Oracle SQLJ実装では、文字列をWHERE句のCHAR列値と比較するときに、自動的に空白埋めを考慮する機能があります。この機能がない場合は、データベース列の文字数と一致させるために文字列に対する埋込みが必要です。この機能は、Oracle固有コード生成ではSQLJトランスレータ・オプション、ISO標準コード生成ではOracleカスタマイザ・オプションとして使用できます。

  • 弱い型指定は、OUTパラメータまたはINOUTパラメータには使用できません。Oracle OPAQUE型と同様に、これはSTRUCTREFおよびARRAYのOracle拡張型および対応する標準的なJDBC 2.0型に適用されます。

  • 次に、Oracle拡張機能の使用に必要な項目を示します。

    • Oracle JDBCドライバ

    • 変換時のOracle固有コード生成またはOracleのカスタマイズ

    • アプリケーション実行時のOracle SQLJランタイム

JDBC 2.0でサポートされている型と要件

表6-1に示したとおり、Oracle JDBCおよびSQLJの実装で標準java.sqlパッケージのJDBC 2.0型がサポートされています。ここでは、JDBC 2.0でサポートされている型および関連するOracleの拡張機能について説明します。

表6-2に、Oracle SQLJ実装でサポートされているJDBC 2.0型を示します。このJDBC 2.0型とそれに対応するOracle拡張型については、この表を参照してください。

Oracle拡張型は、以前のリリースから提供されていたもので、引き続き現行のリリースでも利用できます。これらのoracle.sql.*クラスでは、SQLのRAWデータをラップする機能が用意されています。

表6-2 Oracle拡張型とJDBC 2.0型との相関関係

JDBC 2.0型 Oracle拡張型

java.sql.Blob

oracle.sql.BLOB

java.sql.Clob

oracle.sql.CLOB

java.sql.Struct

oracle.sql.STRUCT

java.sql.Ref

oracle.sql.REF

java.sql.Array

oracle.sql.ARRAY

java.sql.SQLData

該当なし

該当なし

oracle.sql.ORAData (_SQL_TYPECODE = OracleTypes.STRUCT)

ORAData機能は、ユーザー定義型のJavaサポートの標準SQLData機能に対するOracle固有の代替機能です。

次に示すJDBC 2.0型は、現在のOracle JDBCやSQLJの実装ではサポートされていません

  • JAVA_OBJECT: SQL列のJava型のインスタンスを表す型。

  • DISTINCT: 基本SQL型と区別して表現や取出しが可能なSQL型。たとえば、SHOESIZE --> NUMBERなど。

注意:

Oracle Database 11g からは、Oracle SQLJ実装でイテレータ列に配列型を使用できるISO SQLJの機能がサポートされています。java.sql.Arrayまたはoracle.sql.ARRAY列を使用するイテレータを宣言できます。たとえば、次のデータベース表が定義されているとします。

CREATE OR REPLACE TYPE arr_type IS VARRAY(20) OF NUMBER;
CREATE TABLE arr_type (arr_col1 arr_type, arr_col2
                       arr_type);

次のように対応するイテレータ型を定義できます。

#sql static iterator MyIter (oracle.sql.ARRAY arr_col1,
                             java.sql.Array arr_col2);

PL/SQLのBOOLEAN型、RECORD型およびTABLE型の使用

Oracle SQLJおよびJDBCの実装では、コール用引数または戻り値として、PL/SQLのBOOLEAN型またはRECORD型をサポートしていません。

TABLE型のサポート

Oracle JDBCドライバでは、PL/SQLのスカラー索引付き表がサポートされています。

Oracle SQLJ実装によって、スカラー索引付き表でのデータのやりとりが簡素化されます。次の配列型がサポートされます。

  • 数値型: int[]long[]float[]double[]short[]java.math.BigDecimal[]oracle.sql.NUMBER[]

  • 文字型: java.lang.String[]oracle.sql.CHAR[]

次に、索引付き表のデータをデータベースに書き込む例を示します。

int[] vals = {1,2,3};
#sql { call procin(:vals) };

次に、索引付き表のデータをデータベースから取り出す例を示します。

oracle.sql.CHAR[] outvals;
#sql { call procout(:OUT outvals/*[111](22)*/) };

このように/*...*/内の[xxx]構文を使用して、取得する出力配列の最大長を指定する必要があります。また、文字などのバインドの場合は、オプションで(xx)構文を指定し、例のように配列要素の最大長をバイト単位で指定できます。

注意:

oracle.sql.Datumクラスは、直接サポートされません。oracle.sql.CHARまたはoracle.sql.NUMBERなどの適切なサブクラスを使用する必要があります。

サポートされない型に対する次善策

サポートされない型の問題を回避するには、ラッパー・プロシージャを作成し、サポートされている型を使用してデータを処理します。たとえば、PL/SQLのブール値を使用するストアド・プロシージャをラッピングするには、JDBCから文字または数値を受け取って、これを元のプロシージャにBOOLEANとして渡すストアド・プロシージャを作成するか、出力パラメータの場合は元のプロシージャからBOOLEAN引数を受け取って、これをCHARまたはNUMBERとしてJDBCに渡すストアド・プロシージャを作成できます。同様に、PL/SQLレコードを使用するストアド・プロシージャをラッピングするには、レコードをその個々のコンポーネント(CHARNUMBERなど)で処理するストアド・プロシージャを作成できます。PL/SQLのTABLE型を使用するストアド・プロシージャをラッピングするには、データを複数のコンポーネントに分解するか、またはOracleのコレクション型を使用します。

次に、PL/SQLのラッパー・プロシージャMY_PROCの例を示します。このプロシージャは、入力としてBOOLEANを取るストアド・プロシージャPROCをラップします。

PROCEDURE MY_PROC (n NUMBER) IS
BEGIN
   IF n=0
      THEN proc(false);
      ELSE proc(true);
   END IF;
END;

PROCEDURE PROC (b BOOLEAN) IS
BEGIN
...
END;

Oracle JDBCの旧リリースとの下位互換性

ここでは、Oracle SQLJ実装をOracle JDBCの旧リリースとともに使用する際の下位互換性の問題について説明します。

Oracle Database 11g リリース1 (11.1)のSQLJでは、Oracle9i DatabaseおよびOracle Database 10g リリース1 (10.1)で開発したアプリケーションが完全にサポートされます。ただし、Oracle Database 11g リリース1 (11.1)では、JDBCリソースがSQLJランタイム・リソース・ファイナライザによってクローズされません。そのため、Oracle Database 11g リリース1 (11.1)より前のリリースで開発した一部のアプリケーションでは、JDBC接続およびJDBC文がリークされることがあります。このようなリークを回避するには、SQLJアプリケーションで、接続コンテキスト、実行コンテキスト、イテレータなど、すべてのSQLJランタイム・リソースを適切にクローズする必要があります。

注意:

Oracle9i リリース2では、最初にOPAQUE型およびTIMESTAMP型のサポートが追加されました。

Oracle8iデータベースとの下位互換性

次のOracle Database 11gの機能(Oracle9iデータベースでも使用可能)は、Oracle8iのJDBCドライバではサポートされていないか、異なる形でサポートされています。

  • ユーザー定義SQL型のJavaマッピング用のoracle.sql.ORADataおよびORADataFactoryインタフェース

    かわりに、oracle.sql.CustomDatumおよびCustomDatumFactoryインタフェースを使用します。

  • グローバリゼーション・サポートのための文字型に対するOracle拡張型: NCHARNCLOBNStringおよびNcharCharacterStream (または旧リリースでのNcharAsciiStreamおよびNcharUnicodeStream)

ストリームのサポート

標準SQLJには、ロング・データをストリームとして処理する次の2つの専用クラスがあります。

  • sqlj.runtime.BinaryStream

  • sqlj.runtime.CharacterStream

イテレータ列でデータベースからデータを取り出すとき、または入力ホスト変数を使用してデータをデータベースに送信するときに、これらのストリーム型を使用します。一般のJavaストリームと同じように、これらのクラスでは、大きなデータ項目を扱いやすい大きさの塊に分割して処理し、転送できます。

ここでは、これらのクラス、Oracle固有のSQLJ拡張機能およびストリーム・クラスのメソッドの一般的な使用方法について説明します。内容は次のとおりです。

注意:

JDBC 2.0では、AsciiStreamクラスおよびUnicodeStreamクラスは、CharacterStreamクラスに置換されています。CharacterStreamによって、ユーザーはエンコーディングに関する不要なロジスティクスから解放されます。

SQLJストリームの一般的な使用方法

通常、表6-1のデータ型をこれらのストリーム・クラスで処理します。つまり、

  • BinaryStreamは通常、LONG RAW (Types.LONGVARBINARY)で使用されますが、RAW (Types.BINARYまたはTypes.VARBINARY)でも使用されます。

  • CharacterStreamは通常、LONG(java.sql.Types.LONGVARCHAR)で使用されますが、VARCHAR2(Types.VARCHAR)でも使用されます。

ストリームの使用方法は自由です。ホスト変数にSQLJストリーム型を使用して、データの送信または取出しが実行できます。

表6-1に示したように、LONGデータとVARCHAR2データはJavaのStringでも使用可能であり、RAWデータとLONGRAWデータはJavaのbyte[]配列でも使用可能です。また、データベースでBLOBCLOBなどのラージ・オブジェクト型がサポートされる場合は、これらを使用する方がLONGLONG RAWなどの型よりも便利な場合もあります。ラージ・オブジェクトからのデータの抽出には、ストリームも使用できます。Oracle SQLJおよびJDBC実装では、ラージ・オブジェクト型がサポートされます。

SQLJストリーム・クラスは両方ともに標準Javaクラスのサブクラス(BinaryStreamjava.io.InputStreamおよびCharacterStreamjava.io.Reader)で、SQLJに必要な機能を提供するラッパーとして動作します。つまり、基になるデータが適切に処理および変換されるように、データのデータ型とデータ長をSQLJに通知します。

ストリームをサポートするクラスの主な特長

次の略記されたコードは、BinaryStreamクラスの主な特長(拡張される内容、コンストラクタのシグネチャ、および主要メソッドのシグネチャなど)を示しています。

public class sqlj.runtime.BinaryStream extends sqlj.runtime.StreamWrapper
{   public sqlj.runtime.BinaryStream(java.io.InputStream);
    public sqlj.runtime.BinaryStream(java.io.InputStream,int);
    public java.io.InputStream getInputStream();
    public int getLength();
    public void setLength(int);
}

次の略記されたコードは、CharacterStreamクラスの主な特長を示しています。

public class sqlj.runtime.CharacterStream extends java.io.FilterReader
{   public sqlj.runtime.CharacterStream(java.io.Reader);
    public sqlj.runtime.CharacterStream(java.io.Reader,int);
    public int getLength();
    public java.io.Reader getReader();
    public void setLength(int);
}

注意:

  • コンストラクタのintパラメータは、データ長をバイト数または文字数の適切な値で指定します。

  • java.io.InputStreamオブジェクトを入力に使用するメソッドの場合は、かわりにBinaryStreamオブジェクトを使用できます。同様に、java.io.Readerオブジェクトを入力に使用するメソッドの場合は、かわりにCharacterStreamオブジェクトを使用できます。

  • 現在推奨されていないAsciiStreamクラスおよびUnicodeStreamクラスにも、BinaryStreamと同じ主な特長とシグネチャがあります。

データ送信時のSQLJストリームの使用

標準SQLJでは、ストリームをホスト変数として使用して、データベースを更新できます。SQLJストリームをデータベースに送信するときは、データの長さを特定し、この長さをSQLJストリームのコンストラクタに対して指定する必要があります。

次の手順で、SQLJストリームを使用して、データをデータベースに送信します。

  1. データの長さを特定します。

  2. 入力として適切な標準Javaデータ・オブジェクトを作成します。BinaryStreamの場合は、入力ストリーム(java.io.InputStreamまたはいくつかのサブクラスのインスタンス)を作成します。CharacterStreamの場合は、Readerオブジェクト(java.io.Readerまたはいくつかのサブクラスのインスタンス)を作成します。

  3. データ型に応じたSQLJストリーム・クラスのインスタンスを作成するには、データ・オブジェクトと長さをコンストラクタに渡します。

  4. このSQLJのストリーム・インスタンスは、SQLJ実行文のSQL操作用のホスト変数として使用します。

  5. ストリームを終了します。

注意:

ストリームを使用した後に終了することは必須ではありませんが、終了することをお薦めします。

ファイルからのLONGまたはLONG RAWの更新

ここでは、FileオブジェクトからCharacterStreamオブジェクトまたはBinaryStreamオブジェクトを作成し、そのオブジェクトをデータベースの更新に使用する方法を示します。この項の最後にあるコード例では、LONG列にCharacterStreamを使用しています。

データベース列をファイルから更新するときは、長さを特定する必要があります。この作業を行うには、入力ストリームを作成する前に、java.io.Fileオブジェクトを作成します。

次の手順で、ファイルからデータベースを更新します。

  1. ファイルからjava.io.Fileオブジェクトを作成します。このためには、Fileクラスのコンストラクタへのファイル・パス名を指定してください。

  2. Fileオブジェクトのlength()メソッドで、データの長さを特定します。このメソッドから戻される値はlong値なので、この値をSQLJストリーム・クラスのコンストラクタに入力するには、int値にキャストする必要があります。

    注意:

    このキャストを行う前に、long値がint変数に収まることを確認してください。クラスjava.lang.Integerの静的定数MAX_VALUEは、Javaの最大許容値intを示します。

  3. 文字データの場合は、Fileオブジェクトからjava.io.FileReaderオブジェクトを作成します。このためには、FileオブジェクトをFileReaderコンストラクタに渡してください。

    バイナリ・データの場合は、Fileオブジェクトからjava.io.FileInputStreamオブジェクトを作成します。このためには、FileオブジェクトをFileInputStreamコンストラクタに渡してください。

  4. 該当するSQLJストリーム・オブジェクトを作成します。テキスト・ファイルの場合はCharacterStreamオブジェクト、バイナリ・ファイルの場合はBinaryStreamオブジェクトを作成します。FileReaderまたはFileInputStreamの該当するオブジェクトとデータ長(int値)をSQLJのストリーム・クラスのコンストラクタに渡します。

  5. SQLJのストリーム・オブジェクトは、SQLJ実行文中でSQL操作用のホスト変数として使用します

次に、ファイルからデータベースにLONGデータを書き込む例を示します。ここでは、/private/mydir/myfile.htmlにあるHTMLファイルの内容をfiletableデータベース表のLONGchardataに挿入します。

import java.io.*;
import sqlj.runtime.*;

...
File myfile = new File ("/private/mydir/myfile.html");
int length = (int)myfile.length();     // Must cast long output to int.
FileReader filereader = new FileReader(myfile);
CharacterStream charstream = new CharacterStream(filereader, length);
#sql { INSERT INTO filetable (chardata) VALUES (:charstream) };
charstream.close();
...

バイト配列からのLONG RAWの更新

ここでは、バイト配列からBinaryStreamオブジェクトを作成し、そのオブジェクトをデータベースの更新に使用する方法を示します。

バイト配列からデータベースを更新する場合は、あらかじめデータの長さを特定する必要があります。Javaの配列はすべて、データ長を戻す機能を備えているため、配列の方がファイルの場合より簡単です。

次の手順で、バイト配列からデータベースを更新します。

  1. 配列のlength機能を使用して、データの長さを特定します。int値が戻されますが、この値はすべてのSQLJストリーム・クラスのコンストラクタで必要になります。
  2. 配列からjava.io.ByteArrayInputStreamオブジェクトを作成します。このためには、バイト配列をByteArrayInputStreamコンストラクタに渡してください。
  3. BinaryStreamオブジェクトを作成します。ByteArrayInputStreamオブジェクトとデータ長(int値)をBinaryStreamクラスのコンストラクタに渡してください。

    コンストラクタのシグネチャは、次のように指定します。

    BinaryStream (InputStream in, int length)
    

    このシグネチャには、java.io.InputStreamクラスまたは任意のサブクラス(ByteArrayInputStreamなど)のインスタンスを指定してください。

  4. SQLJのストリーム・オブジェクトは、SQLJ実行文中でSQL操作用のホスト変数として使用します

次に、バイト配列からLONG RAWデータをデータベースに書き込む例を示します。ここでは、バイト配列bytearray[]の内容をBINTABLEデータベース表にあるLONG RAW型の列BINDATAに挿入するとします。

import java.io.*;
import sqlj.runtime.*;

...
byte[] bytearray = new byte[100];

(Populate bytearray somehow.)
...
int length = bytearray.length;
ByteArrayInputStream arraystream = new ByteArrayInputStream(bytearray);
BinaryStream binstream = new BinaryStream(arraystream, length);
#sql { INSERT INTO bintable (bindata) VALUES (:binstream) };
binstream.close();
...

注意:

この例に示したように、ストリームは必ずしも必要ではありません。かわりに、バイト配列から直接データベースを更新することも可能です。

ストリームへのデータの取出し: 注意

データの取出しにSQLJストリーム・クラスも使用できますが、一部のデータベース製品に関しては、ストリーム使用時に注意事項があります。ロング・データの読取りおよびストリームへの書込みにOracle Database 12c リリース2 (12.2)とOracle JDBCドライバを使用する場合は、ストリーム・データへのアクセス方法およびデータの処理方法に注意する必要があります。

Oracle JDBCドライバはイテレータ行からデータにアクセスするため、通信パイプからストリーム項目をフラッシュした後で次のデータ項目にアクセスするようになっています。イテレータ行の処理時にストリーム・データがローカル・ストリームに書き込まれる場合でも、JDBCドライバが次のデータ項目にアクセスする前にローカル・ストリームからデータを読み取らないと、ストリーム・データが失われます。このようなストリーム処理は、ストリームの特性である大型化と長さが不明な点を考慮したものです。

したがって、Oracle JDBCドライバでストリーム項目にアクセスし、ローカル・ストリーム変数への書込みが完了した時点で、このローカル・ストリームの読取りと処理を行い、その後で、イテレータから別のアクセスを行う必要があります。

特に、位置イテレータの場合は必須のFETCH INTO構文があるため、注意が必要です。各回のフェッチでは、すべての列の読取りが完了した後で、処理が開始されます。したがって、ストリーム項目は1つのみ、つまり最後にアクセスした項目のみとなります。

次に、注意点をまとめます。

  • 位置イテレータを使用する場合、使用できるのは1つのストリーム列のみで、最後の列である必要があります。イテレータの各行をフェッチしたらすぐに、ストリーム項目をプロセスのローカル入力ストリーム変数に書き込み、イテレータの次の行に進む前にそのローカル・ストリーム変数を読み取って処理する必要があります。

  • 名前付きイテレータを使用すると、複数のストリーム列を操作できます。ただし、各イテレータ行を処理するときは、ストリーム・フィールドにアクセスするたびに、そのデータをプロセスのローカル・ストリーム変数に書き込み、ただちにローカル・ストリームを読み取って処理してから、イテレータの他のデータを読み取る必要があります。

    また、名前付きイテレータの各行を処理するときは、イテレータ設定時の問合せでデータベース列を選択したときと同じ順番で、列のアクセッサ・メソッドをコールする必要があります。前述のように、問合せ後の通信パイプにストリーム・データが残っているためです。列へのアクセス順が異なると、ストリーム・データがスキップされ、他の列へのアクセス時に失われることがあります。

注意:

  • Oracle Database 12c リリース2 (12.2)およびOracle JDBCドライバの場合は、SELECT INTO文でストリームを使用できません。

  • デフォルトで、markメソッドおよびresetメソッドは、入力ストリームでサポートされていません。任意の入力ストリームをコンストラクタに渡すと、InputStreamクラスのresetメソッドがIOExceptionをスローします。このため、NcharAsciiStreamコンストラクタに渡す場合は常に、入力ストリームが適切な状態であることを確認します。たとえば、ストリームにデータが存在しない場合やストリームが終了している場合は、NcharAsciiStreamに渡す前にストリームをリセットします。

データ取得時のSQLJストリームの使用

データをストリームとして取り出すとき、標準SQLJでは、名前付きまたは位置イテレータのSQLJストリーム型列へのデータの読込みができます。

ここでは、位置イテレータまたは名前付きイテレータを使用してデータをSQLJストリームに取り出す基本的な手順を示します。前の項で述べた注意事項に留意してください。

位置イテレータのSQLJストリーム列の使用方法

次の手順で、位置イテレータを使用して、データをSQLJストリームに取り出します。

  1. 位置イテレータ・クラスを宣言します。最終列を該当するSQLJストリーム型として指定します。

  2. イテレータ型のローカル変数を宣言します。

  3. 該当するSQLJストリーム型のローカル変数を宣言します。これをホスト変数として、イテレータのSQLJストリーム列の各行からデータを取得します。

  4. 問合せを実行して、手順2で宣言したイテレータにデータを設定します。

  5. 通常どおり、イテレータを処理します。FETCH INTO文のINTOリスト内のホスト変数は、位置イテレータの列順に記述する必要があるので、ローカル入力ストリーム変数をリストの最後のホスト変数にします。

  6. イテレータの処理ループでは、各イテレータ行にアクセスした時点でローカル入力ストリームの読取りと処理を行い、必要に応じてストリーム・データの格納や出力を行います。

  7. イテレータ処理ループが一巡するたびに、ローカル入力ストリームを終了します。

  8. イテレータを終了します。

注意:

必須ではありませんが、イテレータ処理ループが一巡するたびにローカル入力ストリームを終了することをお薦めします。

<<<[11g用?] コードの例を使用してください。>>>

名前付きイテレータのSQLJストリーム列の使用方法

次の手順で、名前付きイテレータを使用して、データを1つ以上のSQLJストリームに取り出します。

  1. 該当するSQLJストリーム型の列を1つ以上指定して、名前付きイテレータ・クラスを宣言します。
  2. イテレータ型のローカル変数を宣言します。
  3. イテレータの各SQLJストリーム列に対して、入力ストリーム型またはリーダー型のローカル変数を宣言します。ストリーム列のアクセッサ・メソッドからデータを受け取るときに、この変数を使用します。これらのローカル・ストリーム変数は、SQLJのストリーム型にする必要はありません。標準のjava.io.InputStreamまたはjava.io.Readerの該当する型でもかまいません。

    注意:

    ローカル・ストリーム変数は、SQLJのストリーム型にする必要はありません。イテレータ列をSQLJのストリーム型にすると、データの型も正しく変換されます。

  4. 問合せを実行して、手順2で宣言したイテレータにデータを設定します。
  5. 通常どおり、イテレータを処理します。イテレータの各行の処理では、各ストリーム列のアクセッサ・メソッドがストリーム・データを戻すたびに、手順3で宣言したローカル入力ストリーム変数にデータが書き込まれます。

    ストリーム・データの欠損を防ぐには、手順4の問合せで列を選択したときと同じ順番で、列のアクセッサ・メソッドをコールする必要があります。

  6. イテレータの処理ループで、ストリーム列のアクセッサ・メソッドをコールし、データをローカル入力ストリーム変数に書き込んでから、ただちにローカル入力ストリームを読み取って処理し、ストリーム・データを格納または出力します。
  7. イテレータ処理ループが一巡するたびに、ローカル入力ストリームを終了します。
  8. イテレータを終了します。

<<<[11g用?] コードの例を使用してください。>>>

注意:

  • SQLJストリーム・オブジェクトにデータを移入した場合は、ストリームのデータ長属性は意味を持ちません。この属性が意味を持つのは、データ長を明示的に設定した場合、つまり各SQLJストリーム・クラスのsetLength()メソッドを使用したか、またはコンストラクタにデータ長を指定した場合です。

  • 必須ではありませんが、イテレータ処理ループが一巡するたびにローカル入力ストリームを終了することをお薦めします。

ストリーム・クラスのメソッド

名前付きまたは位置イテレータのSQLJストリーム列を処理するときは、ストリーム・データを受け取るローカル・ストリーム変数をSQLJのストリーム型または標準のjava.io.InputStreamまたはjava.io.Readerのいずれか該当する型にできます。いずれの場合も、入力データ・オブジェクトの標準のメソッドを使用できます。

ローカル・ストリーム変数をSQLJのストリーム型、つまりBinaryStreamまたはCharacterStreamにすると、データをSQLJのストリーム・オブジェクトから直接読み取ることも、基になるInputStreamオブジェクトまたはReaderオブジェクトを取得し、このオブジェクトからデータを読み取ることも可能です。

注意:

どちらの方法をとるかは、好みの問題です。最初の方法の方が簡単です。ただし、2番目の方法は、データへのアクセスがより直接的で効率的です。

バイナリ・ストリームのメソッド

BinaryStreamクラスは、sqlj.runtime.StreamWrapperクラスのサブクラスです。StreamWrapperクラスには、次の主なメソッドがあります。

  • InputStream getInputStream(): このメソッドを使用して、基になるjava.io.InputStreamオブジェクトを取得することもできます。SQLJのストリーム・オブジェクトは、このメソッドを使用せずに、直接処理できます。

  • void setLength(int length): SQLJストリーム・オブジェクトのlength属性を設定できます。ストリーム・オブジェクトの作成時に設定したlengthの値をそのまま使用する場合は、このメソッドを使用する必要はありません。

    SQLJストリームをデータベースに送信する場合は、あらかじめlength属性の値を設定する必要があります。

  • int getLength(): SQLJストリームのlength属性の値を戻します。ストリーム・オブジェクトのコンストラクタまたはsetLength()メソッドで明示的にデータ長を設定すると、この属性値が有効になります。データをストリームに取り出すときは、length属性が自動的に設定されません。

sqlj.runtime.StreamWrapperクラスはjava.io.FilterInputStreamクラスのサブクラスであり、このjava.io.FilterInputStreamはjava.io.InputStreamクラスのサブクラスです。InputStreamクラスの次の主要メソッドは、SQLJのBinaryStreamクラスでもサポートされています。

  • int read(): 入力ストリームから次のバイト・データを読み取ります。このバイト・データはint値(0から255)で戻されます。ストリームの終端に達した場合の戻り値は、-1です。次のいずれかの状態になるまで、このメソッドはプログラムを実行しません。

    • 入力データが用意されたとき

    • ストリームの終端に達したとき

    • 例外がスローされたとき

  • int read (byte b[]): 入力ストリームから最大b.lengthバイトのデータを読み取り、指定されたb[]バイト配列に書き込みます。戻り値として、読み取ったバイト数を示すint値が戻されますが、ストリームの終端に達した場合は-1が戻されます。入力が用意されるまで、このメソッドはプログラムをブロックします。

  • int read (byte b[], int off, int len): 入力ストリームのオフセットoffで指定されたバイト位置から最大lenバイトのデータを読み取り、指定されたb[]バイト配列に書き込みます。戻り値として、読み取ったバイト数を示すint値が戻されますが、ストリームの終端に達した場合は-1が戻されます。入力が用意されるまで、このメソッドはブロックします。

  • long skip (long n): 入力ストリームのnバイトのデータをスキップし、破棄します。実際にスキップするバイト数が、指定値より少ない場合もあります。実際にスキップしたバイト数は、long値で戻されます。

  • void close(): ストリームを終了し、関連リソースを解放します。

文字ストリームのメソッド

CharacterStreamクラスには、次の主なメソッドがあります。

  • Reader getReader(): このメソッドを使用して、基になるjava.io.Readerオブジェクトを取得することもできます。SQLJのストリーム・オブジェクトは、このメソッドを使用せずに、直接処理できます。

  • void setLength(int length): このメソッドを使用して、ストリーム・オブジェクトの長さを設定できます。

  • int getLength(): このメソッドを使用して、ストリーム・オブジェクトの長さを取得できます。

sqlj.runtime.CharacterStreamクラスはjava.io.FilterReaderクラスのサブクラスであり、このjava.io.FilterReaderはjava.io.Readerクラスのサブクラスです。Readerクラスの次の主要メソッドは、SQLJのCharacterStreamクラスでもサポートされています。

  • int read(): リーダーから次の文字データを読み取ります。このデータはint値(0から65535)で戻されます。データの終端に達した場合の戻り値は、-1です。次のいずれかの状態になるまで、このメソッドはプログラムを実行しません。

    • 入力データが用意されたとき

    • データの終端に達したとき

    • 例外がスローされたとき

  • int read (char cbuf[]): 文字を配列に読み取り、データを指定されたcbuf[] char配列に書き込みます。戻り値として、読み取った文字数を示すint値が戻されますが、データの終端に達した場合は-1が戻されます。入力が用意されるまで、このメソッドはプログラムをブロックします。

  • int read (char cbuf[], int off, int len): 入力のオフセットoffで指定された文字位置から最大len文字数のデータを読み取り、指定されたchar[]文字配列に書き込みます。戻り値として、読み取った文字数を示すint値が戻されますが、データの終端に達した場合は-1が戻されます。入力が用意されるまで、このメソッドはブロックします。

  • long skip (long n): 入力のn文字のデータをスキップし、破棄します。実際にスキップする文字数が、指定値より少ない場合もあります。実際にスキップした文字数は、long値で戻されます。

  • void close(): ストリームを終了し、関連リソースを解放します。

ストリーム・データの取出しおよび処理例

ここでは、ストリーム・データを取り出す例を示します。

  • 例6-1では、SELECT文でLONG列からデータを選択し、名前付きイテレータのSQLJ CharacterStream列に設定します。

  • 例6-2では、SELECT文でLONG RAW列からデータを選択し、位置イテレータのSQLJ BinaryStream列に設定します。

例6-1: 名前付きイテレータのCharacterStream列へのLONGデータの格納

この例では、データベースのLONG列からデータを選択し、名前付きイテレータのSQLJ CharacterStream列に格納します。

FILETABLEVARCHAR2型の列FILENAMEにファイル名が格納されており、LONG型の列FILECONTENTSにファイルの内容が文字形式で格納されているものとします。このコードを次に示します。

import sqlj.runtime.*;
import java.io.*;
...
#sql iterator MyNamedIter (String filename, CharacterStream filecontents);

...
MyNamedIter namediter = null;
String fname;
CharacterStream charstream;
#sql namediter = { SELECT filename, filecontents FROM filetable };
while (namediter.next()) {
   fname = namediter.filename();
   charstream = namediter.filecontents();
   System.out.println("Contents for file " + fname + ":");
   printStream(charstream);
   charstream.close();
}

namediter.close();
...
public void printStream(Reader in) throws IOException
{
   int character;
   while ((character = in.read()) != -1) {
      System.out.print((char)character);
   }
}

すでに述べたように、入力パラメータとして標準java.io.Readerをとるメソッドには、SQLJ文字ストリームを渡すことが可能です。

例6-2: 位置イテレータのBinaryStream列へのLONG RAWの格納

この例では、LONG RAW列からデータを選択し、位置イテレータのSQLJ BinaryStream列に移入します。

前の項で説明したように、位置イテレータには1つの入力ストリームのみ可能で、必ず最終列の必要があります。表BINTABLEにはNUMBER型の列IDENTIFIERLONG RAW型の列BINDATAがあり、識別子に該当するバイナリ・データがBINDATA列に格納されているものとします。このコードを次に示します。

import sqlj.runtime.*;
...
#sql iterator MyPosIter (int, BinaryStream);

...
MyPosIter positer = null;
int id=0;
BinaryStream binstream=null;
#sql positer = { SELECT identifier, bindata FROM bintable };
while (true) {
   #sql { FETCH :positer INTO :id, :binstream };
   if (positer.endFetch()) break;
   
   (...process data as desired...)
   
   binstream.close();
}
positer.close();
...

出力パラメータおよびファンクションの戻り値としてのSQLJストリーム・オブジェクト

前述のように、標準SQLJでは、sqlj.runtimeパッケージのBinaryStreamおよびCharacterStreamクラスを使用して、ストリーム・データを取り出し、イテレータ列に格納できます。

また、Oracle SQLJ実装では、Oracle9i 以上のデータベース、Oracle JDBCドライバ、Oracle固有コード生成またはOracleカスタマイザ、およびOracle SQLJランタイムを使用すると、SQLJのストリーム型を次のように使用できます。

  • ストアド・プロシージャまたはストアド・ファンクションのコールからのOUTまたはINOUTホスト変数として

  • ストアド・ファンクションのコールの戻り値として

ストアド・プロシージャの出力パラメータとしてのストリーム

BinaryStreamおよびCharacterStream型は、ストアド・プロシージャまたはストアド・ファンクションのOUTまたはINOUTパラメータの代入型として使用できます。

次の表定義を想定します。

CREATE TABLE streamexample (name VARCHAR2 (256), data LONG);
INSERT INTO streamexample (data, name)
   VALUES
   ('0000000000111111111112222222222333333333344444444445555555555',
   'StreamExample');

また、次のストアド・プロシージャ定義を想定します。この定義には、STREAMEXAMPLE表が使用されています。

CREATE OR REPLACE PROCEDURE out_longdata 
                            (dataname VARCHAR2, longdata OUT LONG) IS
BEGIN
   SELECT data INTO longdata FROM streamexample WHERE name = dataname;
END out_longdata;

次のサンプル・コードでは、out_longdataストアド・プロシージャをコールして、ロング・データを読み取ります。

import sqlj.runtime.*;

...
CharacterStream data;
#sql { CALL out_longdata('StreamExample', :OUT data) };
int c;
while ((c = data.read ()) != -1)
   System.out.print((char)c);
System.out.flush();
data.close();
...

注意:

ストリームを必ずしも終了する必要はありませんが、終了することをお薦めします。

ストアド・ファンクションの結果としてのストリーム

BinaryStream型およびCharacterStream型は、ストアド・ファンクションの戻り値の代入型として使用できます。

前述のストアド・プロシージャの例と同じSTREAMEXAMPLE表定義を想定します。また、次のストアド・ファンクション定義を想定し、STREAMEXAMPLE表を使用します。

CREATE OR REPLACE FUNCTION get_longdata (dataname VARCHAR2) RETURN long
   IS longdata LONG;
BEGIN
   SELECT data INTO longdata FROM streamexample WHERE name = dataname;
   RETURN longdata;
END get_longdata;

次のサンプル・コードでは、get_longdataストアド・ファンクションをコールして、ロング・データを読み取ります。

import sqlj.runtime.*;

...
CharacterStream data;
#sql data = { VALUES(get_longdata('StreamExample')) };
int c;
while ((c = data.read ()) != -1)
   System.out.print((char)c);
System.out.flush();
data.close();
...

注意:

ストリームを必ずしも終了する必要はありませんが、終了することをお薦めします。

JDBC 2.0 LOB型とOracle拡張型のサポート

Oracle SQLJ実装では、次に示したようにJDBC 2.0のデータ型とOracle固有のデータ型に対して拡張機能が用意されています。

  • JDBC 2.0ラージ・オブジェクト(LOB)型(BLOBおよびCLOB)

  • Oracle BFILE

  • Oracle ROWID

  • Oracle REF CURSOR型

  • その他のOracle Database 12c リリース2 (12.2)データ型(NUMBERRAWなど)

ここであげたデータ型は、oracle.sqlパッケージ中のクラスでサポートされています。LOBとバイナリ・ファイル(BFILE)は多くの点で同様に処理されるため、まとめて説明します。Oracle SQLJ実装では、標準BigDecimal JDBC型に対するサポートも強化されています。

ユーザー定義のSQLオブジェクト、オブジェクト参照およびコレクションについては、JDBC 2.0機能もサポートされています。

コード中にOracle拡張型を使用する場合は、次の要件が伴うので注意してください。

  • Oracle JDBCドライバを使用する必要があります。

  • Oracle固有コード生成を使用するか、またはISOコード生成の場合は必要に応じてプロファイルをカスタマイズします。カスタマイザはデフォルトのカスタマイザoracle.sqlj.runtime.util.OraCustomizerをお薦めします。

  • アプリケーションの実行時にOracle SQLJランタイムを使用します。

Oracle SQLJランタイムとOracle JDBCドライバは、Oracle拡張型をコード中に使用しない場合であっても、Oracleカスタマイザを使用する場合は常に必要です。

Oracle固有のセマンティクス・チェックを行うには、適切なチェッカを使用する必要があります。デフォルトのチェッカoracle.sqlj.checker.OracleCheckerは、フロントエンドとして機能し、環境に応じたチェッカを起動します。JDBCドライバを使用すると、Oracle固有のチェッカが起動されます。

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

パッケージoracle.sql

oracle.sqlパッケージは、SQLJユーザーにもJDBCユーザーにも重要であり、このパッケージ内のクラスによって、Oracle Database 12c リリース2 (12.2)のすべてのデータ型(oracle.sql.ROWIDoracle.sql.CLOBおよびoracle.sql.NUMBERなど)がサポートされます。oracle.sqlクラスは、SQLのrawデータ用のラッパーであり、Java形式への適切なマッピングと変換メソッドを提供します。oracle.sql.*オブジェクトは、対応するSQLデータのバイナリ表現をバイト配列の形式で保持しています。各oracle.sql.*データ型クラスは、oracle.sql.Datumクラスのサブクラスです。

Oracle固有のセマンティクス・チェックを行うには、適切なチェッカを使用する必要があります。デフォルトのチェッカoracle.sqlj.checker.OracleCheckerは、フロントエンドとして機能し、環境に応じたチェッカを起動します。JDBCドライバを使用すると、Oracle固有のチェッカが起動されます。

BLOB、CLOBおよびBFILEのサポート

Oracle SQLJおよびJDBC実装では、JDBC 2.0 LOB型がサポートされており、このサポート内容はOracle固有のBFILE型(データベースの外部に格納されている読取り専用バイナリ・ファイル)と似ています。これらのデータ型は、次のクラスでサポートされています。

  • oracle.sql.BLOB

  • oracle.sql.CLOB

  • oracle.sql.BFILE

これらのクラスは、Oracle固有のSQLJアプリケーションで次のように使用できます。

  • SQLJ実行文およびINTOリスト中のINOUTまたはINOUTホスト変数として

  • ストアド・ファンクション・コールの戻り値として

  • イテレータ宣言での列型として

関連項目:

LOBとBFILEの詳細およびサポートされているストリームAPIの使用方法は、『Oracle Database JDBC開発者ガイド』を参照してください。

LOBの操作には、BLOBおよびCLOBクラスで定義したメソッドを使用するか(推奨)、DBMS_LOB PL/SQLパッケージで定義したプロシージャおよびファンクションを使用できます。このパッケージで定義されているプロシージャとファンクションはすべて、SQLJプログラムからコールできます。

BFILEの操作には、BFILEクラスで定義したメソッドを使用することをお薦めします。DBMS_LOBパッケージのファイル処理ルーチンも使用できます。

Javaアプリケーションでは、BLOBCLOBおよびBFILEクラスのメソッドの方がDBMS_LOBパッケージより便利です。実行時間が短縮される場合もあります。

読取りまたは書込み対象のチャンクの型は、操作対象のLOBの種類によって異なります。たとえば、キャラクタ・ラージ・オブジェクト(CLOB)の内容は文字データであるため、データ・チャンクをJava文字列で保持します。バイナリ・ラージ・オブジェクト(BLOB)の内容はバイナリ・データであるため、チャンク・データをJavaバイト配列で保持します。

注意:

DBMS_LOBパッケージでは、サーバーへのラウンドトリップが必要になります。BLOBCLOBおよびBFILEクラスのメソッドも、サーバーへのラウンドトリップが必要になることがあります。

BFILEでBFILEクラスを使用した場合とDBMS_LOB機能を使用した場合の比較

例6-3および例6-4では、BFILEでoracle.sqlのメソッドを使用した場合とDBMS_LOBパッケージを使用した場合を比較します。

LOBでBLOBおよびCLOBクラスを使用した場合とDBMS_LOB機能を使用した場合の比較

例6-5および例6-6では、BLOBでoracle.sqlメソッドを使用した場合とDBMS_LOBパッケージを使用した場合を比較します。また、例6-7および例6-8では、CLOBでoracle.sqlメソッドを使用した場合とDBMS_LOBパッケージを使用した場合を比較します。

LOBおよびBFILE型でのストアド・ファンクションの結果

BLOBCLOBおよびBFILE型のホスト変数は、ストアド・ファンクション・コールの結果に代入できます。次に示した例は、CLOBを対象としていますが、BLOBおよびBFILEのコードにも同じ機能があります。

最初に、次のファンクション定義を想定します。

CREATE OR REPLACE FUNCTION longer_clob (c1 CLOB, c2 CLOB) RETURN CLOB IS 
   result CLOB;
BEGIN
   IF dbms_lob.getLength(c2) > dbms_lob.getLength(c1) THEN
      result := c2;
   ELSE 
      result := c1;
   END IF;
   RETURN result; 
END longer_clob;

次の例では、longer_clobファンクションからの戻り値の代入型としてCLOBを使用します。

void readFromLongest(CLOB c1, CLOB c2) throws SQLException
{
   CLOB longest;
   #sql longest = { VALUES(longer_clob(:c1, :c2)) };
   readFromClob(longest);
}

readFromLongest()メソッドは、前に定義したreadFromClob()メソッドを使用して、渡されたCLOBの長い方の内容を出力します。

LOBおよびBFILEホスト変数とSELECT INTOターゲット

BLOBCLOBおよびBFILE型のホスト変数は、SELECT INTO実行文のINTOリストに記述できます。次に示した例は、BLOBとCLOBを対象としていますが、BFILEのコードにも同じ機能があります

次の表定義を想定します。

CREATE TABLE basic_lob_table(x VARCHAR2(30), b BLOB, c CLOB);
INSERT INTO basic_lob_table 
   VALUES('one', '010101010101010101010101010101', 'onetwothreefour');
INSERT INTO basic_lob_table 
   VALUES('two', '020202020202020202020202020202', 'twothreefourfivesix');

次の例では、BLOBとCLOBをホスト変数として使用し、定義した表のデータをSELECT INTO文を使用して受け取ります。

...
BLOB blob;
CLOB clob; 
#sql { SELECT one.b, two.c INTO :blob, :clob 
     FROM basic_lob_table one, basic_lob_table two 
     WHERE one.x='one' AND two.x='two' };
#sql { INSERT INTO basic_lob_table VALUES('three', :blob, :clob) };
...

この例では、BASIC_LOB_TABLEの先頭行からBLOBを取り出し、2番目の行からCLOBを取り出します。その後で、前の操作で選択したBLOBとCLOBを使用して、3番目の行を表に挿入します。

LOBとBFILEを使用したイテレータの宣言

BLOBCLOBおよびBFILE型は、SQLJの位置および名前付きイテレータの列型として使用できます。これらのイテレータには、該当する実行可能なSQLJ操作の結果を格納できます。

次に、宣言の例を示します。

#sql iterator NamedLOBIter(CLOB c);
#sql iterator PositionedLOBIter(BLOB);
#sql iterator NamedFILEIter(BFILE bf);

LOBおよびBFILE型ホスト変数と名前付きイテレータの結果

次の例では、前の例で定義したreadFromLongest()メソッドとBASIC_LOB_TABLE表を使用し、名前付きイテレータでCLOBを使用します。BLOBおよびBFILEのコードも、同様の記述になります。

#sql iterator NamedLOBIter(CLOB c);

...
NamedLOBIter iter;  
#sql iter = { SELECT c FROM basic_lob_table };
if (iter.next())
   CLOB c1 = iter.c();
if (iter.next())
   CLOB c2 = iter.c();
iter.close();
readFromLongest(c1, c2);
...

この例では、イテレータを使用して、BASIC_LOB_TABLEの最初の2行からCLOBを2つ選択します。次にreadFromLongest()メソッドを使用して、2つのうちの大きい方を出力します。

LOBおよびBFILE型ホスト変数と、位置イテレータのFETCH INTOターゲット

BLOBCLOBおよびBFILE型のホスト変数は、位置イテレータで使用できます。イテレータの該当列の属性が同じ型の場合は、対応付けられているFETCH INTO文のINTOリストに、これらのホスト変数を記述できます。

次の例では、前の例で定義したwriteToBlob()メソッドとBASIC_LOB_TABLE表を使用します。CLOBおよびBFILEのコードも、同様の記述になります。

#sql iterator PositionedLOBIter(BLOB);

...
PositionedLOBIter iter;
BLOB blob = null;
#sql iter = { SELECT b FROM basic_lob_table };
for (long rowNum = 1; ; rowNum++) 
{
    #sql { FETCH :iter INTO :blob };
    if (iter.endFetch()) break;
    writeToBlob(blob, 512*rowNum); 
}
iter.close();
...

この例では、BASIC_LOB_TABLE内の各BLOBに対して、writeToBlob()をコールします。各行から、512バイトずつデータが書き込まれます。

例6-3: BFILEでのoracle.sql.BFILEファイル処理メソッドの使用

この例では、oracle.sql.BFILEクラスのファイル処理メソッドでBFILEを操作します。

BFILE openFile (BFILE file) throws SQLException 
{ 
  String dirAlias, name; 
  dirAlias = file.getDirAlias(); 
  name = file.getName(); 
  System.out.println("name: " + dirAlias + "/" + name); 
   
  if (!file.isFileOpen())  
  { 
    file.openFile(); 
  } 
  return file; 
} 

BFILEgetDirAlias()およびgetName()メソッドは、フルパスとファイル名を取得するためのメソッドです。openFile()メソッドは、ファイルをオープンするためのメソッドです。操作するBFILEは、あらかじめオープンしておく必要があります。

例6-4: BFILEでのDBMS_LOBファイル処理ルーチンの使用

この例では、DBMS_LOBパッケージのファイル処理ルーチンを使用してBFILEを操作します。

BFILE openFile(BFILE file) throws SQLException 
{
   String dirAlias, name;
   #sql { CALL dbms_lob.filegetname(:file, :out dirAlias, :out name) };
   System.out.println("name: " + dirAlias + "/" + name);

   boolean isOpen;
   #sql isOpen = { VALUES(dbms_lob.fileisopen(:file)) };
   if (!isOpen) 
   {
      #sql { CALL dbms_lob.fileopen(:inout file) };
   }
   return file;
}

openFile()メソッドは、ファイル・オブジェクトの名前を出力し、その後で、そのファイルのオープンしているバージョンを戻すためのメソッドです。BFILEを操作するには、あらかじめDBMS_LOB.FILEOPENまたはBFILEクラスの該当メソッドをコールし、ファイルをオープンにしておく必要があります。

例6-5: CLOBでのoracle.sql.CLOB読取りメソッドの使用

oracle.sql.CLOBクラスのメソッドを使用して、CLOBからデータを読み取ります。

void readFromClob(CLOB clob) throws SQLException 
{ 
  long clobLen, readLen; 
  String chunk; 
 
  clobLen = clob.length(); 
 
  for (long i = 0; i < clobLen; i+= readLen) { 
    chunk = clob.getSubString(i, 10); 
    readLen = chunk.length(); 
    System.out.println("read " + readLen + " chars: " + chunk); 
  } 
} 

このメソッドには、CLOBからJava文字列を10文字ずつ読み取って戻すループがあります。CLOB全体を読み取るまで、ループが繰り返されます。

例6-6: CLOBでのDBMS_LOB読取りルーチンの使用

DBMS_LOBパッケージのルーチンを使用してCLOBからの読取りを行います。

void readFromClob(CLOB clob) throws SQLException
{
   long clobLen, readLen;
   String chunk;

   #sql clobLen = { VALUES(dbms_lob.getlength(:clob)) };

   for (long i = 1; i <= clobLen; i += readLen) {
       readLen = 10;
       #sql { CALL dbms_lob.read(:clob, :inout readLen, :i, :out chunk) };
       System.out.println("read " + readLen + " chars: " + chunk);
   }
}

このメソッドはCLOBの内容を10文字ずつ読み取ります。チャンクのホスト変数の型は、Stringです。

例6-7: BLOBでのoracle.sql.BLOB書込みルーチンの使用

oracle.sql.BLOBクラスのメソッドを使用して、BLOBへデータを書き込みます。BLOBと指定の長さを入力します。

void writeToBlob(BLOB blob, long blobLen) throws SQLException 
{ 
  byte[] chunk = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 }; 
  long chunkLen = (long)chunk.length; 
   
  for (long i = 0; i < blobLen; i+= chunkLen) { 
    if (blobLen < chunkLen) chunkLen = blobLen; 
    chunk[0] = (byte)(i+1); 
    chunkLen = blob.putBytes(i, chunk); 
  } 
} 

指定されたBLOB長に達するまで、このメソッドはBLOBに10バイトずつ書き込むループを繰り返します。

例6-8: BLOBでのDBMS_LOB書込みルーチンの使用

この例では、DBMS_LOBパッケージのルーチンを使用してBLOBへの書込みを行います。

void writeToBlob(BLOB blob, long blobLen) throws SQLException
{
   byte[] chunk = { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 };
   long chunkLen = (long)chunk.length;

   for (long i = 1; i <= blobLen; i += chunkLen) {
      if ((blobLen - i + 1) < chunkLen) chunkLen = blobLen - i + 1;
      chunk[0] = (byte)i;       
      #sql { CALL dbms_lob.write(:INOUT blob, :chunkLen, :i, :chunk) };
   }
} 

このメソッドは、BLOBに10バイトずつ書き込みます。チャンクのホスト変数はbyte[]型です。

Oracle ROWIDのサポート

Oracle固有のROWID型は、データベース表の各行の一意のアドレスを格納するための型です。oracle.sql.ROWIDクラスはROWID情報をラッピングするもので、ROWID型の変数をバインドおよび定義するために使用します。

oracle.sql.ROWID型の変数は、Oracle Database 12c リリース2 (12.2)に接続するSQLJアプリケーションで次のように使用できます。

  • SQLJ実行文およびINTOリスト中のINOUTまたはINOUTホスト変数として

  • ストアド・ファンクション・コールの戻り値として

  • イテレータ宣言での列型として

ROWIDを使用したイテレータ宣言

oracle.sql.ROWID型は、次の宣言のように、SQLJの位置および名前付きイテレータの列型として使用できます。

#sql iterator NamedRowidIter (String ename, ROWID rowid);

#sql iterator PositionedRowidIter (String, ROWID);

ROWIDホスト変数と名前付きイテレータのSELECT結果

ROWIDオブジェクトは、SQLJ実行文でINOUTおよびINOUTパラメータとして使用できます。また、イテレータのROWID型の列を設定できます。次のコード例では、前の例で示した宣言を使用します。

#sql iterator NamedRowidIter (String ename, ROWID rowid);

...
NamedRowidIter iter; 
ROWID rowid;
#sql iter = { SELECT first_name, rowid FROM employees };
while (iter.next())
{
   if (iter.first_name().equals("Peter Hall"))
   {
       rowid = iter.rowid();
       #sql { UPDATE employees SET salary = salary + 500 WHERE rowid = :rowid };
   }
}
iter.close();
...

この例では、ROWIDに従って、従業員Peter Hallの給与が$500増えます。

ストアド・ファンクションの結果としてのROWID

次のファンクションについて考えてみます。

CREATE OR REPLACE FUNCTION get_rowid (name VARCHAR2) RETURN ROWID IS
   rid ROWID;
BEGIN
   SELECT rowid INTO rid FROM employees WHERE first_name = name;
   RETURN rid;
END get_rowid;

このストアド・ファンクションの場合は、このファンクションの戻り値の代入型として、ROWIDオブジェクトが次のように使用されます。

ROWID rowid;
#sql rowid = { values(get_rowid('AMY FEINER')) };
#sql { UPDATE employees SET salary = salary + 500 WHERE rowid = :rowid };

この例では、ROWIDに従って、従業員Amy Feinerの給与が$500増えます。

SELECT INTOのターゲットとしてのROWID

ROWID型のホスト変数は、SELECT INTO文のINTOリストに記述できます。

ROWID rowid;
#sql { SELECT rowid INTO :rowid FROM employees WHERE first_name='PETER HALL' };
#sql { UPDATE employees SET salary = salary + 500 WHERE rowid = :rowid };

この例では、ROWIDに従って、従業員Peter Hallの給与が$500増えます。

ROWIDホスト変数と位置イテレータのFETCH INTOターゲット

ROWID型のホスト変数は、FETCH INTO文のINTOリストに記述できます(イテレータ内の該当列の属性が同じ型の場合)。

#sql iterator PositionedRowidIter (String, ROWID);

...
PositionedRowidIter iter;
ROWID rowid = null;
String ename = null;
#sql iter = { SELECT first_name, rowid FROM employees };
while (true)
{
   #sql { FETCH :iter INTO :ename, :rowid };
   if (iter.endFetch()) break;
   if (ename.equals("PETER HALL"))
   {
       #sql { UPDATE employees SET salary = salary + 500 WHERE rowid = :rowid };
   }
}
iter.close();
...

この例は、前述の名前付きイテレータの例に似ていますが、位置イテレータの必須FETCH INTO構文を使用します。

位置指定更新および位置指定削除

Oracle Database 11g リリース1以降、SQLJで位置指定更新および位置指定削除の操作がサポートされています。位置指定更新または位置指定削除の操作は、イテレータを使用して実行できます。位置指定更新または位置指定削除に使用されるイテレータは、sqlj.runtime.ForUpdateインタフェースを実装している必要があります。名前付きイテレータ、位置イテレータまたはスクロール可能なイテレータを使用できます。

次のコードは、位置指定更新を示しています。

...
#sql iterator iter implements sqlj.runtime.ForUpdate(String str)
...
#sql iter = {SELECT first_name FROM employees WHERE department_id=10};
...
while(iter.next())
{
 #sql {UPDATE employees SET salary=salary+5000 WHERE CURRENT OF :iter};
}
...

前述のコードでは、イテレータiterが作成され、employees表を更新する際に使用されます。

注意:

同期の問題を回避する場合は、SELECT ... FOR UPDATE文を発行します。

同様に位置指定削除を実行できます。次に例を示します。

...
#sql {DELETE FROM employees WHERE CURRENT OF :iter}
...

前述の例で、iterは、位置指定削除を実行する際に使用されるイテレータです。

WHERE CURRENT OF句で使用可能なイテレータには、次の制限事項があります。

  • イテレータへの移入に使用される問合せは、複数の表に対して実行しないでください。

  • イテレータでREF CURSORを戻すPL/SQLプロシージャは使用できません

  • 結果セットから移入されているイテレータは使用できません。つまり、次の文を使用して移入されたイテレータです。ここでrsは結果セットです。

    #sql iter = {cast :rs}
    

for_updateオプション

変換時にfor_updateオプションを設定すると、SELECT文にFOR UPDATEが追加されます。次に、SELECT文はForUpdateイテレータに結果を次のように戻します。

% sqlj –for_update abc.sqlj
 
/* abc.sqlj */
#sql iterator  SalByName (double sal, String ename)  implements sqlj.runtime.ForUpdate;
 
public class abc {
…..
  void func1()
  {
    SalByName salbn;
    #sql salbn = {select salary, first_name from employees };
  }
…..
}

ここで、SELECT文にFOR UPDATEが追加され、次のようにForUpdateイテレータsalbnが戻されます。

………
String theSqlTS = “SELECT rowid sjT_rowid,first_name, salary FROM employees WHERE first_name =  :1 FOR UPDATE";
………

表6-3に、for_updateオプションに使用可能な値と前述の例の対応するSQL文を示しています。

表6-3 for_updateオプションの値と対応するSQL文

for_updateオプション SQLJ文 SQL文

なし

sqlj -for_update abc.sqlj

SELECT rowid sjT_rowid,first_name, salary FROM employees WHERE first_name = :1 FOR UPDATE

nowait

sqlj -P-for_update=nowait QueryDemo.sqlj

SELECT rowid sjT_rowid,first_name, salary FROM employees WHERE first_name = :1 FOR UPDATE nowait

number

sqlj -P-for_update=10 QueryDemo.sqlj

SELECT rowid sjT_rowid,first_name, salary FROM employees WHERE first_name = :1 FOR UPDATE wait 10

注意:

アプリケーションの選択問合せにFOR UPDATEが含まれる場合、新しい変換オプションを使用すると、変換時のオンライン・チェック中に警告がスローされます。変換中にオフライン解析を選択すると、変換時にエラーは検出されません。

OracleのREF CURSOR型のサポート

Oracle PL/SQLとOracle SQLJ実装では、データベースのカーソルを表すカーソル変数を使用できます。

REF CURSOR型の概要

カーソル変数は、機能的にはJDBCの結果セットに相当し、問合せ結果をカプセル化します。カーソル変数はREF CURSORとも呼ばれますが、REF CURSOR自体は型指定子であって、型名ではありません。指定子でなく、名前付きREF CURSOR型を指定する必要があります。次に、REF CURSORの型指定の例を示します。

TYPE EmpCurType IS REF CURSOR;

OracleのREF CURSOR型のパラメータは、ストアド・プロシージャとストアド・ファンクションで戻されます。REF CURSORパラメータを戻すには、PL/SQLを使用する必要があります。SQLのみを使用した場合は戻りません。PL/SQLのストアド・プロシージャやストアド・ファンクションでは、任意の名前付きREF CURSOR型の変数を宣言したり、SELECT文を実行したり、REF CURSOR変数に戻り値としての結果を代入したりできます。

SQLJのREF CURSOR型

Oracle SQLJ実装では、REF CURSOR型を任意のイテレータ・クラス型またはjava.sql.ResultSet型のイテレータ列またはホスト変数にマッピングできます(ただし、ホスト変数はOUTのみにできます)。次に、REF CURSOR型の用途をまとめます。

  • ストアド・ファンクションの戻り値に使用する結果式として

  • ストアド・プロシージャまたはストアド・ファンクションの出力パラメータの出力ホスト式として

  • INTOリスト中の出力ホスト式として

  • イテレータ列として

SQLのCURSOR演算子は、外部のSELECT文の内側にネストされているSELECTに対して使用できます。これによって、REF CURSORオブジェクトをイテレータ列またはイテレータのResultSet列に書き込むか、REF CURSORオブジェクトをイテレータ・ホスト変数またはINTOリストのResultSetホスト変数に書き込むことが可能になります。

関連項目:

暗黙的なREF CURSOR変数の使用例(たとえば、CURSOR演算子の例など)は、「ホスト変数としてのイテレータおよび結果セットの使用」を参照してください。

注意:

  • REF CURSOR型には、型コードOracleTypes.CURSORを使用する必要があります。

  • REF CURSOR型は、oracle.sqlクラスではサポートされていません。java.sql.ResultSetクラスまたはイテレータ・クラスを使用する必要があります。リソースを解放するために、結果セットまたはイテレータは、処理完了時点で終了してください。

REF CURSORの例

無名ブロックからREF CURSOR型を取り出すサンプル・メソッドを次に示します。

private static EmpIter refCursInAnonBlock(String name, int no) 
  throws java.sql.SQLException {
  EmpIter emps = null;    

  System.out.println("Using anonymous block for ref cursor.."); 
  #sql { begin
           INSERT INTO employees (first_name, employee_id) VALUES (:name, :no);
           OPEN :out emps FOR SELECT first_name, employee_id FROM employees ORDER BY employee_id;
         end
       };
  return emps;
}

その他のOracle Database 11g データ型のサポート

oracle.sqlのあらゆるクラスは、イテレータ列にも、入力、出力または入出力のホスト変数にも、標準Java型と同じように使用できます。たとえば、前述したクラスでは、oracle.sql.NUMBERoracle.sql.CHARoracle.sql.RAWなどのクラスがサポートされています。

oracle.sql.*クラスはJava型の形式に変換する必要がないため、同等のJava型より効率および精度面で優れています。ただし、標準Javaプログラムで使用する場合、またはエンド・ユーザーで表示する場合は、データを標準Java型に変換する必要があります。

BigDecimalのサポート強化

SQLJでは、java.math.BigDecimalを次の用途に使用できます。

  • SQLJ実行文中のホスト変数として

  • ストアド・ファンクション・コールの戻り値として

  • イテレータの列型として

標準SQLJで数値データおよび10進データの値をBigDecimalとして取得するには、JDBCのデフォルト・マッピングを使用する必要があります。

関連項目:

表6-1

標準SQLJに対し、Oracle SQLJ実装では、Oracle9i 以上のデータベース、Oracle JDBCドライバ、Oracle固有コード生成またはOracleカスタマイザ、およびOracle SQLJランタイムを使用すると、数値から変換可能なデータ型をデフォルト以外の型にマッピングできます。変換可能な型としては、CHARVARCHAR2LONGおよびNUMBERがあります。たとえば、CHAR列のデータは、BigDecimal変数への取込みができます。ただし、エラーを防ぐために、文字データの内容を数字のみにする必要があります。

注意:

BigDecimalクラスは標準java.mathパッケージにあります。