遅延ロードと整合性とトランザクション
近頃は遅延ロードを提供する O/R マッパーが普通になってきてますが,なんでもかんでも O/R マッパー任せ,遅延ロードしまくりでうまくいくものなのか,とても疑問に感じる今日この頃です.
特に,照会系だからといってトランザクションの意識が希薄なのは危険じゃないのか,気になる今日この頃でもあります.
例えば証券系のシステムなんかだと,口座に複数の金融資産があったりします.
その資産には現金とか,株とか,債権とか,いろいろな金融商品があります.
ここでは,それぞれの金融商品毎に個別のテーブル (エンティティ) が存在するとしましょう.
んで,口座の持っている金融資産のポートフォリオ (単なる一覧ってことで) を表示する画面があったりした場合.
最初に口座を読み込んで,そこから現金を遅延ロードして,次に株を遅延ロードして,それから債権を遅延ロードして,めでたく一覧が表示されるとします.
ここで,ですね.
裏で債券が約定しちゃったりした場合はどうなるか? (約定日と受渡日とかは気にしない).
現金が減って債権が増えるわけです.
これが一覧を表示するために株を表示している間に起こったら?
その前に表示を終えている現金は債券を買う前の情報.
その後に表示する債権は買った後の情報.
そんな画面出してみ?
あー,この証券会社ってぇ,ただで債権くれるんだー?
って,思われちゃうじゃん?
O/R マッパー導入以前だと,こういう場合は 1 回の SQL でまとめて取ってくるのが普通だったんじゃないかな?
えぇ.
UNION ALL 様の出番です.便利&強力だったねぇ,UNION ALL.
でもでも,O/R マッパー全盛 (?) の現在は?
あ,S2Dao なんかだと普通に UNION ALL しちゃえばいいような気がするわけですが,それはちょっと置いておいて.
O/R マッパー前提じゃないにしても,複数の SQL というか SELECT 文で整合性を保つ必要があるケースはこれまでも普通にあったわけですが,そういう場合によく使ったのは SELECT 〜 FOR UPDATE です.
上みたいな例だと,更新の際は口座に排他かけてから現金を減らしたり債権を追加したりするってお約束にするわけ.
んで,読み込むだけの場合でも,口座に排他かけちゃう.
そうすることで,口座と現金・株・債権をそれぞれ別に SELECT しても整合性を保つことができます.
Oracle なんかだと Isolation Level を Read Only にするとトランザクションが開始された時点のデータしか読まなくできるので,排他しなくても整合性を保つことができたりするので,更新が激しくなければ使えるかも.
PostgreSQL もできるのかな?
おそらく,バージョニングな DBMS じゃないとこれは使えないので万能ではないですが.
さて.
排他をかけるにしても,Isolation Level を Read Only にするにしても,トランザクション境界を明確にする必要があります.
口座に排他をかけてもすぐにコミットしちゃって,現金を読む時に別のトランザクションになっていたりしたら意味がないわけです.
ってことはですよ?
DB アクセスの際に暗黙的にトランザクションが開始〜コミットされちゃうような O/R マッパーで遅延ロードしまくるってことだと整合性なんて保証しようがないんじゃない?
ってことが疑問に感じる今日この頃ですが,WebObjects (EOF) や Cayenne 使いの人達はどうやってるのでしょうか?
ちなみに,JPA では基本的にトランザクション境界は明確なので,EntityManager#lock(Object, LockModtType)
を使って排他制御と組み合わせてあげるのが無難かなーって思ったのですが...
lock()
に LockModeType.WRITE
を指定した場合って,SELECT 〜 FOR UPDATE じゃなくて UPDATE が実行されちゃうのね.
仕様によると,バージョンをインクリメントすることになってるし...
本気で更新したいわけじゃないケースで使いづらいなぁ.
そんな小さいこと気にしちゃイケないのか?
うーみゅ...
ともあれ (JW),参照系だからってトランザクションを意識しなくて済むなんてあり得ないし,遅延ロードが万能だとも思えない今日この頃なのです.