空文字の取扱い
概要
空文字と null の区別の問題は、非常にややこしい問題です。"値が無いこと" を何で表現するか? "(確かに存在する)空っぽの値" というのを業務的に取り扱うか(取り扱う必要があるか)? 業務的な意味合いでの話に加えて、実装上の利便性が絡んできます。 それも、プログラミング言語上での話、SQL上での話、DBMSごとの仕様の違いなど、それぞれ特徴があり、一律の答えを求めるのが難しくなっています。 様々な議論がありますが、こういった状況から、DBFluteとして "明確な答えが(まだ)存在していない状態、もしくは、明確な答えが出しづらい状態にあるため、ポリシーがプロジェクトによって様々である" という風に解釈をしています。
とはいえ、DBFluteとしては避けて通れない部分ではあるので、"デフォルトのポリシー" を打ち出し、プロジェクトにそぐわない場合はアプリケーションにて(プロパティなので)調整してもらう、 というスタンスをとっています。
デフォルトのポリシー
デフォルトの想定
まずは、DBFluteはある状況が(良いかどうかは別にして)比較的多い状況であると想定しています。
- 空文字に業務的な意味があることが少ない
- 例えば、会員名称に空文字が入ったり、会員ステータスコードに空文字が入ったりすることは、業務上考えにくいものです。 "値はあるけど、空っぽである" ということに業務的な意味が付与されることは(少なくとも全体からみて)多くはないと考えています。
- "値がないこと" を示すのには null が利用される
- もちろん、概念的な話は置いて、実装上の null の取扱いに注意が必要な場面があるのは事実ですが、"プログラミング言語上、SQL上、DBMSごとの仕様の違い" など、それぞれの事情の影響から、null で表現されることが多いと考えています。 (もちろん、自分たちの組織ではそうではない(組織統一)、と人によってこのように考えない場合もありますが、 これまでのDBFluteの(組織はそれぞれバラバラな)利用者たちの反応などを総合的に解釈して、というところです)
デフォルトの仕様
これらを踏まえて、DBFluteでは以下のようなデフォルトの仕様があります。
- 検索データ
- DBに格納されているそのままを検索
- 登録・更新データ
- 設定された値そのままを登録・更新
- 検索条件の値
- 空文字は null と同じ扱い(条件設定が無効になる)
"検索データ" および "登録・更新データ" の "業務データそのもの" に関しては、アプリ上で取り扱ったそのままの値が保たれることを重視 しているため、フィルタなどは掛けていません。(これは空文字の話に限りません)
一方で、検索条件の値(条件値)に関しては、利便性を重視 しているため、フィルタを掛けて、空文字も null と同じように、"その設定された条件は無効な条件である" と判定します。ConditionBeanの Query による条件設定や、ParameterBeanでのIFコメントでの null チェックなどが該当します。プログラミング言語上では、少なくとも業務データ以外のデータに関しては、空文字は null と同じく "値がないこと" として取り扱うことも多く、隣接するフレームワークなど仕組みや実装の仕方次第で、 "値がないこと" を示すのに空文字が来るのか null が来るのか様々です。 そして、さらにそもそも空文字で等値条件を利用することが業務的に少ないという状況から、検索条件の値はデフォルトではフィルタされるようにしています。
もちろん、あくまである特定の状況を想定したデフォルトの挙動であり、全てのカラムがそうではない場合もあるし、 そもそもポリシーが違うという場合もあります。そういうときのために、空文字を活かせるようにするオプションがあります(後述)。 (また、逆に空文字を完全に排除するオプションもあります)
少なくともお奨めでないこと
少なくとも、以下のことが、明確になっていない、もしくは、DBスキーマ全体で統一されていない、という状況はお奨めできません。 必ず、(アプリ実装が始まる前の)DB設計の時点で明確にしておくことが大事です。 DBFluteのオプションの利用は、統一的なポリシーが存在することが前提 です。
- DB上で業務的に空文字をどう扱うか?
- 空文字に意味を持たせる(空文字が業務的にありえる)カラムは何か?
- "値がないこと" を(DBスキーマ全体で)何で表現するのか(null or empty)?
空文字による条件
ConditionBean
まず、"null もしくは 空文字" という条件にしたい場合は、IsnullOrEmpty で実現できます。
単独で空文字の等値を行いたい場合、ConditionBean の enableEmptyStringQuery() を呼び出すことで、その ConditionBean においてのみ空文字条件が許可されます。
(Java6版の1.0.x系だと...) allowEmptyStringQuery() になります(@since 0.9.7.8)。
また、プロジェクト全体で一律の設定として空文字条件を許可したい、というような場合は DBFluteConfig を利用します(@since 0.9.7.8)。
カラム限定で空文字条件を実現したい場合は、ConditionQuery のExクラスに、空文字による等値条件を設定するメソッドを(手動で)作成します。 明示的で可読性が良いため、空文字利用対象のカラムが限定できる場合はこのやり方が推奨されます。 (そこまで言うほど、DBFluteでは空文字が取扱いを間違えるとカオスを生み出す原因になると想定しています)
例えば、空文字等値条件を行うメソッドを作成するとしたら、そのメソッド内では doSet[column-name]_Equal(value) を呼び出し、引数には固定で空文字を指定します。メソッド名は任意ですが、習慣的に set[column-name]_Equal_EmptyString() をお奨めします。また、非等値条件も同じ要領で作成します。
e.g. ConditionQueryで空文字による等値条件メソッドを定義 {MEMBER} @Java
public class MemberCQ extends BsMemberCQ {
...
public void setMemberName_Equal_EmptyString() {
doSetMemberName_Equal("");
}
}
e.g. 空文字による等値条件メソッドを利用 {MEMBER} @Java
MemberCB cb = new MemberCB();
cb.query().setMemberName_Equal_EmptyString();
...
自動生成で一括作成
ConditionQueryの空文字による等値条件メソッドを一括で自動生成することもできます。DBスキーマ全体で、"値がないこと" を空文字で取り扱ってる(例えば、全てのカラムがNotnull制約の)場合や、
littleAdjustmentMap.dfprop の isMakeConditionQueryEqualEmptyString を true に設定すると、全ての文字列のカラムに対して空文字による等値条件メソッド、および、非等値条件メソッドが自動生成されます。 DBスキーマ全体のポリシーとして、"値がないこと" を空文字で表現する(例えば、全カラムがNotnull制約の)場合に有効です。
さらに、そのプロパティを利用した状態で、IncludeQuery を利用して、空文字による等値条件の自動生成対象カラムを指定することができます。 DB設計の時点で、空文字に業務的な意味が存在するカラムに対して等値条件をすることがわかってる場合に有効です。 無駄な(大量の)メソッドは、ディベロッパーにとって紛らわしいのと、コンパイル速度の劣化に繋がります。
ParameterBean
get メソッドでフィルタが掛かっているため、ParameterBeanのExクラスにて、isEmptyStringParameterAllowed() メソッドをオーバーライドして、true を戻すことでフィルタが無効になります(@since 0.9.7.8)。 ただし、(DBFluteアップグレード時の)万が一の仕様変更のために、必ず単体テストを書くようにして下さい。
e.g. ParameterBeanで空文字をそのまま扱うようにする @Java
public class SimpleMemberPmb extends BsSimpleMemberPmb {
...
@Override
protected boolean isEmptyStringParameterAllowed() {
return true;
}
}
ただしこの場合、IF コメントでよく利用される "FOO != null" という条件は、空文字が指定された場合に true になりません(通常は、null に変換されるので true になる)。空文字の場合にも true になるようにしたい場合は、空文字を意識した条件に変更する必要があります。
その他様々なパターンでの利用がある場合は、Exクラスで自由にメソッドを拡張して実現できます。 ただし、ここでも同じですが、(DBFluteアップグレード時の)万が一の仕様変更のために、必ず単体テストを書くようにして下さい。
また、プロジェクト全体で一律の設定として空文字条件を許可したい、というような場合は DBFluteConfig を利用します(@since 0.9.7.8)。
Entityで空文字を null に変換
もし、DBスキーマ上の全てのカラムにおいて、空文字を業務的に取り扱わない仕様で、もし空文字を設定されたら null として処理したい、ということが 確定している 場合、littleAdjustmentMap.dfprop の isEntityConvertEmptyStringTonull を利用することで、Entityの中の空文字を null に変換することができます。
これにより、"検索データ" および "登録・更新データ" の "業務データそのもの" においても、空文字を null として取り扱うことができ、実装や仕組み上の "まぎれ" も防ぐことができます。
但し、Entityを経由しないスカラ検索(スカラ値としてデータを受け取る検索)においては、変換処理は掛からないため、 DB上のカラムの値が空文字だった場合は、そのまま空文字が取得されます。
Exampleのススメ
dbflute-sqlite-example では、実際に空文字による等値条件を利用したExampleがあります。
また、dbflute-guice-example では、実際に "Entityで空文字を null に変換" を利用したExampleがあります。