ParameterBeanの自動判別

ParameterBeanのプロパティの自動判別や代理判定メソッドについてのページです。

プロパティ項目の自動判別

自動判別マーク

プロパティ項目をIFコメントやバインド変数コメントなどから(ある程度の精度で)自動判別することが可能です。 プロパティ項目の指定と同じくSQLの行コメント形式 "--" にて、"!!AutoDetect!!" と指定すると、バインド変数コメントとそのテスト値からプロパティ名とプログラム型を導出します(@since 0.9.8.0)。 例えば、"/*pmb.memberId*/3" といったバインド変数コメントがSQLの中に存在する場合は、テスト値の "3" という値から型を推測し、"-- !!Integer memberId!!" と明示的に指定するのと同じ扱いになります。

e.g. プロパティ項目を自動判別 @OutsideSql
-- !df:pmb!
-- !!AutoDetect!!

select ...
  from ...
 where MEMBER_ID = /*pmb.memberId*/3
   and MEMBER_NAME like /*pmb.memberName*/'S%'

プロパティの基本ルール

判別対象となるプロパティの基本ルールは以下の通りです。

  • バインド変数コメントでテスト値を持っているもの
  • IFコメントで比較値で型が判別できるもの(できそうなもの) (@since 0.9.8.4)
  • FORコメントでカレント変数がバインド変数コメントで利用され判別できるもの (@since 0.9.8.4)
  • プロパティのパス指定が "pmb." で始まること
  • プロパティのパス指定がネストしていないこと ("pmb.foo" は OK, "pmb.foo.bar" は対象外)
  • (バインド変数コメントであれば)テスト値を持っていること

厳密にどこまでが判別対象となるかは若干の曖昧さがあります。 実際にやってみて判別されていないものがあれば、同時に明示的なプロパティの指定もするという流れが良いでしょう。

明示的なプロパティの同時利用
明示的なプロパティ指定も同時に利用できます。同じ名前のプロパティが双方に存在した場合は、明示的に指定されたものが(オプションも含めて)優先されます。 自動判別の結果が要件に合わない場合(プログラム型やオプションなど)において、明示的な指定で上書きして調整します。
予約プロパティ名は対象外
DBFluteで予約されているプロパティ名は対象外です(そもそも利用してはいけません)。 例えば、"outsideSqlPath", "entityType", "fetchSize", "pageStartIndex", "pageEndIndex" など、ParameterBean のインターフェースやスーパークラスで利用されているプロパティです。

プログラム型の導出

プログラム型はバインド変数コメントならテスト値、IFコメントなら比較値から導出されます。

導出ロジックは以下の通りです。

Integer
数値リテラルで Integer.MAX_VALUE 以下の値
Long
数値リテラルで Integer.MAX_VALUE を超える値 ※Longにするならテスト値はどでかい値に
BigDecimal
数値リテラルで "." (ドット) が含まれる値
Date
dateリテラル、もしくは、クォート値で日付型としてパースできて時分秒ミリ秒が全て0の値
例えば、以下のような形式。
  • /*pmb.fooDate*/date '2011-01-28'
  • /*pmb.fooDate*/'2011-01-28 00:00:00'
Timestamp
timestampリテラル、もしくは、クォート値で日付型としてパースできてDateと判別されなかった値
例えば、以下のような形式。
  • /*pmb.fooDatetime*/timestamp '2011-01-28'
  • /*pmb.fooDatetime*/'2011-01-28 12:34:56'
Time
timeリテラル、もしくは、クォート値で時刻型としてパースできる値
例えば、以下のような形式。
  • /*pmb.fooTime*/time '12:34:56'
  • /*pmb.fooTime*/'12:34:56'
List<>
('foo', 'bar') といった形式の値。要素型は一番目の要素を元に他の型と同じように判別されるが、文字列型と数値型に限られる。 ※CDef型を利用するような場合は、明示的宣言で調整すると良いでしょう
例えば、以下のような形式。
  • /*pmb.fooList*/('foo', 'bar')
  • /*pmb.fooList*/(2, 5)
