Oracle Databaseでは、標準のJavaアプリケーションが実行されます。ただし、Javaを統合したOracle Database環境は、一般的なJava開発環境とは異なります。この章では、Oracle DatabaseでのJavaアプリケーションの作成、インストールおよびデプロイについて、一般的なJava開発環境との基本的な相違について説明します。この章の内容は、次のとおりです。
Javaを統合したOracle Databaseでは、Javaアプリケーションはデータベース・セッションのコンテキスト内に存在します。Oracle JVMセッションは、従来のOracleセッションとまったく同様です。Oracle JVMの各セッションは、セッション内の複数のコールにわたって、クライアントがアクセスするJavaアプリケーションの状態を保持します。
図2-1に、各Javaクライアントが、データベース内のJavaアプリケーションを実行する環境としてデータベース・セッションを起動する方法を示します。Javaの各データベース・セッションには、独立したガベージ・コレクタ、セッション・メモリーおよびコール・メモリーがあります。
セッションのコンテキストで、クライアントは次の内容を実行します。
データベースに接続してセッションをオープンします。
データベース内でJavaを実行します。これはコールとも呼ばれます。
必要に応じてコールを実行し、セッション内で処理を続行します。
セッションを終了します。
1つのセッション内で、クライアントには独自のJava環境があります。クライアントには、セッションごとに個別のJVMが個々に起動されるように見えますが、実際の実装はそれよりも効率的です。セッション内では、Oracle JVMがアプリケーションのスケーラビリティを管理します。あるクライアントからのすべてのコールは、そのクライアントのセッションで管理され、各クライアントからのコールは個別に処理されます。Oracle JVMは、クライアント間の読取り専用データの共有を最大にし、セッションごとの増分フットプリントを最小限にすることで、複数のクライアントのパフォーマンスを最大にします。
基盤となるサーバー環境では、セッション、ネットワーク、状態および他の共有リソース管理の問題に関する詳細が、Javaコードに対して明示されません。staticとして定義された変数は、クライアントに対してローカルです。メモリーはセッションの境界を超えて使用できないため、クライアントは他のクライアントのstatic変数にはアクセスできません。各クライアントは、それぞれのセッション内でJavaアプリケーション・コールを実行するため、各クライアントのアクティビティは他のクライアントとは切り離されています。コール中はオブジェクトを異なるクラスのstaticフィールドに格納でき、次のセッションでもこの状態で使用できます。Javaプログラムの状態はすべてプライベートで、セッション全体にわたって存続します。
Oracle JVMは、セッション内で次の内容を管理します。
Java2 Platform, Standard Edition(J2SE)環境では、main()メソッドを使用してJavaアプリケーションを開発します。このメソッドは、クラスの実行時にインタプリタからコールされます。main()メソッドは、コマンドラインで次のコマンドを入力したときにコールされます。
java classname
このコマンドは、Javaインタプリタを起動し、実行対象のクラス(つまり、classnameで指定されたクラス)をJavaインタプリタに渡します。インタプリタがmain()をコールすることで、クラスがロードされ、アプリケーションの実行が開始されます。ただし、main()メソッドをコールしても、データベース内のJavaアプリケーションは起動されません。
データベース内のJavaアプリケーションをロードした後は、ロードされたクラス内の任意のstaticメソッドをコールすることでJavaコードを実行できます。実行するには、クラスやメソッドが公開されている必要があります。Oracle Databaseの場合、main()はJavaアプリケーションのエントリ・ポイントとはみなされません。Javaアプリケーションを実行するときは、ロードされたクラス内のメソッド名をエントリ・ポイントとして指定します。
たとえば、通常のJava環境では、次のコマンドを実行することでサーバーのJavaオブジェクトを起動します。
java myprogram
myprogramには、main()メソッドが組み込まれているクラスの名前を指定します。myprogramでは、main()がただちにmymethod()をコールして、着信情報を処理します。
Oracle Databaseでは、データベースにmyprogram.classファイルをロードし、エントリ・ポイントとしてmymethod()を公開します。これで、クライアントまたはトリガーによって、明示的にmymethod()が起動されます。
標準的なJava開発環境では、Javaのソース・コード、バイナリおよびリソースは、次のようにファイルとしてファイル・システムに格納されます。
ソース・コード・ファイルは、.javaファイルとして保存されます。
コンパイルされたJavaバイナリ・ファイルは、.classファイルとして保存されます。
リソースは、.propertiesファイルや.serファイルなどの任意のデータ・ファイルでファイル・システム階層に格納され、実行時にロードまたは使用されます。
また、Javaアプリケーションを実行する場合は、CLASSPATHを設定し、.classファイルが格納されているファイル・システムのファイルまたはディレクトリ・パスを指定します。Javaでは、これらのファイルをZIPファイルやJavaアーカイブ(JAR)ファイルなどの単一のアーカイブ形式にまとめることもできます。
これらの概念は、いずれもOracle Database環境では異なります。
表2-1に、Oracle DatabaseによるJavaクラスの処理方法と依存クラスの位置の特定方法を示します。
表2-1 Javaのコードとクラスの説明
データベースにロードされるJavaクラス
コマンドライン・インタフェースを使用しない場合、Javaファイルをスキーマ・オブジェクトとしてデータベースにロードし、Oracle JVMでこれらを使用できるようにする必要があります。図2-2に示すように、loadjavaツールを使用してOracle JVMのJavaコンパイラをコールできます。このコンパイラは、ソース・ファイルを標準のクラス・ファイルにコンパイルします。
また、図2-2では、loadjavaツールを使用して、システム・データベース表に格納されているオプションの値を設定できることを示しています。これらのオプションは、特にJavaソース・ファイルの処理に影響を与えます。
各Javaクラスは、スキーマ・オブジェクトとして格納されます。オブジェクトの名前は、含まれているパッケージの名前も含めたクラスの完全修飾名から導出されます。たとえば、Handleクラスのフルネームは、次のようになります。
oracle.aurora.rdbms.Handle
Javaスキーマ・オブジェクト名では、ピリオドがスラッシュで置換されるため、前述のクラスのフルネームは、次のようになります。
oracle/aurora/rdbms/Handle
Oracle Databaseでは、最大4000文字までのJava名を使用できます。ただし、Javaスキーマ・オブジェクトには30文字を超える名前は指定できません。したがって、スキーマ・オブジェクト名に30文字を超える名前が指定された場合は、スキーマ・オブジェクトの短縮名または別名が生成されます。それ以外の場合は、完全修飾名(フルネームとも呼ばれる)が使用されます。フルネームは、必要に応じて任意のコンテキストで指定できます。必要な場合は、名前のマッピングがOracle Databaseによって処理されます。
Javaソース・コードをいつコンパイルするかを決定します。
データベース内のJavaクラスの位置の特定に、デフォルトのリゾルバを使用するか、別のリゾルバを使用するかを決定します。
クラスをデータベースにロードします。クラスにデフォルトのリゾルバを使用しない場合は、ロード・コマンドで別のリゾルバを指定してください。
クラスまたはメソッドを公開します。
この項の内容は、次のとおりです。
Javaソース・コードのコンパイルは、次のいずれかの方法で実行できます。
クライアント・システムでソースを明示的にコンパイルしてから、javacなどのJavaコンパイラを介してデータベースにロードできます。
データベースに対して、loadjavaツールで管理されるロード・プロセス中にソースをコンパイルするように指定できます。
この項の内容は、次のとおりです。
javacなど、従来のJavaコンパイラを使用してJavaソース・コードをコンパイルできます。コンパイルが終了した後は、ソースではなく、コンパイル済バイナリをデータベースにロードします。通常は、データベースでデバッグするよりも、各自のシステムでJavaコードをデバッグする方が簡単なため、これは便利なオプションです。
ソース・ファイルのloadjavaツールに-resolveオプションを指定すると、次の処理が実行されます。
ソース・ファイルがソース・スキーマ・オブジェクトとしてロードされます。
ソース・ファイルがコンパイルされます。
コンパイル済の.javaファイルに定義されている各クラスに対して、クラス・スキーマ・オブジェクトが作成されます。
コンパイル済のコードがクラス・スキーマ・オブジェクトに格納されます。
Oracle Databaseによって、すべてのコンパイル・エラーがloadjavaツールのログ・ファイルとUSER_ERRORSビューに書き込まれます。
-resolveオプションを指定せずにデータベースにJavaソースをロードすると、実行時にクラスが必要になったときに、Oracle Databaseによってソースが自動的にコンパイルされます。ソース・ファイルは、ソース・スキーマ・オブジェクトにロードされます。
Oracle Databaseによって、すべてのコンパイル・エラーがloadjavaツールのログ・ファイルとUSER_ERRORSビューに書き込まれます。
コンパイラ・オプションを指定するには、次の方法があります。
loadjavaツールのコマンドラインにコンパイラ・オプションを指定します。loadjavaツールには、encodingオプションを指定することもできます。
JAVA$OPTIONS表に永続コンパイラ・オプションを指定します。JAVA$OPTIONS表はスキーマごとに存在します。コンパイルする度に、これらのオプションが使用されます。ただし、loadjavaツールにコンパイラ・オプションを指定すると、この表に定義されているオプションはオーバーライドされます。この方法でコンパイラ・オプションを指定する場合は、この表を独自に作成する必要があります。
デフォルトのコンパイラ・オプション
コンパイル対象のソース・スキーマ・オブジェクトに、JAVA$OPTIONS表のエントリもコマンドラインのオプション値も指定されていない場合は、次のデフォルト値が使用されます。
このオプションは、SQLJ構文が含まれているJavaソースにのみ適用されます。
このオプションは、次のように指定した場合と同じです。
javac -g
コマンドラインのコンパイラ・オプション
loadjavaツールにencodingコンパイラ・オプションを指定すると、.javaファイルのエンコーディングが識別されます。このオプションは、JAVA$OPTIONS表内の一致する値をすべてオーバーライドします。この値は、次のように指定した場合と同じです。
javac -encoding
このオプションはソース・ファイルのロード時のみ有効です。
データベース表に指定されるコンパイラ・オプション
JAVA$OPTIONS表の各エントリには、オプションの設定を適用するソース・スキーマ・オブジェクトの名前が指定されています。複数行を使用すると、ソース・スキーマ・オブジェクトごとに異なるオプションを設定できます。
次のプロシージャとファンクションを使用することで、JAVA$OPTIONS表のエントリを設定できます。これらのプロシージャとファンクションは、データベース・パッケージDBMS_JAVAに定義されています。
PROCEDURE set_compiler_option(name VARCHAR2, option VARCHAR2, value VARCHAR2); FUNCTION get_compiler_option(name VARCHAR2, option VARCHAR2) RETURNS VARCHAR2; PROCEDURE reset_compiler_option(name VARCHAR2, option VARCHAR2);
表2-2に、これらのメソッドのパラメータを示します。
表2-2 nameパラメータとoptionパラメータの定義
| パラメータ | 説明 |
|---|---|
|
name |
Javaパッケージ名、完全修飾されたクラス名または空の文字列が入ります。コンパイラによって、Javaソース・スキーマ・オブジェクトのコンパイルに使用するオプションが |
|
option |
|
最初、スキーマにはJAVA$OPTIONS表はありません。JAVA$OPTIONS表を作成するには、DBMS_JAVAパッケージのjava.set_compiler_optionプロシージャを使用して値を設定します。このプロシージャは、表が存在しない場合に表を作成します。パラメータは一重引用符で囲みます。次に例を示します。
SQL> execute dbms_java.set_compiler_option('x.y', 'online', 'false');
表2-3は、JAVA$OPTIONSデータベース表のサンプルです。パターン一致規則は、表エントリに対してできるかぎり多くのスキーマ名を一致させるための規則です。パターン一致の名前解決度が高いスキーマ名がエントリとして適用されます。この表には、encodingオプションのエントリがないため、コンパイラはデフォルトまたはコマンドラインで指定された値を使用します。この表のonlineオプションは、次のようにスキーマ・オブジェクト名に対応付けられます。
名前a.b.c.dは、a.b.c.dで始まるクラス名およびパッケージ名に一致します。このパッケージとクラスは、online=trueでコンパイルされます。
名前a.bは、a.bで始まるクラス名およびパッケージ名に一致します。名前a.bは、a.b.c.dとは一致しません。このパッケージとクラスは、online=falseでコンパイルされます。
その他のパッケージとクラスは空文字列のエントリに一致し、online=trueでコンパイルされます。
表2-3 JAVA$OPTIONS表の例
| 名前 | オプション | 値 | 一致例 |
|---|---|---|---|
|
a.b.c.d |
|
|
|
|
a.b |
|
|
|
|
空文字列 |
|
|
|
Oracle Databaseには、依存関係管理および自動作成機能が用意されています。これらの機能は、ソース・プログラムまたはバイナリ・プログラムが変更されると、これらのプログラムに依存しているソース・プログラムを透過的に再コンパイルします。次の例を考えてみます。
public class A
{
B b;
public void assignB()
{
b = new B()
}
}
public class B
{
C c;
public void assignC()
{
c = new C()
}
}
public class C
{
A a;
public void assignA()
{
a = new A()
}
}
システムは詳細なクラス・レベルで依存関係を追跡します。この例では、AがBのインスタンスを保持し、BがCのインスタンスを保持し、CがAのインスタンスを保持しているため、クラスA、BおよびCが相互に依存しています。クラスAに新しいフィールドを追加して、このクラスの定義を変更すると、Oracle Databaseの依存関係メカニズムによって、クラスBとCが無効になったことを示すフラグが設定されます。これらのクラスを次に使用する前に、Oracle Databaseは必要に応じて、これらのクラスを解決して、再コンパイルを試みます。クラスは、ソース・ファイルがサーバーにある場合にのみ再コンパイルされます。
この依存性のあるシステムによって、クラス間の依存関係がOracle Databaseで管理され、解決と再コンパイルが自動的に実行されます。開発作業中に問題を早期に特定する必要がある場合にのみ、コンパイルと解決を強制的に実行する必要があります。loadjavaツールには、依存関係管理機能を利用しない場合に、コンパイルと解決を強制的に実行する機能もあります。
Javaクラスの多くには、他のクラスへの参照が含まれています。これによって、コードの再利用が可能になります。従来のJVMは、CLASSPATHで指定されたディレクトリ内で.class、.zipおよび.jarファイルを検索します。一方、Oracle JVMはデータベース・スキーマでクラス・オブジェクトを検索します。Oracle Databaseでは、すべてのJavaクラスがデータベースにロードされるため、あるJavaクラスが依存しているクラスをデータベース内で検索するには、検索場所を指定する必要があります。
データベースにロードされるすべてのクラスは、クラス・スキーマ・オブジェクトと呼ばれ、特定のスキーマにロードされます。java.lang.*など、事前に定義されているJavaのApplication Programming Interface(API)はすべて、PUBLICスキーマにロードされます。あるクラスが、事前に定義した他のクラスに依存している場合は、すべてのクラスをスキーマにロードすることになります。たとえば、スキーマがSCOTTである場合、データベース・リゾルバは、PUBLICスキーマを検索する前にSCOTTスキーマを検索します。検索するスキーマのリストはリゾルバ仕様と呼ばれます。リゾルバ仕様は各クラスごとに定義されます。CLASSPATHがすべてのクラスに対してグローバルな、従来のJVMとは対照的です。
クラス間の依存関係を検索および解決する場合、リゾルバは、相互に依存するすべてのクラスを検出したかどうかによって、各クラスに有効または無効のマークを付けます。ロードしたクラスに、適切なスキーマに存在しないクラスへの参照がある場合、そのクラスは無効としてリストされます。実行時に解決できない場合は、ClassNotFound例外が生成されます。また、クラスのツリーが大きすぎると、データベース・リソースが不足するため、実行時に解決できないことがあります。
|
注意: Javaコンパイラと同様に、loadjavaツールはクラスへの参照を解決しますが、リソースへの参照は解決しません。クラスに必要なリソース・ファイルを正しくロードする必要があります。 |
クラス内の各クラス間参照の場合、リゾルバは、参照を満たす有効なクラス・スキーマ・オブジェクトについて、リゾルバ仕様に指定されているスキーマを検索します。すべての参照が解決されると、リゾルバによりこのクラスは「有効」とマークされます。解決処理が行われていないクラス、または正常に解決できなかったクラスは「無効」とマークされます。無効にされたスキーマ・オブジェクトに依存するクラスも無効とマークされます。
依存クラスを簡単に検索できるように、Oracle Databaseには、定義者のスキーマを検索してからPUBLICスキーマを検索するデフォルトのリゾルバとリゾルバ仕様が用意されています。これによって、データベースにロードされたほとんどのクラスを検索できます。ただし、各自またはPUBLIC以外のスキーマにロードされたクラスにアクセスする場合は、独自にリゾルバ仕様を定義する必要があります。
次の方法でクラスを解決できます。
定義者のスキーマとPUBLICを検索するデフォルト・リゾルバを使用してロードします。
loadjava -resolve
独自のリゾルバ仕様定義を使用してロードします。
loadjava-resolve -resolver "((* SCOTT)(* OTHER)(* PUBLIC))"
この例では、リゾルバ仕様定義にSCOTTスキーマ、OTHERスキーマおよびPUBLICが指定されています。
-resolverオプションを使用して、定義されたスキーマ内で検索するオブジェクトを指定します。前述の例では、SCOTT、OTHERおよびPUBLICで、すべてのクラス・スキーマ・オブジェクトが検索されます。スキーマで特定のクラスまたはクラス・グループのみを検索する場合は、検索範囲を絞り込むことができます。たとえば、OTHERスキーマのmy/gui/*クラスのみを検索するには、次のようにリゾルバ仕様を定義します。
loadjava -resolve -resolver '((* SCOTT) ("my/gui/*" OTHER) (* PUBLIC))'
リゾルバ仕様の最初のパラメータには、クラス・スキーマ・オブジェクトを指定し、2番目のパラメータにはこれらのクラス・スキーマ・オブジェクトを検索するスキーマを定義します。
存在しないクラスへの参照の許可
リゾルバ仕様には、存在しないクラスへの未解決の参照を可能にする特別なオプションを指定できます。製品では内部クラスがまったく使用されないこともあります。通常のJava環境では、JVMはメソッドがコールされないかぎりメソッドを無視するため、問題は起こりません。ただし、Oracle Databaseのリゾルバは、未使用クラスも含めてJARファイル内の参照先のクラスをすべて解決しようとします。参照を有効にできない場合、JARファイル内のクラスは無効とマークされます。
参照を無視するには、リゾルバ仕様でワイルドカードのマイナス記号(-)を指定します。次の例は、参照先のクラスがリゾルバ仕様のスキーマ・リストに存在しない場合にも、my/gui内のクラスへのすべての参照を許可するように指定しています。
loadjava -resolve -resolver '((* SCOTT) (* PUBLIC) ("my/gui/*" -))'
ワイルドカードを指定しないと、あるスキーマで依存クラスが見つからないと、そのクラスは無効としてリストされ、実行できなくなります。
また、検索できなかったクラスをすべて無視するように定義することもできます。ただし、依存クラスが見つからなかった場合にも、その依存クラスを必要としているクラスが有効としてマークされる可能性があるため、この方法は危険です。ただし、依存クラスを必要としているクラスは、依存クラスなしでは実行できません。この場合は、実行時に例外となります。
SCOTTまたはPUBLICに存在しないクラスをすべて無視するには、次のリゾルバ仕様を指定します。
loadjava -resolve -resolver "((* SCOTT) (* PUBLIC) (* -))"
後でロードするクラスがマイナス記号(-)ワイルドカードを含むリゾルバを必要とするような場合は、最初からそのようなリゾルバを使用しないでください。かわりに、参照先クラスをすべてスキーマに取り込んでから、解決してください。
|
注意: 存在しないクラスを処理する代替のメカニズムには、loadjavaツールの-gemissingオプションを使用します。このオプションを使用すると、loadjavaツールによって、参照される未定義のクラス定義が作成され、ロードされます。 |
JVMの仕様では、定義したクラスをJVMで使用するには、その前に.classファイルを検証する必要があります。Oracle JVMでは、クラスの解決時に検証が実行されます。
表2-4に、リゾルバで検出される問題と、発行される該当のOracleエラー・コードを示します。
表2-4 ORAエラー
| エラー・コード | 説明 |
|---|---|
|
ORA-29545 |
リゾルバでクラスの形式が不正と判断されると、そのクラスは有効とマークされません。リゾルバでクラスが拒否されると、ORA-29545エラーが発行されます。このエラーは |
|
ORA-29552 |
状況によっては実行時に例外をスローできるように、リゾルバでは、有効とマークしたクラスのバイトコードが変更されることがあります。このような場合、リゾルバは |
マイナス記号(-)ワイルドカードが指定されているリゾルバは、参照先クラスの有無に関係なく、クラスを有効とマークします。継承とインタフェースのために、あるクラスのインスタンスをスーパークラスまたは特定のインタフェースのインスタンスであるかのように使用する有効なJavaメソッドの作成が必要になる場合があります。検証対象のメソッドがクラスAへの参照をクラスBへの参照であるかのように使用している場合、リゾルバはAがBを拡張または実装しているかどうかを確認します。たとえば、次のメソッドについて考えてみます。シグネチャでは暗黙的にBのインスタンスを戻すことになっていますが、そのボディはAのインスタンスを戻します。
B myMethod(A a)
{
return a;
}
このメソッドは、AがBを拡張する場合、またはAがインタフェースBを実装している場合のみ有効です。AまたはBの解決にマイナス記号(-)ワイルドカードが使用された場合、リゾルバはこのメソッドの安全を判断できません。この場合、myMethodがコールされると、myMethodのバイトコードは例外をスローするバイトコードに置換されます。
AとBのクラス定義が明確に識別されるスキーマにAとBがある場合、マイナス記号(-)ワイルドカードがないリゾルバは、そのAとBのクラス定義を検索して適切に解決します。代替リゾルバの使用は、JARファイルにない他の非システム・クラスを参照する既存のJARファイルのロードが必要な場合にのみ検討します。
この項では、loadjavaツールを使用してデータベースにクラスをロードする概要について説明します。loadjavaツールは、SQLコマンド内から実行することもできます。
|
注意: コマンドライン・インタフェースを使用する場合、クラスをスキーマ・オブジェクトとしてデータベースにロードする必要はありません。コマンドライン・インタフェースの詳細は、「コマンドライン・インタフェースの使用」を参照してください。 |
ファイルからコンパイルおよびロードする従来のJVMとは異なり、Oracle JVMはデータベース・スキーマ・オブジェクトからコンパイルおよびロードします。
表2-5に、従来のJVMで使用されるファイルに対応するデータベース・スキーマ・オブジェクトを示します。
すべてのクラスやリソースは、データベースにロードして、そのデータベース内の他のクラスで使用できるようにする必要があります。さらに、ロード時にはデータベース内のクラスを実行できるユーザーを定義します。
表2-6に、loadjavaツールが各タイプのファイルに対して実行するアクティビティを示します。
dropjavaツールは、loadjavaツールとは逆の動作を実行します。つまり、Javaファイルに対応するスキーマ・オブジェクトを削除します。loadjavaツールで作成したJavaスキーマ・オブジェクトは、必ずdropjavaツールを使用して削除します。SQLのデータ定義言語(DDL)コマンドで削除した場合、loadjavaツールおよびdropjavaツールで保持している補助データは更新されません。dropjavaツールは、SQLコマンド内から実行することもできます。
クラスとリソースをロードした後は、データベース・スキーマのUSER_OBJECTSビューにアクセスして、クラスとリソースが正しくロードされたことを確認できます。
ユーザー・クラスロード・クラスのメタデータの共有
データベース常駐クラスをロードするための組込みメカニズムによってロードされたクラスはシステム・クラスロードと呼ばれ、その他の方法によってロードされたクラスはユーザー・クラスロードと呼ばれます。データベースにクラスをロードする場合、クラスの表現はメモリーで作成され、その一部はクラス・メタデータと呼ばれます。クラス・メタデータはクラスを使用するセッションと同じであり、場合によっては共有できます。以前は、システム・クラスロード・クラスの場合のみ共有できていました。11gリリース1(11.1)からは、システム管理者の判断により、ユーザー・クラスロード・クラスのクラス・メタデータも共有できます。
同じクラスの二重定義
同じスキーマに同じ名前の2つのクラス・オブジェクトは指定できません。この規則によって、次の2つの制限を受けることになります。
|
注意: 例外は、データベース常駐JARの-prependjarnamesオプションを使用する場合です。このオプションを使用する場合、同じスキーマに同じ名前の2つのクラスを指定できます。データベース常駐JARの詳細は、「データベース常駐JAR」を参照してください。 |
特定のJavaの.classファイル、またはその.javaファイルのいずれかはロードできますが、両方はロードできません。
Oracle Databaseは、クラス・ファイルまたはソース・ファイルのどちらがロードされたかを追跡します。クラスを更新する場合は、当初ロードしたファイルと同じ種類のファイルをロードする必要があります。他の種類を更新する場合は、最初にロードしたファイルを削除してから次の種類のファイルをロードする必要があります。たとえば、クラスyのソースとしてx.javaをロードした場合、x.classをロードするには、その前にx.javaを削除する必要があります。
同じスキーマの2つの異なるスキーマ・オブジェクトには、同じクラスを定義できません。たとえば、x.javaでクラスyを定義し、yの定義をz.javaに移動するとします。x.javaがすでにロードされている場合、loadjavaツールは、yが定義されているz.javaのロードを拒否します。かわりに、次のいずれかの方法を実行する必要があります。
x.javaを削除し、yが定義されているz.javaをロードします。次にyが定義されていない新しいx.javaをロードします。
yが定義されていない新しいx.javaをロードし、次にyが定義されているz.javaをロードします。
クラスをロードするには、次のSQLデータベース権限が必要です。
スキーマにロードする場合はCREATE PROCEDUREおよびCREATE TABLE権限。
別のスキーマにロードする場合はCREATE ANY PROCEDUREおよびCREATE ANY TABLE権限。
oracle.aurora.security.JServerPermission.loadLibraryInClass.classname。
loadjavaツールでは、.class、.java、.properties、.sqlj、.ser、.jarまたは.zipファイルを使用できます。JARファイルまたはZIPファイルには、ソース・ファイル、クラス・ファイルおよびデータファイルを挿入できます。loadjavaツールにJARファイルまたはZIPファイルを渡すと、loadjavaツールはアーカイブをオープンし、そのメンバーを個別にロードします。JARまたはZIPのスキーマ・オブジェクトはありません。JARまたはZIPの内容が前回のロード時から変更されていない場合は、再ロードされません。したがって、JARファイルまたはZIPファイルのロードによるパフォーマンス低下は、ほとんどありません。実際には、JARファイルまたはZIPファイルのロードが、loadjavaツールを使用する最も簡単な方法です。
データベース常駐JAR
11gリリース1(11.1)以降では、データベースにJARの内容をロードする場合、JAR自体を表すデータベース・オブジェクトを作成するオプションが用意されています。この方法で、JARオブジェクトと、JARからロードされたクラス、リソースおよびソース・オブジェクトとの関連付けを保持できます。その結果、次のことが可能です。
標準的なJVMと同じ方法で、符号付きJARとJARネームスペースの分離を使用します。
JARから導出したクラスを管理する一方で、そのJARを1つの単位としてデータベースにロードします。これは、JARからロードされたクラスを個別に再定義しない場合に便利です。また、外部ファイル・システム上のJARの内容や存続に関係なく、クラスの削除時にJARからロードされたクラス・セット全体を削除できます。
JARをデータベースにロードするには、loadjavaツールの次のオプションを使用します。
-jarsasdbobjects
-prependjarnames
loadjavaツール・オプションの詳細は、「loadjavaツール」の項を参照してください。
すべてのクラスを自分のスキーマにロードし、自分のスキーマの外部のクラスを参照しない場合は、クラスを実行する権限がすでにあります。自分のオブジェクトが同じスキーマにロードされた他のオブジェクトをコールするために必要な権限を持っています。つまり、クラスAがクラスBを起動する能力です。クラスAにはクラスBをコールする権限が付与されている必要があります。
Javaアプリケーションを定義するクラスは、その所有者のSQLスキーマの下にある、Oracle Database内に格納されます。デフォルトでは、特定のユーザーのスキーマに常駐するクラスは、セキュリティ保護のために他のユーザーからは実行できません。次の方法で、自分のクラスの実行権限を他のユーザーに付与できます。
loadjava -grantオプションを使用します。
次のコマンドを使用します。
SQL> grant execute on myclass to scott;
myclassは、基礎となるJavaクラスの名前です。
クラス名の長さが30文字を超える場合、次のコマンドで検出される短縮名に変更する必要があります。
SQL> select dbms_java.shortname('long classname') from dual;
自分のクラスの実行権限は特定のユーザーまたはスキーマに付与できます。ただし、スーパーユーザーのDBAロールが指定されているロールに対しては、実行権限を付与できません。クラスの実行権限の設定は、SQLのDDL文で権限を付与または取り消す際に使用する設定と同じです。
|
注意: Oracle Database 11gリリース1(11.1)以前では、ストアド・プロシージャの実行権限を付与する場合、ストアド・プロシージャと、そのストアド・プロシージャによって参照されるJavaクラスの両方の実行権限が付与されます。Oracle Database 11gリリースでは、基礎となるJavaクラスの実行権限も付与する場合、そのクラスに対する実行権限を明示的に付与する必要があります。これは、セキュリティの向上を目的として実装されています。 |
図2-3は、クラスを実行するために必要な権限を示しています。
PL/SQLコードの実行中は、常に現行ユーザーが存在します。Javaコードの実行にも同じ概念が使用されます。まず、現行ユーザーは、Javaコードを起動するセッションを作成するユーザーです。Javaメソッドは、対応するラッパーによってSQLまたはPL/SQLからコールされます。Javaラッパーは、JavaメソッドをPL/SQLストアド・プロシージャまたはファンクションとしてSQLおよびPL/SQLに公開する特殊なPL/SQLエンティティです。このようなラッパーは、有効な現行ユーザーを変更できます。有効な現行ユーザーをラッパーの所有者に変更するラッパーは、定義者権限ラッパーと呼ばれます。ラッパーが有効な現行ユーザーを変更しない場合、有効なユーザーはそのまま変わりません。
Javaラッパーは、デフォルトで定義者権限ラッパーです。これをオーバーライドするには、AUTHID CURRENT_USERオプションを使用してラッパーを作成します。
Javaコードの実行中は常に、Javaコール・スタックが保持されます。スタックには、現在実行中のメソッドに対応する最内フレームを含め、入力したJavaメソッドに対応するフレームがあります。デフォルトでは、Javaメソッドは現行ユーザーを変更せずにスタックで実行されます。つまり、定義者権限ではなく現在の有効な実行者権限で実行されます。
Javaクラスは、loadjava -definerオプションを使用してデータベースにロードできます。定義者属性がマークされているクラスのメソッドは、定義者権限メソッドになります。このようなメソッドが入力されると、定義者フレームと呼ばれる特殊なフレームがJavaスタックに作成されます。このフレームは、有効な現行ユーザーをこのようなクラスの所有者(定義者)に切り替えます。新しいユーザーIDは、定義者フレームがスタックからなくなるまで、あるいはネストされた定義者フレームが入力されるまで、すべての内部フレームに対して有効です。
したがって、SQLまたはPL/SQLからそのラッパーによってコールされたJavaメソッドの実行中は常に、有効なユーザーは次のいずれかになります。
Javaスタックの最内部の定義者フレーム
最上部JavaメソッドのPL/SQLラッパーの所有者(定義者権限の場合)またはラッパーをコールしたユーザー
販売分析に定義者権限プロシージャを使用する企業を考えてみます。地区ごとの販売統計を取得するには、analyzeプロシージャによって各地区のsales表にアクセスする必要があります。そのためには、各地区サイトにプロシージャが存在している必要があります。これによりメンテナンス上の問題が発生します。問題を解決するために、この企業ではanalyzeプロシージャの実行者権限バージョンを本社にインストールしました。
図2-4に示すように、すべての地区サイトで同じプロシージャを使用して、各地区のsales表を問合せできます。
実行者権限のデフォルト動作のオーバーライドが必要になる場合もあります。本社がanalyzeプロシージャを使用して販売手数料を算出し、中央のpayroll表を更新する場合を考えます。analyzeの実行者が従業員の給料や他の機密性の高いデータが格納されているpayroll表に直接アクセスすることは問題です。
図2-5に示すように、analyzeプロシージャから定義者権限プロシージャcalcCommをコールし、このプロシージャでpayroll表を更新することで解決します。
USER_OBJECTSデータベース・ビューを問い合せることで、Javaソース、クラスおよびリソースを含め、独自に所有しているスキーマ・オブジェクトに関する情報を取得できます。たとえば、ロードしたソース、クラスまたはリソースがスキーマ・オブジェクトに正しく格納されているかどうかを確認できます。
表2-7に、USER_OBJECTSの主な列とその説明を示します。
表2-7 主なUSER_OBJECT列
| 名前 | 説明 |
|---|---|
|
|
オブジェクトの名前。 |
|
|
|
|
|
オブジェクトの状態。値は、 |
オブジェクト名とオブジェクト・タイプ
USER_OBJECTSのOBJECT_NAMEは別名です。30文字を超える完全修飾名は別名で格納されます。
サーバーでスキーマ・オブジェクトの別名を使用する場合は、別名や変換規則が不明であっても、DBMS_JAVAパッケージのLONGNAME()ファンクションを使用することで、完全修飾名による問合せから別名を取得できます。
SQL> SELECT dbms_java.longname(object_name) FROM user_objects WHERE object_type='JAVA SOURCE';
この文を実行すると、Javaソース・スキーマ・オブジェクトの完全修飾名が表示されます。別名が使用されていない場合、変換は実行されません。
|
注意: SQLおよびPL/SQLでは、大/小文字は区別されません。 |
データベースで別名に変換されたかどうかが不明であっても、DBMS_JAVAパッケージのSHORTNAME()ファンクションを使用することで、問合せ基準として完全修飾名を使用できます。
SQL*Plus> SELECT object_type FROM user_objects WHERE object_name=dbms_java.shortname('known_fullname ');
この文を実行すると、指定された完全修飾名のスキーマ・オブジェクトのOBJECT_TYPEが表示されます。ただし、完全修飾名をデータベース・キャラクタ・セットで表現できることが前提となります。
SQL> select * from javasnm; SHORT LONGNAME ---------------------------------------------------------------------- /78e6d350_BinaryExceptionHandl sun/tools/java/BinaryExceptionHandler /b6c774bb_ClassDeclaration sun/tools/java/ClassDeclaration /af5a8ef3_JarVerifierStream1 sun/tools/jar/JarVerifierStream$1
状態(STATUS)
STATUSは、Javaスキーマ・オブジェクトの妥当性を示す文字列です。Javaソース・スキーマ・オブジェクトは、正常にコンパイルされるとVALIDと表示され、Javaクラス・スキーマ・オブジェクトは、正しく解決されるとVALIDと表示されます。リソースは解決されないため、Javaリソース・スキーマ・オブジェクトは常にVALIDです。
次のSQL*Plusスクリプトは、USER_OBJECTSビューにアクセスして、アップロードされたJavaソース、クラスおよびリソースの情報を表示します。
COL object_name format a30
COL object_type format a15
SELECT object_name, object_type, status
FROM user_objects
WHERE object_type IN ('JAVA SOURCE', 'JAVA CLASS', 'JAVA RESOURCE')
ORDER BY object_type, object_name;
次の例のように、必要な場合はUSER_OBJECTSの問合せにワイルドカードを使用できます。
SELECT object_name, object_type, status
FROM user_objects
WHERE object_name LIKE '%Alerter';
この文は、Alerterの文字で終わるOBJECT_NAMEエントリを検索します。
Oracle Databaseでは、データベースにロードされたJavaメソッドは、公開されるとクライアントおよびSQLからコールできるようになります。オブジェクト自体、または個々のメソッドを公開できます。SQLのデータ操作言語(DML)文またはPL/SQLで、直接あるいは間接的にトリガーでコールするJavaストアド・プロシージャを作成する場合は、クラスの個々のメソッドを公開する必要があります。コール仕様を使用してメソッドへのアクセス方法を指定します。Javaプログラムは多数のクラスの様々なメソッドで構成されます。ただし、コール仕様で通常公開されるstaticメソッドは少数です。
Oracle Database 10g リリース2(10.2)より前のリリースでは、データベース内のJavaクラスは直接監査できません。ただし、PL/SQLラッパーは監査できます。通常、Javaストアド・プロシージャはすべて同じラッパーから起動されます。したがって、Javaストアド・プロシージャはすべて、直接ではありませんが監査可能です。
他のDDL文と同様に、Oracle Database 10g リリース2(10.2)では、ソース、クラスおよびリソースのJavaスキーマ・オブジェクトを作成、変更または削除するDDL文を監査できます。Oracle Database 10g リリース2(10.2)では、Javaアクティビティを簡単かつ直接的に監査する監査オプションが用意されています。また、Javaソース、クラスおよびリソースの変更も監査できます。
Javaスキーマ・オブジェクトに関連するデータベース・アクティビティを、文レベルおよびオブジェクト・レベルの2種類のレベルで監査できます。文レベルでは、特定パターンの文に関連したすべてのアクティビティを監査できます。
表2-8に、Javaスキーマ・オブジェクトに関連する文監査オプションと対応するSQL文を示します。
表2-8 Javaスキーマ・オブジェクトに関連する文監査オプション
| 文のオプション | SQL文 |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
たとえば、ALTER JAVA SOURCEのDDL文を監査する場合は、SQLプロンプトに次の文を入力します。
AUDIT ALTER JAVA SOURCE
オブジェクト・レベルの監査では、より詳細な監査が可能です。このレベルの監査では、特定のオブジェクトをクローズアップすることで、特定の問題を識別できます。
表2-9に、各Javaスキーマ・オブジェクトごとのオブジェクト監査オプションを示しています。セル内のエントリXは、そのJavaスキーマ・オブジェクトについて、対応するSQLコマンドを監査できることを示します。エントリNAは、そのJavaスキーマ・オブジェクトについて対応するSQLコマンドが適用不可であることを示します。
表2-9 Javaスキーマ・オプションに関連するオブジェクト監査オプション
| オブジェクト・オプション | Javaソース | Javaリソース | Javaクラス |
|---|---|---|---|
|
|
X |
NA |
X |
|
|
NA |
NA |
X |
|
|
X |
X |
X |
|
|
X |
X |
X |
|
関連項目:
|
Oracle Databaseでは、ユーザー・インタフェースの表示に関連するライブラリも含めて、コアなJavaクラス・ライブラリはすべてサーバー上にあります。しかし、ユーザー・インタフェースを作成または表示するために、コードをサーバー上で実行することは適切ではありません。Oracle JVM環境でアプリケーションを実行しているユーザーは、Oracle Databaseを実行しているサーバーの表示と入力のハードウェアとの対話や依存は不可能です。
ディスプレイ、キーボードまたはマウスをサポートしないプラットフォームへの互換性の問題に対処するために、Java 1.4ではヘッドレスAbstract Window Toolkit(AWT)サポートが採用されています。このヘッドレスAWT APIによって、publicランタイム例外クラスjava.awt.HeadlessExceptionが新たに導入されました。ネイティブな表示デバイスに依存しているAppletクラスのコンストラクタ、軽量でないすべてのコンポーネントおよびToolkitクラスやGraphicsEnvironmentクラスのメソッドの多くは、プラットフォームで表示がサポートされていない場合に、HeadlessExceptionをスローするように変更されました。Oracle Databaseでは、ユーザー・インタフェースはクライアント・アプリケーションのみでサポートされます。したがって、Oracle JVMはヘッドレス・プラットフォームであり、これらのメソッドをコールするとHeadlessExceptionがスローされます。
ヘッドレスAWTでは、基礎となるネイティブな表示デバイスまたは入力デバイスにはアクセスしないほとんどのAWT計算が許可されます。実際に、ヘッドレスAWTは非常に強力な機能であり、プログラマに対してフォント、イメージ、印刷、および色やICC操作へのアクセスを提供します。たとえば、Oracle JVMで実行中のアプリケーションは、サーバー上でイメージを物理的に表示しようとしないかぎり、そのイメージを解析、操作および出力できます。Sun社のJVMのリファレンス実装は、ヘッドレス・モードで(プロパティを-Djava.awt.headless=trueに指定して)起動し、Oracle JVMの場合と同様のヘッドレスAWT制限で実行できます。Oracle JVMは、ヘッドレスAWTに関してJava Compatibility Kit(JCK)に完全に準拠しています。
Oracle JVMでは、サウンド・サポートと同様のアプローチを採用しています。Oracle JVMのアプリケーションは、サウンドの再生や記録の目的で、基盤となるサウンド・システムにアクセスできません。また、システム・サウンド・リソースは、リソースにアクセスしようとするメソッドのサウンドAPI仕様に一致する方法では使用できません。たとえば、基盤となるシステム・サウンド・リソースへのアクセスを試みるjavax.sound.midi.MidiSystemのメソッドでは、システムが使用できないことを示すチェック済の例外MidiUnavailableExceptionがスローされます。ただし、ヘッドレスAWTサポートと同様に、Oracle Databaseでは、ネイティブなサウンド・デバイスなしでサウンド・ファイルを操作できるAPIをサポートしています。また、Oracle JVMは、サウンドAPIを実装する際、JCKに完全に準拠します。
Javaソース、クラスおよびリソースは、サーバーのそれぞれのスキーマ・オブジェクトに格納されます。スキーマ・オブジェクトの名前は、関連するパスやパッケージ情報を含む完全修飾名から導出されます。ドットはスラッシュに置換されます。
ただし、スキーマ・オブジェクト名は最大30文字で、すべての文字が有効であり、データベース・キャラクタ・セットの文字に変換可能であることが必要です。完全修飾名が30文字を超える場合、または無効な文字または変換できない文字が含まれている場合、Oracle Databaseは、完全修飾名を短縮名または別名に変換して、その名前をスキーマ・オブジェクト名として使用します。Oracle Databaseは、それらの名前とその変換方法を追跡します。完全修飾名が30文字以下で、無効な文字や変換できない文字が含まれていない場合は、完全修飾名がスキーマ・オブジェクト名として使用されます。
Javaクラスとメソッドの名前はSQL識別子の最大長よりも長くなることがあるため、Oracle Databaseでは、SQLアクセスのために内部的に短縮名を使用します。Oracle DatabaseのDBMS_JAVAパッケージには、短縮名に対応する元のJavaクラス名を取得するLONGNAME()ファンクションがあります。
FUNCTION longname (shortname VARCHAR2) RETURN VARCHAR2
このファンクションは、別名を使用して指定されたJavaスキーマ・オブジェクトの完全修飾名を戻します。次に、無効なクラスの完全修飾名を表示するために使用する文の例を示します。
SELECT dbms_java.longname (object_name) FROM user_objects WHERE object_type = 'JAVA CLASS' and status = 'INVALID';
また、DBMS_JAVAパッケージのSHORTNAME()ファンクションを使用して、データベースにフルネームを指定することもできます。このファンクションは、フルネームを入力として取得し、対応する短縮名を戻します。このファンクションは、USER_OBJECTSビューの問合せによって、クラスが正しくロードされたことを検証する際に役立ちます。
FUNCTION shortname (longname VARCHAR2) RETURN VARCHAR2
JLSでは、Class.forName()について次のように記述されています。
クラスの完全修飾名を指定すると、このメソッドはクラスを検索、ロードおよびリンクしようとします。正常に実行されると、このクラスのClassオブジェクトへの参照を戻します。正しく実行されなかった場合は、ClassNotFoundExceptionのインスタンスがスローされます。
クラスの参照は、参照側のクラスにかわって常にClassLoaderのインスタンスを介して実行されます。Java Development Kit(JDK)実装とOracle JVM実装の違いは、クラスの検索方法です。
JDKは、ClassLoaderの1つのインスタンスを使用して、環境変数CLASSPATHに指定されている一連のディレクトリ・ツリー・ルートを検索します。
Oracle JVMは複数のリゾルバを定義します。クラスの検索方法は、このリゾルバで指定されます。各クラスにはリゾルバが対応付けられており、各クラスは異なるリゾルバを保持できます。Class.forName()をコールするメソッドを実行すると、現在実行中のクラス(this)のリゾルバを使用してクラスが検索されます。
予定にないリゾルバを使用してクラスを検索しようとすると、間違った結果となります。たとえば、スキーマXのクラスXがスキーマYのクラスYに対して、クラスZを参照するように要求する場合は、クラスXのリゾルバを使用しようとするとエラーになります。参照を実行しているのはクラスYであるため、クラスZの検索にはクラスYに対応付けられたリゾルバが使用されます。つまり、クラスが別のスキーマに入っているときに、別のクラスの異なるリゾルバを指定すると(異なるスキーマにクラスが存在する場合はデフォルトでこの状態になります)、クラスを検索できない場合があります。
このようなリゾルバの問題は、次の方法で解決できます。
Classオブジェクト自体を指定してクラス名の参照を回避します。
Class.forName()メソッドに対してClassLoaderインスタンスを指定します。
classForNameAndSchema()メソッドに対してクラスとそのクラスが常駐するスキーマを指定します。
スキーマ名とクラス名でオブジェクトをシリアライズします。
|
注意: システム・クラスがClass.forName()を起動すると、予期しない動作が発生することがあります。目的のクラスがSYSまたはPUBLICに常駐している場合のみ、そのクラスが検索されます。クラスがSYSまたはPUBLICのいずれにも常駐していない場合は、そのクラスに対してPUBLICシノニムを宣言できます。 |
この項の内容は、次のとおりです。
Oracle Databaseは、スキーマ内でのクラスの検索にリゾルバを使用します。各クラスには指定のリゾルバが対応付けられており、各クラスには異なるリゾルバを対応付けることができます。したがって、クラスの検索は対応付けられたリゾルバの定義によって決まります。ClassLoaderインスタンスは、指定されたクラスに基づいて検索に使用するリゾルバを判断します。Class.forName()にClassLoaderインスタンスを指定すると、クラスは、そのクラスのリゾルバに定義されているスキーマ内で検索されます。このClass.forName()の改良型に対する構文は次のとおりです。
Class forName (String name, boolean initialize, ClassLoader loader);
次の例は、現行のクラス・インスタンスまたはコール側のクラス・インスタンスのクラス・ローダーを指定する方法を示します。
例2-1 現行のクラスからのリゾルバの取得
Class.getClassLoader()メソッドを使用することで、任意のインスタンスのクラス・ローダーを取得できます。次の例は、インスタンスxで表されたクラスのクラス・ローダーを取得します。
Class c1 = Class.forName (x.whatClass(), true, x.getClass().getClassLoader());
例2-2 コール側クラスからのリゾルバの取得
oracle.aurora.vm.OracleRuntime.getCallerClass()メソッドを使用することで、実行中のメソッドをコールしたインスタンスのクラスを取得できます。クラスを取得した後は、戻されたクラスのClass.getClassLoader()メソッドをコールします。次の例では、workForCaller()メソッドをコールしたインスタンスのクラスを取得します。次に、そのクラス・ローダーを取得し、Class.forName()メソッドに渡します。結果として、クラスの検索に使用されたリゾルバがコール側クラスのリゾルバとなります。
void workForCaller()
{
ClassLoader c1=oracle.aurora.vm.OracleRuntime.getCallerClass().getClassLoader();
...
Class c=Class.forName(name, true, c1);
...
}
検索するスキーマを特定できるリゾルバを指定することで、クラスを検索する位置の問題を解決できます。あるいは、クラスがロードされているスキーマを指定できます。クラスのロード先スキーマが判明している場合は、classForNameAndSchema()メソッドを使用できます。このメソッドは、Oracle Databaseが提供するDbmsJavaクラスにあります。このメソッドには、クラス名とそのクラスが常駐するスキーマ名の両方を指定します。このメソッドによって、指定されたスキーマでのクラスの位置が特定されます。
例2-3 スキーマ名とクラス名の指定
次の例は、save()メソッドを使用してスキーマ名とクラス名を保存する方法を示しています。クラス名とスキーマ名が取得され、DbmsJava.classForNameAndSchemaメソッドを使用してそのクラスの位置が特定されます。
import oracle.aurora.rdbms.ClassHandle;
import oracle.aurora.rdbms.Schema;
import oracle.aurora.rdbms.DbmsJava;
void save (Class c1)
{
ClassHandle handle = ClassHandle.lookup(c1);
Schema schema = handle.schema();
writeName (schema.getName());
writeName (c1.getName());
}
Class restore()
{
String schemaName = readName();
String className = readName();
return DbmsJava.classForNameAndSchema (schemaName, className);
}
スキーマ名とクラス名が入った1つのString値をoracle.aurora.util.ClassForName.lookupClass()メソッドに指定できます。このメソッドをコールすると、指定されたスキーマでそのクラスの位置が特定されます。文字列は次の形式で指定する必要があります。
"<schema>:<class>"
たとえば、スキーマSCOTTでcom.package.myclassの位置を特定するには、次のコードを使用します。
oracle.aurora.util.ClassForName.lookupClass("SCOTT:com.package.myclass");
|
注意: スキーマ名には大文字を使用してください。この場合、スキーマ名は大/小文字が区別されます。 |
クラスのシリアライズを解除すると、処理の一部として、名前に基づいてクラスが検索されます。検索が正常に行われるためには、シリアライズされたオブジェクトにクラス名とスキーマ名の両方が含まれている必要があります。
Oracle Databaseでは、オブジェクトをシリアライズまたはシリアライズを解除する際に、次のクラスを使用できます。
次に、クラスを参照するいくつかのメソッドの例を示します。
import oracle.aurora.vm.OracleRuntime;
import oracle.aurora.rdbms.Schema;
import oracle.aurora.rdbms.DbmsJava;
public class ForName
{
private Class from;
/* Supply an explicit class to the constructor */
public ForName(Class from)
{
this.from = from;
}
/* Use the class of the code containing the "new ForName()" */
public ForName()
{
from = OracleRuntime.getCallerClass();
}
/* lookup relative to Class supplied to constructor */
public Class lookupWithClassLoader(String name) throws ClassNotFoundException
{
/* A ClassLoader uses the resolver associated with the class*/
return Class.forName(name, true, from.getClassLoader());
}
/* In case the schema containing the class is known */
static Class lookupWithSchema(String name, String schema)
{
Schema s = Schema.lookup(schema);
return DbmsJava.classForNameAndSchema(name, s);
}
}
この例では、次のメソッドを使用してクラスを検索します。
インスタンスのクラスのリゾルバを使用するには、lookupWithClassLoader()をコールします。このメソッドによって、from変数のClass.forName()メソッドにクラス・ローダーが指定されます。from変数に指定されたクラス・ローダーは、このクラスにデフォルト設定されます。
特定のクラスのリゾルバを使用するには、明示したクラス名の後にlookupWithClassLoader()を指定してForName()をコールします。ForName()メソッドによって、from変数が指定のクラスに設定されます。lookupWithClassLoader()メソッドは指定されたクラスのクラス・ローダーを使用します。
コール側クラスのリゾルバを使用するには、最初に、パラメータを指定せずにForName()メソッドをコールします。このメソッドによってfrom変数がコール側クラスに設定されます。次に、lookupWithClassLoader()メソッドをコールし、コール側クラスのリゾルバを使用してクラスを検索します。
スキーマを指定してクラスを検索するには、lookupWithSchema()メソッドをコールします。このメソッドは、classForNameAndSchema()メソッドにクラス名とスキーマ名を渡します。
オペレーティング・システムのリソースは、どのコンピュータでも制限されています。Javaは、プログラミング言語であると同時に、コンピューティング・プラットフォームの提供を目指しています。したがって、プラットフォームに依存しないクラス、およびプラットフォーム固有のリソースにアクセスするためのフレームワークが用意されています。Javaのクラス・メソッドはJVMを介してオペレーティング・システム・リソースにアクセスします。Javaのこのモデルには潜在的な問題があります。この問題は、ガベージ・コレクタが管理するのはJavaオブジェクトで、そのJavaオブジェクトが保持するオペレーティング・システム・リソースではない場合に、プログラマが全リソースの管理をそのガベージ・コレクタに委任することに起因しています。
さらに、共有サーバーを使用している場合、Javaオブジェクト内に保持されているオペレーティング・システム・リソースは、1つのセッションの複数のコールにわたって保持されると無効になる可能性があります。
次の各項では、これらの問題について説明します。
一般に、オペレーティング・システム・リソースには次のリソースが含まれています。
| オペレーティング・システム・リソース | 説明 |
|---|---|
| メモリー | Oracle Databaseはメモリーを内部的に管理します。オブジェクトを作成するとメモリーを割り当て、オブジェクトが不要になるとメモリーを解放します。言語とクラス・ライブラリでは、メモリーの割当ておよび解放の直接的な手段はサポートされていません。 |
| ファイルとソケット | Javaにはファイル・リソースまたはソケット・リソースを表すクラスが含まれています。これらのクラスのインスタンスは、ファイル・ハンドルなど、オペレーティング・システムのファイルまたはソケットの構成メンバーを保持します。 |
| スレッド | Oracle JVMスレッドでは、データベースによる複数の同時実行セッションのサポートで提供される機能に対して追加のスケーラビリティは提供されません。ただし、Oracle JVMは、Javaスレッドの完全なAPIをサポートしています。
関連項目: 「Oracle Databaseのスレッド」 |
デフォルトでは、Javaユーザーはオペレーティング・システムの大半のリソースに直接アクセスできません。システム管理者は、JVMセキュリティ制限を変更することで、オペレーティング・システムのリソースにアクセスするパーミッションをユーザーに付与できます。JVMセキュリティは、Java2セキュリティに準拠したシステム・リソースで施行されます。
オペレーティング・システム・リソースへのアクセスには、標準のコアJavaクラスとメソッドを使用できます。リソースにアクセスした後、そのリソースがアクティブである期間は、リソースの種類によって異なります。メモリーはガベージ・コレクションされます。専用モードのサーバーを使用している場合、ファイル、スレッドおよびソケットは、複数コールにわたって存続します。共有サーバー・モードでは、ファイル、スレッドおよびソケットはコール終了時に終了します。
メモリーがJavaオブジェクト・メモリーとオペレーティング・システムの構成メンバーの2つの領域に分割されているとします。Javaオブジェクト・メモリー領域にはすべてのオブジェクトと変数が含まれています。オペレーティング・システムの構成メンバーには、オペレーティング・システムが要求に応じてオブジェクトに割り当てるリソースがあります。これらのリソースにはファイルやソケットなどが含まれます。
基本的なプログラミング規則では、Javaオブジェクトとオペレーティング・システム構成メンバーの両方のメモリーをクローズするように規定されています。Javaプログラマは、ガベージ・コレクタによってメモリーが解放されると誤解することがあります。ガベージ・コレクタは、未使用のJavaオブジェクト・メモリーを収集するために作成されました。ただし、オペレーティング・システムの構成メンバーはクローズされません。オペレーティング・システムの構成メンバーは、Javaオブジェクトをガベージ・コレクションする前にプログラムでクローズする必要があります。
たとえば、オブジェクトがファイルをオープンするたびに、オペレーティング・システムはそのファイルを作成してオブジェクトにファイル・ハンドルを割り当てます。ファイルがクローズされないと、オペレーティング・システムはコールの終了またはJVMの終了までファイル・ハンドルの構成メンバーを保持します。そのため、これらの構成メンバーが不足する可能性があります。各オペレーティング・システムのハンドルは無限ではありません。ハンドルの不足を回避するには、メソッドを終了する前にリソースをクローズします。ソケットに連結されたストリームについてもソケットをクローズする前にクローズします。
パフォーマンス上の理由から、ガベージ・コレクタは各オブジェクトにハンドルがあるかどうかを確認できません。したがって、ガベージ・コレクタはJavaオブジェクトおよび変数を収集しますが、ハンドルを解放するための適切なオペレーティング・システム・メソッドは発行しません。
例2-4に、オペレーティング・システムの構成メンバーのクローズ方法を示します。
public static void addFile(String[] newFile)
{
File inFile = new File(newFile);
FileReader in = new FileReader(inFile);
int i;
while ((i = in.read()) != -1)
out.write(i);
/*closing the file, which frees up the operating system file handle*/
in.close();
}
inFileをクローズしない場合、Fileオブジェクトは最終的にはガベージ・コレクションされます。Fileオブジェクトがガベージ・コレクションされても、ファイルがクローズされていないため、オペレーティング・システムでは、そのファイルが使用中と判断されます。
JMX(Java Management Extensions)は、アプリケーション、システム・オブジェクト、デバイス、サービス指向のネットワーク、JVM(Java仮想マシン)を管理および監視するツールを提供するJavaテクノロジです。このAPIを使用すると、そのクラスの動的な作成と変更が可能です。そのため、リソースが作成、インストールおよび実装されると、このテクノロジを使用してリソースを監視および管理できます。また、JMX APIにはリモート・アクセスが含まれているため、リモート管理プログラムはこのような目的で実行中のアプリケーションと対話できます。
JMXでは、特定のリソースは、MBean(マネージドBean)と呼ばれる1つ以上のJavaオブジェクトで構成されます。これらのMBeanは、管理エージェントとして機能するコア・マネージド・オブジェクト・サーバー(MBeanサーバーと呼ばれる)に登録され、Javaプログラミング言語に対応するほとんどのデバイスで実行できます。JMXエージェントは、MBeanが登録されるMBeanサーバーと、MBeanを処理するサービスのセットで構成されます。
この項の内容は、次のとおりです。
Oracle Database 11gリリース1(11.1)では、新規ロールJMXSERVERと新規プロシージャdbms_java.start_jmx_agentが導入され、データベースでJMXをサポートします。JMXSERVERロールは、セッションでJMXエージェントの起動および管理に必要なパーミッションを提供します。プロシージャdbms_java.start_jmx_agentは、特定のセッションでエージェントを起動します。このエージェントは、通常、セッションの期間内はアクティブのままです。JMXを有効にして起動するには、次の操作を実行します。
SYSまたはSYSTEMからJMXSERVERを取得します。
SQL> grant jmxserver to scott;
scottはユーザー名です。
プロシージャdbms_java.start_jmx_agentを起動して、セッションでJMXを起動します。このプロシージャは、OJVM JMXサーバーおよびリスナーをアクティブ化するエージェントを起動します。JMXサーバーは、現行セッションで1つ以上のデーモン・スレッドとして実行され、通常、セッションの期間内は使用可能です。JMXエージェントをセッションで起動すると、セッションで実行中のJavaコードを監視できます。
dbms_java.start_jmx_agentプロシージャは、次の引数を使用して起動できます。
port: JMXリスナーのポート。このパラメータの値は、Javaプロパティcom.sun.management.jmxremote.portを設定します。
ssl: Javaプロパティcom.sun.management.jmxremote.sslの値を設定します。値がtrueおよびfalseの場合は、無視されます。
auth: プロパティcom.sun.management.jmxremote.authenticateの値。それ以外の場合は、JAAS資格証明のセミコロンで区切られたリスト。値は大/小文字の区別がありません。
各引数は、nullをデフォルト値としてnullにするか、または省略できます。引数がnullの場合、セッションの対応するプロパティの以前からある値は変更されません。
|
注意: dbms_java.start_jmx_agentのパラメータに対応するJavaプロパティは、Sun Java 5.0 JMXドキュメントで指定されたJavaプロパティのセットからのものです。Java JMXプロパティの一覧については、http://java.sun.com/j2se/1.5.0/docs/guide/management/agent.htmlを参照してください。 |
dbms_java.start_jmx_agentをアクティブにする場合、プロパティcom.sun.management.jmxremoteはtrueに設定されます。start_jmx_agentを起動する前に、JMXSERVER権限のユーザーは、次の方法で様々な管理プロパティを初期設定できます。
PL/SQLファンクションdbms_java.set_propertyを使用します。
メソッドjava.lang.System.setPropertyを起動します。
JMXSERVERロールのユーザーは、Javaプロパティcom.sun.management.config.fileで指定されたデータベース常駐Javaリソースのプロパティも初期設定できます。com.sun.management.config.fileが設定されていない場合、このリソースのデフォルト名はlib.management.management.propertiesです。このリソースのメカニズムは、Sun社によるファイルベースのJMX構成管理のOJVM拡張機能です。これは、セキュリティの向上とスキーマごとの管理を提供するため、OJVMにとって優れたメカニズムです。リソースがスキーマに存在しない場合、ファイルの読取りはフォールバックとして行われます。com.sun.management.config.fileが設定されていない場合、デフォルトのファイル・パスは$(java.home)/lib/management/management.propertiesです。Oracle Database 11gリリース1(11.1)では、このファイルに次の初期設定が含まれています。
com.sun.management.jmxremote.ssl.need.client.auth = true com.sun.management.jmxremote.authenticate = false
プロパティcom.sun.management.jmxremote.ssl.need.client.authとcom.sun.management.jmxremote.sslを併用すると、クライアントおよびサーバー証明書を使用する、双方向暗号化SSL認証のJMXが設定されます。com.sun.management.jmxremote.sslのデフォルト値はtrueです。この構成はデフォルトであり、JAASパスワード認証よりも好まれます。
|
注意: パスワードのファイルベースの格納を提供するデフォルトのSun JMXログイン・モジュールは、セキュリティ上の理由からOJVMでサポートされていません脚注1。そのため、SSLクライアント認証のかわりにJAASパスワード認証を使用する必要がある場合、この項で示すように、一時的なJAAS資格証明をauthパラメータとしてdbms_java.start_jmx_agentに安全に渡すか、または安全なカスタムLDAPログイン・モジュールを使用するようにJMXを構成します。 |
脚注1 JDK 5.0の以前のバージョンで特定されたRMI関連のJAASの脆弱性は、JDK 5.0_10で修正されています。11.1 OJVMはJDK 5.0_10に基づいています。ただし、クリアテキスト・ファイルを使用してパスワードを格納するデフォルトのJMXログイン・モジュールはサポートされません。パスワードを格納しないファイルjmxremote.accessは、サポートされています。
JMXサーバーの起動例を次に示します。
前述の項で説明したようなデフォルトの設定、または同じセッションの以前に設定された値を使用してJMXサーバーおよびリスナーを起動します。
call dbms_java.start_jmx_agent;
前述の項で説明したようなデフォルトの設定、または同じセッションの以前に設定された値を使用してJMXサーバーおよびリスナーを起動します。
call dbms_java.start_jmx_agent(null, null, null);
JMXサーバーおよびリスナーをポート9999で起動します。他のJMX設定は、デフォルト値または同じセッションで以前に設定された値です。
call dbms_java.start_jmx_agent('9999');
JMXサーバーおよびリスナーをポート9999で起動します。他のJMX設定は、デフォルト値または同じセッションで以前に設定された値です。
call dbms_java.start_jmx_agent('9999', null, null);
JMXサーバーおよびリスナーを起動します。JMX設定は、デフォルト値または同じセッションで以前に設定された値です。JAAS資格証明monitorRole/1z2xおよびcontrolRole/2p3oを使用します。
call dbms_java.start_jmx_agent(null, null, 'monitorRole/1z2x;controlRole/2p3o');
これらの資格証明は一時的なものです。プロパティcom.sun.management.jmxremote.authenticateはtrueに設定されています。
JMXリスナーをポート9999で起動します。SSLやJAAS認証は使用しません。開発またはデモンストレーションの場合にのみ使用します。
call dbms_java.start_jmx_agent('9999', 'false', 'false');
SSLと認証を無効にしてリモート・リスナーを起動すると、一般的なセキュリティのガイドラインに違反するため、攻撃に対してサーバーが脆弱になります。そのため、本番環境ではこのようなモードを常に使用しないことをお薦めします。このモードは、JDKとの互換性や開発に対してサポートされます。OJVMでJMXを本番で使用する場合は、安全なJMX接続を使用する必要があります。
セキュリティ関連のプロパティ値をdbms_java.set_property、System.setPropertyまたはdbms_java.start_jmx_agentに指定する場合、非エコー・リスナーを使用するか、またはOracle Application Serverなどの安全なアプリケーション層から暗号化されたJDBC接続によってこれらを起動します。パスワードは、クリアテキスト・ファイルで保存しないでください。Oracleウォレットを使用して、証明書を作成および管理します。セキュリティを向上させるには、SSL認証のクライアント証明書を使用します。
|
関連項目: Oracleデータベースのセキュリティ機能の詳細は、『Oracle Databaseセキュリティ・ガイド』を参照してください。 |
Oracle JVMは、データベース・セッション・モデルに基づきます。これは、単一クライアントの非プリエンプティブ・スレッド・モデルです。Oracle DatabaseのJavaではスレッド・プログラムを実行できますが、実行レベルではシングル・スレッドです。このモデルのJVMは、データベース・セッションに関連するすべてのJavaスレッドを1つのオペレーティング・システム・スレッドで実行します。一度ディスパッチすると、Thread.yield()をコールしてスレッドが明示的に生成され、Socket.read()をコールしてスレッドがブロックされるまで、または実行エンジンによって切り替えられるまでスレッドは実行を続けます。スレッドが生成され、ブロックされるか、または切り替えられると、JVMは別のスレッドをディスパッチします。
|
注意: 11gリリース1(11.1)以降では、Oracle JVMはスレッドの切り替えをサポートします。スレッドの切り替えはJava仕様で義務付けられていませんが、JDK1.5にある新しいjava.util.concurrent APIを適切にサポートするために必要です。 |
Oracle JVMには、パフォーマンスとスレッド管理を向上させるために、次の機能が追加されました。
システム・コールを最小限に抑制します。Oracle JVMでは、通常のシステム・コールのかわりに非システム・ソリューションが採用されました。たとえば、システム・コールを使用せずにモニター同期ブロックやメソッドに入ることが可能です。
デッドロックを検出します。
シングル・スレッド・アプリケーションでは、次のイベントの発生でコールが終了します。
スレッドがコール元に戻された場合。
例外がスローされ、Javaコードで捕捉されなかった場合。
System.exit()、OracleRuntime.exitSession()またはoracle.aurora.vm.OracleRuntime.exitCall()メソッドがコールされた場合。
DBMS_JAVA.endsession()またはDBMS_JAVA.endsession_and_related_state()メソッドがコールされた場合。
初期スレッドが他のJavaスレッドを作成して起動した場合は、次のいずれかの方法でコールが終了します。
メイン・スレッドがコール元に戻るか、またはこのスレッドで捕捉されない例外がスローされることで終了します。いずれの場合も、他のすべての非デーモン・スレッドは処理されます。非デーモン・スレッドは、その初期メソッドから戻されるか、スレッドで捕捉されない例外がスローされることで終了します。
いずれかのスレッドでSystem.exit()、OracleRuntime.exitSession()またはoracle.aurora.vm.OracleRuntime.exitCall()メソッドをコールします。
DBMS_JAVA.endsession()またはDBMS_JAVA.endsession_and_related_state()メソッドをコールします。
11gリリース1(11.1)以前は、System.exit()またはoracle.aurora.vm.OracleRuntime.exitCall()をコールすることでコールが終了した場合、Oracle JVMは、専用サーバー・モードと共有サーバー・モードの両方でコールをただちに終了し、すべてのスレッドを終了していました。11gリリース1(11.1)では、DBMS_JAVAパッケージに次のPL/SQLファンクションを追加することでこの問題に対応しています。
FUNCTION endsession RETURN VARCHAR2;
FUNCTION endsession_and_related_state RETURN VARCHAR2;
コール時に、Javaプログラムが再帰的に追加のJavaコードを実行することがあります。たとえば、JDBCまたはSQLJを使用してプログラムでSQL問合せを発行すると、Javaで作成されたトリガーがコールされることがあります。コールの存続期間に関するこれまでの説明は、Javaコードへの最上位コールに関するもので、再帰的コールに関する説明ではありません。たとえば、再帰的コール内からSystem.exit()をコールすると、再帰的コールのみではなく、Javaへの最上位コール全体が終了します。
System.exit()、OracleRuntime.exitSession()およびOracleRuntime.exitCall()
System.exit()メソッドをコールすると、Javaの状態が保持されずにJVMが終了します。これによってデータベース・セッションが終了したりクライアントが切断されることはありません。ただし、その直後にデータベース・セッション自体が終了することが頻発します。また、OracleRuntime.exitSession()でも、Javaの状態が保持されずにJVMが終了します。ただし、このメソッドではデータベース・セッションも終了し、クライアントが切断されます。
OracleRuntime.exitCall()の動作は、OracleRuntime.threadTerminationPolicy()によって異なります。このメソッドでは、boolean値が戻されます。この値がtrueの場合、データベース・コールの終了時に、アクティブなスレッドが休止状態ではなく終了します。
共有サーバー・プロセスでは、threadTerminationPolicy()は常にtrueです。
シャドウ(専用)プロセスでは、デフォルト値はfalseです。この値を変更するには、OracleRuntime.setThreadTerminationPolicy()をコールします。
値をfalse(デフォルト値)に設定すると、すべてのスレッドは休止状態になりますが、正常に終了するためのThreadDeath例外が発生します。
値がtrueの場合、すべてのスレッドがただちに終了します。
また、OracleRuntime.callExitPolicy()という別のメソッドがあります。このメソッドでは、OracleRuntime.exitSession()、OracleRuntime.exitCall()またはSystem.exit()メソッドがそれまでにコールされていない場合に、コールを終了するタイミングが決定されます。コール終了ポリシーは、OracleRuntime.setCallExitPolicy()を使用して、次のいずれかの値に設定できます。
OracleRuntime.EXIT_CALL_WHEN_MAIN_THREAD_TERMINATES
この値に設定した場合、メイン・スレッドが戻るか、またはメイン・スレッドで捕捉されない例外が発生するとすぐに、デーモンおよび非デーモンの両方を含む残りのすべてのスレッドは次の状態になります。
共有サーバー・モードでは、threadTerminationPolicy()がtrueの場合、常に停止します。
threadTerminationPolicy()がfalseの場合、休止状態になります。
OracleRuntime.EXIT_CALL_WHEN_ALL_NON_DAEMON_THREADS_TERMINATE
これはデフォルト値です。この値が設定されると、デーモン・スレッドのみが実行中である場合にコールが終了します。この時点では次のようになります。
threadTerminationPolicy()がtrueの場合、共有サーバー・モードでは、デーモン・スレッドは常に停止します。
threadTerminationPolicy()がfalseの場合、デーモン・スレッドは次のコールまで休止状態になります。これは、シャドウ(専用)サーバー・モードのデフォルト設定です。
OracleRuntime.EXIT_CALL_WHEN_ALL_THREADS_TERMINATE
この値に設定した場合、すべてのスレッドが戻るか、捕捉されない例外が原因で終了する場合のみ、コールは終了します。この時点では、threadTerminationPolicy()の値に関係なくコールが終了します。
|
注意: Oracleデータベース9.x以前のデータベース・リリースでは、JVMはcallExitPolicy()がOracleRuntime.EXIT_CALL_WHEN_ALL_NON_DAEMON_THREADS_TERMINATEであり、共有サーバー・プロセスと専用サーバー・プロセスの両方でthreadTerminationPolicy()がtrueであるかのように動作します。これは、この時点でデーモン・スレッドが停止することを意味します。また、exitCall()が実行された場合、共有および専用サーバー・プロセスの両方で、コールの終了前にすべてのスレッドが停止します。 |
|
注意: パフォーマンス上の理由から、専用サーバーをお薦めします。ただし、複数のコールでオープン状態のスレッドやソケットに依存するアプリケーションのクラスを使用することもできます。 |
共有サーバーを使用するセッションでは、複数のコールにわたる制限が存在します。これは、共有サーバーを使用するセッションは、後続のデータベース・コールによる同一プロセスへの接続が保証されていないためです。したがって、複数コールにわたって保持する必要があるセッション固有のメモリーおよびオブジェクトは、SGAに保存されます。つまり、スレッド、オープン・ファイルおよびソケットなどのプロセス固有のリソースは、各コールの終了時にクリーン・アップする必要があるため、次のコールでは使用できません。
この項の内容は、次のとおりです。
共有サーバー・モードの場合、Oracle Databaseでは、Javaプログラムの状態を次のコールまで保持するために、コール終了時にstatic変数から参照可能なすべてのオブジェクトがセッション・スペースに移行されます。セッション・スペースはクライアントのセッション内にあり、複数コールにわたって存続するstatic変数およびオブジェクトが格納されます。この移行処理は、各コールの終了時にOracle JVMで自動的に実行されます。
この移行処理にはメモリーが使用され、パフォーマンスにも影響します。したがって、複数のコールにわたって保持するstatic変数やオブジェクトは最小限に抑えるように注意してください。static変数にオブジェクトを不要に格納すると、メモリー・マネージャが移行処理を実行し、セッションごとにリソースを使用することになるため、不要な負担がかかります。static変数を必要最低限に抑えることで、メモリー・マネージャの負担を軽減し、サーバーのパフォーマンスを向上させることができます。
Javaプログラムを同時に実行できるユーザー数を最大にするには、セッションのフットプリントを最小にすることが重要です。特に、拡張性を最大にするには、非アクティブなセッションが占有するメモリー領域を必要最小限にする必要があります。フットプリントを最小化する簡単な方法は、コール終了ごとに大きなデータ構造を解放することです。多くのデータ構造は、別のコールで再度必要になったときに遅延して再作成できます。このような理由から、Oracle JVMには、コール終了時など、セッションが非アクティブになる直前に、指定のJavaメソッドをコールするためのメカニズムがあります。
このメカニズムは、EndOfCallRegistry通知と呼ばれます。このメカニズムによって、コール終了時にstatic変数を消去し、次のコールの着信時に遅廷初期化テクニックを使用して変数を再初期化できます。この方法は、複数コール間で静的変数を保持するためにメモリー・マネージャが必要としている記憶域の容量に懸念がある場合のみ実行してください。複雑でステートフルなサーバー・アプリケーションをJavaで実装する場合に考慮する必要があります。
コール終了時にデータ構造をNULLにリセットし、その後新しいコールごとにデータ構造を再作成するかどうかの決定は、時間と領域のトレードオフの問題です。データ構造の再作成によって処理時間は多少長くなりますが、次のコールまでデータ構造を保持する必要がないため、メモリー領域をかなり節約できます。また、セッション領域に移行されたオブジェクト(特に大型のオブジェクト)のアクセスには時間がかかり、効率が悪くなります。これは、セッションの表現がコール領域に基づくオブジェクトとは異なることに起因しています。
このような最適化の対象として、次のデータ構造があります。
バッファまたはキャッシュ。
静的フィールド(配列など)。これは、一度初期化するとプログラムの実行が完了するまで変化しません。
動的に作成されるデータ構造。最適化によって、コール間のメモリー使用効率およびコール中の速度効率の向上が期待できます。このデータ構造の最適化によってコードが複雑になり、管理が困難になることもあります。したがって、労力に見合う程度の領域が節約される場合のみ、この最適化の実施を検討してください。
コール終了時に消去するstatic変数は、バッファ、フィールドまたはデータ構造の作成時に登録できます。oracle.aurora.memoryManager.EndOfCallRegistryクラスでは、registerCallback()メソッドによって、Callbackオブジェクトを実装するオブジェクトが取得されます。registerCallback()メソッドには、このオブジェクトがコール終了時まで格納されます。コール終了時に、Oracle JVMは登録されたすべてのCallbackオブジェクトのact()メソッドをコールします。Callbackオブジェクト内のact()メソッドは、ユーザー定義のバッファ、フィールドまたはデータ構造を消去するために実装されます。消去されると、Callbackオブジェクトはレジストリから削除されます。
|
注意: コール終了時がセッションの終了時でもある場合は、いずれにしてもセッション・スペースが消去されるため、コールバックは開始されません。 |
弱い表では、エンドオブコール・コールバックのレジストリが保持されます。Callbackオブジェクトまたは値のいずれかがJavaプログラムから参照できない場合は、オブジェクトと値の両方が表から削除されます。弱い表を使用してコールバックを保持すると、コールバックを登録しても、ガベージ・コレクタによるオブジェクトの削除は回避されません。したがって、必要なコールバックを自分で保持する必要があり、表に保持しておくことはできません。
EndOfCallRegistryの使用方法は、操作対象のオブジェクトがstaticフィールドに保持されているか、インスタンス・フィールドに保持されているかによって異なります。
静的フィールド
EndOfCallRegistryを使用して、クラス全体に対応付けられた状態を消去します。この場合、Callbackオブジェクトはprivate staticフィールドに保持する必要があります。コール間で削除されたキャッシュ内のデータにアクセスする必要があるコードは、キャッシュ・データを遅延して作成または再作成するメソッドをコールする必要があります。
import oracle.aurora.memoryManager.Callback;
import oracle.aurora.memoryManager.EndOfCallRegistry;
class Example
{
static Object cachedField = null;
private static Callback thunk = null;
static void clearCachedField()
{
// clear out both the cached field, and the thunk so they don't
// take up session space between calls
cachedField = null;
thunk = null;
}
private static Object getCachedField()
{
if (cachedField == null)
{
// save thunk in static field so it doesn't get reclaimed
// by garbage collector
thunk = new Callback () {
public void act(Object obj)
{
Example.clearCachedField();
}
};
// register thunk to clear cachedField at end-of-call.
EndOfCallRegistry.registerCallback(thunk);
// finally, set cached field
cachedField = createCachedField();
}
return cachedField;
}
private static Object createCachedField()
{
...
}
}
この例では、次の手順が実行されます。
staticフィールドthunkにCallbackオブジェクトを作成します。
このCallbackオブジェクトをコール終了時の移行に登録します。
Callback.act()メソッドを実装して、Callbackオブジェクト自体も含めて、すべてのstatic変数を解放します。
キャッシュを遅延して再作成するためのcreateCachedField()メソッドを指定します。
ユーザーがキャッシュを作成すると、CallbackオブジェクトがgetCachedField()メソッドに自動的に登録されます。コール終了時に、Oracle JVMは、登録されたCallback.act()メソッドをコールして、静的メモリーを解放します。
インスタンス・フィールド
インスタンス・フィールドに保持されているデータ構造を消去状態にするには、EndOfCallRegistryを使用します。たとえば、状態があるクラスの各インスタンスに対応付けられている場合、各インスタンスにはそのインスタンスのキャッシュ状態を保持するフィールドがあり、必要に応じてキャッシュ・フィールドが埋められます。状態が確実にキャッシュされるメソッドを使用してキャッシュ・フィールドにアクセスできます。
次の例を考えてみます。
import oracle.aurora.memoryManager.Callback;
import oracle.aurora.memoryManager.EndOfCallRegistry;
class Example2 implements Callback
{
private Object cachedField = null;
public voidact (Object obj)
{
// clear cached field
cachedField = null;
obj = null;
}
// our accessor method
private static Object getCachedField()
{
if (cachedField == null)
{
// if cachedField is not filled in then you must
// register self, and fill it in.
EndOfCallRegistry.registerCallback(self);
cachedField = createCachedField();
}
return cachedField;
}
private Object createCachedField()
{
...
}
}
この例では、次の手順が実行されます。
インスタンスをCallbackオブジェクトとして実装します。
Callback.act()メソッドを実装して、インスタンス・フィールドを解放します。
ユーザーがキャッシュを要求すると、Callbackオブジェクトによって、このオブジェクト自体がコール終了時の移行に登録されます。
キャッシュを遅延して再作成するためのcreateCachedField()メソッドを指定します。
ユーザーがキャッシュを作成すると、CallbackオブジェクトがgetCachedField()メソッドに自動的に登録されます。コール終了時に、Oracle JVMは、登録されたCallback.act()メソッドをコールして、キャッシュを解放します。
この方法を使用すると、Callbackオブジェクトとそのインスタンスが同じオブジェクトになるため、オブジェクトの存続期間とそのインスタンスの存続期間が等しくなります。
registerCallback()メソッドは、Callbackオブジェクトをレジストリにインストールします。コール終了時に、Oracle JVMは登録されたすべてのCallbackオブジェクトのact()メソッドをコールします。
Callbackオブジェクトは、自動的に、またはObjectインスタンスを使用して登録できます。オブジェクトに格納されたその他の情報をact()に渡す必要がある場合は、このオブジェクトをvalueパラメータに登録できます。このパラメータは、Objectのインスタンスです。
registerCallback()メソッドの有効なシグネチャは、次のとおりです。
public static void registerCallback(Callback thunk, Object value); public static void registerCallback(Callback thunk);
次の表に、registerCallbackのパラメータとその説明を示します。
| パラメータ | 説明 |
|---|---|
thunk |
コール終了時の移行でコールされるCallbackオブジェクトを示します。 |
value |
オブジェクトに格納されたその他の情報をact()に渡す必要がある場合は、このオブジェクトをvalueパラメータに登録できます。場合によっては、コールバックに必要な状態の保持にvalueパラメータが必要になります。ただし、大半のユーザーは、このパラメータの値を指定する必要はありません。 |
runCallbacks()メソッドのシグネチャは、次のとおりです。
static void runCallbacks()
JVMはコール終了時にこのメソッドをコールし、registerCallback()を使用して登録されたすべてのCallbackオブジェクトごとにact()をコールします。コール終了時にこのメソッドがコールされた後で、オブジェクトが移行され、最終処理が行われます。
|
注意: このメソッドは、作成したコード内ではコールしないでください。 |
インタフェースの宣言は、次のとおりです。
Interface oracle.aurora.memoryManager.Callback
EndOfCallRegistry.registerCallback()を使用して登録するオブジェクトは、Callbackインタフェースを実装する必要があります。このインタフェースは、アプリケーションで、コール終了時に通知が必要な場合に役立ちます。
act()メソッドのシグネチャは、次のとおりです。
public void act(Object value)
コールの終了時に実行する必要がある任意のアクティビティを実装できます。通常、このメソッドにはセッション・スペースに保存されるメモリーを消去するプロシージャが含まれます。
次の表に示すとおり、共有サーバー・モードでは、データベース・コールの終了時にOracle JVMが、オープン状態のオペレーティング・システム・リソースをすべてクローズします。
| リソース | 存続期間 |
|---|---|
| ファイル | データベースのコールが終了すると、オープン状態のすべてのファイルがクローズされます。 |
| スレッド | コールが終了すると、すべてのスレッドが終了します。 |
| ソケット |
|
| オペレーティング・システム・リソースに依存するオブジェクト | Javaオブジェクトの有効期間は、そのオブジェクトの使用可能期間に関係なく、セッションが存続している期間になります。たとえば、staticクラス変数あるいはこのクラスを直接または間接的に参照するクラス変数にJavaオブジェクトを格納すると、セッション終了まで有効になります。使用可能期間の終了後にJavaオブジェクトを使用しようとすると、Oracle Databaseから例外がスローされます。これは次のような例に当てはまります。
|
1つのコールの終了時には、そのコールに対してローカルなリソースをクローズしてください。ただし、オペレーティング・システム・リソースを保持するstaticオブジェクトについては、これらのリソースがコール終了後に受ける影響を考慮する必要があります。
ファイル
共有サーバー・モードでは、コールが終了すると、Oracle JVMはオープン状態のオペレーティング・システム構成メンバーを自動的にクローズします。これは、Javaオブジェクト内のオペレーティング・システム・リソースすべてに影響を与えます。static変数の中でファイルをオープンした場合、そのファイル・ハンドルはコールの終了時にクローズされます。したがって、複数のコールにまたがってFileオブジェクトを保持すると、次にファイル・ハンドルを使用するときに例外がスローされます。
例2-5のConcatクラスは、複数のファイルを1つのファイルoutFileに書き込むことができます。最初のコールでoutFileが作成されます。最初の入力ファイルがオープンされ、読み取られてoutFileに記録され、コールが終了します。outFileはstatic変数として定義されているため、次のコールが起動される間にセッション・スペースに移動します。ただし、ファイル・ハンドルは、コールが終了するとクローズします。次回addFile()をコールすると、例外が発生します。
例2-5 オペレーティング・システム・リソースの調整
public class Concat
{
static File outFile = new File("outme.txt");
FileWriter out = new FileWriter(outFile);
public static void addFile(String[] newFile)
{
File inFile = new File(newFile);
FileReader in = new FileReader(inFile);
int i;
while ((i = in.read()) != -1)
out.write(i);
in.close();
}
}
これには対処法があります。コールが終了するたびにファイルやバッファなどをクローズし、次のコールの開始時にリソースを再度オープンして、ハンドルを有効な状態に維持します。もう1つの方法は、オペレーティング・システム・リソースではなくデータベースを使用することです。たとえば、ファイルではなくデータベース表を使用します。あるいは、複数のコールにわたって存続するstaticオブジェクトにオペレーティング・システム・リソースを格納しないようにします。かわりに、そのコールに対するローカルなオブジェクトの中でのみ、オペレーティング・システム・リソースを使用します。
例2-6に、例2-5の場合のようにオペレーティング・システム・リソースを調整せずに連結する方法を示します。addFile()メソッドは、各コールでoutme.txtファイルをオープンし、そのファイルに書き込まれた内容がすべて最後に追加されていることを確認します。このファイルは、各コールの終了時にクローズします。この場合、次の2つの状態が発生します。
Fileオブジェクトはコール外では存続しなくなります。
オペレーティング・システム・リソースであるoutme.txtファイルはコールごとに再度オープンされます。Fileオブジェクトをstatic変数に指定した場合は、各コール内でoutme.txtをクローズすることで、オペレーティング・システム・リソースを調整する必要はなくなります。
例2-6 オペレーティング・システム・リソースの正しい管理
public class Concat
{
public static void addFile(String[] newFile)
{
/*open the output file each call; make sure the input*/
/*file is written out to the end by making it "append=true"*/
FileWriter out = new FileWriter("outme.txt", TRUE);
File inFile = new File(newFile);
FileReader in = new FileReader(inFile);
int i;
while ((i = in.read()) != -1)
out.write(i);
in.close();
/*close the output file between calls*/
out.close();
}
}
ソケット
ソケットは、クライアントとサーバー間の接続をセットアップするために使用されます。データベース接続ごとに、接続の片側でソケットが使用されます。接続のセットアップはアプリケーションでは行われません。接続は、基礎となるプロトコル(Oracle NetのTTCまたはIIOP)で実行されます。
データベースに格納された1つのクラス内から指定のURLに接続するなど、別の接続をセットアップすることもできます。このセットアップでは、次のコンストラクタを使用して接続のクライアント側とサーバー側に対応するようにソケットをインスタンス化します。
java.net.Socket()コンストラクタで、クライアント・ソケットを作成します。
java.net.ServerSocket()コンストラクタで、サーバー・ソケットを作成します。
ソケットは接続の両側にあります。着信コールをリスニングするサーバー側の接続に対応するのはServerSocketインスタンスです。要求を送るクライアント側の接続に対応するのはSocketインスタンスです。JVMに定義されたソケットは、共有サーバー内のServerSocketインスタンスは複数のコールにまたがって存続できないという制限の範囲内で使用できます。
次の表に、ソケットの種類とその説明を示します。
| ソケットの種類 | 説明 |
|---|---|
| Socket | 接続のクライアント側は送信側になるため、Socketインスタンスは共有サーバー内の複数のコールにわたって対応できます。 |
| ServerSocket | 接続のサーバー側のリスナーです。ServerSocketインスタンスは、共有サーバー内の1つのコールが終了するとクローズします。共有サーバーはコールの終了時に別のクライアントに移動します。作成元以外のコールでServerSocketインスタンスを使用すると、ソケットがクローズしていることを示すI/O例外が発生します。 |
共有サーバー・モードでは、戻りまたは捕捉されない例外が原因でコールが終了すると、Oracle JVMは、すべてのデーモン・スレッドでThreadDeathExceptionをスローします。このThreadDeathExceptionは、原則的にスレッドの実行を停止します。複数のコールにわたって存続するスレッドに依存しているコードは、共有サーバー・モードでは予測どおりには動作しません。たとえば、データベース・コールの終了時にはすべてのスレッドが停止するため、スレッドの初期化を追跡するstatic変数の値は、後続のコールでは正確さに欠ける可能性があります。
具体的な例として、Sun社が提供するRMIサーバーは、共有サーバー・モードで機能します。ただし、1つのコールのコンテキスト内のみで有効です。これは、共有サーバー・モードではコール終了時、つまり、すべての非デーモン・スレッドが戻ったときに停止するデーモン・スレッドが、RMIサーバーによってフォーク処理されるためです。後続のコールでRMIサーバー・セッションを再び開始しても、これらのデーモン・スレッドは再開されないため、RMIサーバーは適切に機能しません。