ヘッダーをスキップ
Oracle Database Java開発者ガイド
10gリリース2(10.2)
B19189-01
  目次へ
目次
索引へ
索引

前へ
前へ
次へ
次へ
 

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

Javaクラスをデータベースにロードしたとき、そのメソッドは自動的には公開されません。これは、Oracle Databaseでは、SQLからのコールに対してどのメソッドが安全なエントリ・ポイントであるか認識されないためです。メソッドを公開するには、コール仕様を記述する必要があります。このコール仕様によって、Javaのメソッド名、パラメータ・タイプおよび戻り型が、SQLのメソッド名、パラメータ・タイプおよび戻り型にマッピングされます。この章では、コール仕様を使用したJavaクラスの公開方法について説明します。

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

6.1 コール仕様の概要

Javaメソッドを公開するには、コール仕様を作成します。特定のJavaメソッドに対して、SQLのCREATE FUNCTION文またはCREATE PROCEDURE文を使用してファンクションまたはプロシージャのコール仕様を宣言します。PL/SQLパッケージ内またはSQLオブジェクト型内で、同様の宣言を使用します。

値を戻すJavaメソッドはファンクションまたはプロシージャとして公開し、Javaメソッドvoidはプロシージャとして公開します。ファンクションまたはプロシージャの本体にはLANGUAGE JAVA句が含まれます。この句では、フルネーム、パラメータ・タイプおよび戻り型などのJavaメソッドに関する情報が記録されます。これらの不整合は、実行時まで検出されません。

図6-1に示すように、アプリケーションでは、コール仕様を使用して、つまりコール仕様名を参照することによってJavaメソッドがコールされます。ランタイム・システムでは、Oracleデータ・ディクショナリでコール仕様の定義を検索して、対応するJavaメソッドを実行します。

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

Javaメソッドのコール
画像の説明

別の方法として、ネイティブなJavaインタフェースを使用すると、データベース内のJavaメソッドをJavaクライアントから直接コールできます。

6.2 コール仕様の定義

コール仕様とそのコール仕様によって公開されるJavaメソッドは、JavaメソッドにPUBLICシノニムがないかぎり、同一スキーマに常駐している必要があります。コール仕様の宣言方法は次のとおりです。

コール仕様によって、Javaメソッドのトップレベルのエントリ・ポイントがOracle Databaseに公開されます。このため、公開できるのはpublic staticメソッドのみです。ただし、例外が1つあります。インスタンス・メソッドは、SQLオブジェクト型のメンバー・メソッドとして公開できます。

パッケージ化されたコール仕様はトップレベルのコール仕様と同様に機能します。このため、メンテナンスを容易にするために、コール仕様をパッケージ本体に配置することもできます。この方法では、他のスキーマ・オブジェクトを無効にせずにコール仕様を変更できます。また、コール仕様をオーバーロードすることもできます。

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

6.2.1 パラメータ・モードの設定

Javaや他のオブジェクト指向言語では、メソッドは引数として渡されたオブジェクトに値を割り当てることはできません。SQLまたはPL/SQLからメソッドをコールするときに引数の値を変更するには、コール仕様でOUTパラメータまたはIN OUTパラメータとして宣言する必要があります。対応するJavaパラメータは、要素の数が1つのみの配列であることが必要です。

要素の値を適切な型の別のJavaオブジェクトに置き換えるか、またはJavaの型によっては値を変更できます。いずれの方法でも、新しい値がコール元に戻されます。たとえば、コール仕様のNUMBER型のOUTパラメータを、float[] pとして宣言されたJavaパラメータにマッピングし、p[0]に新しい値を割り当てます。


注意:

OUTパラメータまたはIN OUTパラメータを宣言するファンクションは、SQLのデータ操作言語(DML)文からはコールできません。

6.2.2 データ型のマッピング

コール仕様では、対応するSQLパラメータとJavaパラメータ、およびファンクションの結果には互換性のあるデータ型を設定する必要があります。表6-1に、有効なデータ型マッピングが示されています。Oracle Databaseでは、SQL型とJavaクラス間で自動的に変換が行われます。

表6-1 有効なデータ型マッピング

SQL型 Javaクラス
CHARLONGVARCHAR2 oracle.sql.CHAR

