ProcedurePmb

プロシージャを呼び出すためのParameterBeanについてのページです。

ProcedurePmbとは?

ProcedurePmb は (ストアド)プロシージャ対応のParameterBean です。外だしのSQLファイルに対するパラメータ(引数)ではなく、プロシージャに対するパラメータオブジェクトという位置付けで、 ProcedurePmbを指定することで容易にプログラムからプロシージャを呼び出すことができます。

通常のParameterBeanとは違い、パラメータを渡すという役割だけでなく、 どのプロシージャを呼び出すか を指定したり、OUTパラメータやプロシージャリターン を受け取ったりとプロシージャコールに対する総合的なオブジェクトとなります。

空文字はnullとして扱われる
通常のParameterBeanと同様です。OUTパラメータやプロシージャリターンにプロシージャ側で空文字が設定された場合も、getメソッド経由ではnullになります。
外だしSQLとの関係は?
DBFluteにおいては、外だしSQLとプロシージャは別物という扱いではなく、プロシージャも 外だしのSQL の一つという扱いをしています。よって、自動生成はSql2Entity経由、実行は BehaviorのoutsideSql()メソッド というようになっています。

ProcedurePmbの自動生成

Sql2Entityで自動生成される

通常のParameterBeanと同様に、ProcedurePmbもSql2Entityタスクで自動生成されます。

但し、これはオプションの動きとなります。outsideSqlDefinitionMap.dfprop にて、ProcedurePmbの自動生成を有効 にすることで初めて自動生成されます。プロシージャは外部プログラムのために作成されることも多く、 必ずしもDBFluteを利用しているアプリケーションから呼び出すとは限らない可能性があるためです。

設定を有効すると、Sql2Entityタスクでプロシージャのメタデータが取得され、ProcedurePmbが自動生成されます。 特にプロシージャ側にSql2Entityマークのようなものを埋め込む必要はありません。

自動生成されるクラス

パッケージ(出力先)は、通常の ParameterBean と同じです。

クラス名は、[プロシージャ名] + Pmb という形式となり、 プロシージャを識別する部分の名前に関しては、プロシージャの名前に "_" (アンダースコア)が含まれている場合は、その文字を区切りとしたキャメルケースに直します。 そうでない場合は、基本的にプロシージャの名前をそのまま利用します。 但し、全てが大文字の場合(メタデータとして)は先頭だけ大文字にして後は小文字になります。

SP_FOO
SpFooPmb
SPFOO
SpfooPmb
SpFoo
SpFooPmb
e.g. SP_NO_PARAMETER という名前のプロシージャ (引数なし) {MySQL} @Procedure
create procedure SP_NO_PARAMETER()
begin
end;
e.g. SP_NO_PARAMETER のプロシージャコール (引数なし) @Java
SpNoParameterPmb pmb = new SpNoParameterPmb();
xxxBhv.outsideSql().call(pmb); // SP_NO_PARAMETERが実行される

パラメータのプロパティ

メタデータで認識されたパラメータ(プロシージャリターンも含む)は、ProcedurePmb のプロパティとして自動生成されます。このプロパティを使って、INパラメータを設定したり、OUTパラメータを受け取ったりします。

プロパティの型は、テーブルの自動生成におけるマッピングと同じです。

e.g. IN, OUT, INOUT パラメータ (SP_IN_OUT_PARAMETER) {MySQL} @Procedure
create procedure SP_IN_OUT_PARAMETER(
      in v_in_varchar varchar(32)
    , out v_out_varchar varchar(32)
    , inout v_inout_varchar varchar(32)
)
begin
  set v_out_varchar = 'qux';
  set v_inout_varchar = 'quux';
