java ストアド・プロシージャ

  1. java ストアド・プロシージャとは
  2. java ストアド・プロシージャの開発の前提
    1. oracle JVM の手動でのインストール
  3. java ストアド・プロシージャの開発手順
    1. java プログラムの作成
    2. java クラスの登録
    3. java クラスの公開
    4. java ストアド・プロシージャのコール
  4. JVM の例外処理
  5. java ストアド・プロシージャの例
  6. JServer 用メモリ初期化パラメータ


  1. java ストアド・プロシージャとは
  2. java ストアド・プロシージャとは、SQL に対して公開され、一般的用途のために oracle データベースに格納された java メソッドのことで、 クライアント・アプリケーションからコールされると、 引数を受け取り、java のメソッドを実行し、結果を戻します。

    java のメソッドを公開するには、コール仕様を記述します。 このコール仕様により、java のメソッド名、引数の型および戻り値の型が、 対応する SQL にマップされます。

    コール仕様は単に java メソッドを公開するだけで、 コール仕様を通してメソッドをコールすると、 ランタイム・システムは最小のオーバーヘッドでコールを処理します。

    java のクラスを oracle 8i データベースに組み込むことで、 java アプリケーションは、 データベース・セッションのコンテキスト内に組み込まれます。

    JServer は、そのセッション内のコール全般にわたって、 クライアントの java の状態を保持します。

    1つのセッションのコンテキストで、 クライアントは次のことを実行します。

    1. データベースに接続してセッションを開きます。
    2. データベース内でコールとして java ストアド・プロシージャを実行します。
    3. セッション内で処理を続けます。
    4. セッションを終了します。

  3. java ストアド・プロシージャの開発の前提
  4. ここでは、oracle 8.1.6 での動作について述べます。

    oracle JVM が正しくインストールされていることを、以下の手順で確認してください。

    > svrmgrl
    Oracle Server Manager Release 3.1.6.0.0 - Production
    
    Copyright (c) 1997, 1999, Oracle Corporation.  All Rights Reserved.
    
    Oracle8i Enterprise Edition Release 8.1.6.1.0 - Production
    With the Partitioning option
    JServer Release 8.1.6.0.0 - Production
    
    SVRMGR> connect internal
    
    SVRMGR> SELECT count(*) FROM dba_objects WHERE object_type LIKE 'JAVA%';
    SVRMGR> 
      COUNT(*)
    ----------
          8663
    
    

    8.1.5 では最低でも 4000、 8.1.6 では最低でも 8000 が返ってこない場合は、 oracle JVM のインストールに失敗していますので、 手動で再インストールする必要があります。

    その場合、以下の条件を満たす必要があります。

    1. init.ora のパラメータ
      • shared_pool_size = 50M
      • java_pool_size = 20M
      • compatibility = 8.1.0 以上

    2. svrmgrlで確認、設定
      • ロールバックセグメントとして 250M
      • システム表領域として 30M を追加

      svrmgrl
      SVRMGR> connect internal
      SVRMGR> select * from dba_data_files;
               :
      SVRMGR> alter database datafile 'ロールバックセグメント・ファイル名' resize 250m;
      SVRMGR> alter database datafile 'システム表領域ファイル名' resize XX+30M;
      
      

    3. クライアントの JDK は、jdk 1.1.6 以上

    1. oracle JVM の手動でのインストール
    2. cd $ORACLE_HOME/javavm/install
      svrmgrl
      SVRMGR> connect internal
      SVRMGR> @initjvm
      

  5. java ストアド・プロシージャの開発手順
    1. java プログラムを作成し、クラスファイルを作る
    2. java クラスを oracle データベースに登録する
    3. java クラスを公開する
    4. java ストアド・プロシージャをコールする

    Hello、Hello2 という java ストアド・プロシージャを作って見ましょう。

    1. java プログラムの作成
    2. java ストアド・プロシージャには、以下の制限があります。

      public class Hello {
         public static void HelloWorld() {
            System.out.println("Hello World !!");
         }
      }
      class Hello2 {
         public static String Hello() {
            return "Hello World 2" ;
         }
      }
      

    3. java クラスの登録
    4. loadjava を使って、oracle データベースにスキーマ・オブジェクトとして、 java のソース、クラス、リソース・ファイルを登録できます。 java アーカイブ (JAR/ZIP) を登録するのが最も簡単です。

      さらに dropjava を使って、java のソース、クラス、スキーマ・オブジェクトを、 データベース・スキーマから削除できます。

      loadjava は java プログラム内のすべてのクラスの外部参照を、 次の3つのモードで解決します。

      モード 説            明
      ロード後 -resolve はクラスをロードし INVALID をつけ、その後解決します。 ロードされていないクラスに依存するクラスは、すべて INVALID です。
      ロード時 -andresolve はクラスをロードし解決します。 一般的に、このモードはお薦めしません。
      解決の延期 上記2つを指定しないと、クラスをロードしますが、 コンパイルも解決もしません。INVALID がついたクラスを call すると、 リゾルバが実行され、解決されたものは VALID に変更されます。

      Hello.class と Hello2.class をロードします。

      loadjava -resolve -user scott/tiger Hello.class
      loadjava -resolve -user scott/tiger Hello2.class
      

      このとき、"ORA-00904:列名が無効です" のエラーが出るのは、 oracle JVM が正しくインストールされていないときです。 「java ストアド・プロシージャの開発の前提」を参考に, 再インストールしてから再試行してください。

      クラスの登録がうまくいったかどうか、以下の手順で確認します。

      SVRMGR> set charwidth 30
      Charwidth                       30
      SVRMGR> select object_name,OBJECT_TYPE,status,timestamp from  user_objects
           2  where status='VALID' and OBJECT_TYPE like 'JAVA%';
      
      OBJECT_NAME                    OBJECT_TYPE        STATUS  TIMESTAMP
      ------------------------------ ------------------ ------- -------------------
      Hello                          JAVA CLASS         VALID   2001-01-29:16:31:49
      Hello2                         JAVA CLASS         VALID   2001-01-30:14:51:38
      
      2 行選択されました。
      
      

      クラス・スキーマ・オブジェクトが、正常に解決されると status は VALID になります。

    5. java クラスの公開
    6. java クラスの公開には、コール仕様を記述します。 これは、ロードされたクラス内のメソッド名をエントリ・ポイントとして指定します。 また、対応する SQL と java メソッドへの引数および戻り値には、 互換性のあるデータ型を設定する必要があります。

      有効なデータ型のマッピング方法は、 以下のデータ型のマッピング表に示します。

      次のコール仕様が宣言できます。

      トップレベルの PL/SQL の場合は、2通あります。
      java ストアド・プロシージャに戻り値がない場合と、ある場合です。

      戻り値がない場合の公開方法:PROCEDURE

      sqlplus scott/tiger
      SQL> create or replace PROCEDURE hello
        2  as language java name 'Hello.HelloWorld';
        3  /
      

      戻り値がある場合の公開方法:FUNCTION

      sqlplus scott/tiger
      SQL> create or replace FUNCTION hello2 return VARCHAR2
        2  as language java name 'Hello2.Hello() return java.lang.String';
        3  /
      

    7. java ストアド・プロシージャのコール
    8. SQL の call 文を使うと、トップレベルで公開された java メソッドを、 PL/SQL パッケージ文または SQL オブジェクト型でコールできます。

      SQL*Plus からは、次のように call 文を対話形式で実行もできますし、 PL/SQL ストアド・ファンクションとしてコールすることもできます。

      SQL> call hello();
      SQL> variable myString varchar2(20);
        2  call hello2() into :myString;
        3  print myString;
      

      hello2 は、以下のようにしても、コールできます。

      SQL> select hello2 from dual;
      

      コールの結果

      コールの結果の出力は hello の場合は、トレース・ファイル ($ORACLE_BASE/admin/$ORACLE_SID/udump ディレクトリの下) にかかれます。
      また hello2 の場合は、直接返ってきます。

      [ /home4/ora816/app/oracle/admin/ora816/udump/ora_14492.trc の内容 ]
      
      Oracle8i Enterprise Edition Release 8.1.6.1.0 - Production
      With the Partitioning option
      JServer Release 8.1.6.0.0 - Production
      ORACLE_HOME = /home4/ora816/app/oracle/product/8.1.6
      System name:    Linux
      Node name:      perugia.it.ns-sol.co.jp
      Release:        2.2.5-22
      Version:        #1 Wed Jun 2 09:17:03 EDT 1999
      Machine:        i686
      Instance name: ora816
      Redo thread mounted by this instance: 1
      Oracle process number: 11
      Unix process pid: 14492, image: oracle@perugia.it.ns-sol.co.jp (TNS V1-V3)
      
      *** 2001-01-29 16:32:20.553
      *** SESSION ID:(10.46818) 2001-01-29 16:32:20.552
      Hello World !!
      
      SQL> コールが完了しました。
      
      
      MYSTRING
      --------------------------------
      Hello World 2
      

      サーバ上の出力のリダイレクト

      System.out と System.err の出力先は、カレント・トレース・ファイルです。 これを SQL*Plus テキスト・バッファにリダイレクトするには、 次のように指定します。 そうするとコール終了時に出力されます。

      最小バッファ・サイズは 2000 バイト、 最大バッファ・サイズは 999,999 バイトです。

      SQL> set serveroutput on size 10000
      SQL> call dbms_java.set_output(10000);
      

  6. JVM の例外処理
  7. java ストアド・プロシージャが SQL 文を実行中に例外を検出すると、 そのプロシージャに対して java.sql.SQLException のサブクラスとしてインスタンス化された例外が投げられます。

    この例外クラスには、getErrorCode() と getMessage() メソッドがあり、 これらを使って Oracle エラーコードとエラーメッセージを得られます。

    SQL や PL/SQL からコールされたストアド・プロシージャで キャッチされない例外が発生した場合は、次のエラーメッセージがでます。

    ORA-29532 不明な Java 例外で Java コールが終了しました:<文字列>
    
    <文字列> には java からのエラーが入ります。
    

  8. java ストアド・プロシージャの例
  9. java でデータベースにアクセスするプログラムを作り、 コール仕様を宣言し、svrmgr から呼んでみましょう。

    import java.sql.*;
    import oracle.jdbc.driver.*;
    
    class Emp {
      public static void delEmp(String cond)  throws SQLException {
         String defconn = "jdbc:default:connection:";
         Emp    e = new Emp();
         int    len;
    
         len = e.chkarg(cond);
         if (len == 0) return;
         Connection conn = DriverManager.getConnection(defconn);
         String sql      = "DELETE FROM EMP WHERE " + cond;
         System.out.println("SQL = " + sql);
         try {
           Statement  stmt = conn.createStatement();
           stmt.executeUpdate(sql);
           stmt.close();
         } catch (SQLException ex) {System.err.println(ex.getMessage());}
      }
      public static void main(String[] args ) throws SQLException {
         Emp e = new Emp();
         if (args.length != 0)   System.out.println("len = " + e.chkarg(args[0]));
      }
      int chkarg(String arg) {
         int len;
    
         try {
            len =  arg.length();
            if (len == 0 )  {
               System.out.println("chkarg : arg is null");
               return 0;
            }
         }  catch (Exception e) {
            System.out.println("chkarg : Exception= "+e.toString());
            System.out.flush();
            return 0;
         }
         return len;
       }
    }
    
    class ROWcnt {
      public static int CountROW(String tblNM)  throws SQLException {
         String defconn  = "jdbc:default:connection:";
         String sql_base = "SELECT COUNT(*) FROM ";
         String tbl_emp  = "EMP";
         String sql,tblnm  ;
         Emp    e = new Emp();
         int    rows = 0,tlen;
    
         tblnm = tbl_emp;
         tlen  = e.chkarg(tblNM);
         if (tlen != 0)   tblnm = tblNM;
    
         sql = sql_base + tblnm;
         System.out.println("SQL = " + sql + ": tblnm = "+ tblnm);
         System.out.flush();
         Connection conn = DriverManager.getConnection(defconn);
         try {
           Statement  stmt = conn.createStatement();
           ResultSet  rset = stmt.executeQuery(sql);
           while (rset.next())  { rows = rset.getInt(1);}
           rset.close();
           stmt.close();
         } catch (SQLException ex) {System.err.println(ex.getMessage());}
         return rows;
      }
    
      public static void main(String[] args) throws SQLException {
         Emp e = new Emp();
    
         if (args.length  != 0)  System.out.println("len = " + e.chkarg(args[0]));
      }
    }
    
    SVRMGR> create or replace PROCEDURE delEmp (table_name IN VARCHAR2)
         2> as language java name 'Emp.delEmp(java.lang.String)';
         3> /
    文が処理されました。
    
    SVRMGR> create or replace FUNCTION cntRow (table_name IN VARCHAR2) return NUMBER
         2> as language java name 'ROWcnt.CountROW(java.lang.String) return int';
         3> /
    文が処理されました。
    
    SVRMGR> select ename,sal from emp;
    ENAME      SAL
    ---------- ----------
    SMITH             800
    ALLEN            1600
    WARD             1250
    JONES            2975
    MARTIN           1250
    BLAKE            2850
    CLARK            2450
    SCOTT            3000
    KING             5000
    TURNER           1500
    ADAMS            1100
    JAMES             950
    FORD             3000
    MILLER           1300
    14 行選択されました。
    
    SVRMGR> call delEmp('sal >1500');
    文が処理されました。
    SQL = DELETE FROM EMP WHERE sal >1500
    
    SVRMGR> select ename,sal from emp;
    ENAME      SAL
    ---------- ----------
    SMITH             800
    WARD             1250
    MARTIN           1250
    TURNER           1500
    ADAMS            1100
    JAMES             950
    MILLER           1300
    7 行選択されました。
    
    SVRMGR> @callRow
    文が処理されました。
    SQL = SELECT COUNT(*) FROM emp: tblnm = emp
    ROWNO
    ----------
             7
    
    ---- callRow.sql --------------
    variable rowno NUMBER
    call cntRow('emp') into :rowno;
    print rowno;
    

  10. JServer 用メモリ初期化パラメータ
  11. Java はヒープをクラスのロード域とオブジェクトの格納域の両方として使用します。
    (オブジェクト格納域は、一般的に new したオブジェクト用格納域です)

    一方、JServer はヒープを2つに分けて使用します。 クラスのロード域としては shared_pool を使用し、 オブジェクトの格納域としては、 専用サーバの場合は UGA を、マルチスレッドサーバの場合は java_pool を使用します。

    init.ora で変更できる JServer のメモリ初期化パラメータは、以下のとおりです。

    パラメータ名 説          明
    shared_pool_size JServer の クラス・ローダがクラスのロード用として使います。
    java_pool_size JServer のメモリ・マネージャが使います。
    MTS の場合は、実行時のオブジェクトを格納するのに使いますが、 専用サーバの場合は、ここを使わず UGA を使います。
    java_soft_sessionspace_limit 1つのセッション期間中、java のメモリ使用量がこれを超えると、 警告をトレース・ファイルに書きこみます。デフォルトは 1MB です。
    java_max_sessionspace_size 使用可能なセッション領域のハード・リミットを設定します。 java のメモリ使用量がセッション期間中このサイズを超えると、 メモリ不足障害エラーが発生します。デフォルトは 4GB です。

    これらのパラメータを確認する方法は、以下のとおりです。

    svrmgrl
    SVRMGR> connect internal
    SVRMGR> show parameter session;
    NAME                                TYPE    VALUE
    ----------------------------------- ------- ------------------------------
    java_max_sessionspace_size          整数値  0
    java_soft_sessionspace_limit        整数値  0
    license_max_sessions                整数値  0
    license_sessions_warning            整数値  0
    mts_sessions                        整数値  0
    session_cached_cursors              整数値  0
    session_max_open_files              整数値  10
    sessions                            整数値  60
    
    SVRMGR> select * from v$sgastat where pool='java pool';
    POOL        NAME                       BYTES
    ----------- -------------------------- ----------
    java pool   free memory                  17989632
    java pool   memory in use                 2981888
    2 行選択されました。
    

    8.1.6 では、java_max_sessionspace_size、java_soft_sessionspace_limit の値は、正しく表示されません。


