7 オブジェクト、コレクションおよびOPAQUE型

この章では、Oracle SQLJ実装でユーザー定義SQL型がどのようにサポートされるかを説明します。この章の最後には、Oracle OPAQUE型に関する項もあります。

この章では、次の項目について説明します。

7.1 Oracleのオブジェクトとコレクションについて

ここでは、Oracle Database 12c リリース2 (12.2)のオブジェクトとコレクションの背景となる概念について説明します。

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

7.1.1 オブジェクトとコレクションの概要

Oracle SQLJ実装では、ユーザー定義のSQLオブジェクト型(複合データ構造体)、関連するSQLオブジェクト参照型およびユーザー定義のSQLコレクション型がサポートされています。Oracleのオブジェクトとコレクションは、複合データ構造体であり、複数のデータ要素で構成されます。

Oracle SQLJ実装では、強い型指定または弱い型指定の、Java表現のオブジェクト型、参照型およびコレクション型を、イテレータまたはホスト式で使用できます。強い型指定の表現では、特定のオブジェクト型、参照型またはコレクション型にマッピングするカスタムJavaクラスを使用しますが、このクラスはJava Database Connectivity (JDBC) 2.0標準のjava.sql.SQLDataインタフェース(オブジェクト型の場合のみ)またはOracleのoracle.sql.ORADataインタフェースを実装する必要があります。

「強い型指定」という用語は、特定のJava型がSQL名前指定型またはユーザー定義型に対応付けられている場合に使用されます。たとえば、PERSON型がある場合、対応するPerson Javaクラスが対応付けられます。

弱い型指定の表現では、oracle.sql.STRUCT(オブジェクトの場合)、oracle.sql.REF(オブジェクト参照の場合)またはoracle.sql.ARRAY(コレクションの場合)を使用します。弱い型指定の表現では、標準java.sql.Structjava.sql.Refまたはjava.sql.Arrayオブジェクトもかわりに使用できます。

「弱い型指定」という用語は、Java型が汎用的な方法で使用され、複数のSQL名前指定型にマップできる場合に使用されます。Javaクラス(インタフェース)には、SQL型に特有の情報はありません。これは、oracle.sql.STRUCToracle.sql.REFおよびoracle.sql.ARRAY型とjava.sql.Structjava.sql.Refおよびjava.sql.Array型の場合です。

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

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

  • デフォルトのOracle固有コード生成を使用するか、またはISOコード生成の場合は必要に応じてプロファイルをカスタマイズします。Oracle固有生成コードの場合、プロファイルが生成されないため、カスタマイズは適用されません。Oracle JDBC Application Program Interface(API)は、生成されたJavaコードによって直接コールされます。

    ノート:

    デフォルトのカスタマイザoracle.sqlj.runtime.util.OraCustomizerを使用することをお薦めします。

  • アプリケーションの実行時にOracle SQLJランタイムを使用します。Oracle SQLJランタイムとOracle JDBCドライバは、Oracle拡張型をコード中に使用しない場合であっても、Oracleカスタマイザを使用する場合は常に必要です。

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

ノート:

Oracleオブジェクトとコレクション用のOracle固有の型は、oracle.sqlパッケージにあります。

カスタムJavaクラスの使用上のノート

  • この章では、主に、ユーザー定義型に対するカスタムJavaクラスの使用方法について説明します。ただし、ORADataを実装したクラスは、他のOracle SQL型に対しても使用できます。ORADataを実装したクラスを使用すると、SQLとJava間のデータ転送時に任意の処理または変換を実行できます。

  • SQLDataインタフェースは、カスタム・オブジェクト・クラス専用です。これに対し、ORADataインタフェースは、どのカスタムJavaクラスにも使用できます。

用語上のノート

  • ユーザー定義のSQLオブジェクト型とSQLコレクション型は、ユーザー定義型(UDT)と呼ばれます。

  • オブジェクト、参照およびコレクションのカスタムJavaクラスは、それぞれカスタム・オブジェクト・クラス、カスタム参照クラスおよびカスタム・コレクション・クラスと呼ばれます。

関連項目:

Oracleオブジェクトの特長と機能の概要は、『Oracle Databaseオブジェクト・リレーショナル開発者ガイド』を参照してください。

7.1.2 Oracleオブジェクトの基本概念

Oracle SQLオブジェクトは複合データ構造体であり、関連するデータ項目(各従業員に関する事実など)を1つのデータ単位にまとめたものです。オブジェクト型は、機能的にはJavaのクラスに相当します。Java型をインスタンス化してオブジェクトをいくつでも使用できるように、特定のオブジェクト型のオブジェクトをいくつでも設定して使用できます。

たとえば、CHAR型の属性nameCHAR型のaddressCHAR型のphonenumberおよびNUMBER型のemployeenumberを持つオブジェクト型EMPLOYEEを定義できます。

Oracleのオブジェクトのメソッド(ストアド・プロシージャ)は、オブジェクト型への対応付けができます。これらのメソッドは、staticメソッドまたはインスタンス・メソッドとして、PL/SQLまたはJavaで実装できます。メソッドのシグネチャとして、任意の数の入力、出力または入出力パラメータを使用できます。すべては初期定義によって決まります。

7.1.3 Oracleコレクションの基本概念

Oracle SQLコレクションは、次の2つのカテゴリに分類されます。

  • 可変長配列(VARRAY型)

  • ネストした表(NESTED TABLE型)

どちらのカテゴリも1次元ですが、その要素として複合オブジェクト型を使用できます。VARRAY型は1次元配列に対して使用しますが、NESTED TABLE型は表に含まれる単一列の表に対して使用します。VARRAY型の変数をVARRAYと呼びます。NESTED TABLE型の変数をネストした表と呼びます。

VARRAYは、配列と同様、データ要素の順序付けられた集合であり、各要素が1つの索引を持ち、すべての要素のデータ型が同じです。VARRAYのサイズは、要素の最大数を示します。OracleのVARRAYは、名前が示すように可変サイズですが、VARRAY型の宣言時に、そのVARRAY型の最大サイズを指定する必要があります。

ネストした表は、順序付けされていない要素の集合です。表内のネストした表要素は、それ自体SQLで問合せを実行します。表と同じように、ネストした表の行数は、作成時に指定する必要はありません。行数は動的に算出されます。

ノート:

VARRAYの各要素またはネストした表内の各行は、ユーザー定義のオブジェクト型にすることが可能であり、ユーザー定義オブジェクト型の属性に対して、VARRAY型およびNESTED TABLE型を使用できます。Oracle Database 12c リリース2 (12.2)では、コレクション型のネストをサポートしています。VARRAYの要素またはネストした表内の行を、別のVARRAY型またはNESTED TABLE型にすることも、VARRAY属性またはネストした表の属性を持つユーザー定義のオブジェクト型にすることもできます。

7.1.4 オブジェクトとコレクションのデータ型

Oracle Database 12c リリース2 (12.2)では、ユーザー定義のオブジェクトおよびコレクション定義は、SQLのデータ型定義として機能します。これらのデータ型は、他のデータ型と同じように、表の列定義、SQLオブジェクトの属性定義およびストアド・プロシージャまたはストアド・ファンクションのパラメータの定義に使用できます。また、オブジェクト型を定義すると、そのオブジェクト参照型を他のSQL参照型のように使用できます。

たとえば、前の項のEMPLOYEE Oracleオブジェクトを考えます。このオブジェクトを定義すると、Oracleのデータ型になります。NUMBER型の表列と同じように、EMPLOYEE型の表列を持つことができます。EMPLOYEE列の各行に、EMPLOYEEオブジェクト全体を格納できます。EMPLOYEEオブジェクトへの参照で構成されるREF EMPLOYEE型の列を持つこともできます。

同様に、NUMBERVARRAY(10)としてVARRAY型MYVARRを定義したり、CHAR(20)のNESTED TABLE型NTBLを定義できます。コレクション型MYVARRNTBLはOracleのデータ型になるので、これらの型の列を表に定義できます。MYVARR列の各行は、最大10数字を収容する配列です。NTBL列の各行は20文字で構成されます。

7.2 カスタムJavaクラス

カスタムJavaクラスはファーストクラスの型で、ユーザー定義SQL型に対する読取りおよび書込みを透過的に行う場合に使用できます。カスタムJavaクラスの用途は、SQLとJava間のデータの変換手段を提供し、データへのアクセスを可能にすることです。この機能は、特に、オブジェクトとコレクションのサポートにおいて、またはデータのカスタム変換を行う場合に重要です。

SQLJアプリケーションで使用するすべてのユーザー定義型に対して、カスタムJavaクラスを用意することをお薦めします。Oracle JDBCドライバではデータ変換にこのクラスのインスタンスが使用され、弱い型指定のoracle.sql.STRUCToracle.sql.REFおよびoracle.sql.ARRAYクラスを使用するも便利で、エラーも発生しにくくなります。

SQLJイテレータまたはホスト式で使用するには、カスタムJavaクラスにoracle.sql.ORADataおよびoracle.sql.ORADataFactoryインタフェース、または標準java.sql.SQLDataインタフェースを実装する必要があります。ここでは、これらのインタフェースとカスタムJavaクラス機能の概要を、次の項目について説明します。

7.2.1 カスタムJavaクラスのインタフェース指定

ここでは、ORADataおよびORADataFactoryインタフェースと標準SQLDataインタフェースの指定について説明します。

Oracle Database 12c リリース2 (12.2)には、ユーザー定義型(oracle.sql.ORADataおよびoracle.sql.ORADataFactory)に対するOracle固有のカスタムJavaクラス機能のための一連のAPIが含まれています。

ORADataおよびORADataFactoryの指定

Oracleには、oracle.sql.ORADataインタフェースと関連するoracle.sql.ORADataFactoryインタフェースが用意されています。これらのインタフェースを使用して、Oracleのオブジェクト型、参照型およびコレクション型をカスタムJavaクラスにマッピングできます。

データの送信や取出しにはoracle.sql.Datumオブジェクトの形式を使用し、元のデータは適切なoracle.sql.Datumのサブクラス(oracle.sql.STRUCTなど)の形式にします。このデータはSQL形式のままです。oracle.sql.Datumオブジェクトはラッパーとしてのみ機能します。