String
他の型に判別されなかった場合

導出された型が要件に一致しない場合は、明示的なプロパティ指定で定義を上書き(そのプロパティだけは自動判別に頼らない)、 もしくは、参照カラムのオプションを利用して型調整を行います。

オプションの導出

一部のオプションはテスト値から判別されます。

likePrefix
クォート値で最後に "%" がある値
likeSuffix
クォート値で最初に "%" がある値
likeContain
クォート値で最初と最後に "%" がある値
like
クォート値でどこかに "%" があり、一致の方向が特定できない値

テスト値で導出されないオプションは、バインド変数コメントの中で直接オプション指定をすることで利用できます。 また、テスト値で導出できるオプションであっても、ここでの明示的オプション指定がある場合はそれが優先されます。 オプションの仕様は、通常のオプション指定のものと全く同じです。

this - ParameterBeanのオプション
e.g. 自動判別にて、バインド変数コメントでオプション指定 @OutsideSql
-- !df:pmb!
-- !!AutoDetect!!
select ...
  from ...
 where MEMBER_ID = /*pmb.memberId*/3
   and FORMALIZED_DATETIME >= /*pmb.fromFormalizedDate:fromDate*/'2011-01-30'
   and FORMALIZED_DATETIME < /*pmb.toFormalizedDate:toDate*/'2011-01-31'
   and MEMBER_STATUS_CODE like /*pmb.memberStatusCode:cls(MemberStatus)*/'FML'

同じプロパティに対して複数のバインド変数コメントがある場合は、どれか一つにオプションが指定されていればそれが有効になります。 (また、それぞれ別のオプションを指定している場合は、最後に評価されたオプションが有効になります)

参照カラムのオプションによる型調整

バインド変数コメントに参照カラムのオプション、ref オプションを指定した場合は、 テスト値から導出したプロパティのプログラム型に代わり、参照カラムに対応するプログラム型が強制的に利用されます。 導出された型が業務に合致しない場合はこのオプションを利用して調整すると良いでしょう。

this - 参照カラムのオプション
e.g. 参照カラムのオプションで自動判別された型を調整 @OutsideSql
-- !df:pmb!
-- !!AutoDetect!!
select ...
  from ...
 where MEMBER_ID = /*pmb.memberId:ref(MEMBER)*/3

リスト型に対して ref オプションを指定した場合は、リストの要素型が参照カラムのプログラム型になります。 さらにそのとき、参照カラムが区分値に関連付いている場合は、対応する CDef の区分値クラスの型が要素型になります。 (つまり、区分値カラムのリスト型の場合は積極的に ref オプションを利用すると良いでしょう)

e.g. 区分値カラムのリスト型には参照カラムのオプションを指定して CDef のリストに @OutsideSql
-- !df:pmb!
-- !!AutoDetect!!
select ...
  from ...
 where MEMBER_STATUS_CODE = /*pmb.memberStatusCodeList:ref(MEMBER)*/('FML')

プロパティのコメントは?

自動判別にするとプロパティのコメントを書く場所がなくなってしまいます。 代わりに、バインド変数コメントにコメントのオプションを付与することができます。

e.g. 区分値カラムのリスト型には参照カラムのオプションを指定して CDef のリストに @OutsideSql
-- !df:pmb!
-- !!AutoDetect!!
select ...
  from ...
 where MEMBER_ID = /*pmb.memberId:comment(指定すると一件検索になる)*/3

ただし、ここはSQL文の中なので、あまり書き過ぎると見づらいでしょう。この方法で大量のコメントを書くのはあまりお奨めではありません。 コメントを重視するようであれば、自動判別は利用せず明示的に指定した方が可読性は良いでしょう。

ParameterBeanのプロパティオプション - プロパティのコメント

明示的宣言と併用して微調整