【 参考書 】

【 用語集 】

用語 意               味
JServer java 対応 oracle 8i データベース・サーバ
Aurora oracle 8i JVM の名称
セッション java コードを実行するユーザは、 サーバ上でセッションを確立する必要があります。通常、 1人のユーザがサーバに接続している期間が1つのセッションになります。
コール セッション内で java コードを実行すること。
java スキーマ・
オブジェクト
java ソース、クラス、リソースの総称

【 ORACLE のエラー 】

ORA 番号 意         味 Page
00904 loadjava で出る場合は oracle JVM が正しくインストールされていないためです。
対策:手動で JServer をインストールしてください。

04031 loadjava で出る場合は java_pool_size の不足ですから、多くしてください。
メッセージには shared pool と出ますが java_pool_size のことです。

29532 java ストアド・プロシージャ実行中にエラーが検出されました。:XXXX
対策:java からのメッセージ XXX を参考にしてください。

29545 クラスファイルの形式が不正です
対策:クラスファイルを作り直して dropjava/loadjava してください。
D:2-20
29549 "jdbc:default:connection:" で接続しているセッションが すでに終了しています。
対策:connect しなおしてください。

29552 クラスファイルの形式が不正です
対策:クラスファイルを作り直して dropjava/loadjava してください。
D:2-20