java.lang.String

java.sql.Date

java.sql.Time

java.sql.Timestamp

java.lang.Byte

java.lang.Short

java.lang.Integer

java.lang.Long

java.lang.Float

java.lang.Double

java.math.BigDecimal

byteshortintlongfloatdouble

DATE oracle.sql.DATE

java.sql.Date

java.sql.Time

java.sql.Timestamp

java.lang.String

NUMBER oracle.sql.NUMBER

java.lang.Byte

java.lang.Short

java.lang.Integer

java.lang.Long

java.lang.Float

java.lang.Double

java.math.BigDecimal

byteshortintlongfloatdouble

OPAQUE oracle.sql.OPAQUE
RAWLONG RAW oracle.sql.RAW

byte[]

ROWID oracle.sql.CHAR

oracle.sql.ROWID

java.lang.String

BFILE oracle.sql.BFILE
BLOB oracle.sql.BLOB

oracle.jdbc2.Blob

CLOBNCLOB oracle.sql.CLOB

oracle.jdbc2.Clob

OBJECT

オブジェクト型

oracle.sql.STRUCT

java.sql.Struct

java.sql.SqlData

oracle.sql.ORAData

REF

参照型

oracle.sql.REF

java.sql.Ref

oracle.sql.ORAData

TABLEVARRAY

ネストした表の型およびVARRAY

oracle.sql.ARRAY

java.sql.Array

oracle.sql.ORAData

前述のSQL型のいずれか oracle.sql.CustomDatum

oracle.sql.Datum


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

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

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

  • Javaラッパー・クラス(java.lang.Bytejava.lang.Shortなど)は、SQLからNULLを戻す場合に役立ちます。

  • oracle.sql.CustomDatumクラスを使用してパラメータを宣言するときは、次のメンバーを定義する必要があります。

    public static oracle.sql.CustomDatumFactory.getFactory();
    
    
  • oracle.sql.Datumは抽象クラスです。oracle.sql.Datum型のパラメータに渡される値は、SQL型と互換性のあるJavaクラスに属している必要があります。同様に、戻り型がoracle.sql.Datumであるメソッドによって戻される値は、SQL型と互換性のあるJavaクラスに属している必要があります。

  • oracle.sqlクラスへのマッピングは、データ・フォーマットが保持され、キャラクタ・セットの変換も必要ないため最適です。ただし、通常のネットワーク変換は除きます。これらのクラスは、SQLとJavaの間でデータを移動するアプリケーションで特に役立ちます。

6.2.3 サーバー側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アプリケーションを開発するときは、次の点を考慮する必要があります。

  • サーバー側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)ドライバを使用します。

  • サーバー側JDBC内部ドライバによって確立されたデータベースとの物理接続をクローズすることはできません。ただし、デフォルトの接続でclose()メソッドをコールすると、同じオブジェクトを参照するすべての接続インスタンスがクリーン・アップされ、クローズされます。新しい接続オブジェクトを取得するには、getConnection()を再度コールする必要があります。


関連項目:

『Oracle Database JDBC開発者ガイドおよびリファレンス』

6.3 トップレベルのコール仕様の作成

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を指定してデフォルトの動作をオーバーライドできますただし、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メソッドとそのコール仕様には同じ名前を設定できます。

6.4 パッケージのコール仕様の記述

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句は、すべてのパッケージ・サブプログラムをその定義者(デフォルト)と実行者のどちらの権限を使用して実行するかを指定します。また、スキーマ・オブジェクトに対する未修飾の参照を定義者と実行者のどちらのスキーマで解決するかも指定します。

例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');

6.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句は、すべてのメンバー・メソッドを現行ユーザーの権限(定義者権限または実行者権限)で実行するかを決定します。

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

6.5.1 属性の宣言

オブジェクト型仕様部では、属性はすべてメソッドの前に宣言する必要があります。また、少なくとも1つの属性を宣言する必要があります。宣言できる属性の最大数は1000個です。メソッドはオプションです。

Java変数と同様に、属性は名前とデータ型で宣言します。名前はオブジェクト型内で一意であることが必要ですが、他のオブジェクト型では再利用できます。データ型は任意のSQL型に設定できますが、LONGLONG RAWNCHARNVARCHAR2NCLOBROWIDおよびUROWIDには設定できません。

