DBFlute:「業務的にone-to-one」への対応

丁度、仕事で必要になったので実装しました。
(明日リリース予定のDBFlute-0.5.6)

今まで、「業務的にone-to-one」というのがDBFluteにとっては鬼門でした。

  o 会員(MEMBER)テーブル
  o 会員住所情報(MEMBER_ADDRESS)テーブル

とあったとします。
ここで互いに同じ主キー値を持つなり、MemberAddressでユニーク制約
の掛かったMemberIdカラムを持つなどで、制約的にone-to-oneであれば、
全く問題ありません。DBFluteはその制約を検知して、
  MemberCB member = new MemberCB();
  member.setupSelect_MemberAddressAsOne();
というように実装が可能になります。

しかし、MemberAddressで「VALID_FLGが有効なもので絞ればone-to-one」
というDB構造になっている場合、そのことを知っているのは人間だけなので
DBFluteで上記のようなメソッドを作成することはできません。
つまり、構造的にはone-to-manyだが業務的にone-to-oneになるような状態。
from MEMBER mb
  left outer join MEMBER_ADDRESS adr
    on mb.MEMBER_ID = adr.MEMBER_ID and adr.VALID_FLG = 1
履歴を同じテーブルで扱う場合とかによく利用される手法だと思います。
自分が設計する場合は極力避けるようにしますが、仕方ない場合もあるのだと思います。
この場合だと、今までのConditionBeanでは、MemberからMemberAddressを
外部結合することができませんでした。しかも、一つそういった関係のテーブル構造が
あると、大抵プロジェクトでは他にもたくさんあります。(DB設計者の好みだし)
また、アプリケーションでも多々「簡単なSQLなのにConditionBeanが利用できないや」
ということが多くあるかと思います。

そこで、AdditionalForeignKeyに「業務的な固定の条件」を付与できるようにしました。
自動判別はどうしても無理なのですが、明示的に指定することによって可能にします。
map:{
    ; FK_MEMBER_VALID_MEMBER_ADDRESS = map:{
        ; localTableName = MEMBER ; foreignTableName = MEMBER_ADDRESS
        ; fixedCondition = $$foreignAlias$$.VALID_FLG = 1
    }
}
※「$$foreignAlias$$」は動的に結合時の「結合先Alias名(Foreign)」として解決されます
すると、
  MemberCB member = new MemberCB();
  member.setupSelect_MemberAddress();
で実装できるようになり、SQLは以下のようになります。
from MEMBER mb
  left outer join MEMBER_ADDRESS adr
    on mb.MEMBER_ID = adr.MEMBER_ID and adr.VALID_FLG = 1
これがあれば、DB設計者も安心して設計することができますし、
プログラマも先ほどのDB構造で悩むこともないかと思います。

ちなみに
map:{
    ; FK_MEMBER_VALID_MEMBER_ADDRESS = map:{
        ; localTableName = MEMBER ; foreignTableName = MEMBER_ADDRESS
        ; fixedCondition = $$alias$$.VALID_FLG = 1 ; fixedSuffix = AsValid
    }
}
とすると、
member.setupSelect_MemberAddress();

    ↓

member.setupSelect_MemberAddressAsValid();
というように関連プロパティ名に意味をつけることが出来ます。
もし、同じテーブルに対してfixedConditionだけが違う関連を2つ付ける場合は、
これを利用してプロパティ名をユニークにします。

もちろん、明示的に意味のあるものを付けるということで、
そうで無い場合でもfixedSuffixを付けることを推奨します。

また、これは、上記のようなパターンだけでなく、
「何かを指定したらone-to-one」という関連にも利用できます。

o 会員(MEMBER)テーブル
    - MEMBER_ID (PK)
    - MEMBER_NAME
o 会員メルマガ情報(MEMBER_MAIL_MAGAZINE)テーブル
    - MEMBER_MAIL_MAGAZINE_ID (PK)
    - MEMBER_ID (FK and UQ)
    - MAIL_MAGAZINE_TYPE (UQ) {aaa / bbb / ccc の3つ}

この場合、MAIL_MAGAZINE_TYPE = 'aaa'を指定すればone-to-oneになります。
そして、このような結合も実務では頻繁に発生します。
map:{
    ; FK_MEMBER_MEMBER_MAIL_MAGAZINE_AAA = map:{
        ; localTableName = MEMBER ; foreignTableName = MEMBER_ADDRESS
        ; fixedCondition = $$alias$$.MAIL_MAGAZINE_TYPE = 'aaa'
        ; fixedSuffix = TypeAaa
    }
}

  ↓

MemberCB member = new MemberCB();

// 'aaa'を前提とした結合
member.setupSelect_MemberAddressTypeAaa();

  ↓

from MEMBER mb
  left outer join MEMBER_MAIL_MAGAZINE mgz
    on mb.MEMBER_ID = mgz.MEMBER_ID and mgz.MAIL_MAGAZINE_TYPE = 'aaa'
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
追記:(2007/10/23)

$$alias$$ を $$foreignAlias$$ に修正しました。
ローカルテーブルのAlias名も置換したい場合に$$localAlias$$とするため、
命名の対照性を合わせました。m_m

  ※但し、$$alias$$はそのまま動作します。
  $$alias$$.equals($$foreignAlias$$))
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =

= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =
追記:(2008/12/01)

http://d.hatena.ne.jp/jflute/20081201/1228126523
にてバインド変数で扱えるようになりました。
= = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = = =