SpecifyColumn

概要

基本概念

取得カラム(select句に並べるカラム)の明示的な指定をします。

通常、ConditionBean では取得対象となったテーブルにおいて、デフォルトでは全てのカラムが取得対象(select句に並ぶ)となります。 厳密なパフォーマンスを求められる場面においては、SpecifyColumn を使うことで必要なカラムだけを取得対象(select句に並ぶ)にするようにできます。 特に、一つのテーブルにおける定義カラム数があまりに多い場合に有効です。

会話上では、すぺしふぁいからむ と表現します。

バランス指向のデータ取得ポリシー

SpecifyColumn に絡んで、DBFlute にはデータ取得の明確なポリシーがあります。

実装方法

実装の流れ

まずは通常通り、取得対象のテーブルを確定します。基点テーブルだけであれば、単に new するだけ。関連テーブルがある場合は、SetupSelect を指定。その後、SpecifyColumn を使って取得カラムを調整します。また、カラムの明示的指定をするかどうかは、テーブル単位で調整します。

基点テーブル

specify() を呼び出し、column[column-name]() で取得カラムを指定します。

e.g. 会員の名称だけを取得 (Eclipseでコード補完) @Java
MemberCB() cb = new MemberCB();
cb.sp // .sp と打って enter
--
cb.specify()
--
// 1. .c まで打つとカラム選択
// 2. MN (MemberName) で enter
//  ※JavaDocコメントにカラム定義あり
cb.specify().cMN
--
cb.specify().columnMemberName()

主キーに関しては、必須のカラムという扱いになり、明示的に指定しなくても取得対象となります(主キーを外しての検索ではできない)。 よって、例の場合、検索して取得した Entity には、会員ID(PK)と会員名称しか値が入りません。 他のカラムの get メソッドを呼び出しても null が戻ってきます。

二つ以上のカラムを指定する場合は同じ要領でメソッドを指定します。

e.g. 会員の名称と生年月日だけを取得 (Eclipseでコード補完) @Java
MemberCB() cb = new MemberCB();
cb.specify().columnMemberName();
cb.specify().columnBirthdate();

関連テーブル

まずは、通常通り、SetupSelect(Relation) を使って取得テーブルを指定します(関連が無い場合はそのまま)。その後、specify() そして、specify[relation-table]() を呼び出し、column[column-name]() で取得カラムを指定します。(specify[relation-table]() を Specify(Relation) と呼びます)

e.g. 会員ステータスは会員ステータス名称だけ取得 (Eclipseでコード補完) @Java
MemberCB() cb = new MemberCB();
cb.setupSelect_MemebrStatus();
// 1. まずは specify() を呼び出して
// 2. .sp まで打つと関連テーブル選択
// 3. MSt (MemberStatus) で enter
cb.specify().spMSt
--
cb.specify().specifyMemberStatus()
--
cb.specify().specifyMemberStatus().columnMemberStatusName()

関連テーブルの主キーも、基点テーブルと同じく必須のカラムという扱いです。 よって、例の場合、会員テーブルのカラムは全て取得し、会員ステータスは、会員ステータスコード(PK)と会員ステータス名称だけが取得されます。

SetupSelect された関連テーブルだけが Specify(Relation) 経由でカラムを指定できます。それ以外の関連テーブルのカラムを指定すると例外になります。

基点テーブルと関連テーブルの両方

e.g. 会員名称と生年月日と会員ステータス名称だけ取得 @Java
MemberCB() cb = new MemberCB();
cb.specify().columnMemberName();
cb.specify().columnBirthdate();
cb.setupSelect_MemebrStatus();
cb.specify().specifyMemberStatus().columnMemberStatus()

SetupSelect した関連テーブルに対する基点テーブルの関連カラム(FKカラム)は、必須のカラムとして明示的に指定しなくても取得対象となります。 よって、例の場合、会員の会員ステータスコードは明示的な指定はされていませんが、SetupSelect していることで取得対象となります。

子テーブル

LoadReferrer の子テーブルに対する ConditionBean の中でも SpecifyColumn は利用できます。関連をつなぐカラム(基点テーブルへの子テーブルのFKカラム)はマッピング処理で利用するため必要なのですが、 明示的に指定しなくても内部的に解決されます(@since 0.9.7.6)

e.g. 購入の購入日時だけを取得 @Java
MemberCB cb = new MemberCB();
List<Member> memberList = memberBhv.selectList(cb);
cb.query().loadPurchaseList(memberList, new ConditionBeanSetu...() {
    public void setup(PurchaseCB cb) {
        cb.specify().columnPurchaseDatetime();
        //cb.specify().columnMemberId(); 不要 (但し、旧バージョンでは必要)
        cb.query().setPaymentCompleteFlg_Equal_True(); // 支払が完了したものだけ
        cb.query().addOrderBy_PurchaseDatetime_Desc(); // 購入日時の降順で取得
    }
})

主キーだけを取得

主キーだけを取得するという場合は、通常は指定しない主キーカラムを明示的に指定します。 (SpecifyColumn を利用するというスイッチを押すようなニュアンスになります)

e.g. 会員IDだけを取得 @Java
MemberCB cb = new MemberCB();
cb.specify().columnMemberId();

UnionQuery には引き継がれる