Java形式からSQL形式へのデータ変換に使用するtoDatum()メソッドは、ORADataインタフェースで指定します。このメソッドでは、接続オブジェクトを入力として見なし、データをoracle.sql.*表現に変換します。JDBCドライバは、実行時に接続オブジェクトを使用して、型チェックと型変換を行います。ORADatatoDatum()は、次のように指定します。

interface oracle.sql.ORAData
{
   oracle.sql.Datum toDatum(java.sql.Connection c) throws SQLException;
}

カスタムJavaクラスのインスタンスを作成し、SQL形式からJava形式に変換するcreate()メソッドは、ORADataFactoryインタフェースで指定します。このメソッドは、入力としてDatumオブジェクトを取り、このオブジェクトにはデータおよび型コード(基になるデータがSQL型であることを示すOracleTypes.RAWなど)が含まれます。このメソッドから戻されるカスタムJavaクラスのオブジェクトには、ORADataインタフェースが実装されています。このオブジェクトは、入力されたDatumオブジェクトからデータを取り出します。ORADataFactorycreate()は、次のように指定します。

interface oracle.sql.ORADataFactory
{
   oracle.sql.ORAData create(oracle.sql.Datum d, int sqlType) 
                      throws SQLException;
}

ORADataインタフェースとORADataFactoryインタフェース間の関係を確立するには、ORADataインタフェースを実装したカスタムJavaクラスに、static getORADataFactory()メソッドを実装する必要があります。このメソッドで戻されるオブジェクトは、ORADataFactoryインタフェースが実装されているので、カスタムJavaクラスのインスタンスの作成に使用できます。戻されたオブジェクト自体が、カスタムJavaクラスのインスタンスであることも可能であり、この場合、Oracle JDBCドライバは、必要に応じてそのcreate()メソッドを使用して、さらにカスタムJavaクラスのインスタンスを生成します。

SQLDataの指定

標準JDBC 2.0には、構造化オブジェクト型をJavaクラスにマッピングし、変換するためのjava.sql.SQLDataインタフェースが提供されています。このインタフェースは、構造化オブジェクト型のみをマッピングの対象としているため、コレクションや配列などのSQL型はマッピングできません。

SQLDataインタフェースは、JDBC 2.0標準であり、readSQL()メソッドを指定してデータをJavaオブジェクトに読み取る一方で、writeSQL()メソッドを指定してJavaオブジェクトからデータベースに書き込みます。

標準SQLData機能の追加詳細は、Sun社のJDBC 2.0以上のAPI仕様を参照してください。

7.2.2 オブジェクト・メソッドでのカスタムJavaクラスのサポート

Oracleオブジェクト・メソッドは、カスタムJavaクラスのラッパーから起動できます。基になるストアド・プロシージャの記述言語がPL/SQL、Javaのどちらであるか、またSQLに公開されているかどうかは、ユーザーには表示されません。

Javaのラッパー・メソッドでサーバー側メソッドを起動するには、サーバーと通信するための接続が必要です。接続オブジェクトは、明示的パラメータとして提供することも、別の方法で関連付けることも可能です。たとえば、カスタムJavaクラスの属性として提供します。ラッパー・メソッドで使用する接続オブジェクトを非静的属性とした場合、このラッパー・メソッドをカスタムJavaクラスのインスタンス・メソッドとすると、この接続にアクセスできます。

Oracleオブジェクト・メソッドの出力および入出力パラメータに関しては、次のことに留意してください。ストアド・プロシージャ(SQLオブジェクト・メソッド)によって、ある引数の内部状態が変更された場合、ストアド・プロシージャに渡された実引数も変更されます。Javaで記述した場合、こうした現象は起こりません。JDBC出力パラメータがストアド・プロシージャ・コールから戻る場合、新規に作成されたオブジェクトに格納する必要があります。元のオブジェクト識別性は失われます。

出力または入出力パラメータをコール元に戻す場合は、パラメータを配列要素として渡す方法もあります。入出力パラメータの場合は、ラッパー・メソッドが入力として配列要素をとります。処理後、ラッパーによって出力が配列要素に代入されます。

7.2.3 カスタムJavaクラスの要件

カスタムJavaクラスに求められる要件としては、有効なホスト変数型としてOracle SQLJトランスレータで認識できることおよびトランスレータで型チェックできることが挙げられます。

ノート:

ユーザー定義型用のカスタムJavaクラスは、このマニュアルで「ラッパー・クラス」と呼ばれている場合が多くあります。

ORADataを実装したクラスに対するOracleの要件

ORADataを実装するためのOracleでの要件は、どのカスタムJavaクラスの場合にも基本的には同じです。ただし、クラスによるマッピング先が、オブジェクト、オブジェクト参照、コレクションまたはその他のSQL型のどれに該当するかによって、この要件は若干異なってきます。

この要件を次に示します。

  • oracle.sql.ORADataインタフェースを実装したクラスであること。

  • oracle.sql.ORADataFactoryオブジェクトを戻り値とするgetORADataFactory()メソッドを実装したクラスであること。メソッドのシグネチャは、次のように指定します。

    public static oracle.sql.ORADataFactory getORADataFactory();
    
  • クラスがString定数_SQL_TYPECODEを持ち、この定数が、toDatum()から戻されるDatumサブクラス・インスタンスのoracle.jdbc.OracleTypes型コードに初期化されること。型コードを次に示します。

    • カスタム・オブジェクト・クラスの場合

      public static final int _SQL_TYPECODE = OracleTypes.STRUCT;
      
    • カスタム参照クラスの場合

      public static final int _SQL_TYPECODE = OracleTypes.REF;
      
    • カスタム・コレクション・クラスの場合

      public static final int _SQL_TYPECODE = OracleTypes.ARRAY;
      

    これ以外のクラスの場合は、それぞれ適切な型コードで初期化します。たとえば、カスタムJavaクラスで、RAWフィールドに対してJavaオブジェクトをシリアライズおよびシリアライズ解除するには、_SQL_TYPECODEOracleTypes.RAWに初期化します。

    ノート:

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

  • _SQL_TYPECODESTRUCTREFまたはARRAYのいずれかに該当する(つまり、オブジェクト、オブジェクト参照またはコレクションを表す)カスタムJavaクラスの場合、該当のユーザー定義型の名前を示す定数を保持していること。次に例を示します。

    • カスタム・オブジェクト・クラスおよびカスタム・コレクション・クラスのString定数_SQL_NAMEは、ユーザー定義型に対して宣言したSQL名に初期化する必要があります。次に例を示します。

      public static final String _SQL_NAME = UDT name;
      

      たとえば、ユーザー定義のPERSONオブジェクトのカスタム・オブジェクト・クラスの定数は次のようになります。

      public static final String _SQL_NAME = "PERSON";
      

      次のように、必要に応じて、スキーマで同様に指定できます。

      public static final String _SQL_NAME = "HR.PERSON";
      

      PERSONオブジェクトのコレクションをPERSON_ARRAYと宣言した場合のカスタム・コレクション・クラスの定数は、次のようになります。

      public static final String _SQL_NAME = "PERSON_ARRAY";
      
    • カスタム参照クラスのString定数_SQL_BASETYPEは、参照先のユーザー定義型に対して宣言したSQL名に初期化する必要があります。

      public static final String _SQL_BASETYPE = UDT name;
      

      PERSON参照のカスタム参照クラスの定数は、次のようになります。

      public static final String _SQL_BASETYPE = "PERSON";
      

      前述以外のORADataで使用する場合、UDT名の指定はありません。

次の使用上のノートに留意してください。

  • コレクション型の名前は、ベース型ではなく、コレクション型を表す名前にします。たとえば、PERSONオブジェクトに対し、VARRAYまたはNESTED TABLE型のPERSON_ARRAYを宣言した場合は、_SQL_NAMEエントリに指定するコレクション型の名前は、PERSONではなく、PERSON_ARRAYになります。

  • SQL型を_SQL_NAMEフィールドに指定するときに、SQL型が大/小文字を区別して(引用符で囲んで)宣言されている場合、CaseSensitiveHR.CaseSensitiveなど、宣言されているとおりにSQL名を指定する必要があります。

SQLDataを実装したクラスに対する要件

ISO SQLJ規格では、SQLDataインタフェースを実装するクラスへの型マップ定義の要件を概説しています。一方、SQLDataラッパー・クラスは、public static finalフィールドでSQL関連オブジェクト型を識別できます。

次の点は重要なので、十分に注意してください。

  • マッピングの指定に型マップを使用する場合でも、標準でないpublic static finalフィールドを使用する場合でも、一貫して指定する必要があります。public static finalフィールドを不要にする目的ですべての関連マッピングを指定する型マップを使用するか、または型マップをまったく使用せずにすべてのマッピングをpublic static finalフィールドで指定します。

  • SQLDataは、ORADataとは異なり、構造化オブジェクト型のマッピング専用です。オブジェクト参照やコレクションや配列などのSQL型のマッピングには使用できません。ORADataを使用しない場合、オブジェクト参照とコレクションをマッピングする方法は、それぞれ弱い型指定のjava.sql.Refjava.sql.Array、またはoracle.sql.REForacle.sql.ARRAYを使用する方法のみです。

  • SQLData実装には、Java Development Kit (JDK) 1.4.xまたは1.5.x環境が必要です。

  • SQL型からJava型へのマッピングを指定するとき、SQL型が大/小文字を区別して宣言されている場合、CaseSensitiveHR.CaseSensitiveなど、宣言されているとおりにSQL名を指定する必要があります。

型マップ・リソースで指定したマッピング

まず、ISO SQLJ規格に従ったマッピング表現を想定します。Addresspack.Personおよびpack.Manager.InnerPM(InnerPMManagerの内部クラスです)がjava.sql.SQLDataを実装している3つのラッパー・クラスであるとします。

次の点を考慮する必要があります。

  • これらのクラスは、宣言済の接続コンテキスト型の明示的接続コンテキスト・インスタンスを使用する文でのみ採用する必要があります。たとえば、この型をSDContextと呼ぶとします。

    Address               a =...;
    pack.Person           p =...;
    pack.Manager.InnerPM pm =...;
    SDContext ctx = new SDContext(url,user,pwd,false);
    #sql [ctx] { ... :a ... :p ... :pm ... };
    
  • 接続コンテキスト型は、java.util.PropertyResourceBundleを実装している関連クラスを指定するwith属性のtypeMapで宣言されている必要があります。前述の例では、SDContextが次のように宣言されています。

    #sql public static context SDContext with (typeMap="SDMap");
    
  • 型マップのリソースは、SQLオブジェクト型からjava.sql.SQLDataインタフェースを実装するJavaクラスにマッピングしている必要があります。このマッピングは、次の形式のエントリで指定します。

    class.java_class_name=STRUCT sql_type_name
    

    STRUCTキーワードは省略可能です。例では、SDMap.propertiesリソース・ファイルに次のエントリがあります。

    class.Address=STRUCT HR.ADDRESS
    class.pack.Person=PERSON
    class.pack.Manager$InnerPM=STRUCT PRODUCT_MANAGER
    

    パッケージとクラス名の分割にはピリオド(.)を使用しますが、内部クラス名を分割するにはドル記号($)を使用する必要があります

ノート:

デフォルトのOracle固有コード生成を使用している場合、コンテキスト型がSDContextの文に使用されるイテレータにも同じ型マップSDMapが宣言されていることが必要です。次に例を示します。

#sql public static iterator SDIter with (typeMap="SDMap");
...
SDContext sdctx = ...
SDIter sditer;
#sql [sdctx] sditer = { SELECT ...};

これにより、イテレータ・クラスに正しいコードが生成されたことを確認します。

マッピングを型マップ・リソースに指定するこのメカニズムは、非標準の方法よりも複雑になっています。さらに、型マップ・リソースをデフォルトの接続コンテキストと関連付けることはできません。すべてのマッピング情報が1つの場所、型マップ・リソースに置かれるというメリットがあります。つまり、コンパイル済アプリケーションでの型マッピングは、後で簡単に調整できます。たとえば、新規SQL型およびJavaラッパーを拡張SQL-Java型階層に含めることができます。

以下の点に注意します。

  • この機能を使用するには、SQLJ runtime12またはruntime12eeライブラリを採用する必要があります。型マップはjava.util.Mapオブジェクトで表されます。これらは、SQLJランタイムAPIで公開されていますが、汎用ランタイム・ライブラリではサポートできません

  • SQLDataラッパー・クラスがSQLJ文のOUTまたはINOUTパラメータに現れた場合、Oracle SQLJランタイムおよびOracle固有コード生成またはプロファイルのカスタマイズを使用する必要があります。これは、Oracle JDBCがそのようなパラメータのSQL型をregisterOutParameter()で必要とするためです。さらに、OUTパラメータ型を登録するため、SQL型は変換中に有力な型マップにより凍結されます。

  • SQLJ型マップは基礎となる接続で使用しているJDBC型マップとは独立しています。このため、SQLDataラッパーを使用しているSQLJコードおよびJDBCコードを混在させている場合は、注意が必要です。しかし、指定されたSQLJ接続コンテキスト上の有効な型マップは、簡単に抽出できます。

    ctx.getTypeMap();

ラッパー・クラスの静的フィールドで指定されたマッピング

SQLDataを実装しているクラスは、次の非標準要件を満たすことができます。

  • JavaクラスはString定数_SQL_NAMEを宣言し、そこでJavaクラスによってラップされるSQL型の名前を定義します。例として、Addressクラスには次のようなフィールド宣言があります。

    public static final String _SQL_NAME="HR.ADDRESS";
    

    次の宣言はpack.Person内にあります。

    public static final String _SQL_NAME="PERSON";
    

    また、pack.Manager.InnerPMクラスには次の要素があります。

    public static final String _SQL_NAME="PRODUCT_MANAGER";
    

ノート:

  • _SQL_NAMEフィールドを実装するクラスが明示的接続コンテキスト型および関連型マップとともにSQLJ文内で使用されている場合、その型マップが使用され、_SQL_NAMEフィールドは無視されます。これにより、既存のSQLJプログラムのISO SQLJ規格への移行は簡素化されます。

  • _SQL_NAMEフィールドで指定した静的SQL-Java型の対応は、基礎となる接続で使用しているJDBC型マップからは独立しています。このため、SQLDataラッパーを使用しているSQLJコードおよびJDBCコードを混在させている場合は、注意が必要です。

7.2.4 カスタムJavaクラスのコンパイル

SQLJのコマンドラインには、カスタムJavaクラス(ORADataまたはSQLDataを実装)の.javaファイルを、アプリケーションの.sqljファイルと一緒に指定できます。ただし、この指定は、SQLJの-checksourceフラグをtrue (デフォルト)に設定し、CLASSPATHにカスタムJavaソースの保存ディレクトリを指定した場合には必要ありません。

ノート:

ここでは、カスタム・オブジェクトとカスタム・コレクション用に、.sqljファイルではなく.javaファイルを作成していることを想定しています。.sqljファイルは、SQLJコマンドラインで指定される必要があります。

たとえば、ObjectDemo.sqljでOracleのオブジェクト型ADDRESSおよびPERSONを使用し、これらのオブジェクトのカスタムJavaクラスが生成されている場合は、次のようにSQLJを実行できます。

  • -checksource=trueを指定し、CLASSPATHにカスタムJavaソースの保存ディレクトリを指定した場合は、次のようにします。

    % sqlj ObjectDemo.sqlj
    
  • -checksource=falseを指定した場合は、次のようにします(この行は、全体を1行で入力します)。

    % sqlj ObjectDemo.sqlj Address.java AddressRef.java Person.java PersonRef.java
    

また、Javaコンパイラを使用してカスタム.javaソース・ファイルを直接コンパイルすることもできます。この場合は、.sqljファイルの変換前に、コンパイルする必要があります。

ノート:

ORAData実装はOracle固有の機能を必要とするため、トランスレータの移植設定を-warn=noportable (デフォルト)に設定する必要があり、この設定にしない場合は移植性に関する多数の警告がSQLJから通知されます。

7.2.5 カスタム・データの読込みと書込み

カスタムJavaクラス・インスタンスを使用すると、Oracle SQLJおよびJDBC実装では、ユーザー定義型の読込みと書込みが可能になります(ただし、これらは組込み型です)。この仕組みをユーザーが意識することはありません。

ORAData実装およびSQLData実装でのデータの読込み/書込みの仕組みは、『Oracle Database JDBC開発者ガイド』を参照してください。

7.2.6 ORAData実装のその他の使用例

ここまでは、カスタムJavaクラスについて、次のような使用例を取り上げてきました。

  • SQLオブジェクトのラッパー: このカスタム・オブジェクト・クラスは、oracle.sql.STRUCTインスタンスに対して使用します。

  • SQL参照のラッパー: このカスタム参照クラスは、oracle.sql.REFインスタンスに対して使用します。

  • SQLコレクションのラッパー: このカスタム・コレクション・クラスは、oracle.sql.ARRAYインスタンスに対して使用します。

これ以外のoracle.sql.*型をラッピングする場合でも、カスタムJavaクラスを用意すると独自の変換または処理に役立つことがあります。このためには、次の例に示すように、ORADataを実装したクラスを使用してください(SQLData使用しません)。

  • データの暗号化と復号化、またはデータの妥当性チェックを行う場合

  • 読込み後または書込み前の値をロギングする場合

  • CHAR列(URL情報を格納した文字フィールドなど)をより小さいコンポーネントに解析する場合

  • 文字列を数値定数にマッピングする場合

  • データをより適切なJava形式にマッピングする場合(DATEフィールドをjava.util.Date形式にマッピングする場合など)

  • データ表現をカスタマイズする場合(選択した表のフィート単位データをメートル単位で表現する場合など)

  • Javaオブジェクトをシリアライズおよびシリアライズ解除する場合(RAWフィールドなどに対して)

ノート:

このような機能は、SQLDataインタフェースを使用しても実現されません。SQLData実装でラッピング対象となるのは構造化オブジェクト型のみに限られているためです。

ORADataの一般的な使用方法: BetterDate.java

ここでは、ORADataインタフェースを実装したクラスを使用して、Javaの日付をカスタマイズする方法を示します。これは、java.sql.Dateのかわりに使用できます。

ノート:

これは、完全なアプリケーションではありません。main()メソッドはありません。

import java.util.Date;
import oracle.sql.ORAData;
import oracle.sql.DATE;
import oracle.sql.ORADataFactory;
import oracle.jdbc.OracleTypes;

// a Date class customized for user's preferences:
//      - months are numbers 1..12, not 0..11
//      - years are referred to through four-digit numbers, not two.