代入演算子またはDEFAULT句を使用して宣言部で属性を初期化することはできません。また、NOT NULL制約を属性に課すこともできません。ただし、制約を課すことができるデータベース表にオブジェクトを格納することはできます。

6.5.2 メソッドの宣言

属性の宣言後に、メソッドを宣言できます。MEMBERメソッドは、SELFと呼ばれる組込みパラメータを受け入れます。このパラメータは、オブジェクト型のインスタンスです。暗黙的に宣言されたか明示的に宣言されたかに関係なく、このパラメータは常に最初にMEMBERメソッドに渡されるパラメータです。メソッド本体では、SELFはメソッドがコールされたオブジェクトを示します。MEMBERメソッドは、次のように、インスタンスに対してコールされます。

instance_expression.method()

STATICメソッドは、SELFの受入れまたは参照ができず、次のように、インスタンスではなくオブジェクト型に対して起動されます。

object_type_name.method()

STATICでないJavaメソッドをコールする場合は、コール仕様でキーワードMEMBERを指定する必要があります。同様に、STATICのJavaメソッドをコールする場合は、コール仕様でキーワードSTATICを指定する必要があります。

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

6.5.2.1 マップ・メソッドとオーダー・メソッド

CHARなどのSQLスカラー・データ型の値には、事前定義済の順序があり、これによって他の値と比較できます。ただし、オブジェクト型のインスタンスには事前定義済の順序はありません。これらのインスタンスに順序を付けるには、SQLでユーザー定義のマップ・メソッドをコールします。

SQLは、順序付けを使用してx > yなどのブール式を評価し、DISTINCTGROUP BYおよびORDER BYの各句に伴う比較を行います。マップ・メソッドは、それらすべてのオブジェクトの順序付けにおけるオブジェクトの相対的な位置を戻します。オブジェクト型に含めることができるマップ・メソッドは1つのみで、戻り型がDATENUMBERまたはVARCHAR2のいずれかである、パラメータのないファンクションであることが必要です。

また、2つのオブジェクトを比較するオーダー・メソッドを持つSQLを提供することもできます。オーダー・メソッドでは、2つのパラメータ、つまり組込みパラメータSELFとそれと同じ型の別のオブジェクトのみが使用されます。o1o2のオブジェクトがある場合は、o1 > o2のような比較によってオーダー・メソッドが自動的にコールされます。メソッドは、負数、0(ゼロ)または正数を戻し、それぞれSELFがもう一方のパラメータと比較して小さい、等しいまたは大きいことを示します。オブジェクト型に含めることができるオーダー・メソッドは1つのみで、そのオーダー・メソッドは数値結果を戻すファンクションであることが必要です。

マップ・メソッドまたはオーダー・メソッドのどちらかを宣言できますが、両方を宣言することはできません。どちらかのメソッドを宣言すると、SQLとPL/SQLでオブジェクトを比較できます。ただし、どちらのメソッドも宣言しない場合、比較できるのは、SQLのオブジェクトの等価または非等価のみです。


注意:

同じ型の2つのオブジェクトは、対応する属性の値が等しい場合は等価です。

6.5.2.2 コンストラクタ・メソッド

オブジェクト型にはすべて、そのオブジェクト型と名前が同じシステム定義のファンクションであるコンストラクタがあります。コンストラクタでは、そのオブジェクト型のインスタンスが初期化されて戻されます。

Oracle Databaseでは、すべてのオブジェクト型に対してデフォルト・コンストラクタが生成されます。コンストラクタの仮パラメータは、オブジェクト型の属性と一致します。つまり、パラメータと属性は同じ順序で宣言され、名前とデータ型が同じです。SQLではコンストラクタは暗黙的にはコールされません。そのため、明示的にコールする必要があります。コンストラクタ・コールは、ファンクション・コールが使用できる場所で使用できます。


注意:

JavaコンストラクタをSQLから起動するには、コンストラクタのコールをstaticメソッドにラップし、対応するコール仕様をオブジェクト型のSTATICメンバーとして宣言する必要があります。

6.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を宣言するには、データ型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を使用します。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);
  }
}

パラメータがoralce.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);
  }
}