使えるO/Rマッパーの見分け方
SQL以外直さなくていいはずなのに
某ファミレスの業務システムはこんなデータ構造を持っていた。
商品マスタ = { 商品ID(PK), 商品名, 単価 }
メニューマスタ = { メニューID(PK), メニュー名 }
メニュー別商品 = { 商品ID, メニューID }
メニューマスタには、春夏秋冬の4種類のメニューが登録されている。
メニューと商品の関係は m:n だ。
ある時、一部商品の単価を季節によって変動させることになった。
そこで単価の置き場所を「商品マスタ」から「メニュー別商品」に変更した。
商品マスタの単価は、メニューごとの単価を入力する際のデフォルト値という位置づけで「標準単価」と呼ぶことにした。
商品マスタ = { 商品ID, 商品名, 標準単価 }
メニューマスタ = { メニューID, メニュー名 }
メニュー別商品 = { 商品ID, メニューID, 単価 }
メニューに含まれる商品とその単価を取得するクエリは、この変更の前後で以下のように変わる。
select 商品マスタ.商品名, 商品マスタ.単価
from 商品マスタ
inner join メニュー別商品 on メニュー別商品.商品ID=商品マスタ.商品ID
inner join メニューマスタ on メニューマスタ.メニューID=メニュー別商品.メニューID
where メニューマスタ.メニューID=?
↓select 商品マスタ.商品名, メニュー別商品.単価
<以下省略>
結果表の形はまったく同じなので、アプリケーションの修正点はSQLだけ、のはすだ。
ところが、ある種のO/Rマッパーは、いろんなところの修正を要求する。
- メニュー別商品クラスを新規作成する*1。
- メニュー別商品クラスに「単価」プロパティを追加する。
- マッピングの定義ファイルを変更する。
- 商品クラスのプロパティとして単価を参照しているところを、メニュー別商品クラスのプロパティを参照するように書き換える。例えばO/Rマッパーの出力をJSPにフォワードしているなら、こんな修正が必要かもしれない:
<!-- 単価は「商品」オブジェクトのプロパティ --> <bean:write name="item" property="price" /> ↓ <!-- 単価は「商品」オブジェクトのプロパティ「メニュー別商品」のプロパティ --> <bean:write name="item" property="menuItem.price" />
こんな修正は、大した手間ではない。
が、何かおかしいなあと感じる。
SQLでは同じ形のものを返せるのに、O/Rマッパー経由でRDBにアクセスすると同じものが返せなくなる。これは、問題のO/Rマッパーが、結果表をラップせずに、生の基底テーブルをラップしているから起きる問題だ。
現在、O/Rマッパーの出力を直接JSPにフォワードするのは、あまり良い設計とは考えられていない。
サービス層でDTOに詰め替えるのがよい設計らしい。
そのようなサービス層を作ってあれば、10画面のJSPを修正しなくてはならなかったところ、サービス層の1メソッドを直しただけで修正が完了する ...
のだが、やっぱりおかしいなあと感じる。
それは、SQLで射影を切り出せば済むことを、わざわざ人力でやっているのではないか。
まとめ
オブジェクトを特定のSQLにマップできるO/Rマッパーは便利だが、基底テーブルにしかマップできないO/Rマッパーは、書かなくていいプログラムを書かされる可能性がある。
その面倒を省いてくれる周辺ツールがあるか、チェックした方がよい。
基底テーブルにしかマップできないO/Rマッパー経由でRDBを扱う時は、オブジェクト指向DBにアクセスしているつもりになった方がよいのかもしれない。
- 射影を切り出せない
- 集合演算(unionなど)が使えない
というデータベースは、RDBではない。
あなたが使っているO/Rマッパーが union(, except, intersect)を含むクエリを発行できないなら、
あるいは、単価の置き場所が「商品マスタ」から「メニュー別商品」に移ったときに、クエリが返すオブジェクトグラフの形も変わってしまうなら、あなたはRDBの上にアプリケーションを構築しているのではない。仮想的なオブジェクト指向DBの上にアプリケーションを構築しているのだ。
そのオブジェクト指向DBは、十分な機能を提供しているだろうか?ドメインモデリングの結果をそのまま実装できるだろうか?
RDBをRDBとして使うより、本当にコード量が減っているだろうか?メンテ時の作業量が少ないアプリケーションになっているだろうか?
*1:交差エンティティをオブジェクトの相互参照に変換して、オブジェクトにマップしないO/Rマッパーも多い。その場合は新規作成が必要