end;
e.g. IN, OUT, INOUT パラメータの利用 (SP_IN_OUT_PARAMETER) @Java
SpInOutParameterPmb pmb = new SpInOutParameterPmb();
pmb.setVInVarchar("foo"); // INパラメータの設定
//pmb.setVOutVarchar("bar"); // OUTパラメータなので設定なし
pmb.setVInOutVarchar("baz"); // INOUTパラメータの設定
xxxBhv.outsideSql().call(pmb);
String outParam = pmb.getVOutVarchar(); // OUTパラメータの受け取り (qux)
String inOutParam = pmb.getVInOutVarchar(); // INOUTパラメータの受け取り (quux)
e.g. プロシージャリターンのあるプロシージャ (SP_RETURN_PARAMETER) {PostgreSQL} @Procedure
create or replace function SP_RETURN_PARAMETER()
returns integer as
$BODY$
begin
  return 1;
end;
$BODY$ LANGUAGE 'plpgsql';
e.g. プロシージャリターンの利用 (SP_RETURN_PARAMETER) @Java
SpReturnParameterPmb pmb = new SpReturnParameterPmb();
xxxBhv.outsideSql().call(pmb); // 実行
Integer returnValue = pmb.getReturnValue(); // プロシージャリターンの受け取り (1)

プロパティ名は、(メタデータとして取得された)パラメータ名を元にして、クラス名と同じ法則("Pmb" の付与は除外)が適用されます。 但し、NotParamResultは除く(後述)。

サポートされる型

基本的に、Entityの自動生成におけるサポートと変わりはありません。それぞれのDBMSごとにサポートされる型が決まっています。

但し、非常にプロシージャのみで利用される型(されやすい型)もありますので、幾つかの型はサポートされています。 例えば、Oracle のTABLE型(PL/SQL表)やVARRAY型、OBJECT型など、複雑な構造の場合はサポートされないなど限定的ではありますが、 実現できる範囲での対応がされています。

サポートされない型は、Entityのプロパティでは String 型にマッピングされるのに対し、プロシージャのパラメータでは Object 型にマッピングされます(@since 0.9.7.6)。プロシージャにおけるサポートされない型の場合、文字列では表現できない特殊な型の場合が多いと想定されるためです。 加えて、ValueType を独自に作成し設定することで、サポートされない多くの型も扱えるようになります。 その場合、アプリでは Object 型で渡し Object 型でもらい、独自の ValueType でJDBCに対応する型に変換して処理する、という形になります。 また、Object 型もどうにかしたい場合は、プロシージャのパラメータ定義の設定も含めて ProcedurePmb を手動作成すれば独自のプロパティ型を利用できます。

自動生成によるメリット

DBFluteではSql2Entityタスクを利用することで、どのプロシージャを呼び出すのか、どのパラメータに値をセットするのか、これらが全て タイプセーフに解決 されます。プロシージャ側で名前やパラメータに変更があった場合も、それらをコンパイルエラーで検知することができます。

古いクラスは削除される

外だしSQLのどこにも記述されていない既存の古いProcedurePmbは(ファイルシステム上から)削除されます。 古いProcedurePmbを利用しているプログラムはコンパイルエラーとして検知できます。

自動生成対象を指定

自動生成するプロシージャを指定したい場合は、outsideSqlDefinitionMap.dfprop にて指定 することができます。アプリケーションから呼び出さないプロシージャが大量にあるときに有効です。

プロシージャ参照のシノニムも自動生成

プロシージャ参照のシノニムも同様に自動生成したい場合は、outsideSqlDefinitionMap.dfprop にて指定 することができます。別スキーマのプロシージャを呼び出すのにシノニム経由にする場合などに有効です。

ResultSet 対応のCustomizeEntityも自動生成

ResultSet 対応の CustomizeEntity を自動生成することも可能です。

オーバーロードはもうひと手間

例えば、Oracle のパッケージプロシージャにおけるオーバーロードされたプロシージャ(パラメータ構成の違う同名のプロシージャ)を呼び出すには、 もうひと手間必要です。

自動生成される ProcedurePmb は、プロシージャ名でユニーク判定をしているため、最小公倍数のパラメータが定義されます。 よってそのままでは呼び出せず、自動生成された ProcedurePmb を継承してパラメータ情報のアノテーションの上書きでパラメータ構成を調整(不要なパラメータを上書きで無効化)することで呼び出すことができます。 (プロシージャ変更による影響をすぐに検知するために、自動テストを必ず書くことが推奨されます)

