ヘッダーをスキップ
Oracle® Database Java開発者ガイド
11gリリース2 (11.2)
B56280-05
  目次へ移動
目次
索引へ移動
索引

前
 
次
 

6 コール仕様を使用したJavaクラスの公開

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メソッドを実行します。

図6-1 Javaメソッドのコール

図6-1の説明が続きます
「図6-1 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クラス

CHARVARCHAR2LONG

java.lang.String

oracle.sql.CHAR

NUMBER

boolean

char

byte

short

int

long

float

double

java.lang.Byte

java.lang.Short

java.lang.Integer

java.lang.Long

java.lang.Float

java.lang.Double

java.math.BigDecimal

oracle.sql.NUMBER

BINARY_INTEGER

boolean

char

byte

short

int

long

BINARY_FLOAT

oracle.sql.BINARY_FLOAT

BINARY_DOUBLE

oracle.sql.BINARY_DOUBLE

DATE

oracle.sql.DATE

RAW

oracle.sql.RAW

BLOB

oracle.sql.BLOB

CLOB

oracle.sql.CLOB

BFILE

oracle.sql.BFILE

ROWID

oracle.sql.ROWID

TIMESTAMP

oracle.sql.TIMESTAMP

TIMESTAMPWITHTIMEZONE

oracle.sql.TIMESTAMPTZ

TIMESTAMPWITHLOCALTIMEZONE

oracle.sql.TIMESTAMPLTZ

ref cursor

java.sql.ResultSet

sqlj.runtime.ResultSetIterator

ユーザー定義の名前付き型、ADT

oracle.sql.STRUCT

opaque名前付き型

oracle.sql.OPAQUE

NESTED TABLEおよびVARRAY名前付き型

oracle.sql.ARRAY

名前付き型の参照

oracle.sql.REF


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

  • 最後の4つのSQL型は、まとめて名前付き型と呼ばれています。

  • BLOBCLOBBFILEREF CURSORおよび名前付き型を除くすべてのSQL型は、Javaのバイト配列であるJava型byte[]にマップできます。この場合、引数の変換は、SQL値のRAWバイナリ表現をJavaのバイト配列にコピーする(あるいはその逆へのコピーを行う)ことを意味します。

  • ORADataインタフェースおよび関連メソッドを実装するJavaクラス、または表でoracle.sqlクラスのサブクラスとして示されたJavaクラスは、BINARY_INTEGERおよびREF CURSOR以外のSQL型からマップできます。通常はこれを使用して、名前付き型をJPublisherで生成されるJavaクラスにマッピングします。

  • UROWID型およびNUMBERのサブタイプ(INTEGERREALなど)はサポートされていません。

  • LONGまたはLONG RAWの列から、32KBを超える値をJavaストアド・プロシージャに取り出すことはできません。

サーバー側JDBC内部ドライバの使用

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()メソッドを使用すると次の行に移動し、その行がカレント行になります。カレント行から列の値を取り出すには、getXXX()メソッドを使用します。次に例を示します。

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つの戻りパラメータと任意の数のINOUTおよび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句は、次の事項を指定します。

  • ストアド・プロシージャをその定義者(AUTHID DEFINER)または実行者(AUTHID CURRENT_USER)のどちらの権限を使用して実行するか

  • スキーマ・オブジェクトに対する未修飾の参照を定義者と実行者のどちらのスキーマで解決するか

AUTHIDを指定しない場合、デフォルトの動作はDEFINERになり、つまり、ストアド・プロシージャは定義者の権限で実行されます。AUTHIDCURRENT_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およびempdropIt()に渡すと、メソッドは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のサブタイプ(INTEGERREALおよび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型に設定できますが、LONGLONG RAWNCHARNVARCHAR2NCLOBROWIDおよび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などのブール式を評価し、DISTINCTGROUP BYおよびORDER BYの各句に伴う比較を行います。マップ・メソッドは、それらすべてのオブジェクトの順序付けにおけるオブジェクトの相対的な位置を戻します。オブジェクト型に含めることができるマップ・メソッドは1つのみで、戻り型がDATENUMBERまたはVARCHAR2のいずれかである、パラメータのないファンクションであることが必要です。

また、2つのオブジェクトを比較するオーダー・メソッドを持つSQLを提供することもできます。オーダー・メソッドでは、2つのパラメータ、つまり組込みパラメータSELFとそれと同じ型の別のオブジェクトのみが使用されます。o1o2のオブジェクトがある場合は、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を宣言するには、データ型REFREFの対象となるオブジェクト型を指定します。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マッピングが使用されます。たとえば、NUMBERBigDecimalにマッピングされます。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に対応しているため、SELFIN 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);
  }
}