7 オブジェクト、コレクションおよびOPAQUE型
この章では、Oracle SQLJ実装でユーザー定義SQL型がどのようにサポートされるかを説明します。この章の最後には、Oracle OPAQUE型に関する項もあります。
この章では、次の項目について説明します。
Oracleのオブジェクトとコレクションについて
ここでは、Oracle Database 12c リリース2 (12.2)のオブジェクトとコレクションの背景となる概念について説明します。
この項の内容は次のとおりです。
オブジェクトとコレクションの概要
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.Struct
、java.sql.Ref
またはjava.sql.Array
オブジェクトもかわりに使用できます。
「弱い型指定」という用語は、Java型が汎用的な方法で使用され、複数のSQL名前指定型にマップできる場合に使用されます。Javaクラス(インタフェース)には、SQL型に特有の情報はありません。これは、oracle.sql.STRUCT
、oracle.sql.REF
およびoracle.sql.ARRAY
型とjava.sql.Struct
、java.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オブジェクト・リレーショナル開発者ガイド』を参照してください。
Oracleオブジェクトの基本概念
Oracle SQLオブジェクトは複合データ構造体であり、関連するデータ項目(各従業員に関する事実など)を1つのデータ単位にまとめたものです。オブジェクト型は、機能的にはJavaのクラスに相当します。Java型をインスタンス化してオブジェクトをいくつでも使用できるように、特定のオブジェクト型のオブジェクトをいくつでも設定して使用できます。
たとえば、CHAR
型の属性name
、CHAR
型のaddress
、CHAR
型のphonenumber
およびNUMBER
型のemployeenumber
を持つオブジェクト型EMPLOYEE
を定義できます。
Oracleのオブジェクトのメソッド(ストアド・プロシージャ)は、オブジェクト型への対応付けができます。これらのメソッドは、staticメソッドまたはインスタンス・メソッドとして、PL/SQLまたはJavaで実装できます。メソッドのシグネチャとして、任意の数の入力、出力または入出力パラメータを使用できます。すべては初期定義によって決まります。
Oracleコレクションの基本概念
Oracle SQLコレクションは、次の2つのカテゴリに分類されます。
どちらのカテゴリも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属性またはネストした表の属性を持つユーザー定義のオブジェクト型にすることもできます。
オブジェクトとコレクションのデータ型
Oracle Database 12c リリース2 (12.2)では、ユーザー定義のオブジェクトおよびコレクション定義は、SQLのデータ型定義として機能します。これらのデータ型は、他のデータ型と同じように、表の列定義、SQLオブジェクトの属性定義およびストアド・プロシージャまたはストアド・ファンクションのパラメータの定義に使用できます。また、オブジェクト型を定義すると、そのオブジェクト参照型を他のSQL参照型のように使用できます。
たとえば、前の項のEMPLOYEE
Oracleオブジェクトを考えます。このオブジェクトを定義すると、Oracleのデータ型になります。NUMBER
型の表列と同じように、EMPLOYEE
型の表列を持つことができます。EMPLOYEE
列の各行に、EMPLOYEE
オブジェクト全体を格納できます。EMPLOYEE
オブジェクトへの参照で構成されるREF EMPLOYEE
型の列を持つこともできます。
同様に、NUMBER
のVARRAY(10)
としてVARRAY型MYVARR
を定義したり、CHAR(20)
のNESTED TABLE型NTBL
を定義できます。コレクション型MYVARR
とNTBL
はOracleのデータ型になるので、これらの型の列を表に定義できます。MYVARR
列の各行は、最大10数字を収容する配列です。NTBL
列の各行は20文字で構成されます。
カスタムJavaクラス
カスタムJavaクラスはファーストクラスの型で、ユーザー定義SQL型に対する読取りおよび書込みを透過的に行う場合に使用できます。カスタムJavaクラスの用途は、SQLとJava間のデータの変換手段を提供し、データへのアクセスを可能にすることです。この機能は、特に、オブジェクトとコレクションのサポートにおいて、またはデータのカスタム変換を行う場合に重要です。
SQLJアプリケーションで使用するすべてのユーザー定義型に対して、カスタムJavaクラスを用意することをお薦めします。Oracle JDBCドライバではデータ変換にこのクラスのインスタンスが使用され、弱い型指定のoracle.sql.STRUCT
、oracle.sql.REF
およびoracle.sql.ARRAY
クラスを使用するも便利で、エラーも発生しにくくなります。
SQLJイテレータまたはホスト式で使用するには、カスタムJavaクラスにoracle.sql.ORAData
およびoracle.sql.ORADataFactory
インタフェース、または標準java.sql.SQLData
インタフェースを実装する必要があります。ここでは、これらのインタフェースとカスタムJavaクラス機能の概要を、次の項目について説明します。
カスタム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ドライバは、実行時に接続オブジェクトを使用して、型チェックと型変換を行います。ORAData
とtoDatum()
は、次のように指定します。
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
オブジェクトからデータを取り出します。ORADataFactory
とcreate()
は、次のように指定します。
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仕様を参照してください。
オブジェクト・メソッドでのカスタムJavaクラスのサポート
Oracleオブジェクト・メソッドは、カスタムJavaクラスのラッパーから起動できます。基になるストアド・プロシージャの記述言語がPL/SQL、Javaのどちらであるか、またSQLに公開されているかどうかは、ユーザーには表示されません。
Javaのラッパー・メソッドでサーバー側メソッドを起動するには、サーバーと通信するための接続が必要です。接続オブジェクトは、明示的パラメータとして提供することも、別の方法で関連付けることも可能です。たとえば、カスタムJavaクラスの属性として提供します。ラッパー・メソッドで使用する接続オブジェクトを非静的属性とした場合、このラッパー・メソッドをカスタムJavaクラスのインスタンス・メソッドとすると、この接続にアクセスできます。
Oracleオブジェクト・メソッドの出力および入出力パラメータに関しては、次のことに留意してください。ストアド・プロシージャ(SQLオブジェクト・メソッド)によって、ある引数の内部状態が変更された場合、ストアド・プロシージャに渡された実引数も変更されます。Javaで記述した場合、こうした現象は起こりません。JDBC出力パラメータがストアド・プロシージャ・コールから戻る場合、新規に作成されたオブジェクトに格納する必要があります。元のオブジェクト識別性は失われます。
出力または入出力パラメータをコール元に戻す場合は、パラメータを配列要素として渡す方法もあります。入出力パラメータの場合は、ラッパー・メソッドが入力として配列要素をとります。処理後、ラッパーによって出力が配列要素に代入されます。
カスタム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_TYPECODE
をOracleTypes.RAW
に初期化します。注意:
OracleTypes
クラスは、各Oracleデータ型の型コード(整数の定数)のみを定義します。標準SQL型のOracleTypes
エントリは、標準java.sql.Types
型定義クラスのエントリと同じです。関連項目:
-
-
_SQL_TYPECODE
がSTRUCT
、REF
または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名の指定はありません。
-
使用時には次の事項に注意してください。
SQLDataを実装したクラスに対する要件
ISO SQLJ規格では、SQLData
インタフェースを実装するクラスへの型マップ定義の要件を概説しています。一方、SQLData
ラッパー・クラスは、public static final
フィールドでSQL関連オブジェクト型を識別できます。
次の点は重要なので、十分に注意してください。
-
マッピングの指定に型マップを使用する場合でも、標準でない
public static final
フィールドを使用する場合でも、一貫して指定する必要があります。public static final
フィールドを不要にする目的ですべての関連マッピングを指定する型マップを使用するか、または型マップをまったく使用せずにすべてのマッピングをpublic static final
フィールドで指定します。 -
SQLData
は、ORAData
とは異なり、構造化オブジェクト型のマッピング専用です。オブジェクト参照やコレクションや配列などのSQL型のマッピングには使用できません。ORAData
を使用しない場合、オブジェクト参照とコレクションをマッピングする方法は、それぞれ弱い型指定のjava.sql.Ref
とjava.sql.Array
、またはoracle.sql.REF
とoracle.sql.ARRAY
を使用する方法のみです。 -
SQLData
実装には、Java Development Kit (JDK) 1.4.xまたは1.5.x環境が必要です。 -
SQL型からJava型へのマッピングを指定するとき、SQL型が大/小文字を区別して宣言されている場合、
CaseSensitive
やHR.CaseSensitive
など、宣言されているとおりにSQL名を指定する必要があります。
型マップ・リソースで指定したマッピング
まず、ISO SQLJ規格に従ったマッピング表現を想定します。Address
、pack.Person
およびpack.Manager.InnerPM
(InnerPM
はManager
の内部クラスです)が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コードを混在させている場合は、注意が必要です。
カスタム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から通知されます。
カスタム・データの読込みと書込み
カスタムJavaクラス・インスタンスを使用すると、Oracle SQLJおよびJDBC実装では、ユーザー定義型の読込みと書込みが可能になります(ただし、これらは組込み型です)。この仕組みをユーザーが意識することはありません。
ORAData
実装およびSQLData
実装でのデータの読込み/書込みの仕組みは、『Oracle Database JDBC開発者ガイド』を参照してください。
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(); } }
ユーザー定義型
ここでは、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つのオブジェクト型、
PERSON
とADDRESS
。 -
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) -
PERSON
とADDRESS
オブジェクト(前に使用した同じ定義が示されています) -
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 ) /
SQLJ実行文の強い型指定のオブジェクトと参照
Oracle SQLJ実装では、強い型指定のオブジェクトまたは参照をホスト式およびイテレータで使用して、オブジェクト・データの読込みおよび書込みを柔軟に行えます。
イテレータの場合は、カスタム・オブジェクト・クラスをイテレータの列型として使用できます。または、属性のSQLデータ型にマップしている列型を使用して、イテレータ列をオブジェクトの各属性に対応付けることができます(エクステント表と同様)。
ホスト式の場合は、カスタム・オブジェクト・クラス型またはカスタム参照クラス型のホスト変数を使用できます。または、属性のSQLデータ型にマップしている変数型を使用して、ホスト変数をオブジェクトの属性に対応付けることができます。
次に、Oracleオブジェクトの操作例を示します。SQLJ実行文のホスト変数とイテレータ列に対して、カスタム・オブジェクト・クラス、カスタム・オブジェクト・クラスの属性およびカスタム参照クラスを使用します。
次の2つの例は、オブジェクト・レベルでの操作です。
「各オブジェクト属性から作成したオブジェクトの挿入」の例は、スカラー属性レベルでの操作です。
「オブジェクト参照の更新」の例は、参照による操作です。
オブジェクトとオブジェクト参照のイテレータ列への取出し
この例では、カスタム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
を作成すると、次のようにAddress
とAddressRef
を名前付きイテレータで使用できます。
#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()
メソッドを使用します。
オブジェクトの更新
この例では、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 + "'" ); }
各オブジェクト属性から作成したオブジェクトの挿入
この例では、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); } } ...
オブジェクト参照の更新
この例では、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言語リファレンス』を参照してください。
SQLJ実行文の強い型指定のコレクション
Oracle SQLJ実装では、強い型指定のオブジェクトおよび参照と同じように、強い型指定のコレクションをイテレータまたはホスト式で使用して、データの読込みおよび書込みを行えます。
SQLJ開発者にとっては、2種類のコレクション(VARRAYとネストした表)の扱いは基本的に同じですが、実装とパフォーマンスの面で相違点が多少あります。
Oracle SQLJ実装では、構文の複数の選択肢がサポートされていて、ネストした表(内側の表)にアクセスして、操作できるようになっています(外側の表から切り離して、または外側の表と一緒に操作することも可能です)。ここでは、ネストの内側の表のみの操作を詳細レベルの操作と呼び、ネストの内側の表と外側の表の同時操作をマスター・レベルの操作と呼びます。
ここでは、いくつかの構文について簡単に説明してから、ネストした表の操作例を示します。ネストした表の操作は、VARRAYの操作より複雑です。
注意:
Oracle SQLJ実装では、VARRAY型およびNESTED TABLE型を型単位でのみ取得できます。これは、Oracle SQL実装とは違い、ネストした表の問合せを選択的に実行できます。
この項の内容は次のとおりです。
ネストした表へのアクセス: 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
)が使用されています。
ネストした表を含む行の挿入
この例では、マスター・レベル(ネストの外側の表)と詳細レベル(ネストの内側の表)を同時に明示的に操作する処理を示します。ここでは、projects
表に行を挿入します。この表の各行にはMODULETBL_T
型のネストした表が含まれ、ネストした表にはMODULE_T
オブジェクトの行が含まれています。
最初に、スカラー値を設定し(id
、name
、start_date
、duration
)、ネストした表の値を設定します。ネストした表の要素は複数の属性を持つオブジェクトであるため、別のレベルの抽象化も行いますネストした表の値を設定するとき、ネストした表の各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(); } }
ホスト式へのネストした表の取出し
この例では、ネストした表を詳細レベルで直接操作します。
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(); } }
TABLE構文によるネストした表の操作
この例では、TABLE
構文を使用して、ネストした表を詳細レベルで操作します。マスター・レベルの基準に基づき、ネストした表の要素に直接アクセスして更新します。
assignModule()
メソッドを使用すると、PROJECTS
表のMODULES
列からMODULE_T
オブジェクトのネストした表が選択された後、このネストして表の特定の行のMODULE_NAME
が更新されます。同様に、deleteUnownedModules()
メソッドを使用すると、MODULE_T
オブジェクトのネストした表が選択され、その後でこのネストした表で所有者のいないモジュール(MODULE_OWNER
がNULL
のモジュール)が削除されます。
これらのメソッドでは、前述のように表の別名構文を使用します。この例では、ネストした表にm
、participants
表に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(); } }
ネスト・イテレータによるネストした表からのデータの選択
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(); }
ホスト式への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]); }
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); }
シリアライズされたJavaオブジェクト
Javaオブジェクトのインスタンスをデータベースとの間で読み書きする場合、Javaクラスに対応するSQLオブジェクト型を定義して、前述のカスタムJavaクラスのマッピング機構を使用すると、利便性が高まることがあります。これによって、Javaオブジェクトへの完全なSQL問合せが可能になります。
ただし、RAW
またはBLOB
型のデータベース列を使用して、Javaオブジェクトをそのまま格納した後で取り出す場合もあります。このためには、2つの方法があります。
-
標準でない型マップの拡張機能を使用するか、型コード・フィールドをシリアライズ可能なクラスに追加することによって、シリアライズ可能なJavaクラスを
RAW
またはBLOB
列にマップできます。これにより、RAW
またはBLOB
としてシリアライズ可能なクラスのインスタンスを格納できます。 -
ORAData
機能を使用して、RAW
またはBLOB
列に格納できるインスタンスを持つ、シリアライズ可能なラッパー・クラスを定義できます。
これらの方法で実行するシリアライズは、Oracle SQLJランタイム・ライブラリのみ対象です。
この項の内容は次のとおりです。
RAWおよびBLOB列に対するJavaクラスのシリアライズ
RAW
列またはBLOB
列内に直接Javaクラスのインスタンスを格納する場合は、SQLとJavaのマッピングを指定する非標準要件を満たす必要があります。SQLJ文内では、組込み型のように透過的に、シリアライズ可能なJavaオブジェクトを読み書きできます。
SQLとJavaのマッピングを指定する場合、2つのオプションがあります。
-
型マップを接続コンテキスト宣言で宣言して、この型マップでマッピングを指定します。
-
public static final
フィールド_SQL_TYPECODE
を使用して、マッピングを指定します。
シリアライズ可能なクラスに対する型マップの定義
SAddress
、pack.SPerson
およびpack.Manager.InnerSPM
(InnerSPM
はManager
の内部クラスです)がシリアライズ可能な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クラスがRAW
とBLOB
のどちらの列にマップされているかによって形式が異なります。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コードに直接生成されます。
SerializableDatum: ORADataの実装
oracle.sql.STRUCT
、oracle.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.io
、java.sql
、oracle.sql
およびoracle.jdbc
パッケージのクラスを使用しています。インポート文はここには含まれていません。
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(); ...
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; }
弱い型指定のオブジェクト、参照およびコレクション
SQLJでは、弱い型指定のオブジェクト、参照およびコレクションを使用できます。これらのクラスの使用は一般的には推奨されておらず、使用方法も制限されますが、便利な場合もあります。たとえば、汎用コードを記述して、どのようなSTRUCT
またはREF
でも使用できます。
この項の内容は次のとおりです。
弱い型指定のオブジェクト、参照およびコレクションのサポート
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.NUMBER
やoracle.sql.CHAR
など)です。STRUCT
オブジェクト内の属性は匿名です。STRUCT
およびARRAY
クラスは汎用クラスであるため、これらのクラスのインスタンスに対してオブジェクトまたはコレクションの読取りや書込みを行う際は、SQLJで型チェックを行うことはできません。
一般的には、オブジェクト、参照およびコレクションに対してカスタムJavaクラスの使用をお薦めします。
弱い型指定のオブジェクト、参照およびコレクションの使用制限
弱い型指定のオブジェクト(Struct
またはSTRUCT
インスタンス)、参照(Ref
またはREF
のインスタンス)、またはコレクション(Array
またはARRAY
のインスタンス)は、次の状況では、ホスト式では使用できません。
-
IN
パラメータとして(NULLの場合) -
ストアド・プロシージャまたはストアド・ファンクションのコールで、
OUT
またはINOUT
パラメータとして -
ストアド・ファンクションの結果式で、
OUT
パラメータとして
これらのパラメータとして使用できない理由は、元のSQL型の名前(Person
など)を特定できないからです。Oracle JDBCドライバは、元のSQL型の名前を使用して、Javaのユーザー定義型のインスタンスを生成します。
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を介して使用することもできます。
関連項目: