DBFlute-1.1のDerivedReferrer

DBFlute-1.1だとこうなるシリーズ、
I'm sorry...still continue

DBFluteには「DerivedReferrer」という
強力な機能があります。
 -> (Specify)DerivedReferrer

こいつがいるからこその ConditionBean と
言えるかもしれません。

購入回数や最終ログイン日時

例えば、会員の一覧の項目の中で、

o それぞれの会員の購入回数も表示する
o それぞれの会員の最終ログイン日時も表示する
o 購入回数、ログイン回数で並べる、絞り込む

みたいな要件があった場合に、
DerivedReferrerが大活躍します。

で、こういうのって、わりとよくあるんです。

シンプルな検索一覧だと思ったら、
ちょっとはじっこの方に「最大購入価格」出して欲しいとか。
気軽にそういう項目って入ってしまいます。

その項目が欲しいと思った人は、
わりと気楽な感じで出てたらうれしいなぁという感じですが、
プログラマーは、
「そういった項目こそが、実装とテストの時間を増やす原因!」
って思うわけです。

でも、確かに詳細画面に行かないと見えないってのは
不便だったりします。
実際にjfluteもWebサイトを使ってるときに、
「ああ、ここ DerivedReferrer 使って欲しいなぁ」
って思うことがよくあって、
表示できるもんなら表示されていた方がうれしいものでもあります。
MemberCB cb = new MemberCB();
cb.specify().derivedMemberLoginList().max(loginCB -> {
    subCB.specify().columnLoginDatetime();
    subCB.query().setMobileLoginFlg_Equal_False();
}, Member.ALIAS_latestLoginDatetime);
select dfloc.MEMBER_ID as c1, dfloc.MEMBER_NAME as c2, ...
     , (select max(sub1loc.LOGIN_DATETIME)
          from MEMBER_LOGIN sub1loc 
         where sub1loc.MEMBER_ID = dfloc.MEMBER_ID
           and sub1loc.MOBILE_LOGIN_FLG = 0
       ) as LATEST_LOGIN_DATETIME
  from MEMBER dfloc

ちょっと面倒手続きが...

そんな感じで、
DerivedReferrer (でぃらいぶどりふぁらー) が活躍するわけで、
実際に現場でがっつり使われている機能なのですが、
ちょっと面倒手続きが一つあります。

DerivedReferrer は、実際にテーブルには存在しない、
「導出カラム」と呼んでいる論理的なカラムを取得します。
合計値だったり、レコード件数だったり。

それらは物理的なカラムではないので、
普通にDBFluteで自動生成されるEntityには、
プロパティは作られないのです。
なので、なにもせずには受け取れない。。。
幾らSQLが発行できても受け取る箱がないのです。

ということで、ジェネレーションギャップで自動生成されている、
Entityのサブクラス、つまり、Exクラスの部分に、
Getter/Setterを手動で定義する必要があります。
すると、DBFluteがそのSetter経由で値を突っ込んでくれます。

EclipseEMechaで支援

まずは、その Getter/Setter を作るのがちょい面倒。
もちろん、Eclipseとか使えばわりと楽には実装できます。

また、Eclipseプラグインの「EMecha」を使えば、
補完でサクッと作成することができます。
ドキュメントには、それを使った手順が書いてあります。
 -> DerivedReferrer - まずはプロパティの作成

jfluteが見かける現場では、
だいたいこれを使ってサクッと実装できています。
で、特に問題無し。

Entityに手を入れられない!?

それができてる現場は何も問題ないし、
これからも問題ないのですが、
時に、Entityに手を入れるのがつらい現場あります。

巨大なプロジェクトで、自動生成されたEntityは
共通基盤のjarファイルの中に入っちゃってるとか。
アプリ開発者は、自分の画面のコードにしか手を入れられない。

すると、DerivedReferrerさんは、
一気にまったく使えない残念な子になってしまうのです。

ううぅぅぅ。。。

でぃらいぶどまっぱぶるえりあす

いずれにせよ、自分でプロパティを作成するという、
少し敷居の高い機能であるということには変わらないので、
なんとかして、Entityに手を入れずに取得できないものか!?

もう答えは一つです。
これはずっと昔から発想はしていて、
「ちょっとないなぁ」と思っていたのですが、
今になってサポートしました。
変な体裁よりも便利さを追うべきだろうということで。
//
// いままでのやり方、もちろんこれからもOK
//
MemberCB cb = new MemberCB();
cb.setupSelect_MemberStatus();
cb.specify().derivedPurchaseList().max(purchaseCB -> {
    purchaseCB.specify().columnPurchasePrice();
}, ALIAS_sea); // ★EMechaでEntityに自動生成

ListResultBean<Member> memberList
                = memberBhv.selectList(cb);

for (Member member : memberList) {
    Integer foo = member.getSea(); // ★作成したメソッドでget
    log(member.getMemberName(), foo);
}
//
// こういうやり方「も」できる
//
MemberCB cb = new MemberCB();
cb.setupSelect_MemberStatus();
String keyOfSea = "$sea";
cb.specify().derivedPurchaseList().max(purchaseCB -> {
    purchaseCB.specify().columnPurchasePrice();
}, keyOfSea); // ★単にキーの設定

ListResultBean<Member> memberList
                = memberBhv.selectList(cb);

for (Member member : memberList) {
    Integer foo = member.derived(keyOfSea); // ★キーで取れる
    log(member.getMemberName(), foo);
}
そうです、単なる Map です。
名付けて、
「DerivedMappableAlias」
(でぃらいぶどまっぱぶるえりあす)

だらー '$' を付けると、
それはメソッドではなく普通にキーとして扱います。
Entity の derived() で取得できます。

Beanのプロパティの形式になってないことがためらう部分ですが、
実際は、すぐにEntityは画面用のDTOに詰め替えられることが多く、
検索したクラス、もしくは、それを呼び出しクラスくらいで、
すぐに取り出されたりするもので、ちゃんとEntityのプロパティに
なってないと困るってケースはそんなにないかと。

そもそも、Entityを JSP や Velocity などの
コンパイルセーフでないテンプレートに渡すのは非推奨です。
必ずJavaの世界の中で取り扱って欲しいという思いもあります。

マッピングするデータ型のルール

今までのBeanにプロパティを作るやり方では、
プロパティのプログラム型がそのまま受け取るデータ型でした。
検索データのマッピングでも、その型に変換をします。

DerivedMappableAliasは、単なるMapなので、
型を事前に指定するところがあります。
derived()の戻り値も単にダウンキャストしているだけです。
Genericでキャストは省略していますが要はダウンキャスト。

以下のようなルールになっています:

o count   : Integer
o max/min : (column type) ※Entityで定義されている型
o sum/avg : BigDecimal

いたってシンプルです。

逆に「自由が効きません」ので、
受け取ってから必要に応じて変換します。
そして、型を間違えるとClassCastExceptionです。

というか、本来 Bean のプロパティのプログラム型も、
こういう感じで規則的なはずで、何の型でマッピングさせるか
ってのはどのみち考えないといけないことには変わりません。

今までのプロパティでよければ別に

もちろん、今までのBeanのプロパティ形式で別に問題はないです。
EMecha の ctrl+1 補完で一発でプロパティを作成してしまえばOK。
それができない環境のためと考えて頂ければと。

もし、この方法自体を一切使えないようにしたいというときは、
littleAdjustmentMap.dfpropにて、

isEntityDerivedMappable = false

で、OKです。
derived()メソッドが生成されませんし、
そもそも、DerivedReferrerのキー指定の時点で、
「対応するプロパティメソッドないよ」例外に。
Beanにプロパティを作るやり方を強制できます。

ということで

まあ、

「手軽なDerivedReferrer」

というような位置づけで捉えて頂ければと。