ただし、"同名型違いのパラメータ" がオーバーロードプロシージャ間で存在する場合はサポートされません。例えば、同じ foo_value という名前でありながらプロシージャが変わると文字列になったり数値になったりするような場合です。ProcedurePmb に生成されるプロパティの型はそのいずれかの型に限定されるため、パラメータ構成を調整したとしても実行時に型違いエラーになる可能性があります。

DBMSごとの取扱い

DBMSへの依存が強い領域ですので、DBMSごとの取扱いを必ずご確認下さい。

結果セットの取扱い

プロシージャが結果セット(ResultSet)を戻す場合、DBFluteにおける扱いは大きく二つに分かれます。

  • A. 結果セット対応の CustomizeEntity を自動生成
  • B. List<Map<String, Object>>を利用

A. 結果セット対応のCustomizeEntityを自動生成

Sql2Entityにてプロシージャを実際に実行し、そこから得られるメタデータから CustomizeEntity を自動生成します(オプション)。outsideSqlDefinitionMap.dfprop にて、プロシージャの CustomizeEntity の自動生成を有効に することで自動生成されるようになります。

e.g. OUTパラメータの結果セットの受け取り {PostgreSQL} @Procedure
create or replace function SP_RESULT_SET_PARAMETER(cur_member out refcursor)
as
$BODY$
begin
  open cur_member for
    select MEMBER_ID, MEMBER_NAME, BIRTHDATE
      from MEMBER;
end;
$BODY$ LANGUAGE 'plpgsql';
e.g. OUTパラメータの結果セットの受け取り {PostgreSQL} @Java
SpResultSetParameterPmb pmb = new SpResultSetParameterPmb();
memberBhv.outsideSql().call(pmb);
List<SpResultSetParameterCurMember> memberList = pmb.getCurMember();
for (SpResultSetParameterCurMember member : memberList) {
    ... = member.getMemberId();
    ... = member.getMemberName();
    ... = member.getBirthdate();
}

これにより、プロシージャ内のSQLの select 句の構成を変更したときに、その変更のアプリへの影響をプログラムでコンパイルエラーとして検知できるようになります。

テスト値は内部で自動設定

通常の外だしSQLでは、テスト値はアプリで任意のものを指定できますが、プロシージャのパラメータは、DBFlute内部で自動で設定して実行されます。 例えば、以下のような値が設定されます。

STRING
"0"
NUMBER
0
BOOLEAN
FALSE
DATE
2006-09-26 18:21:00 @since 0.9.7.1
UUID
FD8C7155-3A0A-DB11-BAC4-0011F5099158
OTHER
"0" ※文字列として

クラスのパッケージ(出力先)と名前

クラスのパッケージ(出力先)は、通常の CustomizeEntity と同様です。クラス名は、[プロシージャ名] + [パラメータ名] + Pmb となります。但し、NotParamResult の場合はメタデータとして名前が存在しないため、パラメータ名部分が "...NotParamResult1..." という形式の固定名で(複数対応のため)番号付きとなります。

プロパティの自動生成

自動生成した CustomizeEntity を要素にしたList型の、結果セットに対応するプロパティ が ProcedurePmb に自動生成されます。プロパティ名は、OUTパラメータやプロシージャリターンに関しては、通常のパラメータと同じです。 NotParamResultに関しては、"notParamResult1" という形式の固定名で番号付きとなります

NotParamResultの検知

実は "B" 方式(デフォルト)では、NotParamResult のためのプロパティは自動生成されず、この "A" 方式においてのみ、自動生成されます。これは、NotParamResultの情報は(実行なしで取得できる)メタデータからは取得できないためです。 この実際に実行して結果セットの情報を得る方式であれば、NotParamResult の有無を検知して、自動生成することができます。

e.g. NotParamResult形式の結果セットの受け取り {MySQL} @Procedure
create procedure SP_RETURN_RESULT_SET()
begin
  select MEMBER_ID, MEMBER_NAME, BIRTHDATE from MEMBER;
  select * from MEMBER_LOGIN;
