ConditionBeanについて
このページでは、ConditionBeanの概念などわりと事務的な話をします。 (早く使い方を知りたい人にはじれったい内容かもなので、すぐさま "ConditionBeanの使い方" に飛んでください)
ConditionBeanとは?
プログラム上で タイプセーフ に 目的ドリブン でSQLを組み立てるための RDB指向API です。主には検索のためですが、更新系の絞り込みに利用されることもあります。こんでぃしょんびーん と呼びます。
省略表記
CB (しーびー)と略して表現されることがあります。主にクラス名などで利用されます。
自動生成される
ConditionBeanのクラスは、(関連するクラスも含めて)全てGenerateタスクにて自動生成されます。
ConditionBeanのクラス名
[Entityの名前] + CB という形式です。例えば、MEMBER なら MemberCB。
タイプセーフ
ConditionBeanはテーブル毎にクラスとして自動生成されます。 テーブル情報やfK制約情報から生成される関連テーブルの指定や条件付与のメソッドを利用することで、 カラム名やテーブル名などをタイプセーフに指定することができます(さようなら、スペルミス)。
e.g. 会員名称が[S]で始まる会員を検索(会員ステータスも取得)
MemberCB cb = new MemberCB(); // 基点テーブルは "会員"
cb.setupSelect_MemberStatus(); // 関連する "会員ステータス" も取得
cb.query().setMemberName_PrefixSearch("S"); // 会員名称が'S'で始まること
タイプセーフのメリットはさらにDB変更時に現れます。 変更の影響範囲はそのままコンパイルエラーとして検知できるため、DB変更時のデグレ防止に大きく貢献します。
目的ドリブン
以下のような目的ドリブンの思考手順を基調とします。
- 基点テーブルは何か?
- 一件検索なのか?リスト検索なのか?
- 取得したい関連テーブルは何か?
- どんな絞り込み、並び替えをしたいか?
これを検索処理を実装をする際の基本と考え、ConditionBeanではこの思考手順がそのまま実装にリンクします。 ディベロッパーはこの思考のレールに乗ってスムーズに実装に結びつけることができ、 そして、目的が実装として表現されるため "何をしたいのか" が一目瞭然のプログラムとなります。
目的ドリブンの習慣
この思考手順はとても重要だと考えています。これらポイントを無視すると、実装しながら思考の迷路に迷い込んでしまうことがあり, 意外なことにSQLが得意な人でもそういうことがあります。
SQLの実装で悩んでる人をフォローするときに "基点テーブルは何?"、"関連テーブルは何と何と何を取得したいの?" と聞くと、意外にも曖昧な答えが返ってくることがあります。SQLの要件を再整理してこれらのポイントを明確にすると、実は簡単に問題が解決することもあります。
特に、基点テーブルは何か? は、根本的な最初の分岐点です。 jfluteがSQLのフォローをするときによく聞くのが "最終的に欲しい結果セットは?" ホワイトボードに期待する検索結果を書いてもらいます。目的地がわからないのに、どの道を歩けばいいのかわからないので、まずはそこから。 検索結果のレコードの粒度が一致するテーブル を見つけましょう。
DBfluteはディベロッパーにConditionBeanを通して、これらポイントを身につけて欲しいという気持ちがあります。 (他のO/Rマッパを利用する時、SQLを実装するときにもプラスになるはずです)
目的ドリブンの堅いAPI
ConditionBeanは目的ドリブンでの実装を主にすることにより安全で堅いAPIを提供しています。
わかりやすい例を一つ挙げてみます。SQLにおける "結合"、これは手段でしょうか?目的でしょうか? その関連テーブル、何のために使うの? (この感覚とっても大切)
現場におけるほとんどの場合の結合は、以下の二つの目的のどちらかの "手段" になります:
- 関連テーブルのデータも取得したい
- 関連テーブルのカラムで絞り込み・並び替えをしたい
非依存のリレーション(NullableなカラムでのfK)における内部結合の場合は結合するだけで絞り込みが発生するので違った話になりますが、 頻繁に利用される外部結合、そして、依存のリレーション(NotNullなカラムでのfK)での内部結合は上記の目的以外の目的を持つことはほとんどありません。 ゆえに、ConditionBeanでは結合という手段を指定するメソッドはなく、代わりに上記の二つの目的を指定するメソッドが存在します。
e.g. 会員ステータスの表示順が 3 以上の会員を検索(会員ステータスも取得) @Java
// 結合先のテーブルのデータも取得したい
cb.setupSelect_MemberStatus();
// 結合先のテーブルのカラムで絞り込みをしたい
cb.query().queryMemberStatus().setDisplayOrder_GreaterEqual(3);
指定された目的を達成するために手段(結合)が必要となれば、ConditionBeanが内部で判断してその手段(結合)を利用します。
e.g. 会員ステータスの表示順が 3 以上の会員を検索(会員ステータスも取得) @SQL
select member.*
, status.* -- 関連テーブルのデータも取得したい
from MEMBER member
left outer join MEMBER_STATUS status on ...
where status.DISPLAY_ORDER >= 3 -- 関連テーブルのカラムで絞り込みをしたい
逆に解釈すると、結合せずに関連テーブルを取得するカラムがselect句に入ったり、結合せずに関連テーブルのカラムでの絞り込み条件がwhere句に入ったりなど、目的を達成出来ない不整合な状態が発生しません。
SQLと違うところはどこでしょうか? SQLは最高の自由度を持っていますが、例えばConditionBeanはfK制約で関連付いたテーブルのみを関連テーブルとして扱うことができます。 定型的に利用されるポイントに対するサポートに徹する ことで、ディベロッパーがスムーズに実装しやすい目的ドリブンで安全重視の堅いAPIを提供しています。 それで対応できない要件が発生したときにだけ、SQLの最高の自由度を発揮出来る 外だしSQL(OutsideSql) で実装するのです。
ConditionBeanの構造
全ての(自動生成される)ConditionBeanは、AbstractConditionBean クラスを継承していて、それ経由で、ConditionBean インターフェースを実装しています。
e.g. ConditionBeanのBsクラスの宣言 {BeMemberCB} @Java
public class BsMemberCB extends AbstractConditionBean {
e.g. AbstractConditionBeanの宣言 @Java
public abstract class AbstractConditionBean implements ConditionBean {
ConditionBean要素の一覧
- 関連テーブルの取得
- setupSelect...: この後(必要であれば)、with...(唯一の se 始まり)
- select句の調整的な指定
- specify(): この後、col...やderiv... (唯一の sp 始まり)
- 検索条件の指定
- query(): この後、set...やaddOrderBy... (唯一の q 始まり)
- union句の指定
- union() (唯一の u 始まり)
- or句の指定
- orScopeQuery() (唯一の o 始まり)
- カラム同士の比較
- columnQuery() (唯一の col 始まり)
- ページング検索
- paging() (唯一の p 始まり)
- 最初のn件
- fetchfirst() (ff でコード補完)
- 更新ロック
- lockforUpdate() (lf でコード補完)
- StatementConfigの指定
- configure() (唯一の con 始まり)
- 表示用SQL
- toDisplaySql() (toD でコード補完)
- PKの query
- acceptPrimaryKey(pk) (通常は、query().set[PK]_Equal...)
- PKの order-by
- addOrderBy_PK_[Asc or Desc]() (通常は、query().addOrderBy...)
- メタデータ
- getTableDbName()、getDBMeta()、... (internal)
- その他
- hasWhereClause()、hasOrderByClause()など
※具体的な使い方や機能の一覧に関しては、ConditionBeanの使い方 および ConditionBeanの機能 を参考に。
ConditionQueryとは?
基本的には、ConditionBean という言葉だけを意識していれば実装はできますが、ConditionQuery の理解もしておくと、特別な機能などを利用する際に理解が深まります。こんでぃしょんくえり と呼びます。
ConditionQueryは、ConditionBeanの中で検索条件(主にはwhere句やorder by句)を補完するオブジェクトです。 ConditionBeanはConditionQueryを複数保持しています(基点テーブル + 関連テーブルの分)。ConditionBeanで query() とメソッドを呼び出すときに戻ってくるオブジェクトは(そのテーブルの)ConditionQueryです。 但し、明示的にConditionQueryの型で変数として受け取ることはほとんどなく、内部的なメソッドをアプリケーションから明示的に呼び出すことは推奨されません。
主に意識するタイミングは、where句の再利用メソッドを定義・利用するときです。
省略表記
CQ と略して表現されることがあります。特にクラス名でよく利用されます。 例えば、MemberCB のConditionQueryは MemberCQ です。
ConditionKeyとは?
ConditionBeanにおける比較条件(演算子)のキー名称のことを示します。例えば、等値条件の Equal、大なり小なり条件の GreaterThan、LessThan、GreaterEqual、LessEqual のことです。こんでぃしょんきー と呼びます。
条件設定メソッドは、メソッド名に含まれている ConditionKey を選択して、比較条件を指定します。
e.g. ConditionBeanのEqual条件 {MEMBER_ID} @Java
cb.query().setMemberId_Equal(memberId)
SqlClauseとは?
ConditionQueryよりももっとConditionBeanの奥深くに住んでいるのが SqlClause です。CBやCQで指定された条件情報からSQLの文字列を組み立てる役割をもっています。DBMSごとの方言を吸収しているのもこのクラスです。 えすきゅーえるくろーず と呼びます。
こちらは、まずアプリケーションで意識することはないでしょう。ConditionBeanからインスタンスを取得しようと思えばできるのですが、 SqlClause のメソッドをアプリケーションから直接呼び出すことは推奨されません。