ConditionBeanのスコープ

DBFluteは、大きく二つのDBアクセスのやり方を提供しています。それは ConditionBean と 外だしSQL(OutsideSql) です。ここでは "ConditionBeanのスコープ" に焦点を当てて、いざ実装するときにこの2つのどちらを利用すれば良いのか の判断基準をまとめます。

判断の流れ

当然、"ConditionBeanでできないことは外だしSQLで" という正論に間違いはありませんが、なかなか最初からプログラマが ConditionBean の機能を網羅することは難しいです。そこで、素早く判断するための判断の流れを紹介したいと思います。

ここでは、ConditionBeanによる検索に焦点を当てています。更新に関しては別途まとめています。

A. 概念的な違いで判断

ConditionBeanの細かい機能を確認する前に、まずはそれぞれが持つ概念的なスコープを確認することで早い段階で棲み分けが判断できます。 やはり基本中の基本、基点テーブルは何? に着目しています。

基点テーブルが定まる検索

例えば、会員一覧は会員IDでユニークになり、つまり、基点テーブルは会員です。 また、購入一覧のユニーク性は購入ID(もしくは、会員ID、商品ID、購入日時)です。 会員ごとの購入状況の集計(合計購入価格など)を検索という場合、"会員ごと" と言っているので購入IDではなく会員IDでユニークになり、基点テーブルはやはり会員です。 (集計とか購入とかという言葉に惑わされてはいけません)

会員一覧
会員IDでユニーク、基点テーブルは会員
購入一覧
購入ID(会員ID、商品ID、購入日時)でユニーク、基点テーブルは購入
会員ごとの購入状況の集計
会員IDでユニーク、基点テーブルは会員

さて、あなたの検索における 結果セットのユニーク性、つまり、基点テーブル はなんでしょう?

基点テーブルが定まらない検索

例えば、"月ごとの購入状況の集計(最大購入金額など)" というような場合、元になっている情報は購入テーブルですが、 結果セットのユニーク性は購入IDではなく (購入)月ごと です。そのようなユニーク性を持ったテーブルはどこにも存在しないため、厳密には基点テーブルは存在していないと言えます。 プログラムの管理上、"あえて言うなら" と曖昧に基点テーブルを購入と定めることはありますが、それは厳密な基点テーブルではありません。

"会員と月ごとの購入状況の集計" と会員という要素が入ったとしても、あくまで 会員と月ごと でユニークとなるわけで、一致するユニーク性は会員でも購入でもありません。 実際にSQLでは、購入日時の年月部分を抜き出して購入月という実際のテーブル上には存在しない要素で結果セットを構築するでしょう。

月ごとの購入状況の集計
購入月でユニーク、基点テーブルはスキーマ上にはない
会員と月ごとの購入状況の集計
会員IDと購入月でユニーク、基点テーブルはスキーマ上にはない

このような 結果セットのユニーク性がスキーマ上のテーブルと一致しない検索は外だしSQL です。

ConditionBeanのスコープ

もう少しぼかした言い方をすると、基点テーブルが厳密に定まるものは ConditionBean のスコープで、曖昧にしか定まらないものは確実に外だしSQLで実装します。

その違いが、受け取り Entity の違いに現れます。ConditionBean はオブジェクトグラフのDomainEntity なのに対して、外だしSQLは(基本的に)フラット構造の CustomizeEntity になります。

例外が一つ、(一般的な)集計関数を使ったスカラ値の検索は、ConditionBean のスコープです。結果セットの基点テーブルという概念がそもそも存在しない検索形式ですが、 ConditionBean の絞り込み条件を利用してとあるカラムの最大値や合計値などを検索することができます。

結果セットのユニーク性を明確に

結果セットのユニーク性を(実装前に)明確にすることは、DBFluteの話に限らず、検索というDBアクセスを実装する上でとても重要なことです。 例えば、(外だしSQLなどで)SQLを直接書くような場合においても同様です。

SQLは、非常に自由度の高い言語で、一つの目的を色々な手段で実現することができます。 これはとても便利な特徴ですが、逆に言うと最適な手段をしっかり見つけることが大事になってきます。 目的を明確にせずにいきなりSQLを書き始めても、それはSQLの構文(文法)から目的を探すのと同義であり、試行錯誤を繰り返す実装になりがちです。 また、行き当たりばったりで見つけた手段で実装してしまい、(パフォーマンスやわかりやすさなどの面で)最適な別の手段を見失なってしまう可能性もあります。

"結果セットのユニーク性を明確にすること" と "ConditionBeanの目的ドリブンの思考手順" は、非常に密接な関係にあり、一番最初のプロセスである "基点テーブルは何?" は、(ユニーク性の明確化において)とても重要な要素となります。 DBFluteを利用することが、(できるだけ)こういった意識が自然と身に付くことにつながればと考えています。

ちなみに、"結果セットのユニーク性" を "レコードの粒度" と呼ぶこともあります。基本的にはドキュメント上は前者で表現していますが、どちらも同じことを意味しています。

B. ConditionBeanでとりあえず実装

"概念的な違いで判断" を通り過ぎたこの時点では ConditionBean で実装可能である可能性が高いです。実際に ConditionBean を実装しながら確認してみましょう。後に "やっぱり外だしSQLで実装" となってしまっても、その ConditionBean の実装は無駄にはなりません。後述しますが、近いところまで実装した ConditionBean から外だしSQLの土台となるSQLを生成 できるからです。

目的ドリブンで実装

ConditionBean は目的ドリブンでSQLを組み立てるためのオブジェクトです。"ConditionBeanの機能" と、DBアクセスの目的(やりたいこと)をマッチングさせて、実装していきましょう。

C. やはり外だしSQLであると判断

ここまで来たら、やはり "外だしSQLで実装" ということになります。

もし、この時点で ConditionBean で近いところまで実装済みであれば、それは消してはいけません。 以下の手順で外だしSQLの実装の土台にして下さい。

  1. ConditionBean でできるところまでテスト実装
  2. ConditionBean の toDisplaySql() の戻り値をログに出力
  3. ログ出力されたSQL文を外だしSQLの土台として活用
ConditionBean - ToDisplaySql

SQLの構文やテーブル名、カラム名を一から書く必要はなくなり、スペルミスなどのケアレスミスも少なくなります。 判断の流れの中で実装したConditionBeanは無駄にはならないのです。

ConditionBeanによる更新での判断

ConditionBeanの絞り込み条件部分だけを利用した更新や削除(queryUpdate() や queryDelete())においては、まずは更新値(update文のset句)の表現における制約を確認し、明らかに外だしSQLなのかどうか判断します。 その制約は、Entityを利用した更新において共通のものです。

絞り込み条件に関しては、ConditionBeanによる検索の時と基本的に判断は同じなので、同ページの内容を参考にします。 但し、DBMS によって利用できる条件に制限がありますので(例えば、MySQL)、あわせてDBMSごとの取扱いも必ず目を通しましょう。