end;
e.g. NotParamResult形式の結果セットの受け取り {MySQL} @Java
SpReturnResultSetPmb pmb = new SpReturnResultSetPmb();
memberBhv.outsideSql().call(pmb);
List<SpReturnResultSetNotParamResult1> memberList = pmb.getNotParamResult1();
for (SpReturnResultSetNotParamResult1 member : memberList) {
    ... = member.getMemberId();
    ... = member.getMemberName();
    ... = member.getBirthdate();
}
List<SpReturnResultSetNotParamResult2> loginList = pmb.getNotParamResult2();
for (SpReturnResultSetNotParamResult2 login : loginList) {
    ...
}

NotParamResult に対応するプロパティの名前は、"notParamResult[index]" が基本ですが、例えばSQLServerのテーブル値ファンクションのように、call 文 ではなく、select 文にて実行される "戻り値のような、必ず一つだけの NotParamResult" の場合は、"returnResult" という名前になります。

実行する対象のプロシージャを指定

デフォルトでは、基本的に(自動生成対象となっている)全てのプロシージャが実行 されます(NotParamResult をサポートしていないDBMSでは、OUTパラメータ(プロシージャリターンも含む)が無いものは実行されません)。プロシージャは気軽に実行されては困るようなケースがあるため、 その場合は、outsideSqlDefinitionMap.dfprop にて、実際に実行して CustomizeEntity の自動生成を行うプロシージャを明示的に指定 します。但し、対象外にした中に結果セットを取扱うプロシージャがある場合は、そのプロシージャは "B" 方式を利用します。

デフォルトでは、実行に失敗したプロシージャのエラーは、ワーニングとしてタスクは続行されますが(よって、自動生成されてないときは必ずログを見ること)、 DBFluteプロパティで明示的に対象を絞っての実行の場合は、一つでの失敗したときにはタスクが中断します。 明示的に指定しているということは、必要なプロシージャだけが実行されるということになるため、エラーで続行させる必要はないと考えこのようにしています。

B. List<Map<String, Object>>を利用

こちらは、デフォルトの挙動になります。自動生成される結果セットに対応するプロパティの型が List<Map<String, Object>> となります。マップのキー値は、(select句で指定された)カラム名(FlexibleName)となります。

この方式はタイプセーフではないため、できるだけ "A" 方式の利用をお奨め しますが、どうしても "A" 方式を採用できない場合(対象外にしたプロシージャ)においてのみ、この方式を利用します。

NotParamResultのプロパティは手動で

NotParamResultに関しては、その有無を(実行なしで取得できる)メタデータからは取得できないため、ProcedurePmb のExクラスにて手動でプロパティを追加する必要があります。

e.g. ProcedurePmbのExクラスでNotParamResultのプロパティを手動で定義 @Java
public static final String notParamResult1_PROCEDURE_PARAMETER = "notParamResult, 1000";
protected List<Map<String, Object>> _notParamResult1;
public List<Map<String, Object>> getNotParamResult1() {
    return _notParamResult1;
}
public void setNotParamResult1(List<Map<String, Object>> notParamResult1) {
    _notParamResult1 = notParamResult1;
}

PROCEDURE_PARAMETER の定数アノテーションには、一つ目の要素として "notParamResult" を固定で、二つ目の要素として、任意の番号を指定します(基本的に1000からの連番)。この番号は、複数の NotParamResult を定義する場合に、プロシージャ側の定義順序と合わせるためのものです。

手動で作ったEntityでも構わない

このとき、Mapの代わりに手動で作った(独自の) Entity を指定しても構いません。定義されているプロパティ名と結果セットの内容が一致していれば、Entity にデータがマッピングされます。

プロシージャのエスケープ

プロシージャ実行時の call 構文のSQLは、"{"、"}" に囲われます(JDBCのエスケープ処理)。

e.g. 実行されるプロシージャの call 構文のSQL(エスケープされる) @Java
{call SP_FOO('bar', ?)}

