7.5 オブジェクト型コール仕様の記述
SQLでは、オブジェクト指向のプログラミングは、データ構造とそのデータの操作に必要なファンクションおよびプロシージャをカプセル化したユーザー定義のコンポジット・データ型である、オブジェクト型に基づきます。データ構造を形成する変数は属性と呼ばれます。オブジェクト型の動作を特徴づけるファンクションおよびプロシージャはメソッドと呼ばれ、Javaで記述できます。
パッケージと同様に、オブジェクト型には、仕様部と本体の2つの部分があります。仕様部はアプリケーションに対するインタフェースで、一連の属性であるデータ構造と、そのデータの操作に必要な操作方法(メソッド)を宣言します。本体は、PL/SQLサブプログラムの本体またはコール仕様を定義して仕様部を実装します。
仕様部で属性またはコール仕様のみを宣言する場合、本体は必要ありません。すべてのメソッドをJavaで実装する場合は、メソッドのコール仕様をオブジェクト型の仕様部に配置して、本体を省略できます。
SQL*Plusでは、次の構文を使用してSQLオブジェクト型を対話形式で定義できます。
CREATE [OR REPLACE] TYPE type_name [AUTHID {CURRENT_USER | DEFINER}] {IS | AS} OBJECT ( attribute_name data_type[, attribute_name data_type]... [{MAP | ORDER} MEMBER {function_spec | call_spec},] [{MEMBER | STATIC} {subprogram_spec | call_spec} [, {MEMBER | STATIC} {subprogram_spec | call_spec}]...] ); [CREATE [OR REPLACE] TYPE BODY type_name {IS | AS} { {MAP | ORDER} MEMBER function_body; | {MEMBER | STATIC} {subprogram_body | call_spec};} [{MEMBER | STATIC} {subprogram_body | call_spec};]... END;]
AUTHID
句は、そのタイプのすべてのメンバー・メソッドをその定義者(AUTHID
DEFINER
、デフォルト)と実行者(AUTHID
CURRENT_USER
)のどちらの権限を使用して実行するかを指定します。スキーマ・オブジェクトに対する未修飾の参照を定義者と実行者のどちらのスキーマで解決するかも指定します。
この項の内容は次のとおりです。
7.5.1 属性について
オブジェクト型仕様部では、属性はすべてメソッドの前に宣言する必要があります。また、少なくとも1つの属性を宣言する必要があります。宣言できる属性の最大数は1000個です。メソッドはオプションです。
Java変数と同様に、属性は名前とデータ型で宣言します。名前はオブジェクト型内で一意であることが必要ですが、他のオブジェクト型では再利用できます。データ型は任意のSQL型に設定できますが、LONG
、LONG RAW
、NCHAR
、NVARCHAR2
、NCLOB
、ROWID
およびUROWID
には設定できません。
代入演算子またはDEFAULT
句を使用して宣言部で属性を初期化することはできません。また、NOT NULL
制約を属性に課すこともできません。ただし、制約を課すことができるデータベース表にオブジェクトを格納することはできます。
7.5.2 メソッドの宣言
属性の宣言後に、メソッドを宣言できます。MEMBER
メソッドは、オブジェクト型のインスタンスである、SELF
と呼ばれる組込みパラメータを受け入れます。暗黙的に宣言されたか明示的に宣言されたかに関係なく、これは常に最初にMEMBER
メソッドに渡されるパラメータです。メソッド本体では、SELF
はメソッドがコールされたオブジェクトを示します。MEMBER
メソッドは、次のように、インスタンスに対してコールされます。
instance_expression.method()
STATIC
メソッドは、SELF
の受入れまたは参照ができず、次のように、インスタンスではなくオブジェクト型に対して起動されます。
object_type_name.method()
static
でないJavaメソッドをコールする場合は、コール仕様でキーワードMEMBER
を指定する必要があります。同様に、static
のJavaメソッドをコールする場合は、コール仕様でキーワードSTATIC
を指定する必要があります。
この項では、次の項目について説明します。
7.5.2.1 マップ・メソッドとオーダー・メソッド
CHAR
などのSQLスカラー・データ型の値には、事前定義済の順序があり、これによって他の値と比較できます。ただし、オブジェクト型のインスタンスには事前定義済の順序はありません。これらに順序を付けるには、SQLでユーザー定義のマップ
・メソッドをコールします。
SQLは、順序付けを使用してx > y
などのブール式を評価し、DISTINCT
、GROUP BY
およびORDER BY
の各句に伴う比較を行います。マップ・メソッドは、それらすべてのオブジェクトの順序付けにおけるオブジェクトの相対的な位置を戻します。オブジェクト型に含めることができるマップ・メソッドは1つのみで、戻り型がDATE
、NUMBER
またはVARCHAR2
のいずれかである、パラメータのないファンクションであることが必要です。
また、2つのオブジェクトを比較するオーダー
・メソッドを持つSQLを提供することもできます。オーダー
・メソッドでは、2つのパラメータ、つまり組込みパラメータSELF
とそれと同じ型の別のオブジェクトのみが使用されます。o1
とo2
のオブジェクトがある場合は、o1 > o2
のような比較によってorder
メソッドが自動的にコールされます。メソッドは、負数、0(ゼロ)または正数を戻し、それぞれSELF
がもう一方のパラメータと比較して小さい、等しいまたは大きいことを示します。オブジェクト型に含めることができるオーダー
・メソッドは1つのみで、それは数値結果を戻すファンクションであることが必要です。
マップ
・メソッドまたはオーダー
・メソッドのいずれかを宣言できますが、両方を宣言することはできません。どちらかのメソッドを宣言すると、SQLとPL/SQLでオブジェクトを比較できます。ただし、どちらのメソッドも宣言しない場合、比較できるのは、SQLのオブジェクトの等価または非等価のみです。
注意:
同じ型の2つのオブジェクトは、対応する属性の値が等しい場合は等価です。
7.5.2.2 コンストラクタ・メソッド
オブジェクト型にはすべて、そのオブジェクト型と名前が同じシステム定義のファンクションであるコンストラクタがあります。コンストラクタでは、そのオブジェクト型のインスタンスが初期化されて戻されます。
Oracle Databaseでは、すべてのオブジェクト型に対してデフォルト・コンストラクタが生成されます。コンストラクタの仮パラメータは、オブジェクト型の属性と一致します。つまり、パラメータと属性は同じ順序で宣言され、名前とデータ型が同じです。SQLではコンストラクタは暗黙的にはコールされません。そのため、明示的にコールする必要があります。コンストラクタ・コールは、ファンクション・コールが使用できる場所で使用できます。
注意:
JavaコンストラクタをSQLから起動するには、コンストラクタのコールをstatic
メソッドにラップし、対応するコール仕様をオブジェクト型のSTATIC
メンバーとして宣言する必要があります。
7.5.2.3 例
この項の各例は、それぞれ前の例に基づきます。まず、部門と従業員を表す2つのSQLオブジェクト型を作成します。最初に、オブジェクト型Department
の仕様部を記述します。仕様部で宣言されるのは属性のみであるため、本体は必要ありません。仕様部は次のとおりです。
CREATE TYPE Department AS OBJECT ( deptno NUMBER(2), dname VARCHAR2(14), loc VARCHAR2(13) );
次に、オブジェクト型Employee
を作成します。deptno
属性には、REF
と呼ばれる、Department
型のオブジェクトのハンドルが格納されています。REF
は、オブジェクト型のインスタンスを格納するデータベース表である、オブジェクト表の中のオブジェクトの位置を示します。REF
は、メモリーにある特定のインスタンスのコピーは指し示しません。REF
を宣言するには、データ型REF
とREF
の対象となるオブジェクト型を指定します。Employee
型は、次のように作成されます。
CREATE TYPE Employee AS OBJECT ( empno NUMBER(4), ename VARCHAR2(10), job VARCHAR2(9), mgr NUMBER(4), hiredate DATE, sal NUMBER(7,2), comm NUMBER(7,2), deptno REF Department );
次に、Department
型とEmployee
型のオブジェクトを格納するSQLオブジェクト表を作成します。Department
型のオブジェクトを格納するオブジェクト表depts
を作成します。リレーショナル表dept
からデータを選択して、オブジェクト型と同じ名前のシステム定義のファンクションであるコンストラクタに渡すことによって、オブジェクト表にデータを移入します。コンストラクタを使用して、そのオブジェクト型のインスタンスを初期化して戻します。depts
表は、次のように作成されます。
CREATE TABLE depts OF Department AS SELECT Department(deptno, dname, loc) FROM dept;
Employee
型のオブジェクトを格納するオブジェクト表emps
を作成します。オブジェクト表emps
の最後の列は、オブジェクト型Employee
の最後の属性に対応しており、Department
型のオブジェクトへの参照が格納されます。この列に参照をフェッチするには、オブジェクト表の行に関連付けられた表の別名を引数として取る、演算子REF
を使用します。emps
表は、次のように作成されます。
CREATE TABLE emps OF Employee AS SELECT Employee(e.employee_id, e.first_name, e.job_id, e.manager_id, e.hire_date, e.salary, e.commission_pct, (SELECT REF(d) FROM departments d WHERE d.department_id = e.department_id)) FROM employees e;
REF
を選択すると、オブジェクトのハンドルが戻されます。オブジェクト自体は具体化されません。これを行うには、Oracleオブジェクト参照をサポートするoracle.sql.REF
クラスのメソッドを使用できます。このクラスはoracle.sql.Datum
のサブクラスで、標準のJDBCインタフェースoracle.jdbc2.Ref
を拡張します。
oracle.sql.STRUCTクラスの使用
さらに処理を続けて、Javaストアド・プロシージャを次のように記述します。Paymaster
クラスには、従業員の賃金を計算する1つのメソッドがあります。oracle.sql.STRUCT
クラスで定義されているgetAttributes()
メソッドでは、属性の型に対するデフォルトのJDBCマッピングが使用されます。たとえば、NUMBER
はBigDecimal
にマッピングされます。Paymaster
クラスは、次のように作成されます。
import java.sql.*; import java.io.*; import oracle.sql.*; import oracle.jdbc.*; import oracle.oracore.*; import oracle.jdbc2.*; import java.math.*; public class Paymaster { public static BigDecimal wages(STRUCT e) throws java.sql.SQLException { // Get the attributes of the Employee object. Object[] attribs = e.getAttributes(); // Must use numeric indexes into the array of attributes. BigDecimal sal = (BigDecimal)(attribs[5]); // [5] = sal BigDecimal comm = (BigDecimal)(attribs[6]); // [6] = comm BigDecimal pay = sal; if (comm != null) pay = pay.add(comm); return pay; } }
wages()
メソッドは値を戻すため、そのファンクションのコール仕様を次のように記述します。
CREATE OR REPLACE FUNCTION wages (e Employee) RETURN NUMBER AS LANGUAGE JAVA NAME 'Paymaster.wages(oracle.sql.STRUCT) return BigDecimal';
パッケージまたはオブジェクト型の内部で定義されていないため、これはトップレベルのコール仕様です。
SQLDataインタフェースの実装
オブジェクトの属性にさらに自然にアクセスできるように、SQLData
インタフェースを実装するJavaクラスを作成します。そのためには、SQLData
インタフェースで定義されているとおりに、readSQL()
およびwriteSQL()
メソッドを提供する必要があります。JDBCドライバはreadSQL()
メソッドをコールして、データベースの値のストリームを読み取り、Javaクラスのインスタンスに移入します。次の例では、Paymaster
を変更して、raiseSal()
という2番目のメソッドを追加します。
import java.sql.*; import java.io.*; import oracle.sql.*; import oracle.jdbc.*; import oracle.oracore.*; import oracle.jdbc2.*; import java.math.*; public class Paymaster implements SQLData { // Implement the attributes and operations for this type. private BigDecimal empno; private String ename; private String job; private BigDecimal mgr; private Date hiredate; private BigDecimal sal; private BigDecimal comm; private Ref dept; public static BigDecimal wages(Paymaster e) { BigDecimal pay = e.sal; if (e.comm != null) pay = pay.add(e.comm); return pay; } public static void raiseSal(Paymaster[] e, BigDecimal amount) { e[0].sal = // IN OUT passes [0] e[0].sal.add(amount); // increase salary by given amount } // Implement SQLData interface. private String sql_type; public String getSQLTypeName() throws SQLException { return sql_type; } public void readSQL(SQLInput stream, String typeName) throws SQLException { sql_type = typeName; empno = stream.readBigDecimal(); ename = stream.readString(); job = stream.readString(); mgr = stream.readBigDecimal(); hiredate = stream.readDate(); sal = stream.readBigDecimal(); comm = stream.readBigDecimal(); dept = stream.readRef(); } public void writeSQL(SQLOutput stream) throws SQLException { stream.writeBigDecimal(empno); stream.writeString(ename); stream.writeString(job); stream.writeBigDecimal(mgr); stream.writeDate(hiredate); stream.writeBigDecimal(sal); stream.writeBigDecimal(comm); stream.writeRef(dept); } }
パラメータがOracle.sql.STRUCT
からPaymaster
に変更されたため、wages()
のコール仕様を次のように変更する必要があります。
CREATE OR REPLACE FUNCTION wages (e Employee) RETURN NUMBER AS LANGUAGE JAVA NAME 'Paymaster.wages(Paymaster) return BigDecimal';
新しいraiseSal()
メソッドは戻り値がvoid
であるため、そのプロシージャのコール仕様を次のように記述します。
CREATE OR REPLACE PROCEDURE raise_sal (e IN OUT Employee, r NUMBER) AS LANGUAGE JAVA NAME 'Paymaster.raiseSal(Paymaster[], java.math.BigDecimal)';
このコール仕様もトップレベルです。
オブジェクト型メソッドの実装
トップレベルのコール仕様wages
およびraise_sal
を削除し、それらをオブジェクト型Employee
のメソッドとして再宣言するとします。オブジェクト型の仕様部では、すべてのメソッドを属性の後に宣言する必要があります。仕様部で宣言されるのは属性とコール仕様のみであるため、オブジェクト型の本体は必要ありません。Employee
オブジェクト型は、次のように再作成できます。
CREATE TYPE Employee AS OBJECT ( empno NUMBER(4), ename VARCHAR2(10), job VARCHAR2(9), mgr NUMBER(4), hiredate DATE, sal NUMBER(7,2), comm NUMBER(7,2), deptno REF Department MEMBER FUNCTION wages RETURN NUMBER AS LANGUAGE JAVA NAME 'Paymaster.wages() return java.math.BigDecimal', MEMBER PROCEDURE raise_sal (r NUMBER) AS LANGUAGE JAVA NAME 'Paymaster.raiseSal(java.math.BigDecimal)' );
次に、この変更に応じてPaymaster
を変更します。SQLパラメータSELF
は直接Javaパラメータthis
に対応しているため、SELF
がIN OUT
(プロシージャのデフォルト)として宣言されている場合でも、配列をraiseSal()
に渡す必要はありません。
import java.sql.*; import java.io.*; import oracle.sql.*; import oracle.jdbc.*; import oracle.oracore.*; import oracle.jdbc2.*; import java.math.*; public class Paymaster implements SQLData { // Implement the attributes and operations for this type. private BigDecimal empno; private String ename; private String job; private BigDecimal mgr; private Date hiredate; private BigDecimal sal; private BigDecimal comm; private Ref dept; public BigDecimal wages() { BigDecimal pay = sal; if (comm != null) pay = pay.add(comm); return pay; } public void raiseSal(BigDecimal amount) { // For SELF/this, even when IN OUT, no array is needed. sal = sal.add(amount); } // Implement SQLData interface. String sql_type; public String getSQLTypeName() throws SQLException { return sql_type; } public void readSQL(SQLInput stream, String typeName) throws SQLException { sql_type = typeName; empno = stream.readBigDecimal(); ename = stream.readString(); job = stream.readString(); mgr = stream.readBigDecimal(); hiredate = stream.readDate(); sal = stream.readBigDecimal(); comm = stream.readBigDecimal(); dept = stream.readRef(); } public void writeSQL(SQLOutput stream) throws SQLException { stream.writeBigDecimal(empno); stream.writeString(ename); stream.writeString(job); stream.writeBigDecimal(mgr); stream.writeDate(hiredate); stream.writeBigDecimal(sal); stream.writeBigDecimal(comm); stream.writeRef(dept); } }