UnionQuery と合わせて使うこともできますが、UnionQuery の中の ConditionBean にて SpecifyColumn を指定しません。SpecifyColumn の情報は UnionQuery に引き継がれます。(UnionQuery を呼び出す前に SpecifyColumn を指定します)

e.g. 会員名称だけ取得、union @Java
MemberCB cb = new MemberCB();
cb.specify().columnMemberName();
cb.union(new UnionQuery<MemberCB>() {
    public void query(MemberCB unionCB) {
        // ここで再度 SpecifyColumn はしない
        unionCB.query().set...
    }
});

共通カラムや排他制御カラムの除外

基本的には、除外指定はサポートしていませんが、共通カラムや排他制御カラムの除外(SpecifyExceptColumn)だけサポートされています。 @since 1.0.3

specify()の後に、exceptRecordMetaColumn()を指定することで、共通カラムと排他制御カラムを除いたカラムを取得することができます。 SpecifyColumnと同じく、テーブルごとの指定となります。

e.g. 会員名称だけ取得、union @Java
MemberCB cb = new MemberCB();
cb.specify().exceptRecordMetaColumn();

"業務カラムはほとんど利用するのであまり気にしないが、たくさんのテーブルを取得するので共通カラムだけは除外しておきたい" という場合に利用できます。

内部的には、共通カラムと排他制御カラム以外のカラムを SpecifyColumn することで実現しています。この除外指定を利用した場合は、SpecifyColumnを利用することはできません(同時に呼び出した場合は、例外となります)。 ただ、テーブルごとの指定なので、"このテーブルは共通カラムを除外、このテーブルはこのカラムを指定" というように使い分けることができます。

メソッド仕様

必ず SetupSelect の後に指定

呼び出しの順番の制約として、SpecifyColumn は必ず SetupSelect の後に指定します。

同カラムに対する複数回の指定

無意味ですが、特に例外にもならず、上書きとなります。

他の機能から間借りされる

SpecifyColumn は、基本的には、最上位のselect句を調整してアプリで取得するカラムを指定する機能ですが、"取得するカラムを指定" という部分の概念だけを間借りして、他の機能で利用されることがあります。例えば、(Specify)DerivedReferrer においては、サブクエリの中の(唯一の)子テーブルの導出カラムを指定するのに使われます。そのような場合においては、通常の SpecifyColumn にある SetupSelect の有無に依存する仕様などは無関係となります。

e.g. (Specify)DerivedReferrerでのSpecifyColumnの利用(間借り) @Java
MemberCB cb = new MemberCB();
cb.specify().derivedPurchaseList().max(new SubQuery<PurchaseCB>() {
    public void query(PurchaseCB subCB) {
        subCB.specify().columnPurchasePrice();
    }
});

データ取得のポリシーもバランス指向

ConditionBean のデータ取得のポリシーは以下の通りです。

テーブル
明示的に指定されたものを取得
カラム
デフォルトでは全て取得 (必要であれば明示的に指定)

まず、テーブルもデフォルトで全て取得、はあり得ません。 どの階層までの関連テーブルを取得するか、という限界的な問題もありますし、パフォーマンス劣化の温床です。 SetupSelect(Relation) を使って、明示的に取得したい関連テーブルを指定して下さい。LazyLoad に関しては、子テーブル(one-to-many)におけるポリシーと同様にサポートしません。

そして、デフォルトでカラムを明示的な指定、にしなかったのは、妥協点をどこに持ってくるかを考えた結果です。 常に必要なカラムだけを指定すればパフォーマンス上は一番良いに決まっています。ですが、カラムの指定は意外に時間の掛かるもの、 そして、実装しながらあれが必要これが必要と都度都度追加するような状況も生まれ、実はあまり効率がよくありません。 同じことは当然テーブルにも言えますが、粒度が違うのでその影響度は大きく変わります。 パフォーマンスに関しても、テーブル単位の話よりもカラム単位の話の方が影響度は遥かに少なく、不要なカラムを幾つか取得しても、 人が感じるレベルどころか、コンピュータでミリ秒単位で計っても差がでないことが多いです。 この辺のところを踏まえて、カラムに関しては、デフォルトでは全て取得、必要であれば明示的に指定、というスタンスになっています。

また、SpecifyColumn は、テーブル単位で カラムの指定ができるので、例えば名称カラムだけ利用したいけどその他カラムの数が多い関連テーブル、があった場合に、 (基点テーブルは全カラム取得で)その関連テーブルに対してだけ取得カラムを指定するようなことができます。 現場ではこういったパターンが意外に多く、カラムが指定できると一言で言っても、こういった考慮がないとやはり現場では使いづらいという話になります。 かなり悩んだ仕様、かつ、後付けの仕様(@since 0.7.4)ですが、DBFlute らしいバランスの良いものになったと思います。カラムをタイプセーフに、かつ、メソッド選択形式になっているのも DBFlute っぽいところですね。

一方で、パフォーマンスに影響が出るほど、一つのテーブルに定義カラム数が多いDB設計自体があまりお奨めではありません。 大抵の場合、ライフサイクルやカテゴリの分析をすることで、one-to-oneのテーブルに分離することができるかと思います。 本来は、SpecifyColumn が活躍する場面がないのが理想です。 この件は詳しくは別途テーマとして扱いますが、例えば、ExampleDB の会員退会情報テーブルはその典型的な例です。

DB設計 - one-to-oneの活用