ConditionBeanのスコープ
概要
DBFluteは、DBアクセスの大きな2つのやり方を提供しています。それはConditionBeanと外だしSQL(OutsideSql)です。これら2つの存在意義に関してはここでは割愛させて頂き、いざ実装するときにこの2つのどちらを利用すれば良いのかに焦点を当てます。
当然、「ConditionBeanでできないことは外だしSQLで」という正論に間違いはありませんが、 プログラマがConditionBeanの全ての機能を頭の中で抑えていることはなかなかないかと思われます。 そこで、素早く判断するための「判断の流れ」を紹介したいと思います。
A. 明らかな外だしSQLか否かを判断
ConditionBeanのスコープを確認する前に、まずは「明らかに外だしSQLでやるべき」と言えるものを確認していきます。 こうすることで、ConditionBeanを意識する前の早い段階で判断が可能です。
以下はConditionBeanでは実現できないことが確定しているものです。
- 集計結果を取得する {Group By句}
- 独自の計算をする {max(),min(),sum(),avg(),count()以外の関数}
- カラム同士の比較条件 {foo.FOO_DATE > bar.BAR_DATE}
- テーブル構造を更新する {DDL文}
- テーブルデータを問答無用削除 {Truncate}
- テーブル同士をマージする {Merge}
- (FK制約などで)関連の全くないテーブル同士での結合
- 別テーブル同士でのUNIONもしくはUNION ALL
目的(やりたいこと)が明確であることが前提です。そのDBアクセス要件がどういう結果を欲しているのかをSQL(という手段)にとらわれず考えることが大事です。 もし、やりたいことが「テーブル検索の結果セットに子テーブル導出カラムを追加したい」であれば、 Group By句を使っても実現はできますが(インラインビュー内でGroup Byを利用)、実はSelect句での相関サブクエリでも実現でき、 しかも、(状況次第ではありますが)後者の方がパフォーマンスに相性も良く、ConditionBeanの「(Specify)DerivedReferrer」機能で実装することができます。 「Group Byという手段」が頭の中で先行しているとこれを見落としてしまう可能性があります。これはDBFluteでなくても(普通にSQLを書く上で)不利益なことです。 まず目的(やりたいこと)を明確にしてから、それを実現する(最適な)手段が上記のものかどうかを判断して下さい。
B. ConditionBeanのスコープを判断
そして、ConditionBeanのスコープからConditionBeanで実装できるか確認していきます。
「明らかな外だしSQL」を通り過ぎたこの時点ではConditionBeanで実装可能である可能性が高いです。 実際にConditionBeanを実装しながらの確認でも構いません。後に「やはり外だしSQLで実装」となっても、 そのConditionBeanの実装は無駄にはなりません。それは「C」において後述しますが、 近いところまで実装したConditionBeanから外だしSQLの土台となるSQLが生成できるからです。
以下がConditionBeanのスコープです。
Select句
-
取得するカラムは、基本的に「基点テーブルの全てのカラム」と「指定された関連テーブルの全てのカラム」
- 取得するカラムを明示的に指定することも可能 {SpecifyColumn}
- 「select count(*)」も可能 {SelectCount}
- 基点テーブルのカラムの「max(),min(),sum(),avg()」も可能 {ScalarSelect}
- 子テーブルの指定されたカラムの「max(),min(),sum(),avg(),count()」も可能 {(Speficy)DerivedReferrer}
- 指定できる関連テーブルに関してはFrom句の欄を参照
From句
- 結合の指定はSelect句とWhere句の指定から全て自動で判別されるので明示的に指定する必要はない
- 結合できる関連テーブルは無限階層まで可能
-
結合可能な関連テーブルはmany-to-oneもしくはone-to-oneの関係とテーブルのみ
- 親テーブル(many-to-one) → ex) 会員ステータス(MEMBER_STATUS)
- one-to-oneの子テーブル → ex) 会員退会情報(MEMBER_WITHDRAWAL)
- AdditionalForeignKeyのFixedConditionでone-to-oneに調整できる子テーブル → ex) 会員住所情報(MEMBER_ADDRESS)
-
結合方法は全てleft outer join限定
- inner joinと同等の絞込みはInScopeSubQueryで代替可能
-
結合先だけを絞り込む(結合前に絞り込む)
- left outer joinのon句での絞り込み
- インラインビューでの絞り込み
Where句
-
複数条件の連結は全てAnd条件
- Or句はUnionで代替可能 {Unionの詳細はUnion句の欄を参照}
-
Where句条件で以下が利用可能
- 基本演算子 {=, !=, >, >=, <, <=}
- InScope {in ('a', 'b')}
- NotInScope {not in ('a', 'b')}
- Like条件(前方/後方/中間)とエスケープ {like 'S%' escape '|'}
- IsNull / IsNotNull {is null / is not null}
- 結合先カラムでの絞り込み
-
定型的なサブクエリを使った条件
- 子テーブルの条件で絞込み {InScopeSubQuery/ExistsSubQuery}
- 子テーブル導出カラムで絞込み {(Query)DerivedReferrer}
- 基点テーブル導出カラムで絞込み {ScalarSubQuery}
OrderBy句
- 昇順ソート/降順ソート
- 複数カラムのソート
- 結合先カラムでのソート条件
- Null値を先にするか後にするかの指定 {NullsFirst/Last}
Union句
-
基点テーブル同士でのUnion
- UnionAllも可能
- Unionの数は無限
その他
- ページング検索
- 各データベース固有のページング絞り込み条件を利用
- DB2とSQLServerはOffset処理だけResultSet読み飛ばし
- カーソル検索
- コールバックを指定して一件ずつフェッチしながら処理
- マッピングコストを省略したい場合は外だしSQLカーソル検索
- 更新ロック
-
子テーブルをone-to-manyの関係のまま取得 {Behavior.LoadReferrer}
- Batchフェッチで実現(別途SQL一発で関連した子テーブルのレコードを全て取得)
- 子テーブルの絞り込み条件やソート条件はConditionBeanで使えるものと同様
- {1:n:n}というように無限階層そして枝分かれ階層のLoadが可能
-
様々なロック
- DB2の「with RR」や「with CS」などの細かいロック指定は、DBFluteプロパティの設定次第で利用可能
C. やはり外だしSQLであると判断
ここまで来たら、やはり「外だしSQLで」ということになります。
ここで、ちょっとした「外だしSQL」の実装支援を紹介します。 もし、この時点でConditionBeanで近いところまで実装済みであれば、それは消してはいけません。 以下の手順が可能だからです。
- ConditionBeanでできるところまでテスト実装
- ConditionBeanのtoDisplaySql()の戻り値をログに出力
- ログ出力されたSQL文を外だしSQLの土台として活用
このようにすることで、外だしSQLの実装にてSQLの構文やテーブル名、カラム名を一から書く必要はなくなり、 スペルミスなどのケアレスミスもなくなります。また、判断の流れの中で実装したConditionBeanは全く無駄になりません。
