SQLJ文は常に#sql
トークンで開始し、主に次の2つのカテゴリに分割できます。
宣言: イテレータまたは接続コンテキストのJavaクラスを作成します。イテレータは、Java Database Connectivity (JDBC)の結果セットのようなもので、接続コンテキストは、使用されるSQLエントリに応じて強い型指定の接続を作成するために使用するものです。
実行文: 埋込みのSQL操作の実行に使用します。
この章では、次の項目について説明します。
SQLJ宣言は#sql
トークンで開始され、その後にクラスの宣言が続きます。SQLJ宣言により、アプリケーションに専用Java型が導入されます。現行では、SQLJ宣言にはイテレータ宣言と接続コンテキスト宣言の2種類があり、次のようにJavaクラスを定義します。
イテレータ宣言はイテレータ・クラスを定義します。イテレータは、概念的にJDBC結果セットと似ていて、複数行の問合せデータの受取りに使用されます。イテレータは、イテレータ・クラスのインスタンスとして実装されます。
接続コンテキスト宣言は、接続コンテキスト・クラスを定義します。通常、各接続コンテキスト・クラスは、操作の際に特定のSQLエンティティ(表、ビュー、ストアド・プロシージャなど)を使用する接続に使用されます。すなわち、名前と特性が同じSQLエンティティを持つスキーマへの接続には、特定の接続コンテキスト・クラスのインスタンスが使用されています。SQLJでは、各データベース接続が、接続コンテキスト・クラスのインスタンスとして実装されます。
SQLJには、事前定義されたsqlj.runtime.DefaultContext
接続コンテキスト・クラスがあります。必要な接続コンテキスト・クラスが1つのみである場合は、接続コンテキスト宣言の必要がないDefaultContext
を使用できます。
イテレータ宣言や接続コンテキスト宣言には、必要に応じて次の句を挿入できます。
implements
句: 生成されたクラスによって実装されるインタフェースを1つ以上指定します。
with
句: 生成されたクラスに含める初期化済定数を1つ以上指定します。
ここでは、次の項目について説明します。
SQLJソース・コード中には、標準Javaでクラスを定義できる場所であればどこにでもSQLJ宣言を記述できます。次に例を示します。
SQLJ declaration; // OK (top level scope) class Outer { SQLJ declaration; // OK (class level scope) class Inner { SQLJ declaration; // OK (nested class scope) } void func() { SQLJ declaration; // OK (method block) } }
注意: 標準Javaの場合と同様に、Publicクラスは、次のいずれかの方法で宣言する必要があります。
Sun社のJDKの標準 |
イテレータ宣言では、問合せデータの受取り用に、イテレータの一種を定義するクラスを作成します。この宣言でイテレータ・インスタンスの列型を指定しますが、この列型はデータベース表から選択され列型と同じである必要があります。
基本的なイテレータ宣言では、次の構文が使用されます。
#sql <modifiers> iterator iterator_classname (type declarations);
修飾子の使用は任意で、public
やstatic
などの標準Javaクラス修飾子を使用できます。型宣言はカンマで区切ります。
イテレータには、名前付きイテレータと位置イテレータの2種類があります。名前付きイテレータには、列名と型を指定する必要があります。位置イテレータには、型のみを指定する必要があります。
次に、名前付きイテレータ宣言の例を示します。
#sql public iterator EmpIter (String ename, double sal);
この文を実行すると、SQLJトランスレータによって、String
属性のename
およびdouble
属性のsal
で、public EmpIter
クラスが作成されます。このイテレータを使用して、データベース表の従業員名と給料の各列から、名前(ENAME
およびSAL
)とデータ型(CHAR
およびNUMBER
)が一致するデータを選択できます。
EmpIter
を名前付きイテレータではなく位置イテレータとして宣言すると、次のようになります。
#sql public iterator EmpIter (String, double);
接続コンテキスト宣言により、接続コンテキスト・クラスが作成されます。通常、このクラスのインスタンスは、特定のSQLエンティティ・セットを使用するデータベース接続に使用されます。基本的な接続コンテキスト宣言では、次の構文を使用します。
#sql <modifiers> context context_classname;
イテレータ宣言の場合、修飾子は任意で、標準Javaのどのクラス修飾子でも使用できます。次に例を示します。
#sql public context MyContext;
この文を実行すると、SQLJトランスレータによってpublic MyContext
クラスが作成されます。SQLJコード中にこのクラスのインスタンスを使用すると、目的のエンティティ・セット(表、ビュー、ストアド・プロシージャなど)を含むスキーマへのデータベース接続を作成できます。MyContext
の複数のインスタンスを使用して、様々なスキーマに接続することができますが、それぞれのスキーマに、たとえばEMP
表、DEPT
表およびTRANSFER_EMPLOYEE
ストアド・プロシージャが含まれることが前提になります。
宣言済の接続コンテキスト・クラスは高度なトピックであり、1つの相互に関係するSQLエンティティ・セットのみを使用する基本的なSQLJアプリケーションについては、これを使用する必要はありません。また、基本的には、sqlj.runtime.ref.DefaultContext
クラスのインスタンスをいくつか作成することで複数の接続を使用できるため、接続コンテキストを宣言する必要はありません。
イテレータ・クラスや接続コンテキスト・クラスを宣言する場合、生成されたクラスが実装するインタフェースを1つ以上指定できます。
イテレータ・クラスには次の構文を使用します。
#sql <modifiers> iterator iterator_classname implements intfc1,..., intfcN (type declarations);
implements
intfc1,..., intfcN
の部分は、implements
句と呼ばれます。イテレータ宣言では、implements
句に続いてイテレータ型宣言をします。
接続コンテキスト宣言には次の構文を使用します。
#sql <modifiers> context context_classname implements intfc1,..., intfcN;
implements
句は、イテレータ宣言または接続コンテキスト宣言のいずれにおいても有効ですが、特にsqlj.runtime.Scrollable
またはsqlj.runtime.ForUpdate
インタフェースの実装時のイテレータ宣言で有効な場合が多くあります。スクロール可能なイテレータはOracle SQLJ実装でサポートされています。
注意: SQLJのimplements 句は、Javaのimplements 句に相当します。 |
次の例では、名前付きイテレータ・クラスの宣言にimplements
句を使用しています。この例では、イテレータ・インタフェースのMyIterIntfc
を含むmypackage
パッケージが作成されていることを前提とします。
#sql public iterator MyIter implements mypackage.MyIterIntfc (String ename, int empno);
クラスMyIter
の宣言によって、mypackage.MyIterIntfc
インタフェースが実装されます。
次の例では、MyConnCtxtIntfc
という名前のインタフェースを実装した接続コンテキスト・クラスを宣言しています。ここでも、mypackage
パッケージを前提とします。
#sql public context MyContext implements mypackage.MyConnCtxtIntfc;
接続コンテキスト・クラスやイテレータ・クラスの宣言時には、with
句を使用して、生成されたクラスの定義に含める定数を1つ以上指定し初期化できます。Oracle実装ではいくつかの拡張機能をイテレータ宣言に追加していますが、この使用例の大部分は標準です。
ここでは、次の項目について説明します。
with
句を使用する場合、生成される定数は必ずpublic static final
です。イテレータ・クラスには次の構文を使用します。
#sql <modifiers> iterator iterator_classname with (var1=value1,..., varN=valueN) (type declarations);
with
(
var1=value1,..., varN=valueN
)の部分がwith
句と呼ばれます。イテレータ宣言では、with
句に続けてイテレータ型宣言をします。
with
句とimplements
句の両方がある場合は、implements
句を最初にする必要があります。with
リストはカッコで囲みますが、implements
リストはカッコで囲みません。
次に、with
句を使用する接続コンテキスト宣言の構文を示します。
#sql <modifiers> context context_classname with (var1=value1,..., varN=valueN);
注意: 事前に定義された標準SQLJ定数は、with 句に定義できます。ただし、この定数すべてがOracle Database 11g またはOracle SQLJランタイムでサポートされるわけではありません。
Oracle Database 11gでは、標準定数以外の定数を定義できますが、他のSQLJ実装に移植できない可能性があるうえ、 |
サポートされているWITH句の定数
Oracle SQLJ実装では、接続コンテキスト宣言内で、次の標準定数の使用がサポートされています。
typeMap
: 型マップ・プロパティ・リソース名を定義するString
リテラル
イテレータ宣言内でのtypeMap
の使用もサポートされています。
dataSource
: InitialContext
でデータ・ソースを検索する名前を定義するString
リテラル
Oracle SQLJ実装では、イテレータ宣言内で、次の標準定数の使用がサポートされています。
sensitivity
: SENSITIVE
/ASENSITIVE
/INSENSITIVE
で、スクロール可能なイテレータの機密性を定義する
returnability
: true
/false
で、イテレータがJavaストアド・プロシージャまたはファンクションから戻されるかどうかを定義する
サポートされていないWITH句の定数
サポートされていない定数を使用するSQLJコードを使用した場合、エラーは発生しませんが、何の操作も実行されません。Oracle SQLJ実装では、接続コンテキスト宣言内で次の標準定数を使用することはできません。
Oracle SQLJ実装では、イテレータ宣言内で、カーソルの状態に関連する次の標準定数を使用することはできません。
holdability
: true
/false
で、カーソルを保持できるかどうかを指定する
保持できるかどうかの概念は、SQL仕様で定義されています。保持可能なカーソルは、トランザクションが完了した場合でも、アプリケーション・リクエストに従って、オープン状態かつ現在の行の位置で保持できます。そのカーソルは同じSQLセッションの次のトランザクションで継続して使用できますが、いくつかの制限があります。
updateColumns
: 列名のカンマ区切りリストを含むString
リテラル
updateColumns
を指定するwith
句を持つイテレータ宣言には、sqlj.runtime.ForUpdate
インタフェースを指定するimplements
句も含める必要があります。updateColumns
は現在サポートされていませんが、Oracle SQLJ実装ではこれに従う必要があります。
次に、typeMap
を使用した接続コンテキスト宣言の例を示します。
#sql public context MyContext with (typeMap="MyPack.MyClass");
宣言されたクラスMyContext
ではString
属性のtypeMap
が定義され、これはpublic static final
になってMyPack.MyClass
値に初期化されます。この値は、MyContext
クラスのインスタンス上で実行される文のSQL型とJava型のマッピングを提供するListResourceBundle
実装の完全修飾クラス名です。
次に、sensitivity
を使用したイテレータ宣言の例を示します。
#sql public iterator MyAsensitiveIter with (sensitivity=ASENSITIVE) (String ename, int empno);
この宣言では、MyAsensitiveIter
名前付きイテレータ・クラスに対してカーソルsensitivityにASENSITIVE
が設定されます。
次に、implements
句とwith
句の両方を使用した例を示します。
#sql public iterator MyScrollableIterator implements sqlj.runtime.Scrollable with (holdability=true) (String ename, int empno);
このように宣言すると、インタフェースsqlj.runtime.Scrollable
が実装され、カーソルholdability
が名前付きイテレータ・クラスに対して使用可能になります。
注意: holdability は、現在サポートされていません。 |
型マップを接続コンテキスト・クラスに対応付けるために、標準with
句を接続コンテキスト宣言で使用する方法の他に、Oracle SQLJ実装では、型マップをイテレータ宣言内でイテレータ・クラスに対応付けるために、with
句を使用できます。次に例を示します。
#sql iterator MyIterator with (typeMap="MyTypeMap") (Person pers, Address addr);
Oracle固有コード生成を使用して、型マップをアプリケーションで使用する場合、イテレータと接続コンテキスト宣言で同じ型マップを使用する必要があります。
SQLJイテレータ宣言のwith
句にreturnability=true
を使用すると、イテレータがJavaストアド・プロシージャからSQLまたはPL/SQL文にREF CURSORとして戻されます。デフォルトのreturnability=false
設定では、イテレータはこの方法で戻されず、戻そうとすると、実行時にSQL例外が発生します。
次のデータベース表を作成します。
create table sqljRetTab(str varchar2(30)); insert into sqljRetTab values ('sqljRetTabCol');
次のように、RefCursorSQLJ.sqlj
ソース・ファイル内にRefCursorSQLJ
クラスを定義します。イテレータ型MyIter
にreturnability=true
が使用されていることに注意してください。
public class RefCursorSQLJ { #sql static public iterator MyIter with (returnability=true) (String str); static public MyIter sqljUserRet() throws java.sql.SQLException { MyIter iter=null; try { #sql iter = {select str from sqljRetTab}; } catch (java.sql.SQLException e) { e.printStackTrace(); throw e; } System.err.println("iter is " + iter); return iter; } }
データベース内のOracle Java Virtual Machine(JVM)にRefCursorSQLJ.sqlj
をロードします。
% loadjava -u scott -r -f -v RefCursorSQLJ.sqlj
Password: password
sqljUserRet()
メソッドに定義されたJavaストアド・プロシージャを起動します。
create or replace package refcur_pkg as type refcur_t is ref cursor; end; / create or replace function sqljUserRet return refcur_pkg.refcur_t as language java name 'RefCursorSQLJ.sqljUserRet() return RefCursorSQLJ.MyIter'; / select scott.sqljUserRet from dual;
SELECT
文の結果を次に示します。
SQLJRET1 -------------------- CURSOR STATEMENT : 1 STR ------------------------------ sqljRetTabCol
SQLJ実行文は#sql
トークンと、後に続くSQLJ句で構成されます(実行可能なSQL文をJavaコードに埋め込むために、指定された規格に従った構文を使用します)。SQLJ実行文の埋込みSQL操作は、JDBCドライバでサポートされる任意のSQL操作にできます。
ここでは、次の項目について説明します。
SQLJ実行文には次の規則があります。
Javaコードでは、Javaブロック文が許可される場合は常に、SQLJ実行文も許可されます。つまり、メソッド定義や静的な初期化ブロック内では許可されています。
埋込みSQL操作は、中カッコ{...}
で囲む必要があります。
最後にセミコロン(;)を指定する必要があります。
注意:
|
SQLJ句とは、文の実行可能部分であり、#sql
トークンの後に続く内容すべてを指します。この句では埋込みSQLを中カッコで囲んで指定し、必要に応じて、この句の前にJava結果式を指定します(たとえば、次の例のresult
)。
#sql { SQL operation }; // For a statement with no output, like INSERT ... #sql result = { SQL operation }; // For a statement with output, like SELECT
1番目のSQLJ文の例のように結果式のないものは、ステートメント句と呼ばれます。2番目のSQLJ文の例のように結果式のあるものは、代入句と呼ばれます。
結果式には、ストアド・ファンクションの戻り値が代入される単純な変数から、複数行SELECT
からの複数列のデータが代入されるイテレータまで、任意のものを使用できます(イテレータは、イテレータ・クラスまたはイテレータ・サブクラスのインスタンスになります)。
SQLJ文のSQL操作には、標準SQL構文のみを使用するか、SQLJ固有の構文を持つ句を使用できます。
サポートされているSQLJステートメント句を表4-1に、サポートされているSQLJ代入句を表4-2に示します。表4-1の最後にあげた2つの項目は、SQLJ固有の構文ではなく、標準SQL構文またはOracle PL/SQL構文を使用するステートメント句の一般カテゴリの項目です。
表4-1 SQLJステートメント句
カテゴリ | 機能 | 参照先 |
---|---|---|
|
Javaホスト式にデータを取り込みます。 |
|
|
位置イテレータからデータをフェッチします。 |
|
|
データへの変更をコミットします。 |
|
|
データへの変更を取り消します。 |
|
|
以後のロールバックのためのセーブポイントの設定、指定したセーブポイントの解放、セーブポイントのロールバックを行います。 |
|
|
アクセス・モードや分離レベルなど、詳細トランザクション制御を使用します。 |
|
プロシージャ句 |
ストアド・プロシージャをコールします。 |
|
代入句 |
Javaホスト式に値を代入します。 |
|
SQL句 |
|
『Oracle Database SQL言語リファレンス』 |
PL/SQLブロック |
SQLJ文内部の |
『Oracle Database PL/SQL言語リファレンス』 |
表4-2 SQLJ代入句
カテゴリ | 機能 | 参照先 |
---|---|---|
問合せ句 |
SQLJイテレータにデータを取り込みます。 |
|
ファンクション句 |
ストアド・ファンクションをコールします。 |
|
イテレータ変換句 |
JDBC結果セットをSQLJイテレータに変換します。 |
「結果セットから名前付きイテレータまたは位置イテレータへの変換」 |
注意: SQLJ文は、その文の本体を構成する句と同じ名前になります。たとえば、#sql の後にSELECT INTO 句が続く実行文は、SELECT INTO 文になります。 |
複数のデータベース接続が定義されているときに、実行文に特定の接続コンテキスト・インスタンスを指定する場合は、次の構文を使用します。
#sql [conn_context_instance] { SQL operation };
実行コンテキスト・インスタンス(sqlj.runtime.ExecutionContext
クラス)を1つ以上定義していて、その1つを実行文用に指定する場合、次の構文を使用します。
#sql [exec_context_instance] { SQL operation };
実行コンテキスト・インスタンスを使用して、SQLJ実行文のSQL操作の実行状態や実行制御を定義できます。たとえば、同一の接続で複数の操作が行われるマルチスレッド状況で、実行コンテキスト・インスタンスを使用できます。
また、接続コンテキスト・インスタンスと実行コンテキスト・インスタンスの両方を指定することも可能です。
#sql [conn_context_instance, exec_context_instance] { SQL operation };
注意:
|
ここでは、基本的なSQLJ実行文の例を示します。
基本的なINSERT
次の例は、基本的なINSERT
です。ステートメント句は、SQLJ固有の構文を必要としません。
次の行を持つ従業員表EMP
を想定します。
CREATE TABLE EMP ( ENAME VARCHAR2(10), SAL NUMBER(7,2) );
標準SQL構文のSQLJ実行文を使用し、EMP
表に新しい従業員Joeを挿入し、名前と給料を指定します。
#sql { INSERT INTO emp (ename, sal) VALUES ('Joe', 43000) };
接続コンテキスト・インスタンスまたは実行コンテキスト・インスタンスを指定する基本的なINSERT
次の例では、接続コンテキスト・インスタンスとして、デフォルトのsqlj.runtime.ref.DefaultContext
のインスタンスまたは接続コンテキスト宣言で宣言したクラスのインスタンスであるctx
を使用しています。また、実行コンテキスト・インスタンスとしては、execctx
を使用しています。
#sql [ctx] { INSERT INTO emp (ename, sal) VALUES ('Joe', 43000) }; #sql [execctx] { INSERT INTO emp (ename, sal) VALUES ('Joe', 43000) }; #sql [ctx, execctx] { INSERT INTO emp (ename, sal) VALUES ('Joe', 43000) };
単純なSQLJメソッド
この例は、SQLJコードを使用した単純なメソッドを示し、SQLJ文とJava文の関係およびコード中での場所を表しています。このSQLJ文は、Oracle SQL実装でサポートされている標準のINSERT INTO
table
VALUES
構文を使用しています。また、この文では、コロン(:)でマークされたJavaホスト式を使用して値を定義しています。ホスト式は、JavaコードとSQL命令の間でのデータの受渡しに使用されます。
public static void writeSalesData (int[] itemNums, String[] itemNames) throws SQLException { for (int i =0; i < itemNums.length; i++) #sql { INSERT INTO sales VALUES(:(itemNums[i]), :(itemNames[i]), SYSDATE) }; }
注意:
|
PL/SQLブロックは、SQL操作と同様に、SQLJ実行文の中カッコ内で使用できます。次にその例を示します。
#sql { DECLARE n NUMBER; BEGIN n := 1; WHILE n <= 100 LOOP INSERT INTO emp (empno) VALUES(2000 + n); n := n + 1; END LOOP; END };
この例では、新しい従業員をemp
表に挿入するループに入り、従業員番号の2001
から2100
を作成します。従業員番号以外のデータは、後で埋められるとします。
単純なPL/SQLブロックは、次のように1行のコードでも完結します。
#sql { <DECLARE ...> BEGIN ... END; };
SQLJ文中での無名PL/SQLブロックの使用は、アプリケーション内で動的SQLを使用する1つの方法です。動的SQLは、OracleのSQLJ拡張機能を使用するか、またはSQLJアプリケーション内部のJDBCコードを使用することによっても直接使用できます。
注意: PL/SQLはOracle固有なので、PL/SQLをSQLJコードで使用すると、他のプラットフォームへの移植性が失われます。 |
ここでは、SQLJコードで使用されるJava式の3つのカテゴリ(ホスト式、コンテキスト式および結果式)について説明します。ホスト式は、最も頻繁に使用されるJava式です。メタ・バインド式と呼ばれる別のカテゴリの式は、特に動的SQL操作に使用され、ホスト式と同様の構文を使用します。
SQLJは、JavaコードとSQL操作の間の引数の受渡しにホスト式を使用します。これが、JavaとSQLの間で情報を通信する方法です。ホスト式は、SQLJソース・コードの埋込みSQL操作内に間隔をおいて配置されます。
Java識別子のみで構成される最も基本的なホスト式は、ホスト変数と呼ばれます。コンテキスト式には、SQLJ文で使用される接続コンテキスト・インスタンスまたは実行コンテキスト・インスタンスを指定します。結果式には、問合せ結果やファンクションの戻り値の出力変数を指定します。
ここでは、次の項目について説明します。
有効なJava式はすべて、ホスト式として使用できます。最も単純なホスト式は、単一のJava変数のみで構成されます。それ以外のホスト式には、次の式があります。
算術式
Javaメソッドのコールと戻り値
Javaクラス・フィールド値
配列の要素
条件式(a ? b : c
)
論理式
ビット単位式
ホスト変数として使用されたJava識別子、またはホスト式で使用されたJava識別子は、次のいずれかを表します。
ローカル変数
宣言したパラメータ
クラス・フィールド
staticメソッド・コールまたはインスタンス・メソッド・コール
ホスト式で使用されたローカル変数は、他のJava変数を宣言できる場所で宣言できます。フィールドは、スーパークラスから継承できます。
SQLJ実行文があるJavaスコープ内で有効なJava変数は、SQL文のホスト式で使用できますが、Java変数の型がSQLデータ型と相互に変換可能であることが前提になります。ホスト式は、入力、出力または入出力のいずれにもなります。
ホスト式の前にはコロン(:)が付きます。使用するホスト式のモードがデフォルトでない場合は、コロンに続けてIN
、OUT
またはINOUT
を付け、その後にホスト式を置きます。これらはモード指定子と呼ばれます。ホスト式がINTO
リストである場合、またはホスト式がSET
文中の代入式である場合は、デフォルトのモード指定子がOUT
になります。それ以外の場合は、デフォルトがIN
になります。すべてのOUT
またはINOUT
ホスト式は、代入可能である必要があります。
注意: デフォルトを使用している場合でも、必要に応じてモード指定子を設定できます。 |
ホスト式を取り巻くSQLコードには、任意のベンダー固有SQL構文を使用できます。このため、SQL操作の解析およびホスト式の判断を行うときに、構文を想定できません。不明確にならないよう、単純なホスト変数ではないホスト式、つまりドットで区切られていないJava識別子より複雑なホスト式は、カッコで囲んでください。
基本的な構文を要約すると次のようになります。
モード指定子のない単純なホスト変数の場合は、次の例のように、コロンの後にホスト変数を置きます。
:hostvar
モード指定子付きの単純なホスト変数の場合は、次の例のように、コロンの後にモード指定子を付け、続けて空白(スペース、タブ、改行またはコメント)を挿入してからホスト変数を指定します。
:INOUT hostvar
モード指定子と変数名を区別するために、空白が必要です。
その他のホスト式の場合は、次の例のように、式をカッコで囲んでモード指定子の後に置きます。モード指定子がない場合はコロンの後に置きます。
:IN(hostvar1+hostvar2) :(hostvar3*hostvar4) :(index--)
この例では、カッコをセパレータとしているので、モード指定子の後に空白を置く必要はありません。ただし、モード指定子の後に空白を置くことはできます。
次の例のように、式が左カッコで始まる場合でも、全体を囲む左カッコがもう1つ必要です。
:((x+y).z) :(((y)x).myOutput())
構文上の注意
ホスト式の構文には、次の注意事項があります。
コロンやモード指定子の後には、どんな場合にも空白を挿入できます。空白を挿入できる場所には、コメントも記述できます。
次のいずれかのSQLネームスペースにSQLコメントを記入できます。
コロンの後とモード指定子の前
モード指定子がない場合は、コロンの後とホスト式の前
モード指定子の後とホスト式の前
次のJavaネームスペースにJavaコメントを記述できます。
ホスト式内部(小カッコの中)
ホスト変数およびホスト式に使用されるIN
、OUT
およびINOUT
構文には、大/小文字の区別がありません。これらのトークンには、大文字、小文字またはその両方使用できます。
SQLJホスト式のIN
、OUT
およびINOUT
構文は、PL/SQL宣言でのIN
、OUT
およびIN OUT
構文と似ていますが、混同しないようにしてください。PL/SQL宣言でのこの構文は、PL/SQLストアド・ファンクションおよびプロシージャに渡されるパラメータのモードの指定に使用されます。
使用上の注意
ホスト式の使用には、次の注意事項があります。
同じSQLJ文中に単純なホスト変数が複数回出現する場合があります。
ホスト変数を入力変数としてのみ使用する場合は、制約や煩雑な操作は伴いません。
PL/SQLブロック内の出力変数としてホスト変数が少なくとも1回現れた場合、トランスレータの-warn=portability
フラグが設定されていると、移植性に関する警告が出されます。こうした状況では、各ベンダーのSQLJランタイムは、それぞれ固有の動作をします。OracleのSQLJランタイムの場合、ホスト変数が出現するたびに値構文が使用されます(参照セマンティクスと対照)。
ホスト変数が、ストアド・プロシージャ・コール、ストアド・ファンクション・コール、SET
文またはINTO
リスト中の出力変数として1回以上出現した場合、警告は出力されません。こうした場合にSQLJランタイムでは値構文を使用し、それぞれに対して共通の処理をします。
注意: 出力とは、OUT またはINOUT 変数のことです。 |
単純なホスト変数のホスト式がSQLJ文中に複数回出現した場合は、(デフォルトで)値構文を使用し、それぞれを完全に別個のものとして処理します。ただし、SQLJトランスレータで-bind-by-identifier=true
設定を使用している場合は、処理が異なります。true
に設定されている場合、指定されたSQLJ文またはPL/SQLブロック内に同じホスト変数が複数回出現しても、単一のバインドとして処理されます。
CHAR
型データと比較するために文字列のホスト式をWHERE
句にバインドする場合は、SQLJに-fixedchar
オプションがあることに注意してください。このオプションは、比較時にCHAR
列の空白埋めを考慮します。
次に、構文の説明を明確にするために、いくつかの例をあげます。
例1
この例では、2つの入力ホスト変数を、一方はWHERE
句のテスト値として、もう一方はデータベースに送られる新しいデータを含むものとして使用しています。
データベースの従業員表emp
に、従業員名の列ename
および従業員の給料の列sal
があるとします。次に、ホスト変数を定義する関連Javaコードを示します。
String empname = "SMITH"; double salary = 25000.0; ... #sql { UPDATE emp SET sal = :salary WHERE ename = :empname };
IN
はデフォルトですが、明示的に示すことも可能です。
#sql { UPDATE emp SET sal = :IN salary WHERE ename = :IN empname };
ここに示すとおり、IN
トークンを使用しない場合は、コロン(:)のすぐ後ろに変数を置けるのに対し、:IN
の場合はすぐ後ろに空白を置き、その後にホスト変数を置く必要があります。
例2
この例では、SELECT INTO
文で出力ホスト変数を使用し、従業員番号が28959の従業員名を検索します。
String empname; ... #sql { SELECT ename INTO :empname FROM emp WHERE empno = 28959 };
OUT
はINTO
リストのデフォルトですが、明示的に示すことも可能です。
#sql { SELECT ename INTO :OUT empname FROM emp WHERE empno = 28959 };
この文では、emp
表のempno
列で従業員番号28959を検索し、その行のename
列から名前を選択し、それをempname
ホスト変数に出力します。この場合、Java String
で出力されます。
例3
この例では、入力ホスト式として算術式を使用しています。Java変数のbalance
とminPmtRatio
が乗算され、その結果は口座番号537845についてcreditacct
表のminPayment
列を更新する際に使用されます。
float balance = 12500.0; float minPmtRatio = 0.05; ... #sql { UPDATE creditacct SET minPayment = :(balance * minPmtRatio) WHERE acctnum = 537845 };
IN
トークンを使用すると次のようになります。
#sql { UPDATE creditacct SET minPayment = :IN (balance * minPmtRatio) WHERE acctnum = 537845 };
例4
この例では、入力ホスト式としてメソッドのコールの出力を使用し、さらに入力ホスト変数も使用しています。この文では、getNewSal()
からの戻り値を使用して、Java変数empname
で指定された従業員について、emp
表のsal
列を更新します。ホスト変数に初期値を設定するJavaコードも示されています。
String empname = "SMITH"; double raise = 0.1; ... #sql {UPDATE emp SET sal = :(getNewSal(raise, empname)) WHERE ename = :empname};
コンテキスト式は、SQLJ実行文で使用される接続コンテキスト・インスタンスまたは実行コンテキスト・インスタンスの名前を指定する入力式です。式を評価し、そのような名前を生成するJava式を使用できます。
結果式は、問合せ結果やファンクションの戻り値に使用する出力式です。代入可能、つまり論理的に等号の左辺に置くことが可能な正当なJava式を使用できます。これは、左辺値ともいいます。
次に示す例は、結果式とコンテキスト式のいずれにも使用できます。
ローカル変数
宣言したパラメータ
クラス・フィールド
配列の要素
構文上、ホスト式はSQL領域(SQLJ実行文の中カッコ内)に置きますが、結果式とコンテキスト式はSQLJ領域に置きます。このため、結果式またはコンテキスト式の前には、コロンを付けないようにする必要があります。
ここでは、アプリケーションの実行時の、Javaホスト式、接続コンテキスト式、実行コンテキスト式および結果式の評価について説明します。
これらの式をすべて使用する、単純化されたSQLJ実行文を次に示します。
#sql [connctxt_exp, execctxt_exp] result_exp = { SQL with host expression };
Java式には、必要に応じて次の用途があります。
接続コンテキスト式: 使用する接続コンテキスト・インスタンスの指定のために評価されます。
実行コンテキスト式: 使用する実行コンテキスト・インスタンスの指定のために評価されます。
結果式: 結果を受け取ります(たとえば、ストアド・ファンクションから)。
ホスト式
ISO標準コード生成では、式が評価される順序に応じた副作用を使用する場合でも、Java式の評価は明確です。
Oracle固有コード生成では、-bind-by-identifier
オプションが使用可能な場合を除き、副作用がない場合、Java式の評価はISO標準に従います。副作用がある場合、Java式の評価は実装ごとに固有で、変更される可能性があります。
注意: 以降の説明および関連する例は、Oracle固有コード生成には当てはまりません。ここに説明されているような副作用を使用する場合は、変換でISOコードを生成する必要があります。 |
次に、ISOコードの場合の実行時に実行される文に対するJava式の評価、実行および代入の順序の概要を示します。
接続コンテキスト式がある場合は、他のJava式が評価される前に、即座に評価されます。
実行コンテキスト式がある場合は、接続コンテキスト式の後で、結果式の前に評価されます。
結果式がある場合は、コンテキスト式の後で、ホスト式の前に評価されます。
コンテキスト式や結果式の評価後に、ホスト式は、SQL操作での出現順に従って左から右へ評価されます。各ホスト式が出現してそれが評価されると、その値が保存されSQLに渡されます。
各ホスト式の評価は1度のみです。
IN
およびINOUT
パラメータがSQLに渡され、SQL操作が実行されます。
SQL操作の実行後、出力パラメータ、つまりJavaのOUT
およびINOUT
ホスト式に、SQL操作の結果の出力内容が代入されます。このとき、出力パラメータがSQL操作で出現する順序に従って、左から右に代入が行われます。
各出力ホスト式は1度のみ代入されます。
結果式がある場合は、最後に出力内容が代入されます。
注意: PL/SQLブロック内のホスト式はすべて、ブロック内の文が実行される前に、一度に評価されます。ホスト式は、ブロック内のコントロール・フローにかかわらず、出現順に評価されます。 |
文中の式が評価された後、入力および入出力ホスト式がSQLに渡され、SQL操作が実行されます。SQL操作の実行後、Javaの出力ホスト式、入出力ホスト式および結果式に対して、次のように代入が行われます。
OUT
およびINOUT
ホスト式は、左から右の順に出力内容が代入されます。
結果式がある場合は、最後に出力内容が代入されます。
実行時には、すべてのホスト式が別々の値として扱われます。これは、ホスト式が同じ名前を共有する場合、または同じオブジェクトを参照する場合でも同様です。それぞれのSQL操作の実行はリモート・メソッドの起動と同様に処理され、各ホスト式は別々のパラメータとして使用されます。各入力または入出力パラメータは、最初の出現時に評価されて渡された後に、その文に対して出力の代入が行われます。各出力パラメータも別々のものとして扱われ、1度だけ代入されます。
各ホスト式の評価が1度のみであることにも注意してください。INOUT
式は最初に出現したときに評価されます。出力の代入時に、式そのものは再評価されず、副作用も繰り返されません。
ここでは、ISOコード生成でのアプリケーション実行時のJava式の評価方法について説明します。
注意: Oracle固有コード生成を使用する場合、これらの作用を頼りにしないでください。これらの作用を利用する場合は、変換時にISOコード生成を要求してください。 |
前置演算子および後置演算子の評価
Java式にJavaの後置の増分演算子または減分演算子が含まれる場合、Java式が評価された後に、増分または減分が行われます。同様に、Java式にJavaの前置の増分演算子または減分演算子が含まれる場合、式が評価される前に、増分または減分が行われます。この動作は、標準のJavaコードでのこれらの演算子の処理方法と同じです。
後置演算子の例を次に示します。
int indx = 1; ... #sql { ... :OUT (array[indx]) ... :IN (indx++) ... };
この例では、式が次のように評価されます。
#sql { ... :OUT (array[1]) ... :IN (1) ... };
indx
変数は2に増やされ、:IN (indx++)
が評価された後で次に出現するときはこの値になります。
この後置演算子の例を次に示します。
int indx = 1; ... #sql { ... :OUT (array[indx++]) ... :IN (indx++) ... };
この例では、式が次のように評価されます。
#sql { ... :OUT (array[1]) ... :IN (2) ... };
変数indx
は、最初の式が評価された後、2番目の式が評価される前に、2に増分されます。この変数は、2番目の式が評価されると3に増分され、次に出現するときはこの値になります。
前置演算子と後置演算子の両方を含む例を次に示します。
int indx = 1; ... #sql { ... :OUT (array[++indx]) ... :IN (indx++) ... };
この例では、式が次のように評価されます。
#sql { ... :OUT (array[2]) ... :IN (2) ... };
変数indx
は、最初の式が評価される前に、2に増分されます。この変数は、2番目の式が評価されると3に増分され、次に出現するときはこの値になります。
IN、INOUTおよびOUTホスト式の評価の順序
ホスト式は左から右に評価されます。式がIN
、INOUT
またはOUT
のいずれであるかは、評価の順序に影響しません。評価の順序を決めるのは、式が置かれる位置です。
次に示す例で確認してください。
int[5] arry; int n = 0; ... #sql { SET :OUT (arry[n]) = :(++n) };
この例では、式が次のように評価されます。
#sql { SET :OUT (arry[0]) = 1 };
入力式が出力式の前に評価されることを期待するかもしれませんが、この場合は異なります。式:OUT (arry[n])
が最も左端の式であるため、最初に評価されます。前置演算子がnに作用するため、n
が増分されてから++n
が評価されます。そのため、++n
は1と評価されます。n
が最初に出現したときの値が0
(ゼロ)であったため、結果はarry[0]
に代入されます(arry[1]
ではありません)。
文の実行前に評価されるPL/SQLブロック内の式
PL/SQLブロック内のホスト式はすべて、いずれかが実行される前に、一度に評価されます。次に示す例で確認してください。
int x=3; int z=5; ... #sql { BEGIN :OUT x := 10; :OUT z := :x; END }; System.out.println("x=" + x + ", z=" + z);
この例では、式が次のように評価されます。
#sql { BEGIN :OUT x := 10; :OUT z := 3; END };
このため、出力はx=10、z=3
となります。
PL/SQLブロック内の式はすべて、いずれかが実行される前に評価されます。この例では、2番目の文のホスト式、つまり:OUT z
および:x
が、最初の文が実行される前に評価されます。2番目の文が評価されるのは、x
が元の値の3
である場合で、式が評価された後にこのxに値10
が代入されます。
ここでは、PL/SQLブロック内で式が評価される方法について、例を挙げて説明します。
int x=1, y=4, z=3; ... #sql { BEGIN :OUT x := :(y++) + 1; :OUT z := :x; END };
この例では、式が次のように評価されます。
#sql { BEGIN :OUT x := 4 + 1; :OUT z := 1; END };
後置増分演算子は、:(y++)
が評価された後に実行されます。このため、式はy
の初期値である4
と評価されます。2番目の文:OUT z := :x
は、最初の文が実行される前に評価されます。そのため、x
は、初期値の1
のままです。このブロックの実行後、x
の値は5
、z
の値は1
になります。
次の例では、1つのSQLJ実行文のPL/SQLブロックに出現する2つの文と、個別の(連続した)SQLJ実行文に出現する同じ2つの文との違いについて説明します。
まず、PL/SQLブロック内に2つの文がある場合を考えます。
int y=1; ... #sql { BEGIN :OUT y := :y + 1; :OUT x := :y + 1; END };
この例では、式が次のように評価されます。
#sql { BEGIN :OUT y := 1 + 1; :OUT x := 1 + 1; END };
2番目の文の:y
は、いずれかの文が実行される前に評価されます。このため、y
は最初の文からその出力をまだ受け取っていません。このブロックの実行後、x
とy
の両方の値が2
になります。
次に、個別のSQLJ実行文のPL/SQLブロック内に、前の例と同じ2つの文がある場合を考えます。
int y=1; #sql { BEGIN :OUT y := :y + 1; END }; #sql { BEGIN :OUT x := :y + 1; END };
最初の文は次のように評価されます。
#sql { BEGIN :OUT y := 1 + 1; END };
この式が実行された後で、 y
に値2
が代入されます。
最初の文の実行後、2番目の文が次のように評価されます。
#sql { BEGIN :OUT x := 2 + 1; END };
この場合、前述のPL/SQLブロックの例とは異なり、前の文の実行によりy
はすでに値2
を受け取っています。このため、2番目の文の実行後、xに値3
が代入されます。
常に1度のみ評価されるPL/SQLブロック内の文
ホスト式の評価は、プログラム・フローやプログラム・ロジックにかかわらず、1度のみです。
次に示すループでのホスト式の評価の例を考えます。
int count = 0; ... #sql { DECLARE n NUMBER BEGIN n := 1; WHILE n <= 100 LOOP :IN (count++); n := n + 1; END LOOP; END };
Java変数count
がSQLに渡されるときの値は0
(ゼロ)になります。これは、この変数が前置演算子ではなく後置演算子で操作されるためです。その後、値は1
に増分され、このPL/SQLブロックの実行中はその値を維持します。この変数は、SQLJ実行文の解析時に一度だけ評価され、SQLの実行前に値1
で置き換えられます。
次に示す条件ブロック内のホスト式の評価の例を考えます。この例では、プログラム・フローに左右されずに式が評価される方法について説明します。ブロックの実行では、IF...THEN...ELSE
コンストラクトの1つのブランチのみが実行可能です。ただし、ブロックの実行前に、そのブロック内のすべての式が文の出現順序に従って評価されます。
int x; ... (operations on x) ... #sql { DECLARE n NUMBER BEGIN n := :x; IF n < 10 THEN n := :(x++); ELSE n := :x * :x; END LOOP; END };
x
に対して実行された操作の結果、x
の値が15
になったとします。PL/SQLブロックの実行時には、ELSE
ブランチは実行されますがIF
ブランチは実行されません。ただし、PL/SQLブロック内の式はすべて、プログラム・ロジックやプログラム・フローにかかわらず、実行前に評価されます。このため、x++
が評価されてからx
が増分された後、各x
が(x * x)
式で評価されます。IF...THEN...ELSE
ブロックは次のように評価されます。
IF n < 10 THEN n := 15; ELSE n := :16 * :16; END LOOP;
x
の初期値が15
として、このブロックの実行後n
の値が256
になります。
結果式の前に左から右へ代入が行われる出力ホスト式
前述のように、OUT
およびINOUT
ホスト式がその出現順に従って左から右に代入され、結果式がある場合は最後に代入されます。同じ変数に対して複数回代入が行われた場合は、この順序に従って、最後に代入された内容で上書きされます。
同じ変数を参照する複数の出力ホスト式の例を次に示します。
#sql { CALL foo(:OUT x, :OUT x) };
foo()
が、値2
および3
を出力する場合、SQLJ実行文の実行が終了すると、x
の値は3
になります。右端の代入が最後に行われるため、その値が優先されます。
同じオブジェクトを参照する複数の出力ホスト式の例を次に示します。
MyClass x = new MyClass(); MyClass y = x; ... #sql { ... :OUT (x.field):=1 ... :OUT (y.field):=2 ... };
SQLJ実行文の実行後、x.field
の値は、1
ではなく2
になります。これは、x
がy
と同じオブジェクトであり、field
にまず値1
が代入された後、値2
が代入されたためです。
次の例は、ファンクションの出力結果が結果式に代入される場合と、結果がOUT
ホスト式に代入される場合の相違を示します。次に、invar
入力パラメータ、outvar
出力パラメータおよび戻り値を使用するファンクションを考えてみます。
CREATE FUNCTION fn(invar NUMBER, outvar OUT NUMBER) RETURN NUMBER AS BEGIN outvar := invar + invar; return (invar * invar); END fn;
次に、ファンクションの出力結果が結果式に代入される例を考えてみます。
int x = 3; #sql x = { VALUES(fn(:x, :OUT x)) };
このファンクションは、入力パラメータとして3
をとり、出力パラメータとして6
を代入し、また戻り値として9
を戻します。実行後、まず:OUT x
に対して代入が行われ、x
の値が6
になります。ただし、最後には結果式に代入が行われ、x
に代入されていた値6
が、戻り値の9
で上書きされます。このため、
x
が次に出現するとき、その値は9
になります。
次の例では、ファンクションの出力結果が、結果式ではなくOUT
ホスト変数に代入されます。
int x = 3; #sql { BEGIN :OUT x := fn(:x, :OUT x); END };
この場合は結果式がなく、OUT
変数は単純に左から右へ代入されます。実行後、数式の左側にある最初の:OUT x
に代入が行われ、x
の値がファンクション戻り値の9
になります。ただし、左から右へ進むと、数式の右側にある2番目の:OUT x
に最後に代入が行われて、x
が出力値の6
になり、前にx
に代入された値9
がこの値6で上書きされます。このため、x
が次に出現するとき、その値は6
になります。
注意: ここでは、ホスト式の評価方法の概念を説明するために、通常では使用しない例をいくつか示しています。実際のコードでは、1つの文またはPL/SQLブロック内で、OUT とINOUT またはIN ホスト式に同じ変数を使用することはお薦めしません。このように記述した場合の動作は、Oracle SQLJ実装では明確に定義されていますが、SQLJ仕様では定義されていません。このため、この方法で記述されたコードは移植できません。セマンティクス・チェックの際にportable フラグに設定されていると、このようなコードに対してSQLJトランスレータからの警告が生成されます。 |
戻り値がデータ1行のみの場合、SQLJでは、選択項目をSQL構文内のJavaホスト式に直接代入できます。これは、SELECT INTO
文を使用して行います。ここでは、次の項目について説明します。
SELECT INTO
文の構文を次に示します。
#sql { SELECT expression1,..., expressionN INTO :host_exp1,..., :host_expN FROM table <optional_clauses> };
次の注意事項があります。
expression1
からexpressionN
までは、データベースから選択するデータを指定する式です。すべてのSELECT
文に有効な式を使用します。この式のリストは、SELECT
リストと呼ばれます。単純な場合は、データベース表の列名になります。SELECT
リストにはホスト式も記述できます。
host_exp1
からhost_expN
までは、変数や配列要素などのターゲット・ホスト式です。このホスト式のリストは、INTO
リストと呼ばれます。
table
は、データを選択するデータベースの表、ビューまたはスナップショットの名前です。
optional_clauses
は、WHERE
句など、SELECT
文に追加する任意の句です。
SELECT INTO
文の戻り値は、必ず1行のデータのみとします。それ以外の場合は、実行時にエラーが発生します。
デフォルトでは、INTO
リストのホスト式はOUT
ですが、これを明示的に指定することも可能です。
#sql { SELECT column_name1, column_name2 INTO :OUT host_exp1, :OUT host_exp2 FROM table WHERE condition };
INTO
リストでIN
またはINTO
トークンを使用すると、変換時にエラーが発生します。
注意:
|
この項の例では、次の行を持つ従業員表EMP
を使用します。
CREATE TABLE EMP ( EMPNO NUMBER(4), ENAME VARCHAR2(10), HIREDATE DATE );
次に、INTO
リストにホスト式1つを使用したSELECT INTO
文の例を示します。
String empname; #sql { SELECT ename INTO :enpname FROM emp WHERE empno=28959 };
次に、INTO
リストに複数のホスト式を使用したSELECT INTO
文の例を示します。
String empname; Date hdate; #sql { SELECT ename, hiredate INTO :empname, :hdate FROM emp WHERE empno=28959 };
INTO
リストと同様に、SELECT
リストにもJavaホスト式を使用できます。たとえば、次のように、1つのホスト式から他のホスト式に直接データを取り込むことも可能です(ただし、用途は限られています)。
... #sql { SELECT :name1 INTO :name2 FROM emp WHERE empno=28959 }; ...
次の例のように、選択されたデータに対して操作または連結を行う方がより現実的です。ここでは、必要に応じてJava変数がすでに宣言および代入されていることを前提としています。
... #sql { SELECT sal + :raise INTO :newsal FROM emp WHERE empno=28959 }; ... ... #sql { SELECT :(firstname + " ") || emp_last_name INTO :name FROM myemp WHERE empno=28959 }; ...
2番目の例では、emp
表に類似したmyemp
という表に、ename
列ではなくemp_last_name
列があるとします。SELECT
文で、Javaホスト式とJavaの文字列連結演算子(+
)を使用して、firstname
の後に空白1つ(" ")を追加します。この結果はSQLエンジンに渡され、SQLエンジンはSQLの文字列連結演算子(||
)を使用して姓を追加します。
SELECT INTO
文は、1行のデータのみを戻す問合せ用です。0(ゼロ)行または複数の行を検索すると、SELECT INTO
問合せは例外を戻します。
0(ゼロ)行を検索しているSELECT INTO
は、「no data」状態を表す2000
というSQL状態で例外を戻します。
複数行を検索しているSELECT INTO
は、制約違反を表す21000
というSQL状態で例外を戻します。
java.sql.SQLException
クラスのgetSQLState()
メソッドによって、SQL状態を取得できます。
これは、ISO SQLJ規格で指定されたベンダーに依存しない動作です。この場合、ベンダー固有エラー・コードはありません。エラー・コードは常に0
(ゼロ)になります。
多数のSQL操作は複数行の問合せです。SQLJで複数行の問合せ結果を処理するには、SQLJイテレータが必要です。SQLJイテレータは、強い型指定のJDBC結果セットであり、基になるデータベース・カーソルに対応付けられています。最初にSQLJイテレータが使用され、SELECT
文からの問合せ結果を取得します。
Oracle SQLJでは、次の目的でSQLJイテレータと結果セットを使用できます。
SQL実行文のOUT
ホスト変数として使用
SELECT INTO
文などのINTO
リストのターゲットとして使用
ストアド・ファンクション・コールからの戻り型として使用
イテレータ宣言の列型として使用(基本的にはネストされたイテレータ)
注意: 前述の目的でSQLJイテレータを使用するには、そのクラスがpublic として宣言されていることが必要です。クラスレベルまたはネストされたクラスレベルで宣言したイテレータは、public static として宣言することをお薦めします。 |
ここでは、次の項目について説明します。
SQLJイテレータ宣言を使用すると、強い型指定のイテレータになります。これは、一般的なイテレータの使用方法で、SQLJセマンティクス・チェック機能のメリットを変換時に活用します。また、弱い型指定のイテレータを使用することも可能であり、利便性が高いこともあります。弱い型指定のイテレータを使用するために、インスタンス化できる汎用クラスがあります。
ここでは、次の項目について説明します。
強い型指定のイテレータ・オブジェクトを使用する前に、イテレータ・クラスを宣言する必要があります。イテレータ宣言には、SQLJで生成されるJavaクラスを指定し、このクラス属性にはデータ列の型(および必要に応じて名前)を定義します。
SQLJイテレータ・オブジェクトは、事前定義された一定の列数で、前述のとおり個々に宣言されるイテレータ・クラスのインスタンスです。これに対して、JDBC結果セット・オブジェクトは、標準のjava.sql.ResultSet
のインスタンスで、原則的には任意の型の列をいくつも持つことが可能です。
イテレータを宣言する際には、選択された列のデータ型のみを指定するか、選択された列のデータ型と名前の両方を指定します。
名前とデータ型を指定すると、名前付きイテレータ・クラスが定義されます。
データ型のみを指定すると、位置イテレータ・クラスが定義されます。
宣言するデータ型(および名前)により、そのクラスからインスタンス化するイテレータ・オブジェクトへの問合せ結果の格納方法が決まります。イテレータ・オブジェクトに取り込まれたSQLデータは、イテレータ宣言で指定されたJava型に変換されます。
名前付きのイテレータ・オブジェクトに問合せ結果を代入する場合は、SELECT
文内の列の名前やデータ型と、イテレータの列名や列型を一致させる必要があります。ただし、大/小文字の区別はありません。SELECT
文内の列は、どのような順序でもかまいません。重要なことは、SELECT
文の各列名をイテレータの列名に一致させることだけです。最も単純な例では、データベース列とイテレータ列とを同じ名前にすることです。
たとえば、データベース表のENAME
列からのデータの選択や、イテレータename
列への取込みができるようにしてください。また、データベースの列名とイテレータの列名とが異なる場合には、別名を使用して列名どうしをマッピングできるようにする必要があります。より複雑な問合せでは、2つの列の間で操作を実行し、その結果取得された列名が、対応するイテレータの列名と同じになるように変更できることが重要です。
SQLJイテレータは強い型指定であるため、SQLJのセマンティクス・チェック・フェーズで、Javaの型チェックを実行できます。
ここで示す例は、次のように定義された表を前提とします。
CREATE TABLE EMPSAL ( EMPNO NUMBER(4), ENAME VARCHAR2(10), OLDSAL NUMBER(10), RAISE NUMBER(10) );
この表を使用すると、名前付きイテレータを宣言できます。
#sql iterator SalNamedIter (int empno, String ename, float raise);
宣言したら、次のように名前付きイテレータを使用できます。
class MyClass {
void func() throws SQLException {
...
SalNamedIter niter;
#sql niter = { SELECT ename, empno, raise FROM empsal };
... process niter ...
}
}
前述の例は、イテレータの列名と表の列名を同じにした場合の簡単な例です。名前付きイテレータを使用した場合は、SELECT
文中にある項目はどのような順序でもかまいません。データは位置でなく名前で照合されます。
問合せを行い、位置イテレータ・オブジェクトにその結果を取り込む場合は、列の選択順に従ってデータが取り込まれます。データベース表から最初に選択された列のデータはイテレータの最初の列に、2番目に選択された列のデータはイテレータの2番目の列に、順を追って配置されます。表の列のデータ型は、イテレータ列のデータ型に変換可能である必要がありますが、イテレータ列には名前がないため、データベース列はどのような名前でもかまいません。
EMPSAL
表では、次のように位置イテレータを宣言できます。
#sql iterator SalPosIter (int, String, float);
この位置イテレータは、次のように使用できます。
class MyClass {
void func() throws SQLException {
...
SalPosIter piter;
#sql piter = { SELECT empno, ename, raise FROM empsal };
... process piter ...
}
}
SELECT
文でのデータ項目の順序は、イテレータでの順序と同じです。名前付きイテレータと位置イテレータとでは処理が異なります。
イテレータの注意事項
前のコンセプトで述べたことの他に、イテレータ全般に関しては次のような注意事項に留意してください。
イテレータにデータを移入する際にSELECT *
構文を使用することはお薦めしません。位置イテレータにこの構文を使用する場合、表の列数とイテレータの列数が一致し、データ型も一致する必要があります。また、名前付きイテレータにこの構文を使用する場合は、表の列数をイテレータの列数と同じかそれより多くなるようにし、各イテレータ列の名前とデータ型をデータベース表と同じにする必要があります。ただし、トランスレータの-warn=nostrict
フラグが設定されていないと、表の列数がイテレータの列数より多い場合には、警告が生成されます。
位置イテレータと名前付きイテレータは、互換性のない別々のJavaクラスです。ある種類のイテレータ・オブジェクトから別の種類のイテレータ・オブジェクトへのキャストはできません。
SQLカーソルとは異なり、イテレータ・インスタンスはファーストクラスのJavaオブジェクトです。そのため、イテレータ・インスタンスは、たとえばメソッド・パラメータとして受け渡すことができます。また、イテレータ・インスタンスは、public
やprivate
などのJavaクラス修飾子を使用して宣言することもできます。
SQLJでは、SQLJイテレータとJDBC結果セットとの連係性および変換がサポートされています。
一般に、イテレータを設定したSELECT
文が実行された時点でのデータベースの状態のみによって、そのイテレータの内容が決まります。それ以降にUPDATE
、INSERT
、DELETE
、COMMIT
、ROLLBACK
などの操作を行っても、イテレータやその内容には反映されません。これに対する例外は、イテレータがスクロール可能で、データの変更に対してsensitiveであると宣言する場合です。
イテレータ・クラスを宣言しない場合は、Oracle SQLJ実装で弱い型指定のイテレータを使用できます。このようなイテレータを結果セット・イテレータと呼びます。簡単な(スクロール不可能)結果セット・イテレータを使用するには、sqlj.runtime.ResultSetIterator
クラスをインスタンス化します。スクロール可能な結果セット・イテレータを使用するには、sqlj.runtime.ScrollableResultSetIterator
クラスをインスタンス化します。
結果セット・イテレータを使用する場合のデメリットは、強い型指定のイテレータと比較すると、SQLJでは問合せへのセマンティクス・チェックを同じように実行できないことです。
SQLJ名前付きイテレータまたは位置イテレータを使用する場合は、次の手順に従ってください。
SQLJ宣言を使用して、イテレータ・クラス、つまりイテレータの種類を定義します。
イテレータ・クラスの変数を宣言します。
SELECT
文を使用した後、イテレータ変数にSQL問合せ結果を取り込みます。
イテレータの問合せ列にアクセスします。名前付きイテレータと位置イテレータではアクセス方法が異なります。
問合せ結果の処理が終了した後、イテレータを閉じてそのリソースを解放します。
名前付きイテレータと位置イテレータには、それぞれのメリットがあり、使用目的も異なります。
名前付きイテレータは、より柔軟に使用できます。選択されたデータを名前付きイテレータに取り込むときは、SELECT
文内の列とイテレータ列との整合チェックは名前で判定されるため、問合せの順序は判定基準にはなりません。この場合、データが誤った列に取り込まれることがないため、エラーが少なくなります。名前が一致しない場合は、データベースに対してSQL文をチェックするときに、SQLJトランスレータがエラーを生成します。
位置イテレータの場合は、他の埋込みSQL言語と同様のパラダイムや構文を使用できます。たとえば、名前付きイテレータではデータの取込みにnext()
メソッドを使用しますが、位置イテレータではPro*Cの場合と似たFETCH INTO
構文を使用します。各フェッチが暗黙的にイテレータを次の行へ進め、その後で次の値が取り出されます。
また、位置イテレータの場合は、イテレータ列に選択するデータを名前ではなく位置によって識別するため、柔軟性に欠けます。SELECT
文では、項目の順序に注意する必要があるからです。また、イテレータのすべての列にデータを選択する必要もあります。このとき、表で選択されている列のデータ型に偶然一致した他のイテレータ列に、データが誤って書き込まれる可能性もあります。
位置イテレータでは、個々のデータ要素へのアクセスも不便です。名前付きイテレータではデータを名前に基づいて格納するため、各列に対して便利なアクセッサ・メソッドを使用できます。たとえば、ename
イテレータ列からのデータの取出しには、ename()
メソッドを使用できます。位置イテレータでは、FETCH INTO
文でデータを直接Javaホスト式にフェッチする必要があるうえ、ホスト式を正しい順序で記述することが必要です。
問合せに対して強い型指定のイテレータ・クラスを宣言しない場合は、弱い型指定の結果セット・イテレータを使用できます。結果セット・イテレータは、JDBCコードからSQLJコードへの変換時に最も便利です。この考慮事項は、結果セット・イテレータ(ResultSetIterator
インスタンスまたはScrollableResultSetIterator
インスタンス)では、変換時に完全なSQLJセマンティクス・チェックができないという点と比較する必要があります。名前付きイテレータまたは位置イテレータの場合、SQLJによって、SELECT
文内の列のデータ型が、生成されたデータのJava型と一致していることが検証されます。結果セット・イテレータの場合は、このことは検証できません。
イテレータの比較についての注意
SQLJイテレータについて、次の点を注意してください。
位置イテレータに移入する際、データベースから選択する列の数は、イテレータの列の数と同じである必要があります。名前付きイテレータへの移入では、データベースから選択する列数が、イテレータの列数より少なくなることはありませんが、トランスレータ-warn=nostrict
フラグを設定すると、イテレータの列数より多くなる可能性があります。この場合、一致しない列は無視されます。
フェッチの一般的な意味は、データベースからのデータのフェッチのことですが、位置イテレータに対してFETCH INTO
文を実行した場合は、サーバーへのラウンドトリップが必要であるとは限りません。これは、行プリフェッチの値によって異なります。これは、データベースではなくイテレータからデータをフェッチするためです。行プリフェッチの値が1
であるときは、各フェッチのたびにデータベースへのトリップが1回行われます。データベースへの1回のトリップで取り出される行の数は、行プリフェッチの値によって判断されます。
結果セット・イテレータでは、位置イテレータと同じFETCH INTO
構文が使用され、実行時に同じ制限を受けます。つまり、SELECT
リスト内のデータ項目の数とFETCH
文の代入されたデータである変数の数が一致する必要があります。
名前付きイテレータ・クラスを宣言する場合は、イテレータの各列のデータ型と名前を宣言します。データを名前付きイテレータに取り出す場合、SELECT
文内の列とイテレータ列とを次の2つの面で同じにしておく必要があります。
SELECT
文内の各データ項目の名前(表の列名または別名)が、イテレータ列と同じ名前である必要があります。ただし、大/小文字の区別はありません。そのため、ename
およびEname
のいずれもENAME
と一致します。
各イテレータ列のデータ型は、標準のJDBC型マッピングに従って、SELECT
文内の対応するデータ項目のデータ型との互換性を持つ必要があります。
名前付きイテレータ・クラスの宣言では、属性の宣言はどのような順序で行ってもかまいません。データは、名前に基づいてイテレータに選択されます。
名前付きイテレータの場合は、next()
メソッドでデータを行ごとに取り出し、それぞれの列のアクセッサ・メソッドで個々のデータ項目を取り込みます。アクセッサ・メソッド名は列名と同じです。ほとんどのJavaのアクセッサ・メソッド名とは異なり、名前付きイテレータの場合、アクセッサ・メソッド名がget
で始まることはありません。たとえば、sal
列を持つ名前付きのイテレータ・オブジェクトの場合は、使用するアクセッサ・メソッドがsal()
という名前になります。
注意: 名前付きイテレータの列のネーミングには、次の制約事項が適用されます。
|
名前付きイテレータ・クラスの宣言には、次の構文を使用します。
#sql <modifiers> iterator classname <implements clause> <with clause> ( type-name-list );
この構文のmodifiers
は正当なJavaクラス修飾子の順序で、その使用は任意です。classname
はイテレータのクラス名です。また、type-name-list
は、Javaの型と名前のリストです。これは、データベース表の列型と列名に相当します。
implements
句とwith
句の使用は任意です。前者は実装するインタフェースを、後者は定義および初期化する変数を指定します。
次のような表について考えてみます。
CREATE TABLE PROJECTS ( ID NUMBER(4), PROJNAME VARCHAR(30), START_DATE DATE, DURATION NUMBER(3) );
この表で使用する名前付きイテレータは、次のように宣言できます。
#sql public iterator ProjIter (String projname, int id, Date deadline);
この結果、アクセッサ・メソッドprojname()
、id()
およびdeadline()
でアクセス可能なデータ列を持つ、イテレータ・クラスが生成されます。
注意: 標準Javaの場合と同様に、Publicクラスは、次のいずれかの方法で宣言する必要があります。
Sun社のJDKの標準 |
前の項で定義したPROJECTS
表とProjIter
イテレータを引き続き使用します。表にはイテレータのid
列とprojname
列と同じ名前とデータ型の列があります。ただし、別名を使用して、イテレータのdeadline
列への移入操作を実行する必要があります。次に例を示します。
ProjIter projsIter; #sql projsIter = { SELECT start_date + duration AS deadline, projname, id FROM projects WHERE start_date + duration >= sysdate };
この例では、開始日に期間を加算してプロジェクトの最終期限を算出し、deadline
イテレータ列と同じdeadline
という名前をその結果に別名として付けます。また、WHERE
句を使用して、現行のシステムの日付より先の最終期限のみが処理されるようにしています。
同様に、ファンクション・コールを使用する場合も、別名を作成する必要があります。MAXIMUM()
というファンクションがあると仮定し、このファンクションでは入力としてDURATION
エントリおよび整数をとり、この2つのうち大きい方を戻すとします。たとえば、使用しているアプリケーションで各プロジェクトの期間が3か月以上になるようにするには、値3
を入力します。
ここでは、イテレータを次のように宣言したとします。
#sql public iterator ProjIter2 (String projname, int id, float duration);
問合せに使用するMAXIMUM()
ファンクションには、次のように問合せ結果に対して別名を指定できます。
ProjIter2 projsIter2; #sql projsIter2 = { SELECT id, projname, maximum(duration, 3) AS duration FROM projects };
通常、正当なJava識別子ではない名前、またはイテレータの列名とは異なる名前の付いたSELECT
文内のデータ項目の問合せには、別名を使用してください。
前述のように、名前付きイテレータを使用した場合、通常はデータベースから選択できる列数が、イテレータの列数より少なくなることはありません。一致しない列は無視されるため、イテレータの列数を上回る数の列を選択することも不可能ではありません。ただし、SQLJの-warn=nostrict
オプションが設定されていないと、警告が生成されます。
名前付きイテレータ・オブジェクトのnext()
メソッドを使用すると、選択されて入力されたデータを1行ずつ取得できます。各行の各列にアクセスするには、SQLJで生成されるアクセッサ・メソッドを使用します(通常は、while
ループ内で使用します)。
next()
がコールされると、次の処理が行われます。
イテレータから取り出す行がまだ他にも存在するときは、next()
のコールでその行を取り出し、true
を戻り値とします。
イテレータから取り出す行がもう存在しないときは、next()
のコールでfalse
を戻り値とします。
次に、名前付きイテレータのデータにアクセスする方法の例を示します。この例では、前の項に示した宣言、インスタンス化および移入を再び使用します。
注意: イテレータからのデータの取出しが終了した後に、各イテレータのclose() メソッドを必ずコールしてください。このコールは、イテレータを閉じてリソースを解放するために必要です。 |
次に示すようにイテレータ・クラスを宣言するとします。
#sql public iterator ProjIter (String projname, int id, Date deadline);
次に示すように、このイテレータ・クラスのインスタンスを定義してからアクセスします。
// Declare the iterator variable ProjIter projsIter; // Instantiate and populate iterator; order of SELECT doesn't matter #sql projsIter = { SELECT start_date + duration AS deadline, projname, id FROM projects WHERE start_date + duration >= sysdate }; // Process the results while (projsIter.next()) { System.out.println("Project name is " + projsIter.projname()); System.out.println("Project ID is " + projsIter.id()); System.out.println("Project deadline is " + projsIter.deadline()); } // Close the iterator projsIter.close(); ...
前述に示したデータの取出しでは、projname()
、id()
およびdeadline()
アクセッサ・メソッドが簡単に使用できます。SELECT
項目の順序やアクセッサ・メソッドを使用する順序は、あまり関係ありません。
ただし、アクセッサ・メソッド名は、イテレータ・クラスの宣言で大文字を使用した場合は大文字で、小文字を使用した場合は小文字で生成されます。コンパイル・エラーが発生する例を、次に示します。
次のイテレータを宣言するとします。
#sql iterator Cursor1 (String NAME);
イテレータを使用するためのコードは次のようになります。
... Cursor1 c1; #sql c1 = { SELECT NAME FROM TABLE }; while (c1.next()) { System.out.println("The name is " + c1.name()); } ...
Cursor1
クラスのメソッドはNAME()
であり、name()
ではありません。System.out.println
文では、c1.NAME()
を使用する必要があります。
位置イテレータの宣言では、各列のデータ型は宣言しますが、列名は定義しません。SQL問合せ結果の列が取り出されるJava型は、SQLデータのデータ型との互換性があることが必要です。データベースの列やSELECT
文内のデータ項目の名前は、関係がありません。名前が使用されないため、位置イテレータのJava型を宣言する順序と、データを選択する順序が完全に一致する必要があります。
位置イテレータにデータが選択された後、位置イテレータからデータを取り出すには、FETCH INTO
文を使用します。さらに、endFetch()
メソッドをコールして、データの終わりに到達したかどうかを確認します。
位置イテレータ・クラスの宣言には、次の構文を使用します。
#sql <modifiers> iterator classname <implements clause> <with clause> ( position-list );
この構文のmodifiers
は正当なJavaクラス修飾子の順序で、その使用は任意です。また、position-list
は、データベース表の列型に変換可能なJava型のリストです。
implements
句とwith
句の使用は任意です。前者は実装するインタフェースを、後者は定義および初期化する変数を指定します。
ここでは、次の行を持つ従業員表EMP
を想定します。
CREATE TABLE EMP ( EMPNO NUMBER(4), ENAME VARCHAR2(10), SAL NUMBER(7,2) );
次のような位置イテレータを宣言します。
#sql public iterator EmpIter (String, int, float);
前述の例では、JavaクラスEmpIter
の定義に、匿名のString
、int
およびfloat
が使用されています。実際には、String
型がENAME
に対応し、int
型がEMPNO
に対応しますが、この表とイテレータとでは、それぞれの列の順序が対応していないことに注意してください。イテレータ列の順序によって、データを選択する順序が決まります。
注意: 標準Javaの場合と同様に、Publicクラスは、次のいずれかの方法で宣言する必要があります。
Sun社のJDKの標準 |
位置イテレータのインスタンス化と移入は、名前付きイテレータの場合と同じです。ただし、SELECT
文内のデータ項目の順序が適切であるかどうかを確認する必要があります。
EmpIter
イテレータ・クラスの3つのデータ型は、EMP
表の型と互換性がありますが、それぞれの順序は対応していないため、データの選択方法には注意が必要です。次の例は、SELECT
文内のデータ項目がイテレータの列と同じ順序であるため、正常に実行できます。
EmpIter empsIter; #sql empsIter = { SELECT ename, empno, sal FROM emp };
前述のように、位置イテレータを使用する場合は、データベースから選択する列の数と、イテレータの列の数を同じにする必要があります。
位置イテレータで定義された列にアクセスするには、SQLのFETCH INTO
構文を使用します。コマンドのINTO
部分には、結果列を受け取るJavaホスト変数を指定します。ホスト変数の順序は、対応するイテレータ列の順序と同じにする必要があります。endFetch()
メソッドを使用して、最後のフェッチがデータの末尾に達したかどうかを確認します(このメソッドは、すべての位置イテレータ・クラスで使用できます)。
注意:
|
次に例では、前の項に示した宣言、インスタンス化および移入を再度示します。SELECT
文中のJavaホスト変数の順序は、位置イテレータの列の順序と同じです。これは必須条件です。
最初に、次のようにイテレータ・クラスを宣言するとします。
#sql public iterator EmpIter (String, int, float);
次に示すように、このイテレータ・クラスのインスタンスを定義してからアクセスします。
// Declare and initialize host variables int empnum=0; String empname=null; float salary=0.0f; // Declare an iterator instance EmpIter empsIter; #sql empsIter = { SELECT ename, empno, sal FROM emp }; while (true) { #sql { FETCH :empsIter INTO :empnum, :empname, :salary }; if (empsIter.endFetch()) break; // This test must be AFTER fetch, // but before results are processed. System.out.println("Name is " + empname); System.out.println("Employee number is " + empnum); System.out.println("Salary is " + salary); } // Close the iterator empsIter.close(); ...
empname
、empnum
およびsalary
変数は、Javaホスト変数です。これらの変数の型は、イテレータ列の型と対応させる必要があります。
位置イテレータでは、next()
メソッドを使用しないでください。このメソッドは、FETCH
操作が次の行に進むときに暗黙的にコールされます。
注意: FETCH INTO 文のホスト変数は、条件文の1つのブランチで代入されるため、必ず初期化する必要があります。初期化しないと、ホスト変数の代入が行われないことを通知するコンパイル・エラーが表示されます。フェッチ対象の行がある場合にのみ、FETCH で変数が代入されます。 |
前の項で説明した位置イテレータFETCH
句では、ホスト変数(ある場合)に移入する前に、暗黙的なnext()
コールによって移動が実行されています。この方法のかわりに、Oracle SQLJ実装では、JDBC結果セットおよびSQLJ名前付きイテレータと同じ移動ロジックを使用するために、特別なFETCH
構文を明示的なnext()
コールと併用する方法がサポートされています。この特別なFETCH
構文を使用すると、セマンティクスが変化します。INTO
リストに移入される前の暗黙的なnext()
コールがなくなります。
SQLJでは、ホスト変数としてのSQLJイテレータおよびJDBC結果セットの使用がサポートされています。イテレータと結果セットの使用方法は基本的には同じですが、宣言やデータを取り出すアクセッサ・メソッドが異なります。
注意:
|
ここでの例は、次の部門表および従業員表を想定します。
CREATE TABLE DEPT ( DEPTNO NUMBER(2), DNAME VARCHAR2(14) ); CREATE TABLE EMP ( EMPNO NUMBER(4), ENAME VARCHAR2(10), SAL NUMBER(7,2), DEPTNO NUMBER(2) );
OUTホスト変数としての結果セットの使用の例
この例では、JDBC結果セットを出力ホスト変数として使用しています。
... ResultSet rs; ... #sql { BEGIN OPEN :OUT rs FOR SELECT ename, empno FROM emp; END }; while (rs.next()) { String empname = rs.getString(1); int empnum = rs.getInt(2); } rs.close(); ...
この例では、結果セットrs
をPL/SQLブロックでオープンしてSELECT
文からのデータを受け取り、EMP
表のENAME
列とEMPNO
列のデータを選択し、結果セットをループしてデータをローカル変数に取り出します。
OUTホスト変数としてのイテレータの使用の例
この例では、名前付きイテレータを出力ホスト変数として使用しています。
イテレータは、次のように宣言できます。
#sql public <static> iterator EmpIter (String ename, int empno);
public
修飾子は必須です。クラスレベルまたはネストされたクラスレベルでの宣言ではstatic
修飾子も使用することをお薦めします。
このイテレータは、次のように使用できます。
...
EmpIter iter;
...
#sql { BEGIN
OPEN :OUT iter FOR SELECT ename, empno FROM emp;
END };
while (iter.next())
{
String empname = iter.ename();
int empnum = iter.empno();
...process/output empname and empnum...
}
iter.close();
...
この例では、イテレータiter
をPL/SQLブロックでオープンしてSELECT
文からのデータを受け取り、EMP
表のENAME
列とEMPNO
列のデータを選択し、イテレータをループさせてデータをローカル変数に取り出します。
SELECT INTOのOUTホスト変数としてのイテレータの使用の例
この例では、名前付きイテレータを出力ホスト変数として使用し、SELECT INTO
文を介してデータを取得しています。INTO
リスト内のホスト変数のデフォルトは、OUT
です。
イテレータは、次のように宣言できます。
#sql public <static> iterator ENameIter (String ename);
public
修飾子は必須です。クラスレベルまたはネストされたクラスレベルでの宣言ではstatic
修飾子も使用することをお薦めします。
このイテレータは、次のように使用できます。
... ENameIter enamesIter; String deptname; ... #sql { SELECT dname, cursor (SELECT ename FROM emp WHERE deptno = dept.deptno) INTO :deptname, :enamesIter FROM dept WHERE deptno = 20 }; System.out.println(deptname); while (enamesIter.next()) { System.out.println(enamesIter.ename()); } enamesIter.close(); ...
この例では、ネストされたSELECT
文を使用して次の処理を行います。
DEPT
表から従業員番号20の名前を選択し、出力ホスト変数deptname
に取り込みます。
EMP
表を問い合せ、部門番号が20の従業員をすべて選択した後で、カーソルに格納された結果を、出力ホスト変数enamesIter
(名前付きイテレータ)に取り込みます。
部門名を出力します。
従業員名を出力する名前付きイテレータを、ループさせます。こうして、この部門の全従業員の名前が出力されます。
大抵の場合、外部のSELECT
内にある1行を取り出すときはSELECT INTO
を使用した方が、ネストされたイテレータよりも便利です。また、ネストされたイテレータを使用した場合は、データを処理して外部のSELECT
中にある行数を求める必要があります。これに対し、SELECT INTO
を使用すると、1行のみで済みます。
Oracle SQLJ実装では、イテレータ宣言で、ResultSet
型の列、または現在のスコープで宣言されたその他のイテレータ型の列を指定することが可能です。つまり、イテレータ内で他のイテレータや結果セットを使用できます。これらの列型は、カーソルで列を取得するのに使用されます。この機能は、ネストした表の情報を戻り値とするネストされたSELECT
文に便利です。
次に、機能的には同じ例をいくつか示します。これらの例では、ネストされた結果セットまたはイテレータ(他のイテレータ内の列にある結果セットまたはイテレータ)を使用して、DEPT
表から各部門に所属する従業員をすべて出力します。最初の例では名前付きイテレータ内の結果セットを、2番目の例では名前付きイテレータ内の名前付きイテレータを、3番目の例では位置イテレータ内の名前付きイテレータを使用します。
手順を次に示します。
DEPT
表で各部門名(DNAME
)を問い合せます。
ネストされたSELECT
を実行し、各部門のEMP
表から取得した全従業員数をカーソルに取り込みます。
取得した部門名と部門別従業員数を、名前列とイテレータ列を持つ外部イテレータ(iter
)に取り込みます。特定部門の従業員情報を持つカーソルは、外部イテレータの当該部門の行に対応するイテレータ列に移動します。
ネステッド・ループを巡回することによって、部門別に部門名を出力し、その後、内部イテレータ内で部門別の全従業員の名前を出力します。
名前付きイテレータ内の結果セット列の例
この例では、名前付きイテレータでResultSet
型の列を使用します。
イテレータは、次のように宣言できます。
#sql iterator DeptIter (String dname, ResultSet emps);
イテレータを使用するコードは次のようになります。
... DeptIter iter; ... #sql iter = { SELECT dname, cursor (SELECT ename FROM emp WHERE deptno = dept.deptno) AS emps FROM dept }; while (iter.next()) { System.out.println(iter.dname()); ResultSet enamesRs = iter.emps(); while (enamesRs.next()) { String empname = enamesRs.getString(1); System.out.println(empname); } enamesRs.close(); } iter.close(); ...
名前付きイテレータ内の名前付きイテレータ列の例
この例では、前に定義された名前付きイテレータ(ネストされたイテレータ)と同じ型の列を持つ、名前付きイテレータを使用します。
イテレータの宣言は、次のようになります。
#sql iterator ENameIter (String ename); #sql iterator DeptIter (String dname, ENameIter emps);
このイテレータを使用するコードは次のようになります。
... DeptIter iter; ... #sql iter = { SELECT dname, cursor (SELECT ename FROM emp WHERE deptno = dept.deptno) AS emps FROM dept }; while (iter.next()) { System.out.println(iter.dname()); ENameIter enamesIter = iter.emps(); while (enamesIter.next()) { System.out.println(enamesIter.ename()); } enamesIter.close(); } iter.close(); ...
位置イテレータ内の名前付きイテレータ列の例
この例では、前に定義された名前付きイテレータ(ネストされたイテレータ)と同じ型の列を持つ、位置イテレータを使用します。ここでは、位置イテレータのFETCH INTO
構文を使用します。この例は、機能的には前述の2つの例と同じです。
外部イテレータは位置イテレータです。このため、外部指定イテレータが名前付きイテレータである前の例とは異なり、列名を一致させる場合に別名は必要ありません。
イテレータの宣言は、次のようになります。
#sql iterator ENameIter (String ename); #sql iterator DeptIter (String, ENameIter);
このイテレータを使用するコードは次のようになります。
... DeptIter iter; ... #sql iter = { SELECT dname, cursor (SELECT ename FROM emp WHERE deptno = dept.deptno) FROM dept }; while (true) { String dname = null; ENameIter enamesIter = null; #sql { FETCH :iter INTO :dname, :enamesIter }; if (iter.endFetch()) break; System.out.println(dname); while (enamesIter.next()) { System.out.println(enamesIter.ename()); } enamesIter.close(); } iter.close(); ...
SQLJでは、SQL操作内のJavaホスト式に値を代入できます。これは代入文と呼ばれ、次の構文で記述されます。
#sql { SET :host_exp = expression };
host_exp
は、変数や配列索引などのターゲット・ホスト式です。expression
には、数字、ホスト式、算術式、ファンクション・コール、または有効な結果を生成してターゲット・ホスト式に入力するその他のコンストラクトを使用できます。
代入文のターゲット・ホスト式のデフォルトはOUT
ですが、これを明示的に記述することも可能です。
#sql { SET :OUT host_exp = expression };
代入文でIN
またはINOUT
トークンを使用すると、変換時にエラーが発生します。
前述の2つの文は、機能的には次のPL/SQLコードと同じです。
#sql { BEGIN :OUT host_exp := expression; END };
次に、代入文の単純な例を示します。
#sql { SET :x = foo1() + foo2() };
この文では、foo1()
とfoo2()
の戻り値の合計をx
に代入し、x
の型が、ファンクションの出力合計の型と互換性がある場合を想定しています。
次の例についても考えてみます。
int i2; java.sql.Date dat; ... #sql { SET :i2 = TO_NUMBER(substr('750 etc.', 1, 3)) + TO_NUMBER(substr('250 etc.', 1, 3)) }; ... #sql { SET :dat = sysdate }; ...
最初の文では、値1000
をi2
に代入します。substr()
コールでは、文字列の最初の3文字(750と250)を使用します。TO_NUMBER()
コールでは、文字列が数値750と250に変換されます。
2番目の文では、データベース・システムの日付が読み取られ、それがdat
に代入されます。
データベースに格納されているファンクションから戻される変数に対して操作を実行する場合、代入文を使用すると便利です。単にファンクションの結果を変数に代入する場合は、標準のファンクション・コールの構文で実現できるため、代入文は必要ありません。Javaファンクションの出力を操作する場合も、一般的なJava文で実現できるため、代入文は必要ありません。したがって、foo1()
およびfoo2()
は、Javaファンクションではなく、データベース内のストアド・ファンクションと仮定することもできます。
SQLJには、データベース内のストアド・プロシージャやストアド・ファンクションのコールに便利な構文があります。ストアド・プロシージャやストアド・ファンクションは、Java、PL/SQLまたはデータベースでサポートされるその他の言語のいずれでも記述できます。
ストアド・ファンクションの場合、戻り値を受け取るために、SQLJ実行文内に結果式が必要です。また、必要に応じて入力パラメータ、出力パラメータおよび入出力パラメータも使用できます。
ストアド・プロシージャの場合、戻り値はありません。オプションで、入力パラメータ、出力パラメータおよび入出力パラメータを使用できます。ストアド・プロシージャでは、任意の出力または入出力パラメータを介して、出力が戻されます。
注意: ここで説明するプロシージャ・コールやファンクション・コールの構文を使用するかわりに、JPublisherでPL/SQLストアド・プロシージャおよびストアド・ファンクション用のJavaラッパーを作成し、他のJavaメソッドの場合と同様に、Javaラッパーをコールすることも可能です。JPublisherの詳細は、「JPublisherとカスタムJavaクラスの作成」で説明します。詳細は、『Oracle Database JPublisherユーザーズ・ガイド』を参照してください。 |
ここでは、次の項目について説明します。
ストアド・プロシージャには戻り値がありませんが、入力パラメータ、出力パラメータおよび入出力パラメータとしてリストを使用できます。ストアド・プロシージャのコールでは、CALL
トークンを使用します。CALL
トークンのすぐ後に空白を1つ挿入した後に、プロシージャ名を記述します。プロシージャ名と区別するため、CALL
トークンの後には空白が必要です。プロシージャ・コールをカッコで囲むことはできません。これは、ファンクション・コールの構文とは異なります。CALL
トークンの構文は次のとおりです。
#sql { CALL PROC(<PARAM_LIST>) };
PROC
はストアド・プロシージャ名で、入力パラメータ、出力パラメータおよび入出力パラメータを使用できます。PROC
には、スキーマ名またはパッケージ名を含めることも可能で、たとえば、SCOTT.MYPROC()
となります。
次のPL/SQLストアド・プロシージャを定義するとします。
CREATE OR REPLACE PROCEDURE MAX_DEADLINE (deadline OUT DATE) IS BEGIN SELECT MAX(start_date + duration) INTO deadline FROM projects; END;
この例では、PROJECTS
表を読み込み、START_DATE
列とDURATION
列を検索し、各行のstart_date + duration
を計算した後で、START_DATE + DURATION
の合計の最大値がDEADLINE
に割り当てられます。これはDATE
型の出力パラメータです。
SQLJでは、このMAX_DEADLINE
プロシージャを次のようにコールできます。
java.sql.Date maxDeadline; ... #sql { CALL MAX_DEADLINE(:out maxDeadline) };
どのパラメータに対しても、ホスト式のトークンIN
、OUT
およびINOUT
を使用して、ストアド・プロシージャの入力先、出力先および入出力先のパラメータと対応させてください。また、パラメータ・リストで使用するホスト変数の型も、ストアド・プロシージャのパラメータ型に対応させる必要があります。
注意: アプリケーションにOracle7データベースとの互換性を確保する場合は、プロシージャがパラメータをとらないときに、パラメータ・リストに空のカッコを含めないでください。次に例を示します。
次のようには定義できません。
|
ストアド・ファンクションには戻り値があり、入力パラメータ、出力パラメータおよび入出力パラメータのリストを指定することもできます。ストアド・ファンクションのコールでは、VALUES
トークンを使用します。VALUES
トークンの後にファンクション・コールを記述します。標準のSQLJでは、ファンクション・コールをカッコで囲むことが必要です。Oracle SQLJ実装の場合、カッコの使用は任意です。カッコで囲む場合は、VALUES
トークンと左カッコの間に空白があってもかまいません。VALUES
トークンの構文は次のとおりです。
#sql result = { VALUES(FUNC(PARAM_LIST)) };
この構文でresult
は結果式で、ファンクション戻り値を使用します。FUNC
はストアド・ファンクション名で、必要に応じて入力パラメータ、出力パラメータおよび入出力パラメータの並びを取ることが可能です。FUNC
には、スキーマ名またはパッケージ名も含めることも可能で、たとえば、SCOTT.MYFUNC()
となります。
注意: Oracle SQL実装でサポートされているINSERT INTO table VALUES 構文でもVALUES トークンを使用できますが、意味的にも構文的にも関連がありません。 |
次に、「ストアド・プロシージャのコール」の例を再び使用します。ここでは、ストアド・プロシージャのかわりにストアド・ファンクションを定義します。
CREATE OR REPLACE FUNCTION GET_MAX_DEADLINE RETURN DATE IS deadline DATE; BEGIN SELECT MAX(start_date + duration) INTO deadline FROM projects; RETURN deadline; END;
SQLJでは、このGET_MAX_DEADLINE
ファンクションを次のようにコールできます。
java.sql.Date maxDeadline; ... #sql maxDeadline = { VALUES(GET_MAX_DEADLINE) };
結果式の型は、ファンクションの戻り値の型と同じであることが必要です。
Oracle SQLJ実装では、次のような構文を使用することも可能です。
#sql maxDeadline = { VALUES GET_MAX_DEADLINE };
カッコが省略されていることに注意してください。
ストアド・ファンクションのコールでは、ストアド・プロシージャの場合と同様に、ホスト式のトークンIN
、OUT
およびINOUT
を使用して、ストアド・ファンクションの入力先、出力先および入出力先のパラメータと対応させる必要があります。また、パラメータ・リストで使用するホスト変数の型を、ストアド・プロシージャのパラメータ型に対応させる必要もあります。
注意: ストアド・ファンクションをOracle以外の環境にも移植可能にするには、コールで入力パラメータのみを使用し、出力または入出力パラメータは使用しないでください。 |
SQLJでは、ファンクションの戻り値がREF CURSOR型の場合は、ストアド・ファンクションの戻り値をイテレータまたは結果セット変数に代入できます。
次の例では、イテレータを使用してストアド・ファンクションの戻り値を取得します。結果セットを使用する場合も同じです。
ストアド・ファンクションの戻り値としてのイテレータの例
この例では、ストアド・ファンクションの戻り型として、イテレータを使用します。この処理ではREF CURSOR
型を使用します。
ファンクションが次のように定義されているとします。
CREATE OR REPLACE PACKAGE sqlj_refcursor AS TYPE EMP_CURTYPE IS REF CURSOR; FUNCTION job_listing (j varchar2) RETURN EMP_CURTYPE; END sqlj_refcursor; CREATE OR REPLACE PACKAGE BODY sqlj_refcursor AS FUNCTION job_listing (j varchar) RETURN EMP_CURTYPE IS DECLARE rc EMP_CURTYPE; BEGIN OPEN rc FOR SELECT ename, empno FROM emp WHERE job = j; RETURN rc; END; END sqlj_refcursor;
次のようにイテレータを宣言します。
#sql public <static> iterator EmpIter (String ename, int empno);
public
修飾子は必須です。クラスレベルまたはネストされたクラスレベルでの宣言ではstatic
修飾子も使用することをお薦めします。
イテレータおよびファンクションを使用するコードは次のようになります。
EmpIter iter;
...
#sql iter = { VALUES(sqlj_refcursor.job_listing('SALES')) };
while (iter.next())
{
String empname = iter.ename();
int empnum = iter.empno();
... process empname and empnum ...
}
iter.close();
...
この例では、job_listing()
ファンクションのコールにより、役職がSALESであるすべての従業員の名前と従業員番号を含むイテレータが戻されます。その後、イテレータからこのデータが取り出されます。