SQLなどのログまわり

SQLなどのログをアプリ側で取得したいという話をよく聞くので、必要そうなログは特定のインタフェースを実装したクラスへ全部送るようにしてみました。こんなインタフェースです。

public interface JdbcLogger {

    void logMethodEntering(String callerClassName, String callerMethodName,
            Object... parameters);

    void logMethodExiting(String callerClassName, String callerMethodName,
            Object result);

    void logSqlFile(String callerClassName, String callerMethodName,
            SqlFile sqlFile);

    void logSql(String callerClassName, String callerMethodName, Sql<?> sql);

    ...
}

かんたんな説明。

logMethodEnteringとlogMethodExiting

メソッドの呼び出し直後と呼び出し終了直前にそれぞれ呼ばれます。Daoのクラス名、メソッド名、パラメータや戻り値をハンドリングできます。
S2DaoだとDaoにTraceInterceptorをかけること多いと思いますが、それと同等のことをできないと不便だなぁと思って用意しました。

logSqlFile

SQLファイルを使う場合に呼ばれます。
パラメータのSqlFileからはファイルのパス、ファイルに記述されたそのままのSQLを取得できます。
S2DaoS2JDBCのように、ファイルのパスが.../select_oracel.sqlとdbのサフィックスがついていたらサフィックスなしの.../select.sqlより優先するという機能を持つようにしたのですが、そのときに実際にどのパスからSQLが読まれたかわかったほうがいいだろうなということで実装しました。
ファイルに記述されたそのままのSQLをとれるようにしたのは、ページングやfor updateのときにSQLを書き換えることがあるので、書き換え前のSQLがとれたほうがわかりやすいこともあるだろうと思ったからです。まぁ、あまり使わないかもしれない。

logSql

SQLの実行のたびに呼ばれます。
パラメータのSqlクラスからは、?つきのSQLや?をバインド変数の値でフォーマットしたログ用のSQLなどを取得できます。


ログ用SQLのフォーマットについては、日付部分など微調整したい(DB固有のフォーマットにしたい)という話をたまに聞くので、そこをなんとでもできるようにしてみました。
デフォルトではSqlLogFormattingVisitorなるクラスがあって、日付は単にシングルクォートで囲んでいるだけですが、必要なのはここをちょっと変更した実装を作るだけです。

public class SqlLogFormattingVisitor implements
        BuiltInDomainVisitor<String, Void, RuntimeException> {

    @Override
    public String visitAbstractDateDomain(AbstractDateDomain<?> domain, Void p) {
        if (domain.isNull()) {
            return NULL;
        }
        return "'" + domain.get() + "'";
    }
    ...

SqlLogFormattingVisitorは組み込みのDomain(カラムに対応するクラス)のみを扱っていますが、Extensible Visitorパターンなるものを使っているので、自分で追加したDomainについても柔軟に扱えます(組み込みのDomainがいらない場合は、それをはずす(利用しない実装をつくる)こともできる)。