【 例外とエラー 】

例      外 意         味 Page
oracle.aurora.vm.DeadlockError スレッド間でデッドロックを検出した D:2-41
oracle.aurora.vm.LimboError 1つしかないスレッドを中断した D:2-41
ThreadDeathException スレッドの実行を停止した D:2-42
oracle.aurora.awt.
UnsupportedOperation
awt を使おうとした。 aurora ではサポートしていません D:2-28
java.io.IOException 前コールの終了時にクローズした STREAM に対して IO をした。
対策:open しなおしてください。
D:2-36
ClassNotFound コール時に使用するクラスが見つからない
対策:コールするクラスで使う全クラスを loadjava する
S:2-5
OutOfMemoryError JServer が使うヒープ領域がなくなった。
対策:Statement,ResultSet などで不要になったものは close() する。

【 既知バグ 】

JServer 8.1.6 では、以下のバグが報告されています。

項      目 内         容
svrmgrl Total System Global Area に表示時される値に、 java_pool_size 分が含まれていない
svrmgrl show parameter session; の java_max_sessionspace_size、java_soft_sessionspace_limit の 表示される値が正しくない
java.lang.System.arraycopy() java.lang.NullPointerException が出て正しく動作しない

【 データ型のマッピング 】

SQL 型 java クラス
CHAR,NCHAR
LONG,VARCHAR2
NVARCHAR2
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
byte,short,int,long,float,double
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
byte,short,int,long,float,double
RAW,LONG 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
CLOB,NCLOB oracle.sql.CLOB
oracle.jdbc2.Clob
OBJECT oracle.sql.STRUCT
oracle.SqljData
oracle.jdbc2.Struct
REF oracle.sql.REF
oracle.jdbc2.Ref
TABLE,VARRAY oracle.sql.ARRAY
oracle.jdbc2.Array

【 関連リンク 】