通常は、これ以上これに関して意識する必要はありません。 但し、このエスケープ処理が施されている場合に限って実行ができないプロシージャが存在することが 稀に あります。そのようなプロシージャに遭遇したときは、Exクラスにて isEscapeStatement() をオーバーライドして、false を戻すようにします。すると、その ProcedurePmb を使ったプロシージャコールは、エスケープ処理が施されません。 滅多にない現象ですので、その問題が発生してからこの機能を意識する、というので特に問題ないでしょう。

ProcedurePmbの構造

それぞれのプロシージャに対応する ProcedurePmb クラスは、ProcedurePmb インターフェースを実装し、ジェネレーションギャップになっています。

アプリケーションで意識するメソッドは全て set で始まるメソッド(検索条件の指定)が主になりますが、 OUTパラメータやプロシージャリターンを受け取るために get で始まるメソッドも利用することがあります。

プロシージャのパラメータ定義

ProcedurePmbの中では、プロシージャのパラメータ定義が宣言されています。

e.g. ProcedurePmbのパラメータ定義 @Java
// -----------------------------------------------------
//                                   Procedure Parameter
//                                   -------------------
public static final String VInVarchar_PROCEDURE_PARAMETER = "in, 0";
public static final String VOutVarchar_PROCEDURE_PARAMETER = "out, 1";
public static final String VInoutVarchar_PROCEDURE_PARAMETER = "inout, 2";

// [property-name]_PROCEDURE_PARAMETER
// [property-name] は、JavaBeansRule なので
// v_in_varchar は VInVarchar
// vv_in_varchar は vvInVarchar

"[property-name]_PROCEDURE_PARAMETER" という形式の定数アノテーションで、値は String 型で "[in or out or inout], [bind-order]" という形式です。"property-name" は JavaBeansRule ですので、先頭文字の大文字小文字にはご注意下さい。

アプリケーションで直接意識することはありませんが、JDBC次第でメタデータを正しく取得できない場合に、 この定義を参考にExクラスで補完することで解決することがあります。 但し、これらは既にDBFluteの内部構造ですので、仕様が変わったときのために必ず影響が検知できるような自動テストと共に拡張して下さい。

同じ名前の定数をExクラスでも定義した場合は、Exクラスのものが優先されます。また、値が null の場合は、そのパラメータ定義は無効とみなされる、Bsクラスに宣言されたパラメータ定義を無効にすることもできます。 (オーバーロードプロシージャに対応する場合などに利用します)

ValueType の調整

デフォルトの ValueType ではなく、パラメータごとに独自の ValueType を指定することができます。これを利用することで、パラメータのJDBC周りの処理(バインド処理やOUTパラメータ取得など)の調整ができます。

e.g. ProcedurePmb のExクラスで独自の ValueType を定義 @Java
public class SpInOutParameterPmb extends BsSpInOutParameterPmb {

    public static final String VInVarchar_VALUE_TYPE = "fooType";
    public static final ValueType VOutVarchar_VALUE_TYPE = new FooType();

    ...
}
// [property-name]_VALUE_TYPE
// [property-name] は、JavaBeansRule なので
// v_in_varchar は VInVarchar
// vv_in_varchar は vvInVarchar

"[property-name]_VALUE_TYPE" という形式の定数アノテーションで、値が String 型の場合は、"プラグインValueType" のキーの名前で、ValueType 型の場合(@since 0.9.7.6)は適用させる ValueType のインスタンスを指定します。"property-name" は JavaBeansRule ですので、先頭文字の大文字小文字にはご注意下さい。

基本的にBsクラスには定義されていませんが、特殊な方の場合にDBFluteが自動で設定することがあります。 同じ名前の定数をExクラスでも定義した場合や、値が null の場合の挙動は、プロシージャのパラメータ定義と同じです。

ValueType を独自に作成する必要はありますが、このように拡張することでサポートされない型も扱えるようになるかもしれません。

ProcedurePmbの使い方

パラメータを設定したProcedurePmbクラスのインスタンスを 外だしSQLのcall()メソッド の引数に指定します。