5 基本的な言語機能
SQLJ文は常に#sqlトークンで開始し、主に次の2つのカテゴリに分割できます。
-
宣言: イテレータまたは接続コンテキストのJavaクラスを作成します。イテレータは、Java Database Connectivity (JDBC)の結果セットのようなもので、接続コンテキストは、使用されるSQLエントリに応じて強い型指定の接続を作成するために使用するものです。
-
実行文: 埋込みのSQL操作の実行に使用します。
この章では、以下のトピックについて説明します。
SQLJ宣言の概要
SQLJ宣言は#sqlトークンで開始され、その後にクラスの宣言が続きます。SQLJ宣言により、アプリケーションに専用Java型が導入されます。現行では、SQLJ宣言にはイテレータ宣言と接続コンテキスト宣言の2種類があり、次のようにJavaクラスを定義します。
-
イテレータ宣言はイテレータ・クラスを定義します。イテレータは、概念的にJDBC結果セットと似ていて、複数行の問合せデータの受取りに使用されます。イテレータは、イテレータ・クラスのインスタンスとして実装されます。
-
接続コンテキスト宣言は、接続コンテキスト・クラスを定義します。通常、各接続コンテキスト・クラスは、操作の際に特定のSQLエンティティ(表、ビュー、ストアド・プロシージャなど)を使用する接続に使用されます。すなわち、名前と特性が同じSQLエンティティを持つスキーマへの接続には、特定の接続コンテキスト・クラスのインスタンスが使用されています。SQLJでは、各データベース接続が、接続コンテキスト・クラスのインスタンスとして実装されます。
SQLJには、事前定義された
sqlj.runtime.DefaultContext接続コンテキスト・クラスがあります。必要な接続コンテキスト・クラスが1つのみである場合は、接続コンテキスト宣言の必要がないDefaultContextを使用できます。
イテレータ宣言や接続コンテキスト宣言には、必要に応じて次の句を挿入できます。
-
implements句: 生成されたクラスによって実装されるインタフェースを1つ以上指定します。 -
with句: 生成されたクラスに含める初期化済定数を1つ以上指定します。
この項の内容は次のとおりです。
SQLJ宣言の規則
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クラスは、次のいずれかの方法で宣言する必要があります。
-
別個のソース・ファイル中に宣言する方法。ファイルのベース名はクラス名と同じにします。
-
クラスレベルの有効範囲またはネストされたクラスレベルの有効範囲に宣言する方法。この方法の場合、
public static修飾子を使用することをお薦めします。
Sun社のJDKの標準javacコンパイラを使用する場合は、これが要件となります。
イテレータ宣言
イテレータ宣言では、問合せデータの受取り用に、イテレータの一種を定義するクラスを作成します。この宣言でイテレータ・インスタンスの列型を指定しますが、この列型はデータベース表から選択され列型と同じである必要があります。
基本的なイテレータ宣言では、次の構文が使用されます。
#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の複数のインスタンスを使用して、様々なスキーマに接続できますが、それぞれのスキーマに、たとえばEMPLOYEES表、DEPARTMENTS表およびSECURE_EMPLOYEESストアド・プロシージャが含まれることが前提になります。
宣言済の接続コンテキスト・クラスは高度なトピックであり、1つの相互に関係するSQLエンティティ・セットのみを使用する基本的なSQLJアプリケーションについては、これを使用する必要はありません。また、基本的には、sqlj.runtime.ref.DefaultContextクラスのインスタンスをいくつか作成することで複数の接続を使用できるため、接続コンテキストを宣言する必要はありません。
関連項目:
IMPLEMENTS句宣言
イテレータ・クラスや接続コンテキスト・クラスを宣言する場合、生成されたクラスが実装するインタフェースを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句宣言
接続コンテキスト・クラスやイテレータ・クラスの宣言時には、with句を使用して、生成されたクラスの定義に含める定数を1つ以上指定し初期化できます。Oracle実装ではいくつかの拡張機能をイテレータ宣言に追加していますが、この使用例の大部分は標準です。
この項の内容は次のとおりです。
標準WITH句の使用例
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 12c リリース2 (12.2)またはOracle SQLJランタイムでサポートされるわけではありません。
Oracle Database 12c リリース2 (12.2)では、標準定数以外の定数を定義できますが、他のSQLJ実装に移植できない可能性があるうえ、-warn=portableフラグが有効になっていると警告が生成されます。
サポートされているWITH句の定数
Oracle SQLJ実装では、接続コンテキスト宣言内で、次の標準定数の使用がサポートされています。
-
typeMap: 型マップ・プロパティ・リソース名を定義するStringリテラルイテレータ宣言内での
typeMapの使用もサポートされています。 -
dataSource:InitialContextでデータ・ソースを検索する名前を定義するStringリテラル
Oracle SQLJ実装では、イテレータ宣言内で、次の標準定数の使用がサポートされています。
-
sensitivity:SENSITIVE/ASENSITIVE/INSENSITIVEで、スクロール可能なイテレータの機密性を定義する -
returnability:true/falseで、イテレータがJavaストアド・プロシージャまたはファンクションから戻されるかどうかを定義する
サポートされていないWITH句の定数
サポートされていない定数を使用するSQLJコードを使用した場合、エラーは発生しませんが、何の操作も実行されません。Oracle SQLJ実装では、接続コンテキスト宣言内で次の標準定数を使用することはできません。
-
path: Javaストアド・プロシージャおよびファンクションを解決するために先頭に付加するPATHの名前を定義するStringリテラル -
transformGroup: SQL型に適用するSQL変換グループ名を定義するStringリテラル
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は、現在サポートされていません。
Oracle固有のWITH句の使用例
型マップを接続コンテキスト・クラスに対応付けるために、標準with句を接続コンテキスト宣言で使用する方法の他に、Oracle SQLJ実装では、型マップをイテレータ宣言内でイテレータ・クラスに対応付けるために、with句を使用できます。次に例を示します。
#sql iterator MyIterator with (typeMap="MyTypeMap") (Person pers, Address addr);
Oracle固有コード生成を使用して、型マップをアプリケーションで使用する場合、イテレータと接続コンテキスト宣言で同じ型マップを使用する必要があります。
returnabilityの例
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 HR -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 HR.sqljUserRet from dual;
SELECT文の結果を次に示します。
SQLJRET1
--------------------
CURSOR STATEMENT : 1
STR
------------------------------
sqljRetTabColSQLJ実行文の概要
SQLJ実行文は#sqlトークンと、後に続くSQLJ句で構成されます(実行可能なSQL文をJavaコードに埋め込むために、指定された規格に従った構文を使用します)。SQLJ実行文の埋込みSQL操作は、JDBCドライバでサポートされる任意のSQL操作にできます。
この項の内容は次のとおりです。
SQLJ実行文の規則
SQLJ実行文には次の規則があります。
-
Javaコードでは、Javaブロック文が許可される場合は常に、SQLJ実行文も許可されます。つまり、メソッド定義や静的な初期化ブロック内では許可されています。
-
埋込みSQL操作は、中カッコ
{...}で囲む必要があります。 -
最後にセミコロン(;)を指定する必要があります。
注意:
-
セミコロンは、SQL操作の終了を示す符号としては使用しないことをお薦めします。パーサーが操作の終了を認識するのは、SQLJ句の内部に右中カッコが現れた場合です。
-
SQLJ実行文の中カッコに囲まれた部分はすべてSQL構文として扱われ、SQL規則に従います。ただし、Javaホスト式の場合は例外です。
-
SQL操作のオフライン解析では、すべてのSQL構文がチェックされます。ただし、オンラインのセマンティクス・チェックでは、データ操作言語(DML)操作のみを解析およびチェックできます。データ定義言語(DDL)操作、トランザクション制御操作またはSQL操作については解析またはチェックされません。
SQLJ句
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ステートメント句を表1に、サポートされているSQLJ代入句を表2に示します。表5-1の最後にあげた2つの項目は、SQLJ固有の構文ではなく、標準SQL構文またはOracle PL/SQL構文を使用するステートメント句の一般カテゴリの項目です。
表5-1 SQLJステートメント句
| カテゴリ | 機能 | 参照先 |
|---|---|---|
|
|
Javaホスト式にデータを取り込みます。 |
|
|
|
位置イテレータからデータをフェッチします。 |
|
|
|
データへの変更をコミットします。 |
|
|
|
データへの変更を取り消します。 |
|
|
|
以後のロールバックのためのセーブポイントの設定、指定したセーブポイントの解放、セーブポイントのロールバックを行います。 |
|
|
|
アクセス・モードや分離レベルなど、詳細トランザクション制御を使用します。 |
|
|
プロシージャ句 |
ストアド・プロシージャをコールします。 |
|
|
代入句 |
Javaホスト式に値を代入します。 |
|
|
SQL句 |
|
『Oracle Database SQL言語リファレンス』 |
|
PL/SQLブロック |
SQLJ文内部の |
表5-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) };
}
注意:
-
throws SQLExceptionが必要です。 -
SQLJファンクション・コールでも
VALUESトークンが使用されますが、使用目的が異なります。
実行文のPL/SQLブロック
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コードで使用すると、他のプラットフォームへの移植性が失われます。
Javaホスト式、コンテキスト式および結果式
ここでは、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列の空白埋めを考慮します。
ホスト式の例
次に、構文の説明を明確にするために、いくつかの例をあげます。
注意:
次に示すいくつかの例ではSELECT INTO文を使用しています(この文の詳細は、「単一行の問合せ結果: SELECT INTO文」を参照)。
例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式の実行時評価
ここでは、アプリケーションの実行時の、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式は最初に出現したときに評価されます。出力の代入時に、式そのものは再評価されず、副作用も繰り返されません。
Java式の実行時評価の例(ISOコード生成)
ここでは、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トランスレータからの警告が生成されます。
単一行の問合せ結果: SELECT INTO文
戻り値がデータ1行のみの場合、SQLJでは、選択項目をSQL構文内のJavaホスト式に直接代入できます。これは、SELECT INTO文を使用して行います。この項の内容は次のとおりです。
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またはINOUTトークンを使用すると、変換時にエラーが発生します。
注意:
-
expression1からexpressionN、tableおよびオプションの句で使用できる構文は、SQLSELECT文の場合と同じです。 -
任意の数の
SELECTリスト項目およびINTOリスト項目を使用できます。ただし、SELECTリスト項目ごとに、互換性のある型のINTOリスト項目を使用するようにします。
SELECT 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 };
SELECTリストにホスト式を使用する例
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エラー状態
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(ゼロ)になります。
複数行の問合せ結果: SQLJイテレータ
多数の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()という名前になります。
注意:
名前付きイテレータの列のネーミングには、次の制約事項が適用されます。
-
列名にJavaの予約語は使用できません。
-
列名には、
next()、close()、getResultSet()およびisClosed()など、名前付きイテレータ・クラスのユーティリティ・メソッドと同じ名前は使用できません。スクロール可能な名前付きイテレータの場合、previous()、first()およびlast()などのメソッドも使用できません。
名前付きイテレータ・クラスの宣言
名前付きイテレータ・クラスの宣言には、次の構文を使用します。
#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クラスは、次のいずれかの方法で宣言する必要があります。
-
別個のソース・ファイル中に宣言する方法。ファイルのベース名はクラス名と同じにします。
-
public static修飾子を使用して、クラスレベルの有効範囲またはネストされたクラスレベルの有効範囲に宣言する方法。
Sun社のJDKの標準javacコンパイラを使用する場合は、これが要件となります。
名前付きイテレータのインスタンス化と移入
前の項で定義した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クラスは、次のいずれかの方法で宣言する必要があります。
-
別個のソース・ファイル中に宣言する方法。ファイルのベース名はクラス名と同じにします。
-
public static修飾子を使用して、クラスレベルの有効範囲またはネストされたクラスレベルの有効範囲に宣言する方法。
Sun社のJDKの標準javacコンパイラを使用する場合は、これが要件となります。
位置イテレータのインスタンス化と移入
位置イテレータのインスタンス化と移入は、名前付きイテレータの場合と同じです。ただし、SELECT文内のデータ項目の順序が適切であるかどうかを確認する必要があります。
EmpIterイテレータ・クラスの3つのデータ型は、EMP表の型と互換性がありますが、それぞれの順序は対応していないため、データの選択方法には注意が必要です。次の例は、SELECT文内のデータ項目がイテレータの列と同じ順序であるため、正常に実行できます。
EmpIter empsIter;
#sql empsIter = { SELECT ename, empno, sal FROM emp };
前述のように、位置イテレータを使用する場合は、データベースから選択する列の数と、イテレータの列の数を同じにする必要があります。
位置イテレータへのアクセス
位置イテレータで定義された列にアクセスするには、SQLのFETCH INTO構文を使用します。コマンドのINTO部分には、結果列を受け取るJavaホスト変数を指定します。ホスト変数の順序は、対応するイテレータ列の順序と同じにする必要があります。endFetch()メソッドを使用して、最後のフェッチがデータの末尾に達したかどうかを確認します(このメソッドは、すべての位置イテレータ・クラスで使用できます)。
注意:
-
endFetch()メソッドの行のフェッチ開始前の初期戻り値はtrueで、行の取出しが正常に行われると戻り値がfalseになり、FETCHがすべての行の取出しが終了したとみなした時点で戻り値は再びtrueになります。このため、endFetch()テストは、FETCH INTO文の後に行う必要があります。FETCH INTO文の前にendFetch()テストを行うと、行を取り出せなくなります。これは、最初のFETCHの前にendFetch()がTRUEになり、すぐにwhileループから抜けてしまうためです。 -
ただし、
endFetch()テストは、結果が処理される前に行う必要があります。FETCHは、データの最後に達してもSQL例外をスローすることはなく、単に次のendFetch()コールをトリガーしてtrueを戻すためです。結果が処理される前にendFetch()テストをしておかないと、データの最後に到達した後に、コード中の最初のFETCHでの戻り値(NULLまたは無効なデータ)に対して処理が試みられます。 -
イテレータからのデータの取出しが終了した後に、各イテレータの
close()メソッドを必ずコールしてください。このコールは、イテレータを閉じてリソースを解放するために必要です。
次に例では、前の項に示した宣言、インスタンス化および移入を再度示します。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 first_name, employee_id, salary FROM employees };
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で変数が代入されます。
next()メソッドによる位置イテレータ・ナビゲーション
前の項で説明した位置イテレータFETCH句では、ホスト変数(ある場合)に移入する前に、暗黙的なnext()コールによって移動が実行されています。この方法のかわりに、Oracle SQLJ実装では、JDBC結果セットおよびSQLJ名前付きイテレータと同じ移動ロジックを使用するために、特別なFETCH構文を明示的なnext()コールと併用する方法がサポートされています。この特別なFETCH構文を使用すると、セマンティクスが変化します。INTOリストに移入される前の暗黙的なnext()コールがなくなります。
ホスト変数としてのイテレータおよび結果セットの使用
SQLJでは、ホスト変数としてのSQLJイテレータおよびJDBC結果セットの使用がサポートされています。イテレータと結果セットの使用方法は基本的には同じですが、宣言やデータを取り出すアクセッサ・メソッドが異なります。
注意:
-
また、SQLJでは、ストアド・ファンクションの戻り値としてイテレータおよび結果セットを使用できます。
-
Oracle JDBCドライバでは、現在、入力ホスト変数としての結果セットの使用がサポートされていません。
OraclePreparedStatementクラスにはsetCursor()メソッドがありますが、これをコールすると、実行時に例外が発生します。
ここでの例は、次の部門表および従業員表を想定します。
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)に取り込みます。特定部門の従業員情報を持つカーソルは、外部イテレータの当該部門の行に対応するイテレータ列に移動します。 - ネステッド・ループを巡回することによって、部門別に部門名を出力し、その後、内部イテレータ内で部門別の全従業員の名前を出力します。
例5-1: 名前付きイテレータ内の結果セット列
この例では、名前付きイテレータで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();
...例5-2: 名前付きイテレータ内の名前付きイテレータ列
この例では、前に定義された名前付きイテレータ(ネストされたイテレータ)と同じ型の列を持つ、名前付きイテレータを使用します。
イテレータの宣言は、次のようになります。
#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();
...例5-3: 位置イテレータ内の名前付きイテレータ列
この例では、前に定義された名前付きイテレータ(ネストされたイテレータ)と同じ型の列を持つ、位置イテレータを使用します。ここでは、位置イテレータの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();
...代入文 (SET)
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実行文内に結果式が必要です。また、必要に応じて入力パラメータ、出力パラメータおよび入出力パラメータも使用できます。
ストアド・プロシージャの場合、戻り値はありません。オプションで、入力パラメータ、出力パラメータおよび入出力パラメータを使用できます。ストアド・プロシージャでは、任意の出力または入出力パラメータを介して、出力が戻されます。
この項の内容は次のとおりです。
ストアド・プロシージャのコール
ストアド・プロシージャには戻り値がありませんが、入力パラメータ、出力パラメータおよび入出力パラメータとしてリストを使用できます。ストアド・プロシージャのコールでは、CALLトークンを使用します。CALLトークンのすぐ後に空白を1つ挿入した後に、プロシージャ名を記述します。プロシージャ名と区別するため、CALLトークンの後には空白が必要です。プロシージャ・コールをカッコで囲むことはできません。これは、ファンクション・コールの構文とは異なります。CALLトークンの構文は次のとおりです。
#sql { CALL PROC(<PARAM_LIST>) };
PROCはストアド・プロシージャ名で、必要に応じて入力パラメータ、出力パラメータおよび入出力パラメータのリストを取ることができます。PROCには、HR.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データベースとの互換性を確保する場合は、プロシージャがパラメータをとらないときに、パラメータ・リストに空のカッコを含めないでください。次に例を示します。
#sql { CALL MAX_DEADLINE };次のようには定義できません。
#sql { CALL MAX_DEADLINE() };ストアド・ファンクションのコール
ストアド・ファンクションには戻り値があり、入力パラメータ、出力パラメータおよび入出力パラメータのリストを指定することもできます。ストアド・ファンクションのコールでは、VALUESトークンを使用します。VALUESトークンの後にファンクション・コールを記述します。標準のSQLJでは、ファンクション・コールをカッコで囲むことが必要です。Oracle SQLJ実装の場合、カッコの使用は任意です。カッコで囲む場合は、VALUESトークンと左カッコの間に空白があってもかまいません。VALUESトークンの構文は次のとおりです。
#sql result = { VALUES(FUNC(PARAM_LIST)) };
この構文でresultは結果式で、ファンクション戻り値を使用します。FUNCはストアド・ファンクション名で、必要に応じて入力パラメータ、出力パラメータおよび入出力パラメータのリストを取ることができます。FUNCには、HR.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であるすべての従業員の名前と従業員番号を含むイテレータが戻されます。その後、イテレータからこのデータが取り出されます。