db4objects その9(トランザクション)

前回はdb4oの更新処理について言及したが、今回はそれに伴って制御が必要なトランザクションについて言及する。
db4oは他の一般的なDBMS同様にACIDトランザクションを提供するが、管理するトランザクション境界は非常にシンプルであり基本的には

・暗黙的なトランザクション
・明示的なトランザクション

この二通りしかない。

何度か書いているが、db4oはデータベースをオープンしてから(コード上ではObjectContainerインタフェースを取得してから)クローズするまでの間を"セッション"と呼んでいるが、そのセッションの開始時には自動的にトランザクションが開始されており、終了時つまりデータベースクローズ時にコミットが発行される。アプリケーション組込みでの使用等ではこの方法でも問題は無いだろう。

暗黙的なトランザクションに対して明示的なトランザクションは、コード中で明示的にコミット、ロールバックを発行することで行われる。(トランザクションの開始は暗黙的なトランザクションと同様)

例として、サンプルで何度も登場している選手"D.Jeter"の守備位置を"SS"から"3B"に変更してコミットを発行するコードを書いてみよう。

ObjectServer os = Db4o.openServer("roster.db", 0);
ObjectContainer db = os.openClient();
try {
    ObjectSet players = sodaForPlayerName(db, "D.Jeter");
    for ( IPlayer player : players  ) {
        player.setPosition("3B");
        db.set(player);
    }
    db.commit();
    printResult(sodaForPlayerName(db, "D.Jeter"), "SODA for Name='D.Jeter'");
} catch (Exception e) {
    db.rollback();
} finally {
    db.close();
    os.close();
}
public static ObjectSet sodaForPlayerName(ObjectContainer db, String playerName) {
    Query query = db.query();
    query.constrain(IPlayer.class);
    query.descend("name").constrain(playerName);
    return query.execute();
}

内容に関してはSODAクエリで選手名が"D.Jeter"のオブジェクトを検索して、その守備位置を"3B"に変更してデータベースへセット、その後コミットを発行している簡単なものだ。

実行結果

SODA for Name='D.Jeter'
result count = 1
 PlayerImpl[ name=D.Jeter, team=NYY, position=3B, ...]

期待通りpositionが3Bに変更されているのが解る。今度はコミットする替わりにロールバックしてみよう。

ObjectServer os = Db4o.openServer("roster.db", 0);
ObjectContainer db = os.openClient();
try {
    ObjectSet players = sodaForPlayerName(db, "D.Jeter");
    IPlayer player = players.next();
    player.setPosition("3B");
    db.set(player);
    db.rollback();
    printResult(sodaForPlayerName(db, "D.Jeter"), "SODA for Name='D.Jeter'");
} catch (Exception e) {
    db.rollback();
}
} finally {
    db.close();
    os.close();
}

実行結果

SODA for Name='D.Jeter'
result count = 1
 PlayerImpl[ name=D.Jeter, team=NYY, position=3B, ...]

なんと守備位置はロールバックにより"SS"に戻っているはずなのに途中で変更された"3B"のままである。しかしこれはバグではない。
以前にも書いたと思うが、db4oのセッションで開かれたObjectContainerインタフェースで参照されているオブジェクトはキャッシュされている。ロールバックはこのキャッシュされているメモリ上のオブジェクトに対しては効果が無いので、変更されたオブジェクトの守備位置はそのままなのである。
この場合、ロールバックされたはずの最新のオブジェクトを参照するにはメモリ上のオブジェクトを強制的にリフレッシュする必要がある。

try {
    ObjectSet players = sodaForPlayerName(db, "D.Jeter");
    IPlayer player = players.next();
    player.setPosition("3B");
    db.set(player);
    db.rollback();
    db.ext().refresh(player, Integer.MAX_VALUE);
    printResult(sodaForPlayerName(db, "D.Jeter"), "SODA for Name='D.Jeter'");
} catch (Exception e) {
    db.rollback();
}
} finally {
    db.close();
    os.close();
}

実行結果

SODA for Name='D.Jeter'
result count = 1
 PlayerImpl[ name=D.Jeter, team=NYY, position=SS, ...]

このようにロールバックされていることを確認できる。

メモリ上に生きているオブジェクトにロールバックを適用する機能はどうしてデフォルトではOffにされているのだろう。これはやはり処理コストの問題だろうか。
もしロールバックされたオブジェクトを必ずリフレッシュするとなると、コミット前のイメージを保持しておき、そこから復旧するか、既にロールバック済みのデータベース上のイメージを取得し直して全てのライブオブジェクトに反映する必要がある。これはdb4oが組込みモードでオープンされていることを前提にした話であり、仮にクライアント/サーバモードでデータベースがオープンされていた場合は、接続されているクライアント全てのオブジェクトがロールバック対象になってしまう。これは現実的ではないだろう。

db4oCRUD操作に関して一通り言及した。次回からはdb4oを使う上で気をつける点や自分が実際に試してはまった点などを書く予定だ。