DBFlute -- TraceablePreparedStatementの使い方


【概要】
TraceablePreparedStatementは、S2Daoで発行した「SQLの実行情報」を
取得するためのPreparedStatementです。
S2JDBCがDEBUGで出力するような表示用の「Bind変数に値を埋め込んだSQL
を取得することも可能です。



【環境構築】
DBFlute-0.5.2ではデフォルトで生成されますので気にする必要がありません。
DBFlute-0.5.3以降ではデフォルトで生成しませんので、
build-[dbname].propertiesに
isMakeTraceablePreparedStatement = true
を追加して下さい。

また、TraceablePreparedStatementはPreparedStatementに依存していますので、
Java 6 を利用の方は、build-[dbname].propertiesに
targetLanguageVersion = 6.0
を追加して下さい。


【利用方法】
TraceablePreparedStatementはSQLの情報を取得するところまでを受け持ちます。
その取得した情報をアプリケーションで利用するのに、一般的に以下の方法で実現します。

「TraceablePreparedStatementで取得した情報をアプリ独自のThreadLocalに配置」

= = = = = = = = = = = = = = = = = = = = = = = = = = =
ex) 1回のリクエストで発行したSQL文をDBに格納する場合
= = = = = = = = = = = = = = = = = = = = = = = = = = =

[アプリで作成するもの]

A. SQL情報を格納するThreadLocal
B. 独自のStatementFactory
C. リクエスト最後をフックするInterceptor
(TeedaであればPage、ServletFilterでも可)


A. SQL情報を格納するThreadLocal

// Requestで発行されたSQLを蓄積していくThreadLocalを管理するClass
public class RequestSqlContext {

private static ThreadLocal threadLocal = new ThreadLocal();

public static RequestSqlContext getRequestSqlContext() {
return threadLocal.get();
}

public static void setRequestSqlContext(RequestSqlContext requestSqlContext) {
threadLocal.set(requestSqlContext);
}

protected java.util.List sqlList = new java.util.ArrayList();

public java.util.List getSqlList() {
return sqlList;
}

public void addSql(String sql) {
sqlList.add(sql);
}
}


B. 独自のStatementFactory

// 本物のPreparedStatementをWrapしてTraceablePreparedStatementを生成するStatementFactory
// TraceablePreparedStatementに設定するStatementHistoryWitness(履歴の目撃者!?)も同時に設定する。
// StatementHistoryWitnessは、TraceablePreparedStatement内部でCallbackされるwitnessTheHistory()を実装して、
// その中で、RequestSqlContextにSQLを詰め込む。
public class TraceableStatementFactory extends ...allcommon.s2dao.S2DaoStatementFactory {

public PreparedStatement createPreparedStatement(Connection con, String sql) {
final PreparedStatement ps = super.createPreparedStatement(con, sql);
return new TraceablePreparedStatement(ps, sql, createStatementHistoryWitness());
}

protected StatementHistoryWitness createStatementHistoryWitness() {
final StatementHistoryWitness statementHistoryWitness = new StatementHistoryWitness() {
public void witnessTheHistory(StatementHistory statementHistory) {
final List executionBeanList = statementHistory.getPreparedExecutionBeanList();
for (PreparedExecutionBean executionBean : executionBeanList) {
final String displaySql = executionBean.getDisplaySql();
RequestSqlContext.addSql(displaySql);
}
}
};
return statementHistoryWitness;
}
}

jdbc.diconにて StatementFactory の指定を TraceableStatementFactory にして下さい。★


C. リクエスト最後をフックするInterceptor
(TeedaであればPage、ServletFilterでも可)

public class RequestSqlTraceInterceptor extends AbstractInterceptor {

private SqlTraceTableBhv sqlTraceTableBhv;

public Object invoke(final MethodInvocation invocation) throws Throwable {
try {
// ThreadLocalにRequestSqlContextを格納
final RequestSqlContext requestSqlContext = new RequestSqlContext();
RequestSqlContext.setRequestSqlContext(requestSqlContext);

return invocation.proceed();
} finally {
try {
final RequestSqlContext requestSqlContext = RequestSqlContext.getRequestSqlContext
final SqlTraceTable entity = new SqlTraceTable();
entity.setRequestSql(sqlList.toString());
sqlTraceTableBhv.insert(entity);
} catch (Throwable continued) {
// SQLトレースの処理中に何が起きても例外はthrowしない。
// 下手にthrowすると無限ループに陥る可能性もあるため。
log.warn("Skip insert sql trace!", continued);
}

// ThreadLocalからRequestSqlContextを廃棄
RequestSqlContext.setRequestSqlContext(null);
}
}
}


無論、アプリケーションでの色々調整があって、全く上記の通りではないと
思いますので、その辺は各アプリケーションで自由に実装して下さい。


#
# S2Daoのバッチ更新「insertList()/updateList()」にも対応しています。
# 更新した分のSQL文が取得されます。
#