AdditionalForeignKeyのfixedConditionでバインド変数

@DBFlute-0.8.7, Java
> (DBFLUTE-377){Java}: 
> additionalForeignKeyのfixedConditionでバインド変数

以下の記事に関連する話です:
A. 「業務的にone-to-oneへの対応」
  http://d.hatena.ne.jp/jflute/20071022/1193032003
B. 「ExampleDBにテーブル追加」
  http://d.hatena.ne.jp/jflute/20081128/1227868238

一言で言うと、
「additionalForeignKeyのfixedConditionでバインド変数が利用可能」
です。

http://dbflute.sandbox.seasar.org/ja/view/exampledb/EARoot/EA1.html
の「MEMBER_ADDRESS」を例に紹介します。(記事「B」を参考)
「MEMBER」からみた場合、このテーブルは「構造的にone-to-many」です。
なので通常は、MemberCBにsetupSelect_MemberAddress()は
生成されません。MemberBhv.loadMemberAddress()にてListで取得します。
しかし、このテーブルは「有効期間内のものをone-to-oneとして扱う」ものであり、
「one-to-many」で取り扱うことにあまり意味はありません。
(履歴表示画面など全く意味がない訳ではありませんがメインではありません)
まさしく「業務的にone-to-one」で、MemberCBでやりたいのは、
「とある日付を指定してsetupSelectする」ことです。

今までのバージョンでも「条件が完全に固定」であれば、実現はできましたが、
(記事「A」を参考)条件パラメータを動的に渡したいときの対応ができませんでした。
しかし今回のバージョンで、「条件パラメータを動的に渡す」ことが
できるようになりました。
つまり、
「additionalForeignKeyのfixedConditionでバインド変数が利用可能」
ということです。

additionalForeignKeyで以下のように設定をします:
; FK_MEMBER_MEMBER_ADDRESS_VALID = map:{
    ; localTableName  = MEMBER ; foreignTableName = MEMBER_ADDRESS
    ; localColumnName = MEMBER_ID ; foreignColumnName = MEMBER_ID
    ; fixedCondition = 
 $$foreignAlias$$.VALID_BEGIN_DATE <= /*targetDate(Date)*/null
 and $$foreignAlias$$.VALID_END_DATE >= /*targetDate(Date)*/null
    ; fixedSuffix = AsValid
}
fixedConditionで「指定された日付が有効期間内であること」が指定
されています。これはJOINのON句に展開される条件です。
条件パラメータ部分に「バインド変数コメント」っぽいものを指定します。
「ぽいもの」というのは「()」でJavaの型を指定しているところが
外だしSQLの「バインド変数コメント」と違う部分があるからです。
このように指定すると以下のようにプログラム上で実装できます。

※テスト値は「null」固定でお願いします。
java.util.Date targetDate = ...;

MemberCB cb = new MemberCB();
cb.setupSelect_MemberAddressAsValid(targetDate);
cb.query().addOrderBy_MemberId_Asc();
するとSQLはこんな感じです。
select dflocal.MEMBER_NAME as MEMBER_NAME, ... 
  from MEMBER dflocal
    left outer join MEMBER_ADDRESS dfrelation_1
      on dflocal.MEMBER_ID = dfrelation_1.MEMBER_ID
        and dfrelation_1.VALID_BEGIN_DATE <= '2005-12-12'
        and dfrelation_1.VALID_END_DATE >= '2005-12-12'  
 order by dflocal.MEMBER_ID asc
見事に「業務的なone-to-oneの取得」が出来上がりました。
詳しい使い方はdbflute-basic-exampleのConditionBeanPlatinumTest
の「test_fixedCondition_setupSelect_Tx()」をご覧ください。

// additionalForeignKeyMap.dfprop
https://www.seasar.org/svn/dbflute/trunk/dbflute-basic-example/dbflute_exampledb/dfprop/additionalForeignKeyMap.dfprop

// ConditionBeanPlatinumTest.java
https://www.seasar.org/svn/dbflute/trunk/dbflute-basic-example/src/test/java/com/example/dbflute/basic/dbflute/howto/ConditionBeanPlatinumTest.java


実は、fixedConditionを利用しなくても単にadditionalForeignKeyを設定して、
cb.query().queryMemberAddress().on().setXxx...というように
query()にてon句へ条件を指定すれば同じようなことはできるかもしれません。
しかし、それは「安全性を失ったリレーション」であり、
one-to-oneにするための条件が強制されていません。
もっと自由度の高いCriteriaAPIを持つO/Rマッパではもっと簡単ですが、
やはりこの部分でのバグをかなり多く見掛けます。
単に指定し忘れだけでなく、強制力がないので「リレーションを勘違い」
して実装するパターンもよくあります(へんてこりんな条件でSelectしちゃう)。

今回のような「業務的に...」というのはリレーショナルデータベースで
表現されませんが、やはり本来「構造」で解決されている部分であるべき
と思っています。リレーショナルデータベース で表現できないその構造を
補完するという感じで、DFluteの設定ファイルでそれを「表現」します。
DBFluteには、
 DBAや実装リーダが頑張ってレールをひいて(構造を表現して)、
 ディベロッパーには業務仕様の把握・実現に集中してもらいたい
という気持ちがあります。
そうすることでディベロッパーは、
「決まった構造が決まったI/Fで投影されたAPI」
でDBアクセスすることができます。
その分、ディベロッパーの方はしっかり業務を把握して、
正しく業務仕様を実装してください。浮いた分の時間を
仕様確認やテスト、パフォーマンス向上に当ててください。