db4o その15(コールバック その1)

先日の同タイトルのエントリ中で書いた。

例えばdb4oの全てのデータ操作をジャーナルログとして書き出すような処理を書くことを考えているが、そのような用途にはIoAdapterを拡張するのが最も簡単だろう。

少し考えれば解ることなのだが、IoAdapter及びそのピアクラスは低レベルのI/O処理、具体的にはオブジェクトがシリアライズを経てバイト列になった状態を扱うものであり、この段階では既に「どのようなオブジェクトに対して何のトランザクションが施されたのか」は解らない(解るためには少なくともdb4oのオブジェクトのバイナリ構造を知る必要がある)ので懸案のジャーナルロギングを行うのには適していない。

ならば、ジャーナルログのように全てのデータ操作に対して処理を割り込ませるような処理を書きたい場合はどうすれば良いのだろう。

  • EventRegistry

db4oはデータベースへの全ての操作とトランザクションの開始と完了をコールバックとして通知するためにイベントレジストリというインタフェースが提供されている。(コールバックと呼んでいるが、元々リファレンスやチュートリアルにある"ObjectCallbacks"とは全く違うものなので注意が必要だ)

public interface EventRegistry {
    public Event4 queryStarted();
    public Event4 queryFinished();
    public Event4 creating();
    public Event4 activating();
    public Event4 updating();
    public Event4 deleting();
    public Event4 deactivating();
    public Event4 activated();
    public Event4 created();
    public Event4 updated();
    public Event4 deleted();
    public Event4 deactivated();
    public Event4 committing();
    public Event4 committed();
    public Event4 instantiated();
    public Event4 classRegistered();
}

このインタフェースを見ればどのコールバックメソッドがどのような操作に対して発生するか想像がつくだろう。
committingとcommittedはトランザクションの開始と終了に対して、それ以外はデータベースに対してのなんらかの操作によって発生するイベントから通知されるコールバックを表している。(後述するがトランザクションの扱いによって、データベース操作イベントの発生後にはcommitting又はcommitted、もしくはその両方のイベントコールバックが続けて発生することになる)

使い方は簡単、EventRegistryのインスタンスをファクトリから取得して自分がコールバックを受け取りたいメソッドに対して登録を行うだけだ。

・for Java

ObjectContainer db = Db4o.openFile("roster.db");
try {
    //イベントレジストリの取得
    EventRegistry registry = EventRegistryFactory.forObjectContainer(db);
    //コールバック処理の登録
    registry.committed().addListener(new EventListener4() {
        @Override
        public void onEvent(Event4 e, EventArgs args) {
            //コミット完了時に動作させたいロジック
        }
    });
} catch (Exception e) {
    db.rollback();
} finally {
    db.close();
}

このJavaコードの例はデータベースに対してコミットが完了した際に発生するイベントのコールバックを登録するものだ。
Javaコードではイベントコールバックの登録に無名インナクラスを使用していることから予想できると思うが、NET C#では以下のように匿名デリゲートで同じように記述することができる。
for .NET C#

//イベントレジストリの取得
IEventRegistry registry = EventRegistryFactory.ForObjectContainer(db);
//コールバックの登録
registry.Committed += new CommitEventHandler(delegate(object sender, CommitEventArgs args)
    {
        System.out.println("** committed " + args );
    });

ちなみにcommittedイベントコールバックは開発最新版であるdb4o 6.2.501で追加されたイベントであり、まだ試用段階である。

EventRegistryインタフェースによるイベントのコールバックだが、db4objectsは以下の用途をユースケースとして挙げている。

    1. 関連するオブジェクトの並列削除、更新
    2. 参照整合性のチェック
    3. 各種統計値の収集
    4. フィールド値の自動設定
    5. ユニークIDの自動生成とその設定
    6. データの遅延削除(削除マーク後、一括削除)
    7. オブジェクトのフィールドの一意性確保

このように提供されるイベントコールバックだが、特に重要と考えているのが冒頭のサンプルでも採り上げたcommttingとcommittedイベントコールバックである。この両イベントはそれぞれ

・committing - トランザクションのコミット中 (コミット前)
・committed - トランザクションのコミット後

以上のタイミングで通知されるコールバックであり、そのタイミングから「コミット時コールバック」と呼ばれてそれ以外の他のコールバックとは区別される。
コミット時コールバックの使い方だが、以下のように記述することでコミット前とコミット後のオブジェクトのイメージをコールバック中に取得できる。

ObjectContainer db = Db4o.openFile("roster.db");
try {
    //イベントレジストリの取得
    EventRegistry registry = EventRegistryFactory.forObjectContainer(db);
    //コールバック処理の登録
    registry.committing().addListener(new EventListener4() {
        @Override
        public void onEvent(Event4 e, EventArgs args) {
            CommitEventArgs commitArgs = (CommitEventArgs)args;
            //コミット完了前に動作させたいロジック
        }
    });
    registry.committed().addListener(new EventListener4() {
        @Override
        public void onEvent(Event4 e, EventArgs args) {
            CommitEventArgs commitArgs = (CommitEventArgs)args;
            //コミット完了時に動作させたいロジック
        }
    });
} catch (Exception e) {
    db.rollback();
} finally {
    db.close();
}

なお、コミット時コールバックにおけるonEventメソッドの第2パラメタであるCommitEventArgsクラスを調べることでコミットされる予定のオブジェクトとコミットが完了した全てのオブジェクトを参照することができる。

・CommitEventArgsクラス

public class CommitEventArgs extends EventArgs {
    public Object transaction()
    public ObjectInfoCollection added()
    public ObjectInfoCollection deleted()
    public ObjectInfoCollection updated()
}

このクラスのオブジェクトを参照することで、コミット中、後にトランザクションのコンテキスト対象だったオブジェクトを取得、操作することができる。
ただ、added,deleted,updatedそれぞれのメソッドで戻るObjectInfoCollectionだが、java.util.IteratorやCollection,List等の一般的なコレクションのインタフェースを実装していないので使い難い。このままだと一般的なfor文で回すこともできず、一体どうしてこのようなインタフェースを使っているのか、正直な所理解に苦しむ実装だ。(もっともコミット時コールバックはまだEaryAccess扱いなので実装が変わる可能性もある)

コミット時コールバックを使えば、データベースにコミットされた変更のタイミングとそのコンテキストで扱われたオブジェクトを確実に補足できることが解った。
この後はバックアップ機能とこのコミット時コールバック機能とを組合わせることで、HSQLDB等が実現しているジャーナルロギング(.scriptファイル)の採取によるチェックポイントの作成とデータベースの復旧機能をdb4oで実装しようと考えている。