public class BetterDate extends java.util.Date
             implements ORAData, ORADataFactory {
  public static final int _SQL_TYPECODE = OracleTypes.DATE;
  
  String[]monthNames={"JAN", "FEB", "MAR", "APR", "MAY", "JUN",
                      "JUL", "AUG", "SEP", "OCT", "NOV", "DEC"};
  String[]toDigit={"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"};

  static final BetterDate _BetterDateFactory = new BetterDate();

  public static ORADataFactory getORADataFactory() { return _BetterDateFactory;}

  // the current time...
  public BetterDate() {
    super();
  }

  public oracle.sql.Datum toDatum(java.sql.Connection conn) {
    return new DATE(toSQLDate());
  }

  public oracle.sql.ORAData create(oracle.sql.Datum dat, int intx) {
    if (dat==null) return null;
    DATE DAT = ((DATE)dat);
    java.sql.Date jsd = DAT.dateValue();
    return new BetterDate(jsd);
  }
   
  public java.sql.Date toSQLDate() {
    java.sql.Date retval;
    retval = new java.sql.Date(this.getYear()-1900, this.getMonth()-1,
             this.getDate());
    return retval;
  }
  public BetterDate(java.sql.Date d) {
    this(d.getYear()+1900, d.getMonth()+1, d.getDate());
  }
  private static int [] deconstructString(String s) {
    int [] retval = new int[3];
    int y,m,d; char temp; int offset;
    StringBuffer sb = new StringBuffer(s);
    temp=sb.charAt(1);
    // figure the day of month
    if (temp < '0' || temp > '9') {
      m = sb.charAt(0)-'0';
      offset=2;
    } else {
      m = (sb.charAt(0)-'0')*10 + (temp-'0');
      offset=3;
    }

    // figure the month
    temp = sb.charAt(offset+1);
    if (temp < '0' || temp > '9') {
      d = sb.charAt(offset)-'0';
      offset+=2;
    } else {
      d = (sb.charAt(offset)-'0')*10 + (temp-'0');
      offset+=3;
    }

    // figure the year, which is either in the format "yy" or "yyyy"
    // (the former assumes the current century)
    if (sb.length() <= (offset+2)) {
      y = (((new BetterDate()).getYear())/100)*100 +
          (sb.charAt(offset)- '0') * 10 +
          (sb.charAt(offset+1)- '0');
    } else {
      y = (sb.charAt(offset)- '0') * 1000 +
          (sb.charAt(offset+1)- '0') * 100 +
          (sb.charAt(offset+2)- '0') * 10 +
          (sb.charAt(offset+3)- '0');
    }
    retval[0]=y;
    retval[1]=m;
    retval[2]=d;
//    System.out.println("Constructing date from string as: "+d+"/"+m+"/"+y);
    return retval;
  }
  private BetterDate(int [] stuff) {
    this(stuff[0], stuff[1], stuff[2]);
  }
  // takes a string in the format: "mm-dd-yyyy" or "mm/dd/yyyy" or
  // "mm-dd-yy" or "mm/dd/yy" (which assumes the current century)
  public BetterDate(String s) {
    this(BetterDate.deconstructString(s));
  }

  // years are as '1990', months from 1..12 (unlike java.util.Date!), date
  // as '1' to '31' 
  public BetterDate(int year, int months, int date) {
    super(year-1900,months-1,date);
  }
  // returns "Date: dd-mon-yyyy"
  public String toString() { 
    int yr = getYear();
    return getDate()+"-"+monthNames[getMonth()-1]+"-"+
      toDigit[(yr/1000)%10] + 
      toDigit[(yr/100)%10] + 
      toDigit[(yr/10)%10] + 
      toDigit[yr%10];
//    return "Date: " + getDate() + "-"+getMonth()+"-"+(getYear()%100);
  }
  public BetterDate addDays(int i) {
    if (i==0) return this;
    return new BetterDate(getYear(), getMonth(), getDate()+i);
  }
  public BetterDate addMonths(int i) {
    if (i==0) return this;
    int yr=getYear();
    int mon=getMonth()+i;
    int dat=getDate();
    while(mon<1) { 
      --yr;mon+=12;
    }
    return new BetterDate(yr, mon,dat);
  }
  // returns year as in 1996, 2007
  public int getYear() {
    return super.getYear()+1900;
  }
  // returns month as 1..12
  public int getMonth() {
    return super.getMonth()+1;
  }
  public boolean equals(BetterDate sd) {
    return (sd.getDate() == this.getDate() &&
            sd.getMonth() == this.getMonth() &&
            sd.getYear() == this.getYear());
  }
  // subtract the two dates; return the answer in whole years
  // uses the average length of a year, which is 365 days plus
  // a leap year every 4, except 100, except 400 years =
  // = 365 97/400 = 365.2425 days = 31,556,952 seconds
  public double minusInYears(BetterDate sd) {
    // the year (as defined in the preceding text) in milliseconds
    long yearInMillis = 31556952L;
    long diff = myUTC()-sd.myUTC();
    return (((double)diff/(double)yearInMillis)/1000.0);
  }
  public long myUTC() {
    return Date.UTC(getYear()-1900, getMonth()-1, getDate(),0,0,0);
  }
  
  // returns <0 if this is earlier than sd
  // returns = if this == sd
  // else returns >0
  public int compare(BetterDate sd) {
    if (getYear()!=sd.getYear()) {return getYear()-sd.getYear();}
    if (getMonth()!=sd.getMonth()) {return getMonth()-sd.getMonth();}
    return getDate()-sd.getDate();
  }
}

7.3 ユーザー定義データ型

ここでは、Oracle Database 12c リリース2 (12.2)のユーザー定義オブジェクト型とユーザー定義コレクション型の作成例および使用例を示します。

オブジェクト型の作成

オブジェクト型を作成するSQLコマンドは、次の形式で指定します。

CREATE TYPE typename AS OBJECT
( 
  attrname1    datatype1,
  attrname2    datatype2,
  ...         ...
  attrnameN    datatypeN
);

typenameで、オブジェクト型の名前を指定します。attrname1からattrnameNで、属性名を指定します。datatype1からdatatypeNで、属性のデータ型を指定します。

次に、Oracle Database 12c リリース1 (12.1)におけるユーザー定義オブジェクト型の作成例を示します。

この例では、SQLを使用して次の項目を作成します。

  • 2つのオブジェクト型、PERSONADDRESS

  • PERSONオブジェクト用の型付けされた表。

  • EMPLOYEES表。ADDRESS列が1列とPERSON参照列が2列あります。

これらの項目を作成するスクリプトを次に示します。

/*** Using user-defined types (UDTs) in SQLJ ***/
/
/*** Create ADDRESS UDT ***/
CREATE TYPE ADDRESS AS OBJECT
( 
  street        VARCHAR(60),
  city          VARCHAR(30),
  state         CHAR(2),
  zip_code      CHAR(5)
)
/
/*** Create PERSON UDT containing an embedded ADDRESS UDT ***/
CREATE TYPE PERSON AS OBJECT
( 
  name    VARCHAR(30),
  ssn     NUMBER,
  addr    ADDRESS
)
/
/*** Create a typed table for PERSON objects ***/
CREATE TABLE persons OF PERSON
/
/*** Create a relational table with two columns that are REFs 
     to PERSON objects, as well as a column which is an Address ADT. ***/
CREATE TABLE  employees
( 
  empnumber            INTEGER PRIMARY KEY,
  person_data     REF  PERSON,
  manager         REF  PERSON,
  office_addr          ADDRESS,
  salary               NUMBER
)
/*** Insert some data--2 objects into the persons typed table ***/
INSERT INTO persons VALUES (
            PERSON('Wolfgang Amadeus Mozart', 123456,
               ADDRESS('Am Berg 100', 'Salzburg', 'AT','10424')))
/
INSERT INTO persons VALUES (
            PERSON('Ludwig van Beethoven', 234567,
               ADDRESS('Rheinallee', 'Bonn', 'DE', '69234')))
/
/** Put a row in the employees table **/
INSERT INTO employees (empnumber, office_addr, salary) VALUES (
            1001,
            ADDRESS('500 Oracle Parkway', 'Redwood Shores', 'CA', '94065'),
            50000)
/
/** Set the manager and PERSON REFs for the employee **/
UPDATE employees 
   SET manager =  
       (SELECT REF(p) FROM persons p WHERE p.name = 'Wolfgang Amadeus Mozart')
/
UPDATE employees 
   SET person_data =  
       (SELECT REF(p) FROM persons p WHERE p.name = 'Ludwig van Beethoven')

ノート:

Oracle SQL実装では、特にユーザー定義型の表にアクセスする場合に、表の別名(この例のpなど)の使用を習慣付けることをお薦めします。オブジェクトの属性にアクセスする場合は、この構文を使用する必要があります。別名を使用する必要のない場合でも、別名の使用によって明確化できます。表の別名の詳細は、『Oracle Database SQL言語リファレンス』を参照してください。

コレクション型の作成

コレクションは、次の2つの種類があります。

  • 可変長配列(VARRAY)

  • ネストした表

VARRAY型を作成するSQLコマンドは、次の形式で指定します。

CREATE TYPE typename IS VARRAY(n) OF datatype;

typenameでVARRAY型の名前を、nで配列内の最大要素数を、またdatatypeで配列要素のデータ型を指定します。次に例を示します。

CREATE TYPE myvarr IS VARRAY(10) OF INTEGER;

NESTED TABLE型を作成するSQLコマンドは、次の形式で指定します。

CREATE TYPE typename AS TABLE OF datatype;

typenameでネストした表型の目的の名前を指定し、datatypeで表要素のデータ型を指定します。データ型は標準データ型だけでなく、ユーザー定義型も指定できます。ネストした表は1列のみに限られていますが、1つの列型を、複数の属性を持つ複合オブジェクトにすることも可能です。ネストした表には、データベース表と同様に、行数制限がありません。次に例を示します。

CREATE TYPE person_array AS TABLE OF person;

このコマンドを指定すると、PERSONオブジェクトから構成された各行にNESTED TABLE型が作成されます。

次に、Oracle Database 12c リリース2 (12.2)におけるユーザー定義のコレクション型およびオブジェクト型の作成例を示します。

SQLを使用して次の項目が作成および設定されます。

  • 2つのオブジェクト型PARTICIPANT_TおよびMODULE_T

  • コレクション型MODULETBL_T(MODULE_Tオブジェクトのネストした表)

  • PARTICIPANT_T参照の列およびネストした表のMODULETBL_T列を含むPROJECTS

  • コレクション型PHONE_ARRAY(VARCHAR2(30)のVARRAY)

  • PERSONADDRESSオブジェクト(前に使用した同じ定義が示されています)

  • EMPLOYEES表(PHONE_ARRAY列を含む)

これらの項目を作成するスクリプトを次に示します。

Rem This is a SQL*Plus script used to create schema to demonstrate collection 
Rem manipulation in SQLJ 

CREATE TYPE PARTICIPANT_T AS OBJECT (
  empno   NUMBER(4),
  ename   VARCHAR2(20),
  job     VARCHAR2(12),
  mgr     NUMBER(4),
  hiredate DATE,
  sal      NUMBER(7,2),
  deptno   NUMBER(2)) 
/
SHOW ERRORS 
CREATE TYPE MODULE_T  AS OBJECT (
  module_id  NUMBER(4),
  module_name VARCHAR2(20), 
  module_owner REF PARTICIPANT_T, 
  module_start_date DATE, 
  module_duration NUMBER )
