Javaクラスをデータベースにロードしたとき、そのメソッドは自動的には公開されません。これは、Oracle Databaseでは、SQLからのコールに対してどのメソッドが安全なエントリ・ポイントであるか認識されないためです。メソッドを公開するには、Javaメソッド名、パラメータ・タイプおよび戻り型を、対応するSQLにマップするコール仕様を記述する必要があります。この章では、コール仕様を使用したJavaクラスの公開方法について説明します。
この章の内容は、次のとおりです。
Javaメソッドを公開するには、コール仕様を作成します。特定のJavaメソッドに対して、SQLのCREATE FUNCTION
文またはCREATE PROCEDURE
文を使用してファンクションまたはプロシージャのコール仕様を宣言します。PL/SQLパッケージ内またはSQLオブジェクト型内で、同様の宣言を使用します。
値を戻すJavaメソッドはファンクションとして公開し、Javaメソッドvoid
はプロシージャとして公開します。ファンクションまたはプロシージャの本体にはLANGUAGE JAVA
句が含まれます。この句では、フルネーム、パラメータ・タイプおよび戻り型などのJavaメソッドに関する情報が記録されます。これらの不整合は、実行時まで検出されません。
図6-1に示すように、アプリケーションでは、コール仕様を使用して、つまりコール仕様名を参照することによってJavaメソッドがコールされます。ランタイム・システムでは、Oracleデータ・ディクショナリでコール仕様の定義を検索して、対応するJavaメソッドを実行します。
別の方法として、ネイティブなJavaインタフェースを使用すると、データベース内のJavaメソッドをJavaクライアントから直接コールできます。
コール仕様とそのコール仕様によって公開されるJavaメソッドは、JavaメソッドにPUBLIC
シノニムがないかぎり、同一スキーマに常駐している必要があります。コール仕様の宣言方法は次のとおりです。
スタンドアロンPL/SQLファンクションまたはプロシージャ
パッケージ化されたPL/SQLファンクションまたはプロシージャ
SQLオブジェクト型のメンバー・メソッド
コール仕様によって、Javaメソッドのトップレベルのエントリ・ポイントがOracle Databaseに公開されます。このため、公開できるのはpublic
static
メソッドのみです。ただし、例外が1つあります。インスタンス・メソッドは、SQLオブジェクト型のメンバー・メソッドとして公開できます。
パッケージ化されたコール仕様はトップレベルのコール仕様と同様に機能します。このため、メンテナンスを容易にするために、コール仕様をパッケージ本体に配置することもできます。この方法では、他のスキーマ・オブジェクトを無効にせずにコール仕様を変更できます。また、コール仕様をオーバーロードすることもできます。
この項の内容は、次のとおりです。
Javaや他のオブジェクト指向言語では、メソッドは引数として渡されたオブジェクトに値を割り当てることはできません。SQLまたはPL/SQLからメソッドをコールするときに引数の値を変更するには、コール仕様でOUT
パラメータまたはIN OUT
パラメータとして宣言する必要があります。対応するJavaパラメータは、要素の数が1つのみの配列であることが必要です。
要素の値を適切な型の別のJavaオブジェクトに置き換えるか、またはJavaの型によっては値を変更できます。いずれの方法でも、新しい値がコール元に戻されます。たとえば、コール仕様のNUMBER
型のOUT
パラメータを、float[]
p
として宣言されたJavaパラメータにマッピングし、p[0]
に新しい値を割り当てます。
注意: OUT パラメータまたはIN OUT パラメータを宣言するファンクションは、SQLのデータ操作言語(DML)文からはコールできません。 |
コール仕様では、対応するSQLパラメータとJavaパラメータ、およびファンクションの結果には互換性のあるデータ型を設定する必要があります。
表6-1に、有効なデータ型マッピングが示されています。Oracle Databaseでは、SQL型とJavaクラス間で自動的に変換が行われます。
表6-1 有効なデータ型マッピング
SQL型 | Javaクラス |
---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
ref cursor |
j
|
ユーザー定義の名前付き型、ADT |
|
opaque名前付き型 |
|
NESTED TABLEおよびVARRAY名前付き型 |
|
名前付き型の参照 |
|
次の点を考慮する必要もあります。
最後の4つのSQL型は、まとめて名前付き型と呼ばれています。
BLOB
、CLOB
、BFILE
、REF
CURSOR
および名前付き型を除くすべてのSQL型は、Javaのバイト配列であるJava型byte[]
にマップできます。この場合、引数の変換は、SQL値のRAWバイナリ表現をJavaのバイト配列にコピーする(あるいはその逆へのコピーを行う)ことを意味します。
ORAData
インタフェースおよび関連メソッドを実装するJavaクラス、または表でoracle.sql
クラスのサブクラスとして示されたJavaクラスは、BINARY_INTEGER
およびREF
CURSOR
以外のSQL型からマップできます。通常はこれを使用して、名前付き型をJPublisherで生成されるJavaクラスにマッピングします。
UROWID
型およびNUMBER
のサブタイプ(INTEGER
、REAL
など)はサポートされていません。
LONG
またはLONG RAW
の列から、32KBを超える値をJavaストアド・プロシージャに取り出すことはできません。
Java Database Connectivity(JDBC)では、JDBCドライバのセットを管理するDriverManager
クラスを使用して、データベースとの接続を確立します。JDBCドライバがロードされた後、getConnection()
メソッドを使用できます。正しいドライバが検出されると、getConnection()
メソッドによって、データベース・セッションを表すConnection
オブジェクトが戻されます。SQL文はすべて、そのセッションのコンテキスト内で実行されます。
ただし、サーバー側JDBC内部ドライバは、デフォルトのセッションおよびトランザクション・コンテキスト内で動作します。そのため、すでにデータベースに接続された状態であり、SQL操作はすべてデフォルトのトランザクションの一部です。ドライバは事前に登録されているため、登録する必要はありません。Connection
オブジェクトを取得するには、次のコード行を実行します。
Connection conn = DriverManager.getConnection("jdbc:default:connection:");
IN
パラメータを取らず、かつ1回のみ実行されるSQL文に対しては、Statement
クラスを使用します。Connection
オブジェクト上でcreateStatement()
メソッドがコールされると、新しいStatement
オブジェクトが戻されます。次に例を示します。
String sql = "DROP " + object_type + " " + object_name; Statement stmt = conn.createStatement(); stmt.executeUpdate(sql);
IN
パラメータを取るか、または複数回実行されるSQL文に対しては、PreparedStatement
クラスを使用します。1つ以上のパラメータ・プレースホルダを含むことができるSQL文は、プリコンパイルされます。疑問符(?)がプレースホルダとして機能します。Connection
オブジェクト上でprepareStatement()
メソッドがコールされると、プリコンパイルされたSQL文が含まれる新しいPreparedStatement
オブジェクトが戻されます。次に例を示します。
String sql = "DELETE FROM dept WHERE deptno = ?"; PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setInt(1, deptID); pstmt.executeUpdate();
ResultSet
オブジェクトには、SQLの問合せ結果、つまり検索条件を満たす行が含まれます。next()
メソッドを使用すると次の行に移動し、その行がカレント行になります。カレント行から列の値を取り出すには、get
XXX
()
メソッドを使用します。次に例を示します。
String sql = "SELECT COUNT(*) FROM " + tabName; int rows = 0; Statement stmt = conn.createStatement(); ResultSet rset = stmt.executeQuery(sql); while (rset.next()) { rows = rset.getInt(1); }
CallableStatement
オブジェクトを使用すると、ストアド・プロシージャをコールできます。これには、1つの戻りパラメータと任意の数のIN
、OUT
およびIN OUT
のパラメータを含めることができる、コール・テキストが含まれています。コールは、中カッコ({}
)で区切られるエスケープ句を使用して記述されます。次の例に示すように、エスケープ構文には3つの形式があります。
// parameterless stored procedure CallableStatement cstmt = conn.prepareCall("{CALL proc}"); // stored procedure CallableStatement cstmt = conn.prepareCall("{CALL proc(?,?)}"); // stored function CallableStatement cstmt = conn.prepareCall("{? = CALL func(?,?)}");
重要な点
ストアド・プロシージャにアクセスするJDBCアプリケーションを開発するときは、次の点を考慮する必要があります。
各Oracle JVMセッションには、存在するデータベース・セッションに対して1つの暗黙的なネイティブ接続があります。この接続は概念的で、Javaオブジェクトではありません。これはこのセッションに固有な側面であり、JVM内でオープンまたはクローズできません。
サーバー側JDBC内部ドライバは、デフォルトのトランザクション・コンテキスト内で動作します。データベースにすでに接続された状態であり、SQL操作はすべてデフォルトのトランザクションの一部です。このトランザクションはローカル・トランザクションで、Java Transaction API(JTA)またはJava Transaction Service(JTS)によって実装されるようなグローバル・トランザクションの一部ではありません。
文および結果セットは複数のコールにわたって存続するため、ファイナライザはデータベース・カーソルを解放しません。カーソルが不足しないように、処理が終了した後にすべての文および結果セットをクローズしてください。または、DBAに依頼して、初期化パラメータのOPEN_CURSORS
で設定されている制限値を増やすこともできます。
サーバー側JDBC内部ドライバは、自動コミットをサポートしていません。そのため、アプリケーションで、データベースの変更を明示的にコミットまたはロールバックする必要があります。
サーバー側JDBC内部ドライバを使用して、リモート・データベースには接続できません。Javaプログラムを実行しているサーバーにのみ接続できます。サーバー/サーバー接続の場合は、サーバー側JDBC Thinドライバを使用します。クライアント/サーバー接続の場合は、クライアント側JDBC ThinドライバまたはJDBC Oracle Call Interface(OCI)ドライバを使用します。
通常、デフォルトの接続インスタンスはクローズしないでください。このインスタンスは複数の場所に格納される可能性がある単一インスタンスであり、クローズするとそれぞれの場所が使用できなくなります。クローズすると、それ以降にOracleDriver.defaultConnection
メソッドをコールしたときに、新しいオープン・インスタンスが作成されます。OracleDataSource.getConnection
メソッドはコールするたびに新規オブジェクトを戻しますが、毎回、新しいデータベース接続を作成するわけではありません。同じ暗黙的なネイティブ接続を使用し、同じセッション状態(特にローカル・トランザクション)を共有します。
関連項目: 『Oracle Database JDBC開発者ガイド』 |
SQL*Plusでは、次の構文を使用してトップレベルのコール仕様を対話形式で定義できます。
CREATE [OR REPLACE] { PROCEDURE procedure_name [(param[, param]...)] | FUNCTION function_name [(param[, param]...)] RETURN sql_type} [AUTHID {DEFINER | CURRENT_USER}] [PARALLEL_ENABLE] [DETERMINISTIC] {IS | AS} LANGUAGE JAVA NAME 'method_fullname (java_type_fullname[, java_type_fullname]...) [return java_type_fullname]';
param
は次の構文で表されます。
parameter_name [IN | OUT | IN OUT] sql_type
ストアド・プロシージャをその定義者(AUTHID DEFINER
)または実行者(AUTHID CURRENT_USER
)のどちらの権限を使用して実行するか
スキーマ・オブジェクトに対する未修飾の参照を定義者と実行者のどちらのスキーマで解決するか
AUTHID
を指定しない場合、デフォルトの動作はDEFINER
になり、つまり、ストアド・プロシージャは定義者の権限で実行されます。AUTHID
にCURRENT_USER
を指定して、デフォルトの動作をオーバーライドできます。ただし、CURRENT_USER
を指定してloadjava
の-definer
オプションをオーバーライドすることはできません。
PARALLEL_ENABLE
オプションは、ストアド・ファンクションをパラレルDML評価のスレーブ・セッションで安全に使用できることを宣言します。メイン・セッションの状態は、スレーブ・セッションとは共有されません。各スレーブ・セッションには独自の状態があり、セッションの開始時に初期化されます。ファンクションの結果はセッション変数の状態に依存する必要はありません。依存すると、セッション間で結果が異なることがあります。
DETERMINISTIC
オプションは、オプティマイザによる冗長なファンクション・コールの回避に役立ちます。以前に同じ引数を使用してストアド・ファンクションがコールされた場合、オプティマイザは以前の結果を使用できます。ファンクションの結果はセッション変数またはスキーマ・オブジェクトの状態に依存する必要はありません。依存すると、コール間で結果が異なることがあります。ファンクション索引、またはクエリー・リライトが可能なマテリアライズド・ビューからのみDETERMINISTIC
ファンクションをコールできます。
NAME
句の文字列は、Javaメソッドを一意に識別します。Javaの完全修飾名とコール仕様パラメータは、位置によってマッピングされ、対応する必要があります。ただし、この規則はmain()
メソッドには適用されません。Javaメソッドが引数を取らない場合は、ファンクションまたはプロシージャ用ではなく、Javaメソッド用の空のパラメータ・リストを作成してください。
Javaの完全修飾名はドット表記法を使用して記述します。次の例では、完全修飾名はドットで区切り、行をまたいで記述できることを示しています。
artificialIntelligence.neuralNetworks.patternClassification. RadarSignatureClassifier.computeRange()
この項では、次の例を示します。
例6-1 簡単なJDBCストアド・プロシージャの公開
次のJavaクラスの実行可能ファイルがデータベースにロードされたとします。
import java.sql.*; import java.io.*; import oracle.jdbc.*; public class GenericDrop { public static void dropIt(String object_type, String object_name) throws SQLException { // Connect to Oracle using JDBC driver Connection conn = DriverManager.getConnection("jdbc:default:connection:"); // Build SQL statement String sql = "DROP " + object_type + " " + object_name; try { Statement stmt = conn.createStatement(); stmt.executeUpdate(sql); stmt.close(); } catch (SQLException e) { System.err.println(e.getMessage()); } } }
GenericDrop
クラスにはdropIt()
という名前の1つのメソッドがあり、このメソッドはあらゆる種類のスキーマ・オブジェクトを削除します。たとえば、引数table
およびemp
をdropIt()
に渡すと、メソッドはemp
表をスキーマから削除します。
dropIt()
メソッドのコール仕様は次のとおりです。
CREATE OR REPLACE PROCEDURE drop_it (obj_type VARCHAR2, obj_name VARCHAR2) AS LANGUAGE JAVA NAME 'GenericDrop.dropIt(java.lang.String, java.lang.String)';
String
に対する参照は完全に修飾する必要があることに注意してください。java.lang
パッケージはJavaプログラムで自動的に使用できますが、コール仕様で明示的に名前を付ける必要があります。
例6-2 main()メソッドの公開
通常、Java名とコール仕様パラメータは対応する必要があります。ただし、この規則はmain()
メソッドには適用されません。そのString[]
パラメータは、複数のCHAR
またはVARCHAR2
のコール仕様パラメータにマッピングできます。引数を表示する次のクラスのmain()
メソッドについて考えます。
public class EchoInput { public static void main (String[] args) { for (int i = 0; i < args.length; i++) System.out.println(args[i]); } }
main()
を公開するには、次のコール仕様を記述します。
CREATE OR REPLACE PROCEDURE echo_input(s1 VARCHAR2, s2 VARCHAR2, s3 VARCHAR2) AS LANGUAGE JAVA NAME 'EchoInput.main(java.lang.String[])';
コール仕様パラメータに制約(精度、サイズおよびNOT NULL
など)を課すことはできません。そのため、VARCHAR2
パラメータの最大サイズは指定できません。ただし、VARCHAR2
変数の最大サイズを次のように指定する必要があります。
DECLARE last_name VARCHAR2(20); -- size constraint required
例6-3 整数値を戻すメソッドの公開
次の例では、指定したデータベース表の行数を戻すrowCount()
メソッドが公開されます。
import java.sql.*; import java.io.*; import oracle.jdbc.*; public class RowCounter { public static int rowCount (String tabName) throws SQLException { Connection conn = DriverManager.getConnection("jdbc:default:connection:"); String sql = "SELECT COUNT(*) FROM " + tabName; int rows = 0; try { Statement stmt = conn.createStatement(); ResultSet rset = stmt.executeQuery(sql); while (rset.next()) { rows = rset.getInt(1); } rset.close(); stmt.close(); } catch (SQLException e) { System.err.println(e.getMessage()); } return rows; } }
コール仕様では、NUMBER
のサブタイプ(INTEGER
、REAL
およびPOSITIVE
など)が許可されていません。そのため、次のコール仕様では、戻り型はNUMBER
であり、INTEGER
ではありません。
CREATE FUNCTION row_count (tab_name VARCHAR2) RETURN NUMBER AS LANGUAGE JAVA NAME 'RowCounter.rowCount(java.lang.String) return int';
例6-4 引数の値を切り替えるメソッドの公開
引数の値を切り替える、次のSwapper
クラスのswap()
メソッドについて考えます。
public class Swapper { public static void swap (int[] x, int[] y) { int hold = x[0]; x[0] = y[0]; y[0] = hold; } }
コール仕様によって、swap()
メソッドがコール仕様swap()
として公開されます。値を渡して戻す必要があるため、このコール仕様ではIN OUT
仮パラメータが宣言されます。すべてのコール仕様のOUT
パラメータおよびIN OUT
パラメータを、Java配列パラメータにマッピングする必要があります。
CREATE PROCEDURE swap (x IN OUT NUMBER, y IN OUT NUMBER) AS LANGUAGE JAVA NAME 'Swapper.swap(int[], int[])';
注意: Javaメソッドとそのコール仕様には同じ名前を設定できます。 |
PL/SQLパッケージは、論理的に関連のある型、項目、サブプログラムをグループ化するスキーマ・オブジェクトです。通常、パッケージには仕様部と本体の2つの部分があります。仕様部はアプリケーションに対するインタフェースで、型、定数、変数、例外、カーソルおよびサブプログラムを宣言して使用できるようにします。本体は、カーソルとサブプログラムを定義します。
SQL*Plusでは、次の構文を使用してPL/SQLパッケージを対話形式で定義できます。
CREATE [OR REPLACE] PACKAGE package_name [AUTHID {CURRENT_USER | DEFINER}] {IS | AS} [type_definition [type_definition] ...] [cursor_spec [cursor_spec] ...] [item_declaration [item_declaration] ...] [{subprogram_spec | call_spec} [{subprogram_spec | call_spec}]...] END [package_name]; [CREATE [OR REPLACE] PACKAGE BODY package_name {IS | AS} [type_definition [type_definition] ...] [cursor_body [cursor_body] ...] [item_declaration [item_declaration] ...] [{subprogram_spec | call_spec} [{subprogram_spec | call_spec}]...] [BEGIN sequence_of_statements] END [package_name];]
仕様部にはパブリック宣言が保持されていて、アプリケーションで参照できます。本体には、実装の詳細とプライベート宣言が含まれており、これらはアプリケーションから隠されています。パッケージ本体の宣言部に続いて、オプションの初期化部があります。これには、パッケージ変数を初期化する文が記述されています。これは、初めてパッケージを参照するときに1回のみ実行されます。
パッケージ仕様部で宣言されるコール仕様に、パッケージ本体のサブプログラムと同じシグネチャ(つまり、名前とパラメータのリスト)は設定できません。パッケージ仕様部のすべてのサブプログラムをコール仕様として宣言する場合、パッケージ本体は必要ありません(カーソルを定義するか、初期化部を使用する場合を除きます)。
AUTHID
句は、すべてのパッケージ・サブプログラムをその定義者(AUTHID DEFINER
、デフォルト)と実行者(AUTHID CURRENT_USER
)のどちらの権限を使用して実行するかを指定します。スキーマ・オブジェクトに対する未修飾の参照を定義者と実行者のどちらのスキーマで解決するかも指定します。
例6-5に、パッケージのコール仕様の例を示します。
例6-5 パッケージのコール仕様
新規部門の追加、部門の削除および部門の位置の変更を行うメソッドで構成されたJavaクラスDeptManager
について考えます。addDept()
メソッドは、データベースの順序番号を使用して次の部門番号を取得することに注意してください。これらの3つのメソッドは論理的に関連しているため、それらのコール仕様をPL/SQLパッケージにグループ化できます。
import java.sql.*; import java.io.*; import oracle.jdbc.*; public class DeptManager { public static void addDept (String deptName, String deptLoc) throws SQLException { Connection conn = DriverManager.getConnection("jdbc:default:connection:"); String sql = "SELECT deptnos.NEXTVAL FROM dual"; String sql2 = "INSERT INTO dept VALUES (?, ?, ?)"; int deptID = 0; try { PreparedStatement pstmt = conn.prepareStatement(sql); ResultSet rset = pstmt.executeQuery(); while (rset.next()) { deptID = rset.getInt(1); } pstmt = conn.prepareStatement(sql2); pstmt.setInt(1, deptID); pstmt.setString(2, deptName); pstmt.setString(3, deptLoc); pstmt.executeUpdate(); rset.close(); pstmt.close(); } catch (SQLException e) { System.err.println(e.getMessage()); } } public static void dropDept (int deptID) throws SQLException { Connection conn = DriverManager.getConnection("jdbc:default:connection:"); String sql = "DELETE FROM dept WHERE deptno = ?"; try { PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setInt(1, deptID); pstmt.executeUpdate(); pstmt.close(); } catch (SQLException e) { System.err.println(e.getMessage()); } } public static void changeLoc (int deptID, String newLoc) throws SQLException { Connection conn = DriverManager.getConnection("jdbc:default:connection:"); String sql = "UPDATE dept SET loc = ? WHERE deptno = ?"; try { PreparedStatement pstmt = conn.prepareStatement(sql); pstmt.setString(1, newLoc); pstmt.setInt(2, deptID); pstmt.executeUpdate(); pstmt.close(); } catch (SQLException e) { System.err.println(e.getMessage()); } } }
addDept()
、dropDept()
およびchangeLoc()
メソッドをパッケージ化するとします。最初に、次のようにパッケージ仕様部を作成する必要があります。
CREATE OR REPLACE PACKAGE dept_mgmt AS PROCEDURE add_dept (dept_name VARCHAR2, dept_loc VARCHAR2); PROCEDURE drop_dept (dept_id NUMBER); PROCEDURE change_loc (dept_id NUMBER, new_loc VARCHAR2); END dept_mgmt;
次に、Javaメソッドのコール仕様を次のように記述してパッケージ本体を作成する必要があります。
CREATE OR REPLACE PACKAGE BODY dept_mgmt AS PROCEDURE add_dept (dept_name VARCHAR2, dept_loc VARCHAR2) AS LANGUAGE JAVA NAME 'DeptManager.addDept(java.lang.String, java.lang.String)'; PROCEDURE drop_dept (dept_id NUMBER) AS LANGUAGE JAVA NAME 'DeptManager.dropDept(int)'; PROCEDURE change_loc (dept_id NUMBER, new_loc VARCHAR2) AS LANGUAGE JAVA NAME 'DeptManager.changeLoc(int, java.lang.String)'; END dept_mgmt;
dept_mgmt
パッケージのストアド・プロシージャを参照するには、次のように、ドット表記法を使用します。
CALL dept_mgmt.add_dept('PUBLICITY', 'DALLAS');
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
)のどちらの権限を使用して実行するかを指定します。スキーマ・オブジェクトに対する未修飾の参照を定義者と実行者のどちらのスキーマで解決するかも指定します。
この項の内容は、次のとおりです。
オブジェクト型仕様部では、属性はすべてメソッドの前に宣言する必要があります。また、少なくとも1つの属性を宣言する必要があります。宣言できる属性の最大数は1000個です。メソッドはオプションです。
Java変数と同様に、属性は名前とデータ型で宣言します。名前はオブジェクト型内で一意であることが必要ですが、他のオブジェクト型では再利用できます。データ型は任意のSQL型に設定できますが、LONG
、LONG RAW
、NCHAR
、NVARCHAR2
、NCLOB
、ROWID
およびUROWID
には設定できません。
代入演算子またはDEFAULT
句を使用して宣言部で属性を初期化することはできません。また、NOT NULL
制約を属性に課すこともできません。ただし、制約を課すことができるデータベース表にオブジェクトを格納することはできます。
属性の宣言後に、メソッドを宣言できます。MEMBER
メソッドは、オブジェクト型のインスタンスである、SELF
と呼ばれる組込みパラメータを受け入れます。暗黙的に宣言されたか明示的に宣言されたかに関係なく、これは常に最初にMEMBER
メソッドに渡されるパラメータです。メソッド本体では、SELF
はメソッドがコールされたオブジェクトを示します。MEMBER
メソッドは、次のように、インスタンスに対してコールされます。
instance_expression.method()
STATIC
メソッドは、SELF
の受入れまたは参照ができず、次のように、インスタンスではなくオブジェクト型に対して起動されます。
object_type_name.method()
STATIC
でないJavaメソッドをコールする場合は、コール仕様でキーワードMEMBER
を指定する必要があります。同様に、STATIC
のJavaメソッドをコールする場合は、コール仕様でキーワードSTATIC
を指定する必要があります。
この項の内容は、次のとおりです。
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つのオブジェクトは、対応する属性の値が等しい場合は等価です。 |
オブジェクト型にはすべて、そのオブジェクト型と名前が同じシステム定義のファンクションであるコンストラクタがあります。コンストラクタでは、そのオブジェクト型のインスタンスが初期化されて戻されます。
Oracle Databaseでは、すべてのオブジェクト型に対してデフォルト・コンストラクタが生成されます。コンストラクタの仮パラメータは、オブジェクト型の属性と一致します。つまり、パラメータと属性は同じ順序で宣言され、名前とデータ型が同じです。SQLではコンストラクタは暗黙的にはコールされません。そのため、明示的にコールする必要があります。コンストラクタ・コールは、ファンクション・コールが使用できる場所で使用できます。
注意: JavaコンストラクタをSQLから起動するには、コンストラクタのコールをstatic メソッドにラップし、対応するコール仕様をオブジェクト型のSTATIC メンバーとして宣言する必要があります。 |
この項の各例は、それぞれ前の例に基づきます。まず、部門と従業員を表す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.empno, e.ename, e.job, e.mgr, e.hiredate, e.sal, e.comm, (SELECT REF(d) FROM depts d WHERE d.deptno = e.deptno)) FROM emp 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クラスを作成します。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); } }