additionalForeignKeyMap
additionalForeignKeyMapとは?
実質的なFK(ForeignKey)の関連がありながらも実際には(FK)制約が存在しないテーブルおよびビューなどにおいて、 DBFluteが生成するEntityにて関連として扱えるようにする DBFluteプロパティ。DBFluteクライアントの dfprop 配下の additionalForeignKeyMap.dfprop という名前のテキストファイルです。主に Generateタスク を実行する際に参照され、実際にFK制約が存在しないテーブルでも、このプロパティの設定を行うことで関連を持ったEntityを生成できます。
FK制約がない場合のDBFlute
FK制約がない場合(かつ、このプロパティを利用しない場合)は、ConditionBeanやEntityなどにおいてその関連を扱うことができません。例えば "ConditionBeanで会員を基点として会員ステータスも一緒に取得" というような検索ができません。FK制約自体に多くのメリットがあるため、基本的にはFK制約がしっかりDB側に付与されていることが推奨されます。
主な利用パターン
- FK制約は存在しないが実質的にFKである
- 基本的にはFK制約がしっかりDB側に付与されていることが推奨されますが、 DB側の事情によってFK制約が存在しない場合(かつ、実質的なFKの関連がある場合)にこのプロパティが有効です。
- ビューなどそもそもFK情報を持たないオブジェクト
- 参照先のテーブルなどがFK制約を持っている場合(ビューの構造的・要件的にその関連を扱いたい場合)にこのプロパティが有効です。
- "業務的one-to-one" となるような関連
- 業務的one-to-one となるような擬似FKの設定も可能です。ExampleDBにおける 会員(MEMBER)テーブル と 会員住所情報(MEMBER_ADDRESS)テーブル を例として、会員と会員住所情報の関係において "指定された日付が有効期間内であればone-to-one" というような業務的な制約がある場合に、その固定の条件(fixedCondition)を付与することで "擬似FK" を設定することが出来ます。これはつまり ConditionBean や Entity において、会員から会員住所情報を(固定条件付きの結合を利用して)参照できることを意味します。その固定条件にはバインド変数が利用できます。
プロパティ
map型プロパティ で、FKの名前 とそれに対するテーブル名とカラム名を定義します。
additionalForeignKeyMapの仕様 @additionalForeignKeyMap.dfprop
map:{
; [FK-name] = map:{
; localTableName = [local-table-name]
; foreignTableName = [foreign-table-name]
; localColumnName = [local-column-name for FK]
; foreignColumnName = [foreign-column-name for PK (or UQ)]
; fixedCondition = [fixed-condition for "on" clause of join]
; fixedSuffix = [fixed-suffix]
; fixedInline = [true or false]
; fixedReferrer = [true or false]
; fixedOnlyJoin = [true or false]
; suppressJoin = [true or false]
; suppressSubQuery = [true or false]
}
; ...
}
以下、(*)の付いたプロパティは必須です。
FK-name (*)
FKの名前を指定します。mapのキーになっていて、ユニークである必要があります。 習慣的に、実際にDBに存在するFK制約を含めてもユニークな名前(かつ、大文字で "FK_" で始まるもの)を付けることをお奨めします。
localTableName (*)
FK制約を設定するテーブル名を指定します。
- 値候補
- テーブルの名前 (自動生成対象になっていること) or $$ALL$$
- デフォルト
- なし
- 補足
-
- 大文字小文字の区別なし (ただし実体と合わせることを推奨)
$$ALL$$ と指定すると、全てのテーブルの localColumnName と同じ名前のカラムに対して一括設定します。FKカラムの名前が統一的な場合に有効です。@since 0.9.7.1
foreignTableName (*)
FKの参照先テーブル名を指定します。
- 値候補
- テーブルの名前 (自動生成対象になっていること)
- デフォルト
- なし
- 補足
-
- 大文字小文字の区別なし (ただし実体と合わせることを推奨)
localColumnName
FKとして扱うカラム名を指定します。
- 値候補
- カラムの名前 (自動生成対象になっていること)
- デフォルト
- 参照先テーブルのPKカラム名
- 補足
-
- 大文字小文字の区別なし (ただし実体と合わせることを推奨)
- 参照元テーブルのFKカラム名と参照先テーブルのPKカラム名と同一の場合は省略可能
- fixedOnlyJoin が true のときは省略可能 @since 1.0.5M
- 複合FKの場合は、"/" (スラッシュ)区切りで複数のカラムを指定 (foreignColumnNameの順番と合わせること)
foreignColumnName
FKの参照先として扱うカラム名を指定します。
- 値候補
- カラムの名前 (自動生成対象になっていること)
- デフォルト
- 参照元テーブルのFKカラム名
- 補足
-
- 大文字小文字の区別なし (ただし実体と合わせることを推奨)
- 参照元テーブルのFKカラム名と参照先テーブルのPKカラム名と同一の場合は省略可能
- fixedOnlyJoin が true のときは省略可能 @since 1.0.5M
- 複合FKの場合は、"/" (スラッシュ)区切りで複数のカラムを指定 (localColumnNameの順番と合わせること)
文字列と数値の間であれば、localColumnName とデータ型の違うカラムを foreignColumnName として指定することができます(@since 0.9.9.0C)。 推奨されませんが、DBの構造上どうにもならない場合に有効です。ただし、正常に動作するかどうかは DBMS の暗黙の型変換の仕様次第となります。 また、この場合は TwoEdgedSword 認定となるため、ケースバイケース(DBFlute の他の機能との相性など)で利用できないことがあります。
fixedCondition
絞り込み条件を付けることでFKとして参照可能な関連における場合の固定条件をSQLで指定します。
- 値候補
- 条件を記述したSQL (join句の "on句" に追加される条件式)
- デフォルト
- 固定条件なし
- 補足
-
- "$$foreignAlias$$" を参照先テーブルのエリアスとして利用可能
- "$$localAlias$$" を参照元テーブルのエリアスとして利用可能
- "$$over([テーブル名].[リレーション名])$$" で別リレーションのテーブルのエリアスとして利用可能
バインド変数コメント
値の中では、バインド変数コメントを利用して、バインド変数を利用した固定条件にすることができます。 ただし、外だしSQLのバインド変数コメントとは違うところがあって、その変数のプログラム型を、"変数名([プログラム型])" という形式で(必ず)指定します。パッケージ名の解決は、ParameterBeanにおけるプログラム型のパッケージ解決と同じです。 (例えば、java.util.DateはDateでOK)
e.g. targetDate という変数名でjava.util.Date型のバインド変数 @additionalForeignKeyMap.dfprop
$$foreignAlias$$.VALID_BEGIN_DATE <= /*targetDate(Date)*/null
e.g. memberStatus という変数名でCDef.MemberStatus型のバインド変数 @additionalForeignKeyMap.dfprop
$$foreignAlias$$.MEMBER_STATUS_CODE <= /*memberStatus($$CDef$$.MemberStatus)*/null
固定条件で利用する条件値をアプリから指定したい場合に有効です。この条件値は、ConditionBeanの該当リレーションの SetupSelect や Query メソッドの引数で指定できます。
e.g. ConditionBeanで targetDate を指定 @Java
Date currentDate = ...;
cb.setupSelect_MemberAddressAsValid(currentDate);
埋め込み区分値コメント
埋め込み区分値コメントで(定義された)区分値を参照して、コード値を条件に埋め込むことができます(バインド変数ではない)。 埋め込みであることを示す、"$" を付けた後に "cls([区分名.要素名])" という形式で指定します。
e.g. Flg 区分値の True に対応するコード値を埋め込まれた条件に @additionalForeignKeyMap.dfprop
$$foreignAlias$$.VALID_FLG <= /*$cls(Flg.True)*/
e.g. 実行されたときのSQL上の埋め込まれた条件 @ExecutedSql
$$foreignAlias$$.VALID_FLG <= 1
区分値要素ではなく、区分値のグループを指定することもできます(@since 1.0.5L)。 InScope(in)の条件で使うことを想定しています。
e.g. MemberStatus 区分値の serviceAvailable グループを指定 @additionalForeignKeyMap.dfprop
$$foreignAlias$$.MEMBER_STATUS_CODE in /*$cls(MemberStatus.serviceAvailable)*/
e.g. 実行されたときのSQL上の埋め込まれたグループ条件 @ExecutedSql
$$foreignAlias$$.MEMBER_STATUS_CODE in ('FML', 'PRV')
IFコメント (@since 0.9.9.1C)
バインド変数の値に対してIFコメントを付与して、例えばバインド変数の有無によって条件を有効にしたり無効にしたりできます(@since 0.9.9.1C)。 業務的one-to-oneを実現する上で必須でない固定条件がある場合に有効です。
例えば、targetDate というバインド変数を定義していた場合、外だしSQLと同じ形式のIFコメントの中ででは、"$$parameterBase$$.targetDate" という記述でそのバインド変数を参照することができます。$$parameterBase$$ という変数がそのバインド変数を保持するオブジェクトまでのパスに置き換わります。
e.g. targetDate というバインド変数に対するIFコメント @additionalForeignKeyMap.dfprop
/*IF $$parameterBase$$.targetDate != null*/
$$foreignAlias$$.VALID_BEGIN_DATE <= /*targetDate(Date)*/null
/*END*/
バインド変数を利用していない場合は $$parameterBase$$ は利用できません。一つでもバインド変数があれば利用できます。 (埋め込み区分値はここで言うバインド変数には含まれません)
また、IFコメントで区分値に対する判定を行うこともできます(@since 1.1.9)。 IFコメントの条件式にて、バインド変数で指定された区分値の code() の等値条件の比較対象として、$cls([区分値要素]) を指定します。 (code()メソッドを忘れずに!)
e.g. IFコメントで埋め込み区分値との比較、Bronzeならポイントが100以上 @additionalForeignKeyMap.dfprop
$$foreignAlias$$.SERVICE_RANK_CODE = /*serviceRank($$CDef$$.ServiceRank)*/
/*IF $$parameterBase$$.serviceRank.code() == $cls(ServiceRank.Bronze)*/
and $$foreignAlias$$.SERVICE_POINT_COUNT >= 100
/*END*/
一方で、ここ dfprop におけるIFコメントの表現はかなり限定的です。基本的には外だしSQLのIFコメントとして扱われるので、表現力はそれと全く同じです。 少し dfprop の都合による調整が行われていて、$$parameterBase$$ や $cls() などを利用できるようになっていますが、必要性が生じた時に積み上げで対応しているので、ちょっと違うパターンだと動かないこともあるでしょう。
改行をうまく活用 @since 0.9.9.1B
fixedCondition で指定した値の中の改行は、SQL上で反映されます(@since 0.9.9.1B)。 改行後のインデントはそのままSQLに反映されます。その特性を活かしてSQL上でいい感じにフォーマットされるように調整して書くと良いでしょう。
例えば、二つ目以降の条件がある場合は、一つ目の条件の前は 9 個の半角空白、二つ目以降の条件の前、つまり and の前には 5 個の半角空白を入れると良いでしょう。この場合、一つ目の条件は、先頭の空白はトリムされるので(fixedConditionプロパティの値自体はトリムされるため)、 どこに定義してもSQL上は変わりませんので、二つ目以降の条件に合わせてみやすく定義しておくと良いでしょう。
e.g. 会員から会員住所情報への有効期間条件付きのFK @additionalForeignKeyMap.dfprop
map:{
; FK_MEMBER_MEMBER_ADDRESS_AS_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
}
}
サブクエリがある場合などは、サブクエリの開始と終了マーク $$sqbegin$$, $$sqend$$ を指定することで、アプリ実行時のSQLにおいてサブクエリのインデントが綺麗に調整されます(@since 0.9.9.4A)。 この場合、全体的に左にベタッと寄せた上でそれぞれの行(from,whereなど)のインデント調整をすると良いでしょう。
e.g. 会員から直近の会員ログイン情報へのFK @additionalForeignKeyMap.dfprop
map:{
; FK_MEMBER_MEMBER_LOGIN_LATEST = map:{
; localTableName = MEMBER ; foreignTableName = MEMBER_LOGIN
; localColumnName = MEMBER_ID ; foreignColumnName = MEMBER_ID
; fixedCondition =
$$foreignAlias$$.LOGIN_DATETIME = ($$sqbegin$$
select max(LOGIN_DATETIME)
from MEMBER_LOGIN
where MEMBER_ID = $$localAlias$$.MEMBER_ID
)$$sqend$$
; fixedSuffix = AsLatest
}
}
旧バージョン:改行は保たれない @until 0.9.9.1A
fixedCondition で指定した値の中の改行は、SQL上には反映されません(@until 0.9.9.1A)。よって、プロパティ記述上の都合で(見やすいように)改行しても構いません。 (二行目以降は、空白インデントを一つ入れることで、前の行の最後との間に空白が入るようになります)
fixedSuffix
関連名を修飾する Suffix です。自動生成されるメソッド名などに付与されます。例えば "AsValid" と指定した場合、"cb.setupSelect_...AsValid()" となります。
fixedCondition を利用したリレーションに 業務的な意味を付与 するのに有効です。
- 値候補
- 任意の文字列 (関連の業務的な表現に適していること)
- デフォルト
- なし
- 補足
-
- 基本的には大文字始まりのキャメルケース
fixedInline @since 0.9.9.4A
固定条件(fixedCondition)をインラインビューに展開するか否かを指定します。 通常は、on 句に展開されますが、この設定を true にするとインラインビュー(from句のサブクエリ)に展開されます。
主に、導出レコードに対するリレーション(導出的one-to-one)で、DBMSの文法上の制約回避に利用します。
- 値候補
- true or false
- デフォルト
- false
fixedReferrer @since 0.9.9.7A
固定条件(fixedCondition)を付与した時も、逆参照のリレーションを付与するか否かを指定します。 通常は、固定条件がある場合は逆参照は付与されませんが、この設定を true にすると付与されます。
主に、基点テーブルを絞り込むことで成立させる業務的many-to-oneのときに利用します。
- 値候補
- true or false
- デフォルト
- false
fixedOnlyJoin @since 1.0.5M
fixedCondition だけで 1:1 になるか否かを指定します。true にすると、localColummName と foreignColumnName を省略することができます。例えば、消費税のような特定のリレーションを持たず日付だけで管理されているようなテーブルと業務的one-to-oneを結ぶことができます。
- 値候補
- true or false
- デフォルト
- false
suppressJoin @since 1.0.5G
このリレーションによる ConditionBean での join, つまり SetupSelect(Relation) や Query(Relation) などのメソッド生成を抑制するか否かを指定します。
このプロパティは、既に物理FKが存在する場合でも反映されます。 パフォーマンス上の都合などで、とあるテーブルととあるテーブルでは物理的に結合をしたくない場合に有効です。 (利用には細心の注意を、結合が必要なのに抑制されているとディベロッパーが混乱します)
- 値候補
- true or false
- デフォルト
- false
suppressSubQuery @since 1.0.5G
このリレーションによる ConditionBean での SubQuery, つまり ExistsReferrer や DerivedReferrer などのメソッド生成を抑制するか否かを指定します。
このプロパティは、既に物理FKが存在する場合でも反映されます。 パフォーマンス上の都合などで、とあるテーブルととあるテーブルでは物理的に SubQuery でつなげたくない場合に有効です。 (利用には細心の注意を、SubQueryが必要なのに抑制されているとディベロッパーが混乱します)
- 値候補
- true or false
- デフォルト
- false
comment
そのリレーションシップにコメントを付与します。JavaDocなどに利用されるので、業務的な説明や利用上の注意などを書いておくと良いでしょう。
- 値候補
- 文字列
- デフォルト
- null
e.g. commentで業務的な説明を入れておこう @additionalForeignKeyMap.dfprop
; FK_MEMBER_MEMBER_ADDRESS_AS_VALID = map:{
; localTableName = MEMBER ; foreignTableName = MEMBER_ADDRESS
; localColumnName = MEMBER_ID ; foreignColumnName = MEMBER_ID
; fixedCondition = ...
; fixedSuffix = AsValid
; comment = ここはコメントだよぅ
}
dfpropファイルの分割
ある程度は工夫...
この dfprop は、しっかり使いこなし始めると、かなり大きなファイルになる可能性があります。
ある程度は、改行を工夫して、ファイルが長くならないようにしますが...
e.g. あまり縦長にしないようにいいかんじに @additionalForeignKeyMap.dfprop
; FK_MEMBER_MEMBER_ADDRESS_AS_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
; comment = "ここはコメントだよぅ"
}
ぜんぜん変わりませんね。。。見やすさをキープするためには、既にこれが限界。
ファイルを分割しよう
かなり大きくなってきたら、会員系、商品系、レポート系など、カテゴリごとにファイルを分割すると良いでしょう。
e.g. カテゴリごとにファイルを分割 @Directory
dfprop
|-...
|-additionalForeignKeyMap_land.dfprop // ランド系
|-additionalForeignKeyMap_sea.dfprop // シー系
|-additionalForeignKeyMap.dfprop // 本体
|-...
e.g. ファイル分割の設定を本体のdfpropにて @classificationDefinitionMap.dfprop
map:{
; $$split$$ = map:{
; land = dummy
; sea = dummy
}
# 本体の dfprop にも定義はできる
; FK_MEMBER_MEMBER_ADDRESS_AS_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
; comment = "ここはコメントだよぅ"
}
...
}
dummyは、将来拡張のための固定値です。
EMechaによるキー重複チェックが、ファイル間では効かないので設定ミスに注意しましょう。
Example
e.g. 会員から会員ステータスへのFK @additionalForeignKeyMap.dfprop
; FK_MEMBER_MEMBER_STATUS_CODE = map:{
; localTableName = MEMBER ; foreignTableName = MEMBER_STATUS
; localColumnName = MEMBER_STATUS_CODE ; foreignColumnName = MEMBER_STATUS_CODE
}
e.g. カラム名が同じ場合のカラム名指定の省略 @additionalForeignKeyMap.dfprop
; FK_MEMBER_MEMBER_STATUS_CODE = map:{
; localTableName = MEMBER ; foreignTableName = MEMBER_STATUS
}
e.g. 会員から会員ステータスへの複合FK (会員ステータスコードと生年月日でFKの場合) @additionalForeignKeyMap.dfprop
; FK_MEMBER_MEMBER_STATUS_CODE = map:{
; localTableName = MEMBER ; foreignTableName = MEMBER_STATUS
; localColumnName = MEMBER_STATUS_CODE/BIRTHDATE
; foreignColumnName = MEMBER_STATUS_CODE/BIRTHDATE
}
e.g. 会員から会員ステータスへの有効フラグ条件付きのFK @additionalForeignKeyMap.dfprop
; FK_MEMBER_MEMBER_STATUS_CODE_AS_VALID = map:{
; localTableName = MEMBER ; foreignTableName = MEMBER_STATUS
; localColumnName = MEMBER_STATUS_CODE ; foreignColumnName = MEMBER_STATUS_CODE
; fixedCondition = $$foreignAlias$$.VALID_FLG = 1
; fixedSuffix = AsValid
}
e.g. 会員から会員住所情報への有効期間条件付きのFK @additionalForeignKeyMap.dfprop
; FK_MEMBER_MEMBER_ADDRESS_AS_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
}
e.g. 最終ログインのレコードに対する業務的one-to-one @additionalForeignKeyMap.dfprop
; FK_MEMBER_MEMBER_LOGING_LATEST = map:{
; localTableName = MEMBER ; foreignTableName = MEMBER_LOGIN
; localColumnName = MEMBER_ID ; foreignColumnName = MEMBER_ID
; fixedCondition =
$$foreignAlias$$.LOGIN_DATETIME = ($$sqbegin$$
select max(login.LOGIN_DATETIME)
from MEMBER_LOGIN login
where login.MEMBER_ID = $$foreignAlias$$.MEMBER_ID
)$$sqend$$
; fixedSuffix = AsLatest
}
e.g. とある関連はつながらないように @since 1.0.5G
FK制約はあるけど、そのテーブルとそのテーブルは結合やサブクエリでコラボしないようにする。
e.g. 会員ログインと会員は一緒には過ごせない @additionalForeignKeyMap.dfprop
; FK_MEMBER_LOGIN_MEMBER = map:{
; localTableName = MEMBER_LOGIN ; foreignTableName = MEMBER
; localColumnName = MEMBER_ID ; foreignColumnName = MEMBER_ID
; suppressJoin = true ; suppressSubQuery = true
}
実際にFK制約が無い場合でも、SchemaHTMLなどのドキュメント上はリレーションを表現させつつ、でも結合やサブクエリでのコラボを抑制したい場合にも利用できます。
e.g. 誰からも参照されないように @since 1.0.5G
FK制約はあるけど、そのテーブルが誰からも結合やサブクエリされないようにする。 (厳密には、参照元のカラム名に合致するリレーションにおいてのみ適用される。カラム名が違うリレーションは別途定義を追加する必要がある)
e.g. (MEMBER_ID経由での)会員に対する結合やサブクエリはできない @additionalForeignKeyMap.dfprop
; FK_ALL_MEMBER_ID_MEMBER = map:{
; localTableName = $$ALL$$ ; foreignTableName = MEMBER
; localColumnName = MEMBER_ID ; foreignColumnName = MEMBER_ID
; suppressJoin = true ; suppressSubQuery = true
}