/
SHOW ERRORS
CREATE TYPE MODULETBL_T AS TABLE OF MODULE_T;
/
SHOW ERRORS
CREATE TABLE projects (
  id NUMBER(4),
  name VARCHAR(30),
  owner REF PARTICIPANT_T,
  start_date DATE,
  duration NUMBER(3),
  modules  MODULETBL_T  ) NESTED TABLE modules STORE AS modules_tab;

SHOW ERRORS
CREATE TYPE PHONE_ARRAY IS VARRAY (10) OF varchar2(30)
/

/*** Create ADDRESS UDT ***/
CREATE TYPE ADDRESS AS OBJECT
( 
  street        VARCHAR(60),
  city          VARCHAR(30),
  state         CHAR(2),
  zip_code      CHAR(5)
)
/
/*** Create PERSON UDT containing an embedded ADDRESS UDT ***/
CREATE TYPE PERSON AS OBJECT
( 
  name    VARCHAR(30),
  ssn     NUMBER,
  addr    ADDRESS
)
/
CREATE TABLE  employees
( empnumber            INTEGER PRIMARY KEY,
  person_data     REF  person,
  manager         REF  person,
  office_addr          address,
  salary               NUMBER,
  phone_nums           phone_array
)
/

7.4 SQLJ実行文の強い型指定のオブジェクトと参照

Oracle SQLJ実装では、強い型指定のオブジェクトまたは参照をホスト式およびイテレータで使用して、オブジェクト・データの読込みおよび書込みを柔軟に行えます。

イテレータの場合は、カスタム・オブジェクト・クラスをイテレータの列型として使用できます。または、属性のSQLデータ型にマップしている列型を使用して、イテレータ列をオブジェクトの各属性に対応付けることができます(エクステント表と同様)。

ホスト式の場合は、カスタム・オブジェクト・クラス型またはカスタム参照クラス型のホスト変数を使用できます。または、属性のSQLデータ型にマップしている変数型を使用して、ホスト変数をオブジェクトの属性に対応付けることができます。

次に、Oracleオブジェクトの操作例を示します。SQLJ実行文のホスト変数とイテレータ列に対して、カスタム・オブジェクト・クラス、カスタム・オブジェクト・クラスの属性およびカスタム参照クラスを使用します。

次の2つの例は、オブジェクト・レベルでの操作です。

「各オブジェクト属性から作成したオブジェクトの挿入」の例は、スカラー属性レベルでの操作です。

「オブジェクト参照の更新」の例は、参照による操作です。

7.4.1 オブジェクトとオブジェクト参照のイテレータ列への取出し

この例では、カスタムJavaクラスとカスタム参照クラスをイテレータの列型として使用します。ADDRESS Oracleオブジェクト型が次のように定義されているものとします。

CREATE TYPE ADDRESS AS OBJECT
(  street VARCHAR(40),
   zip NUMBER );

また、EMPADDRS表が次のように定義されているものとします。この表には、ADDRESS列とADDRESS参照列があります。

CREATE TABLE empaddrs
(  name VARCHAR(60),
   home ADDRESS,
   loc REF ADDRESS );

ADDRESS Oracleオブジェクト型に対応するカスタムJavaクラスAddressとカスタム参照クラスAddressRefを作成すると、次のようにAddressAddressRefを名前付きイテレータで使用できます。

#sql iterator EmpIter (String name, Address home, AddressRef loc);

...
EmpIter ecur;
#sql ecur = { SELECT name, home, loc FROM empaddrs };
while (ecur.next()) {
   Address homeAddr = ecur.home();
   // Print out the home address.
   System.out.println ("Name: " + ecur.name() + "\n" +
                       "Home address: " + homeAddr.getStreet() + "   " +
                       homeAddr.getZip());
   // Now update the loc address zip code through the address reference.
   AddressRef homeRef = ecur.loc();
   Address location = homeRef.getValue();
   location.setZip(new BigDecimal(98765));
   homeRef.setValue(location);
   }
...

ecur.home()メソッド・コールは、イテレータのhome列からAddressオブジェクトを抽出し、homeAddrローカル変数に代入します(効率化)。このオブジェクトの属性にアクセスするには、次に示したJavaの標準ドット区切り構文を使用します。

homeAddr.getStreet()

ロケーション・アドレス(この例ではzipコード)を操作するには、getValue()およびsetValue()メソッドを使用します。

7.4.2 オブジェクトの更新

この例では、Address Java型の入力ホスト変数を宣言し、設定することで、employees表の列にあるADDRESSオブジェクトを更新します。更新前と更新後の両方で、アドレスが選択されてAddress型の出力ホスト変数に入力され、検証用に出力されます。

...
// Updating an object 

static void updateObject() 
{

   Address addr;
   Address new_addr;
   int empnum = 1001;

   try {
      #sql {
         SELECT office_addr
         INTO :addr
         FROM employees
         WHERE empnumber = :empnum };
      System.out.println("Current office address of employee 1001:");

      printAddressDetails(addr);

      /* Now update the street of address */

      String street ="100 Oracle Parkway";
      addr.setStreet(street);

      /* Put updated object back into the database */

      try {
         #sql {
            UPDATE employees
            SET office_addr = :addr
            WHERE empnumber = :empnum };
         System.out.println
            ("Updated employee 1001 to new address at Oracle Parkway.");

         /* Select new address to verify update */
      
         try {
            #sql {
               SELECT office_addr
               INTO :new_addr
               FROM employees
               WHERE empnumber = :empnum };
      
            System.out.println("New office address of employee 1001:");
            printAddressDetails(new_addr);

         } catch (SQLException exn) {
         System.out.println("Verification SELECT failed with "+exn); }
      
      } catch (SQLException exn) {
      System.out.println("UPDATE failed with "+exn); }

   } catch (SQLException exn) {
   System.out.println("SELECT failed with "+exn); }
}
...

AddressオブジェクトのsetStreet()アクセッサ・メソッドが使用されています。

この例では、printAddressDetails()ユーティリティを使用しています。このメソッドのソース・コードを次に示します。

static void printAddressDetails(Address a) throws SQLException
{

  if (a == null)  {
    System.out.println("No Address available.");
    return;
   }

   String street = ((a.getStreet()==null) ? "NULL street" : a.getStreet()) ;
   String city = (a.getCity()==null) ? "NULL city" : a.getCity();
   String state = (a.getState()==null) ? "NULL state" : a.getState();
   String zip_code = (a.getZipCode()==null) ? "NULL zip" : a.getZipCode();

   System.out.println("Street: '" + street + "'");
   System.out.println("City:   '" + city   + "'");
   System.out.println("State:  '" + state  + "'");
   System.out.println("Zip:    '" + zip_code + "'" );
}

7.4.3 各オブジェクト属性から作成したオブジェクトの挿入

この例では、PERSONオブジェクトとネストされているADDRESSオブジェクトの属性に対応する入力ホスト変数を宣言し、設定します。これらの値を使用して、新しいPERSONオブジェクトをデータベース内のpersons表に挿入します。

...
// Inserting an object

static void insertObject() 
{
   String new_name   = "NEW PERSON";
   int    new_ssn    = 987654;
   String new_street = "NEW STREET";
   String new_city   = "NEW CITY";
   String new_state  = "NS";
   String new_zip    = "NZIP";
  /*
   * Insert a new PERSON object into the persons table
   */
   try {
      #sql {
         INSERT INTO persons
         VALUES (PERSON(:new_name, :new_ssn,
         ADDRESS(:new_street, :new_city, :new_state, :new_zip))) };

      System.out.println("Inserted PERSON object NEW PERSON."); 

   } catch (SQLException exn) { System.out.println("INSERT failed with "+exn); }
}
...

7.4.4 オブジェクト参照の更新

この例では、persons表からPERSON参照を選択し、この値を使用してemployees表内のPERSON参照を更新します。属性値の基準チェックには、単純な入力ホスト変数を使用します。新しく更新された参照を使用して、その参照先のPERSONオブジェクトを選択することで、情報を出力して、ユーザーが変更内容を検証できるようになります。

...
// Updating a REF to an object

static void updateRef()
{
   int empnum = 1001;
   String new_manager = "NEW PERSON";

   System.out.println("Updating manager REF.");
   try {
      #sql {
         UPDATE employees
         SET manager = 
            (SELECT REF(p) FROM persons p WHERE p.name = :new_manager)
         WHERE empnumber = :empnum };

      System.out.println("Updated manager of employee 1001. Selecting back");

   } catch (SQLException exn) {
   System.out.println("UPDATE REF failed with "+exn); }

   /* Select manager back to verify the update */
   Person manager;

   try { 
      #sql {
         SELECT deref(manager)
         INTO :manager
         FROM employees e
         WHERE empnumber = :empnum };

      System.out.println("Current manager of "+empnum+":");
      printPersonDetails(manager);

   } catch (SQLException exn) {
   System.out.println("SELECT REF failed with "+exn); }

}
...

ノート:

この例では、前述の表の別名構文(p)を使用しています。また、参照先オブジェクトから参照を選択するには、REF構文が必要になり、参照からオブジェクトを選択するには、DEREF構文が必要になります。表の別名、REFおよびDEREFの詳細は、『Oracle Database SQL言語リファレンス』を参照してください。

7.5 SQLJ実行文の強い型指定のコレクション

Oracle SQLJ実装では、強い型指定のオブジェクトおよび参照と同じように、強い型指定のコレクションをイテレータまたはホスト式で使用して、データの読込みおよび書込みを行えます。

SQLJ開発者にとっては、2種類のコレクション(VARRAYとネストした表)の扱いは基本的に同じですが、実装とパフォーマンスの面で相違点が多少あります。

Oracle SQLJ実装では、構文の複数の選択肢がサポートされていて、ネストした表(内側の表)にアクセスして、操作できるようになっています(外側の表から切り離して、または外側の表と一緒に操作することも可能です)。ここでは、ネストの内側の表のみの操作を詳細レベルの操作と呼び、ネストの内側の表と外側の表の同時操作をマスター・レベルの操作と呼びます。

ここでは、いくつかの構文について簡単に説明してから、ネストした表の操作例を示します。ネストした表の操作は、VARRAYの操作より複雑です。

ノート:

Oracle SQLJ実装では、VARRAY型およびNESTED TABLE型を型単位でのみ取得できます。これは、Oracle SQL実装とは違い、ネストした表の問合せを選択的に実行できます。

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

7.5.1 ネストした表へのアクセス: TABLE構文とCURSOR構文

Oracle SQLJ実装では、ネストしたイテレータを使用して、ネストした表のデータにアクセスできます。外部のSELECT文でCURSORキーワードを使用し、内部のSELECT文をカプセル化します。「ネスト・イテレータによるネストした表からのデータの選択」を参照してください。

Oracleでは、TABLEキーワードを使用すると、ネストした表を行単位で操作できます。Oracleではこのキーワードを使用すると、副問合せで戻される列値が、スカラー値ではなくネストした表であることが認識されます。単一列値を戻す副問合せまたはネストした表に解決される式の前に、TABLEキーワードを指定する必要があります。

次に、TABLE構文の使用例を示します。

UPDATE TABLE(SELECT a.modules FROM projects a WHERE a.id=555) b
       SET module_owner= 
       (SELECT ref(p) FROM employees p WHERE p.ename= 'Smith') 
       WHERE b.module_name = 'Zebra';

この例のTABLEを見ると、ネストの外側の表の列から選択された、ネストの内側の単一の表を参照していることがわかります。

ノート:

この例では、前述のように、表の別名構文(projectsにはa、ネストした表にはb、またemployeesにはp)が使用されています。

7.5.2 ネストした表を含む行の挿入

この例では、マスター・レベル(ネストの外側の表)と詳細レベル(ネストの内側の表)を同時に明示的に操作する処理を示します。ここでは、projects表に行を挿入します。この表の各行にはMODULETBL_T型のネストした表が含まれ、ネストした表にはMODULE_Tオブジェクトの行が含まれています。

最初に、スカラー値を設定し(idnamestart_dateduration)、ネストした表の値を設定します。ネストした表の要素は複数の属性を持つオブジェクトであるため、別のレベルの抽象化も行いますネストした表の値を設定するとき、ネストした表の各MODULE_Tオブジェクトに対して、各属性値を設定する必要があります。最後に、owner値(初期値null)を別の文で設定します。

// Insert Nested table details along with master details 

  public static void insertProject2(int id)  throws Exception 
  {
    System.out.println("Inserting Project with Nested Table details..");
    try {
      #sql { INSERT INTO Projects(id,name,owner,start_date,duration, modules) 
             VALUES ( 600, 'Ruby', null, '10-MAY-98',  300, 
             moduletbl_t(module_t(6001, 'Setup ', null, '01-JAN-98', 100),
                        module_t(6002, 'BenchMark', null, '05-FEB-98',20) ,
                        module_t(6003, 'Purchase', null, '15-MAR-98', 50),
                        module_t(6004, 'Install', null, '15-MAR-98',44),
                        module_t(6005, 'Launch', null,'12-MAY-98',34))) };
    } catch ( Exception e) {
      System.out.println("Error:insertProject2");
      e.printStackTrace();
    }

    // Assign project owner to this project 

    try {
      #sql { UPDATE Projects pr
          SET owner=(SELECT ref(pa) FROM participants pa WHERE pa.empno = 7698)
         WHERE pr.id=600 };
    } catch ( Exception e) {
      System.out.println("Error:insertProject2:update");
      e.printStackTrace();
    }
  }

7.5.3 ホスト式へのネストした表の取出し

この例では、ネストした表を詳細レベルで直接操作します。

  static ModuletblT mymodules=null;
  ...

  public static void getModules2(int projId)
  throws Exception 
  {
    System.out.println("Display modules for project " + projId );

    try {
      #sql {SELECT modules INTO :mymodules 
                           FROM projects  WHERE id=:projId };
      showArray(mymodules);
    } catch(Exception e) {
      System.out.println("Error:getModules2");
      e.printStackTrace();
    }
  }

  public static void showArray(ModuletblT a) 
  {
    try {
      if ( a == null )
        System.out.println( "The array is null" );
      else {
        System.out.println( "printing ModuleTable array object of size "
                             +a.length());
        ModuleT[] modules = a.getArray();

        for (int i=0;i<modules.length; i++) {
          ModuleT module = modules[i];
          System.out.println("module "+module.getModuleId()+
                ", "+module.getModuleName()+
                ", "+module.getModuleStartDate()+
                ", "+module.getModuleDuration());
        }
      }
    }
    catch( Exception e ) {
      System.out.println("Show Array");
      e.printStackTrace();
    }
  }

7.5.4 TABLE構文によるネストした表の操作

この例では、TABLE構文を使用して、ネストした表を詳細レベルで操作します。マスター・レベルの基準に基づき、ネストした表の要素に直接アクセスして更新します。

assignModule()メソッドを使用すると、PROJECTS表のMODULES列からMODULE_Tオブジェクトのネストした表が選択された後、このネストして表の特定の行のMODULE_NAMEが更新されます。同様に、deleteUnownedModules()メソッドを使用すると、MODULE_Tオブジェクトのネストした表が選択され、その後でこのネストした表で所有者のいないモジュール(MODULE_OWNERNULLのモジュール)が削除されます。

これらのメソッドでは、前述のように表の別名構文を使用します。この例では、ネストした表にmparticipants表にpを使用しています。

  /* assignModule 
     Illustrates accessing the nested table using the TABLE construct 
     and updating the nested table row 
  */
  public static void assignModule(int projId, String moduleName, 
                                  String modOwner) throws Exception 
  {
    System.out.println("Update:Assign '"+moduleName+"' to '"+ modOwner+"'");

    try {
      #sql {UPDATE TABLE(SELECT modules FROM projects WHERE id=:projId) m
            SET m.module_owner=
           (SELECT ref(p) FROM participants p WHERE p.ename= :modOwner) 
            WHERE m.module_name = :moduleName };
    } catch(Exception e) {
      System.out.println("Error:insertModules");
      e.printStackTrace();
    }
  }

  /* deleteUnownedModules 
  // Demonstrates deletion of the Nested table element 
  */

  public static void deleteUnownedModules(int projId)
  throws Exception 
  {
    System.out.println("Deleting Unowned Modules for Project " + projId);
    try {
      #sql { DELETE TABLE(SELECT modules FROM projects WHERE id=:projId) m
             WHERE m.module_owner IS NULL };
    } catch(Exception e) {
      System.out.println("Error:deleteUnownedModules");
      e.printStackTrace();
    }
  }

7.5.5 ネスト・イテレータによるネストした表からのデータの選択

SQLJでは、ネストした表にアクセスする1つ方法として、ネストしたイテレータの使用があります。このためには、次の例で使用されているCURSOR構文が必要です。コードで名前付きイテレータ・クラスModuleIterを定義し、このクラスをmodules列の型として、別の名前付きイテレータ・クラスProjIterで使用します。移入されたProjIterインスタンスの中の各modules項目は、ネスト・イテレータとして表されたネストした表です。

CURSOR構文は、ネストされているSELECT文の要素であり、ネストされているイテレータを移入します。選択されたデータは、イテレータのアクセッサ・メソッドによってユーザーに出力されます。

この例では、前述のように、表の別名構文を使用します。ここでは、projects表にはaを使用し、ネストした表にはbを使用しています。

...

//  The Nested Table is accessed using the ModuleIter 
//  The ModuleIter is defined as Named Iterator 

#sql public static iterator ModuleIter(int moduleId , 
                                       String moduleName , 
                                       String moduleOwner);

// Get the Project Details using the ProjIter defined as 
// Named Iterator. Notice the use of ModuleIter:

#sql public static iterator ProjIter(int id, 
                                     String name, 
                                     String owner, 
                                     Date start_date, 
                                     ModuleIter modules);

...

public static void listAllProjects() throws SQLException
{
  System.out.println("Listing projects...");

   // Instantiate and initialize the iterators 

   ProjIter projs = null;
   ModuleIter  mods = null;
   #sql projs = {SELECT a.id, 
                        a.name, 
                        initcap(a.owner.ename) as "owner", 
                        a.start_date,
                        CURSOR (
                        SELECT b.module_id AS "moduleId",
                               b.module_name AS "moduleName",
                                 initcap(b.module_owner.ename) AS "moduleOwner"
                        FROM TABLE(a.modules) b) AS "modules"  
                 FROM projects a };
  
  // Display Project Details
  
  while (projs.next()) {
    System.out.println( "\n'" + projs.name() + "' Project Id:" 
                + projs.id() + " is owned by " +"'"+ projs.owner() +"'"
                + " start on "  
                + projs.start_date());
              
    // Notice the modules from the ProjIter are assigned to the module
    // iterator variable 

    mods = projs.modules();
    System.out.println ("Modules in this Project are : ");

    // Display Module details 

    while(mods.next()) { 
      System.out.println ("  "+ mods.moduleId() + " '"+ 
                                mods.moduleName() + "' owner is '" +
                                mods.moduleOwner()+"'" );
    }                    // end of modules 
    mods.close();
  }                      // end of projects 
  projs.close();
}

7.5.6 ホスト式へのVARRAYの取出し

ここでは、VARRAYをホスト式に取り出す例を示します。次のSQL定義を想定します。

CREATE TYPE PHONE_ARRAY IS VARRAY (10) OF varchar2(30)
/
/*** Create ADDRESS UDT ***/
CREATE TYPE ADDRESS AS OBJECT
( 
  street        VARCHAR(60),
  city          VARCHAR(30),
  state         CHAR(2),
  zip_code      CHAR(5)
)
/
/*** Create PERSON UDT containing an embedded ADDRESS UDT ***/
CREATE TYPE PERSON AS OBJECT
( 
  name    VARCHAR(30),
  ssn     NUMBER,
  addr    ADDRESS
)
/

CREATE TABLE  employees
( empnumber            INTEGER PRIMARY KEY,
  person_data     REF  person,
  manager         REF  person,
  office_addr          address,
  salary               NUMBER,
  phone_nums           phone_array
)
/

さらに、PHONE_ARRAY SQL型からマッピングするPhoneArrayカスタム・コレクション・クラスを作成したとします。

次のメソッドでこの表から行を選択し、データをPhoneArray型のホスト変数に格納します。