自動判別されたプロパティが想定外の方に認識されてしまったり、そもそもプロパティが認識されていないというような場合は、自動判別のルールを見直してみましょう。 それでも、どうにもならない場合は、明示的宣言と併用して微調整すると良いでしょう。(自動判別は完璧ではない)

ParameterBeanマーク
e.g. 明示的宣言と併用して微調整 @OutsideSql
-- !df:pmb!
-- !!AutoDetect!!
-- !!String memberName!!

...

補助機能である

多くのケースにてこの自動判別機能が利用できると想定されます。ただし、ご覧の通りある程度の曖昧さを許容する仕様となっており、 項目指定コストを軽減するための(あくまで)補助的な機能と位置付けられています。 (一方で、ParameterBeanのプロパティ項目がベタっとSQLの上部の一覧記述されることは、それはそれでドキュメント的な役割があるため、 それを重視するケースにおいてはこの自動判別機能を遠慮なく利用しないでも良いでしょう)

代理判定メソッドの自動判別

外だしSQLで複雑な条件での分岐がどうしても必要なとき、IFコメントの中で複雑な条件を書くよりも、ParameterBeanのExクラスに 代理判定メソッド を作成して、IFコメントではそのメソッドを単に呼び出すやり方が推奨されています。

通常、その代理判定メソッドの作成するときは、IFコメントで代理判定メソッドを指定した後、Sql2EntityでParameterBeanを自動生成し、Exクラスに手動で作成します。 メソッドの定義で名前を間違えたり、そもそも定義するのを忘れたりすれば、当然実行時に "メソッドがない例外" となります。

プロパティ項目の自動判別機能を利用している場合(自動判別マークを指定している場合)は、 IFコメントで指定された代理判定メソッドが自動判別され、ParameterBeanのBsクラスに abstract メソッドとして定義されます(@since 0.9.8.2)。 つまり、自動生成された瞬間いきなりコンパイルエラーになり、定義漏れがなくなるのはもちろん、メソッド名を間違えるということもなくなります。 また、Exクラスの実装で @Override アノテーションをしっかり指定していれば、 IFコメント側で代理判定メソッドの名前を変更したときも古いメソッドがコンパイルエラーで検知できます。

e.g. IFコメントで代理判定メソッドを指定 @OutsideSql
-- !df:pmb!
-- !!AutoDetect!!
select ...
  from ...
 where ...
...
 /*IF pmb.existsPurchase()*/
 and exists (select PURCHASE_ID
             from PURCHASE
             ...
 /*END*/
e.g. ParameterBeanのBsクラスにて代理判定メソッドのテンプレート @Java
// この抽象メソッドは自動生成され、
// 自動生成直後はExクラスでコンパイルエラー
public abstract boolean existsPurchase();
e.g. ParameterBeanのExクラスにて代理判定メソッドを実装 @Java
// コンパイルエラーの赤い波線にカーソル合わせて
// ctrl + 1 -> enter で実装メソッド補完 (Eclipse)
@Override
public boolean existsPurchase() {
    return (_purchaseCount != null || _purchasePrice != null) && ...;
}

この機能は、プロパティ項目の自動判別機能の付加要素としての提供となります。

自動判別対象となるのは、/*IF pmb.foo()*/ というシンプルな形式のboolean型メソッドです。

  • 引数のないメソッドであること
  • 条件が一つであること(ANDやORは不可)
  • 否定演算子はあってもよい e.g. /*IF !pmb.foo()*/

逆に明確に自動判別の対象外となる状況は以下の通りです。

  • ネストした呼び出しは対象外 e.g. /*IF pmb.foo().bar()*/
  • 比較式があるものは対象外 e.g. /*IF pmb.foo() == true*/
  • 予約された名前のメソッドは対象外 e.g. /*IF isPaging()*/
  • 定義されたプロパティ名のメソッドは対象外 e.g. /*IF getMemberName()*/, /*IF isMemberName()*/

ただし、普段は細かいルールを意識する必要はあまりないでしょう。自然に書いていればOKです。