private static void selectVarray() throws SQLException
{
  PhoneArray ph;
  #sql {select phone_nums into :ph from employees where empnumber=2001};
  System.out.println(
    "there are "+ph.length()+" phone numbers in the PhoneArray.  They are:");

  String [] pharr = ph.getArray();
  for (int i=0;i<pharr.length;++i) 
    System.out.println(pharr[i]);
}

7.5.7 VARRAY行への挿入

ここでは、ホスト式からVARRAYにデータを挿入する例を示します。前例と同じSQL定義とカスタム・コレクション・クラス(PhoneArray)を使用します。

次のメソッドはPhoneArrayインスタンスを定義し、ホスト変数として使用して、データをデータベース内のVARRAYに挿入します。

// creates a varray object of PhoneArray and inserts it into a new row
private static void insertVarray() throws SQLException
{
  PhoneArray phForInsert = consUpPhoneArray();
  // clean up from previous demo runs
  #sql {delete from employees where empnumber=2001};
  // insert the PhoneArray object
  #sql {insert into employees (empnumber, phone_nums)
        values(2001, :phForInsert)};
}

private static PhoneArray consUpPhoneArray()
{
  String [] strarr = new String[3];
  strarr[0] = "(510) 555.1111";
  strarr[1] = "(617) 555.2222";
  strarr[2] = "(650) 555.3333";
  return new PhoneArray(strarr);
}

7.6 シリアライズされたJavaオブジェクト

Javaオブジェクトのインスタンスをデータベースとの間で読み書きする場合、Javaクラスに対応するSQLオブジェクト型を定義して、前述のカスタムJavaクラスのマッピング機構を使用すると、利便性が高まることがあります。これによって、Javaオブジェクトへの完全なSQL問合せが可能になります。

ただし、RAWまたはBLOB型のデータベース列を使用して、Javaオブジェクトをそのまま格納した後で取り出す場合もあります。このためには、2つの方法があります。

  • 標準でない型マップの拡張機能を使用するか、型コード・フィールドをシリアライズ可能なクラスに追加することによって、シリアライズ可能なJavaクラスをRAWまたはBLOB列にマップできます。これにより、RAWまたはBLOBとしてシリアライズ可能なクラスのインスタンスを格納できます。

  • ORAData機能を使用して、RAWまたはBLOB列に格納できるインスタンスを持つ、シリアライズ可能なラッパー・クラスを定義できます。

これらの方法で実行するシリアライズは、Oracle SQLJランタイム・ライブラリのみ対象です。

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

7.6.1 RAWおよびBLOB列に対するJavaクラスのシリアライズ

RAW列またはBLOB列内に直接Javaクラスのインスタンスを格納する場合は、SQLとJavaのマッピングを指定する非標準要件を満たす必要があります。SQLJ文内では、組込み型のように透過的に、シリアライズ可能なJavaオブジェクトを読み書きできます。

SQLとJavaのマッピングを指定する場合、2つのオプションがあります。

  • 型マップを接続コンテキスト宣言で宣言して、この型マップでマッピングを指定します。

  • public static finalフィールド_SQL_TYPECODEを使用して、マッピングを指定します。

シリアライズ可能なクラスに対する型マップの定義

SAddresspack.SPersonおよびpack.Manager.InnerSPM(InnerSPMManagerの内部クラスです)がシリアライズ可能なJavaクラスであるとします。つまり、これらのクラスはjava.io.Serializableインタフェースを実装します。

このクラスは、宣言済の接続コンテキスト型の明示的接続コンテキスト・インスタンスを使用する文でのみ採用する必要があります。次のSerContextがその例です。

SAddress               a =...;
pack.SPerson           p =...;
pack.Manager.InnerSPM pm =...;
SerContext ctx = new SerContext(url,user,pwd,false);
#sql [ctx] { ... :a ... :OUT p ... :INOUT pm ... };

この場合の要件を次に示します。

  • 接続コンテキスト型は、java.util.PropertyResourceBundleを実装している関連クラスを指定するwith句のtypeMap属性で宣言されている必要があります。例では、SerContextが次のように宣言されています。

    #sql public static context SerContext with (typeMap="SerMap");
    
  • 型マップ・リソースはRAW列またはBLOB列から、シリアライズ可能なJavaクラスまで、非標準マッピングを提供する必要があります。このマッピングは、次の形式のエントリで指定しますが、JavaクラスがRAWBLOBのどちらの列にマップされているかによって形式が異なります。

    oracle-class.java_class_name=JAVA_OBJECT RAW
    oracle-class.java_class_name=JAVA_OBJECT BLOB
    

    キーワードoracle-classは、これがOracle固有の拡張機能であることを示しています。例では、SerMap.propertiesリソース・ファイルには次のエントリが含まれます。

    oracle-class.SAddress=JAVA_OBJECT RAW
    oracle-class.pack.SPerson=JAVA_OBJECT BLOB
    oracle-class.packManager$InnerSPM=JAVA_OBJECT RAW
    

    パッケージとクラス名の分割にはピリオド(.)を使用しますが、内部クラス名を分割するにはドル記号($)を使用する必要があります

このOracle固有の拡張機能は、標準SQLData型マップ・エントリと同じ型マップ・リソースに配置できます。

フィールド使用によるシリアライズ可能クラスのマッピングの指定

シリアライズ可能クラスに型マップを使用する別の方法として、シリアライズ可能クラスのstaticフィールドを使用して、型マッピングを指定できます。前述の例のSAddressおよびSPersonクラスなど、java.io.Serializableインタフェースを実装するクラスに次のフィールドのいずれかを追加できます。

public final static int _SQL_TYPECODE = oracle.jdbc.OracleTypes.RAW;
public final static int _SQL_TYPECODE = oracle.jdbc.OracleTypes.BLOB;

ノート:

手動でのクラスへの_SQL_TYPECODEフィールド追加は、型マップ機能の使用に置き換えられました。

Javaオブジェクトのシリアライズにおける制限

シリアライズの効果について、認識しておく必要があります。2つのオブジェクトAおよびBが同じオブジェクトCを共有している場合、AおよびBは、そのシリアライズとその後のシリアライズ解除の際に、オブジェクトCのそれぞれのクローンをポイントすることになります。共有は解除されます。

さらに、与えられたJavaクラスに対して宣言できるシリアライズは、RAWまたはBLOBの1種類のみです。SQLJトランスレータは、実際の使用方法がRAWまたはBLOBのいずれかに適合することのみをチェックします。

RAW列では、サイズが制限されています。シリアライズしたJavaオブジェクトが、列のサイズを超えると、ランタイム・エラーが発生します。

BLOB列の列サイズ制限がより緩やかです。シリアライズされたJavaオブジェクトのBLOB列への書込みは、Oracle JDBC Oracle Call Interface(OCI)ドライバおよびOracle JDBC Thinドライバでサポートされます。BLOB列からのシリアライズされたオブジェクトの取得は、Oracle9i 以上のすべてのOracle JDBCドライバでサポートされています。

最後に、シリアライズされたJavaオブジェクトをこの方法で処理することは、Oracle固有の拡張機能であり、Oracle SQLJランタイムと、デフォルトのOracle固有コード生成(変換時の-codegen=oracle)またはISO標準コード生成(-codegen=iso)の場合はOracle固有のプロファイルのカスタマイズが必要です。

10iProd: 将来のバージョンのOracleでは、シリアライズされたJavaオブジェクトを直接カプセル化するSQL型がサポートされる可能性があります。これらは、JDBC 2.0ではJAVA_OBJECT SQL型として記載されています。その時点で、BLOBおよびRAWの各指定を対応するJAVA_OBJECT SQL型の名前で置き換えて、エントリの接頭辞oracle-を削除できます。

ノート:

この特定のシリアライズ・メカニズムの実装では、JDBC型マップは使用しません。BLOBまたはRAWに対するマップは、変換時にカスタマイズされたOracleのプロファイルにハードコードされるか、Javaコードに直接生成されます。

7.6.2 SerializableDatum: ORADataの実装

oracle.sql.STRUCToracle.sql.REFまたはoracle.sql.ARRAY以外のoracle.sql.*型にマッピングするカスタムJavaクラスの定義例は、「ORAData実装のその他の使用例」を参照してください。

RAWフィールドに対してJavaオブジェクトのシリアライズおよびシリアライズ解除を行う場合の例では、カスタムJavaクラスをoracle.sql.RAW型にマッピングしています。これは、BLOBフィールドにも同様に当てはまり、カスタムJavaクラスをoracle.sql.BLOB型にマッピングします。

ここでは、このようなアプリケーションの例として、ORADataインタフェースを実装し、汎用形式のカスタムJavaクラスに準拠するSerializableDatumクラスの作成について説明します。この例では、SerializableDatumの開発ステップを詳細に示してから、サンプル・コード全体を示します。

ノート:

このアプリケーションでは、java.iojava.sqloracle.sqlおよびoracle.jdbcパッケージのクラスを使用しています。インポート文はここには含まれていません。

  1. まず、このクラスのスケルトンから始めます。
    public class SerializableDatum implements ORAData
    {
       // Client methods for constructing and accessing the Java object
    
       public Datum toDatum(java.sql.Connection c) throws SQLException
       {
          // Implementation of toDatum()
       }
    
       public static ORADataFactory getORADataFactory()
       {
          return FACTORY;
       }
    
       private static final ORADataFactory FACTORY =
               // Implementation of an ORADataFactory for SerializableDatum
    
       // Construction of SerializableDatum from oracle.sql.RAW
    
       public static final int _SQL_TYPECODE = OracleTypes.RAW;
    }
    

    SerializableDatum自体では、ORADataFactoryインタフェースが実装されることはありませんが、このクラスのgetORADataFactory()メソッドを使用すると、このインタフェースを実装したstaticメンバーが戻されます。

    _SQL_TYPECODEを、OracleTypes.RAWに設定します(データベースに対する読取りおよび書込みに使用されるデータ型であるため)。SQLJトランスレータは、この型コード情報に基づき、オンラインで型チェックを行い、ユーザー定義のJava型とSQL型間の互換性を検証します。

  2. 次の処理を行うクライアント・メソッドを定義します。
    • SerializableDatumオブジェクトの作成

    • SerializableDatumオブジェクトの設定

    • SerializableDatumオブジェクトからのデータの取得

    // Client methods for constructing and accessing a SerializableDatum
    
    private Object m_data;
    public SerializableDatum()
    {
       m_data = null;
    }
    public void setData(Object data)
    {
       m_data = data;
    }
    public Object getData()
    {
       return m_data;
    }
    
  3. toDatum()メソッドを実装して、SerializableDatumオブジェクトからoracle.sql.RAWオブジェクトへデータをシリアライズします。toDatum()の実装では、m_dataフィールドのオブジェクトをoracle.sql.RAWインスタンスとしてシリアライズした表現で戻す必要があります。
    // Implementation of toDatum()
    
    try {
       ByteArrayOutputStream os = new ByteArrayOutputStream();
       ObjectOutputStream oos = new ObjectOutputStream(os);
       oos.writeObject(m_data);
       oos.close();
       return new RAW(os.toByteArray());
    } catch (Exception e) {
      throw new SQLException("SerializableDatum.toDatum: "+e.toString()); }
    
  4. oracle.sql.RAWオブジェクトからSerializableDatumオブジェクトへのデータ変換を実装します。この段階で、データをシリアライズ解除します。
    // Constructing SerializableDatum from oracle.sql.RAW
    
    private SerializableDatum(RAW raw) throws SQLException
    {
       try {
          InputStream rawStream = new ByteArrayInputStream(raw.getBytes());
          ObjectInputStream is = new ObjectInputStream(rawStream);
          m_data = is.readObject();
          is.close();
       } catch (Exception e) {
         throw new SQLException("SerializableDatum.create: "+e.toString()); }
    }
    
  5. ORADataFactoryを実装します。この例では、無名クラスとして実装します。
    // Implementation of an ORADataFactory for SerializableDatum
    
    new ORADataFactory()
    {
       public ORAData create(Datum d, int sqlCode) throws SQLException
       {
          if (sqlCode != _SQL_TYPECODE)
          {
             throw new SQLException
                       ("SerializableDatum: invalid SQL type "+sqlCode);
          }
          return (d==null) ? null : new SerializableDatum((RAW)d);
       }
    };
    

7.6.3 SQLJアプリケーションのSerializableDatum

ここでは、前項で作成したSerializableDatumクラスのインスタンスをホスト変数およびイテレータ列として、SQLJアプリケーションで使用する方法を示します。

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

CREATE TABLE PERSONDATA (NAME VARCHAR2(20) NOT NULL, INFO RAW(2000));

ホスト変数としてのSerializableDatum

次に、SerializableDatumインスタンスをホスト変数として使用します。

...
SerializableDatum pinfo = new SerializableDatum();
pinfo.setData (
   new Object[] {"Some objects", new Integer(51), new Double(1234.27) } );
String pname = "MILLER";
#sql { INSERT INTO persondata VALUES(:pname, :pinfo) };
...

イテレータ列としてのSerializableDatum

次に、SerializableDatumを名前付きイテレータの列として使用する例を示します。

#sql iterator PersonIter (SerializableDatum info, String name);

...
PersonIter pcur;
#sql pcur = { SELECT * FROM persondata WHERE info IS NOT NULL };
while (pcur.next())
{
   System.out.println("Name:" + pcur.name() + " Info:" + pcur.info());
}
pcur.close();
...

7.6.4 SerializableDatum(クラス全体)

次に、前項で詳細ステップを示したSerializableDatumクラスのすべてのコードを示します。

import java.io.*;
import java.sql.*;
import oracle.sql.*;
import oracle.jdbc.*;

public class SerializableDatum implements ORAData
{
// Client methods for constructing and accessing a SerializableDatum

   private Object m_data;
   public SerializableDatum()
   {
      m_data = null;
   }
   public void setData(Object data)
   {
      m_data = data;
   }
   public Object getData()
   {
      return m_data;
   }

// Implementation of toDatum()

   public Datum toDatum(Connection c) throws SQLException
   {

      try {
         ByteArrayOutputStream os = new ByteArrayOutputStream();
         ObjectOutputStream oos = new ObjectOutputStream(os);
         oos.writeObject(m_data);
         oos.close();
         return new RAW(os.toByteArray());
      } catch (Exception e) {
        throw new SQLException("SerializableDatum.toDatum: "+e.toString()); }
   }

   public static ORADataFactory getORADataFactory()
   {
      return FACTORY;
   }

// Implementation of an ORADataFactory for SerializableDatum

   private static final ORADataFactory FACTORY =
   
      new ORADataFactory()
      {
         public ORAData create(Datum d, int sqlCode) throws SQLException
         {
            if (sqlCode != _SQL_TYPECODE)
            {
               throw new SQLException(
                  "SerializableDatum: invalid SQL type "+sqlCode);
            }
            return (d==null) ? null : new SerializableDatum((RAW)d);
         }
      };

// Constructing SerializableDatum from oracle.sql.RAW

   private SerializableDatum(RAW raw) throws SQLException
   {
      try {
         InputStream rawStream = new ByteArrayInputStream(raw.getBytes());
         ObjectInputStream is = new ObjectInputStream(rawStream);
         m_data = is.readObject();
         is.close();
      } catch (Exception e) {
        throw new SQLException("SerializableDatum.create: "+e.toString()); }
   }

   public static final int _SQL_TYPECODE = OracleTypes.RAW;
}

7.7 弱い型指定のオブジェクト、参照およびコレクション

SQLJでは、弱い型指定のオブジェクト、参照およびコレクションを使用できます。これらのクラスの使用は一般的には推奨されておらず、使用方法も制限されますが、便利な場合もあります。たとえば、汎用コードを記述して、どのようなSTRUCTまたはREFでも使用できます。

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

7.7.1 弱い型指定のオブジェクト、参照およびコレクションのサポート

Oracleのオブジェクト、参照またはコレクションをSQLJアプリケーションで使用する場合は、強い型指定のカスタム・オブジェクト・クラス、参照クラスまたはコレクション・クラス(ORADataインタフェースを実装したクラス)または強い型指定のカスタム・オブジェクト・クラス(SQLDataインタフェースを実装したクラス)を使用するかわりに、弱い型指定の汎用java.sqlインスタンスまたはoracle.sqlインスタンスを使用できます。ただし、SQLData実装をカスタム・オブジェクト・クラスとして使用した場合、弱い型指定のカスタム参照インスタンスしか使用できません。

Oracle SQLJ実装では、イテレータ列またはホスト式に対して、次に示した弱い型指定を使用できます。

  • java.sql.Structまたはoracle.sql.STRUCT(オブジェクトの場合)

  • java.sql.Refまたはoracle.sql.REF(オブジェクト参照の場合)

  • java.sql.Arrayまたはoracle.sql.ARRAY(コレクションの場合)

ホスト式の場合は、次の方法で使用できます。

  • 入力ホスト式として

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

こうした弱い型指定は、通常は使用しないようにしてください。SQLJの強い型指定のパラダイムのあらゆるメリットが損なわれるためです。

STRUCTオブジェクト内の各属性またはARRAYオブジェクト内の各要素がoracle.sql.Datumオブジェクトに格納されます。基礎となるデータの形式は、Datumの適切なoracle.sql.*サブタイプ(oracle.sql.NUMBERoracle.sql.CHARなど)です。STRUCTオブジェクト内の属性は匿名です。STRUCTおよびARRAYクラスは汎用クラスであるため、これらのクラスのインスタンスに対してオブジェクトまたはコレクションの読取りや書込みを行う際は、SQLJで型チェックを行うことはできません。

一般的には、オブジェクト、参照およびコレクションに対してカスタムJavaクラスの使用をお薦めします。

7.7.2 弱い型指定のオブジェクト、参照およびコレクションの使用制限

弱い型指定のオブジェクト(StructまたはSTRUCTインスタンス)、参照(RefまたはREFのインスタンス)、またはコレクション(ArrayまたはARRAYのインスタンス)は、次の状況では、ホスト式では使用できません

  • INパラメータとして(NULLの場合)

  • ストアド・プロシージャまたはストアド・ファンクションのコールで、OUTまたはINOUTパラメータとして

  • ストアド・ファンクションの結果式で、OUTパラメータとして

これらのパラメータとして使用できない理由は、元のSQL型の名前(Personなど)を特定できないからです。Oracle JDBCドライバは、元のSQL型の名前を使用して、Javaのユーザー定義型のインスタンスを生成します。

7.8 Oracle OPAQUE型

Oracle OPAQUE型は抽象データ型です。単なる一連のバイトとして実装されたデータの場合、内部表現は公開されません。通常、OPAQUE型はOracleで提供され、カスタマによる実装はありません。

OPAQUE型は、基本的な点でオブジェクト型に類似しています。つまり、staticメソッド、インスタンスおよびインスタンス・メソッドの概念が類似しています。一般的に、状態や内部バイト表現を操作できるのは、OPAQUE型を使用して提供されるメソッドのみです。Javaでは、oracle.sql.OPAQUEとして、またはoracle.sql.ORADataインタフェースを実装しているカスタム・クラスとして、OPAQUE型を表現できます。クライアント側では、Javaコードを実装してバイトを操作できます(バイト・パターンが判明していることが前提です)。

OPAQUE型の主な例は、Oracle Database 12c リリース2 (12.2)で提供しているXMLTypeです。Oracleが提供する型によって、データベースのXMLデータをネイティブに処理することが容易になります。

SYS.XMLTypeによって次の機能が提供され、Javaのoracle.xdb.XMLTypeクラスを介して公開されます。

  • 表またはビューの列のデータ型として使用できます。XMLTypeにはあらゆるコンテンツを格納できますが、XMLコンテンツを最適に格納するように設計されています。そのインスタンスはSQLでXMLドキュメントを表現できます。

  • XMLコンテンツの操作を行う組込みメンバー・ファンクションを含むSQL APIがあります。たとえば、XMLTypeファンクションを使用すると、Oracle Database 12c リリース1 (12.1)インスタンスに格納されているXMLデータの作成、問合せ、抽出および索引付けができます。

  • ストアド・プロシージャで、パラメータ、戻り値および変数に使用できます。

  • この機能は、PL/SQL、JavaおよびC(OCI)で提供されるAPIを介して使用することもできます。