在logging模块中有一个包jdbc,这个包虽然和其他包并列,但并不是一种简单的Log代理实现。而是在mybatis操作jdbc的时候,用记录相关SQL的复杂组件。我们最常用的打印sql日志的功能就是由这个模块实现的。
所有StatementLogger,ConnectionLogger,ResultSetLogger,PreparedStatementLoggerr
除了继承BaseJdbcLogger以外也同时实现了InvocationHandler接口。也就是说mybatis到底是怎么去实现打印jdbc相关信息的日志的呢?其实就是利用动态代理,拦截了我们熟悉的Statement,Connection,ResultSet,PreparedStatement
中相关的方法,在执行前后加入了打印日志的逻辑。这是一种典型的AOP编程场景的运用。
因为对jdbc的操作,通常是
Connection->Statement->Resultset
所以这几个代理类其实最开始的入口是从生成Connection的代理开始。
ConnectionLogger
public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler {
private final Connection connection;
private ConnectionLogger(Connection conn, Log statementLog, int queryStack) {
super(statementLog, queryStack);
this.connection = conn;
}
@Override
public Object invoke(Object proxy, Method method, Object[] params)
throws Throwable {
try {
//如果是Object本身的方法,不走Connection,而是直接调用,也就是object本身的方法不做任何代理。
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
//如果是Connection的prepareStatement方法,打印要执行的sql,这里的sql会美化一次
if ("prepareStatement".equals(method.getName())) {
if (isDebugEnabled()) {
debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
}
//真正调用Connection的prepareStatement方法,这是反射和动态代理的典型逻辑了
PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
//这里是在将我们创建的PreparedStatement生成动态代理。因为PreparedStatement也是需要被代理的。
stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
//返回被代理过的PreparedStatement
return stmt;
} else if ("prepareCall".equals(method.getName())) {
//prepareCall方法和上面一模一样的,感觉可以在一个分支里啊,干嘛又写一遍?
if (isDebugEnabled()) {
debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
}
PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else if ("createStatement".equals(method.getName())) {
//createStatement方法不同的是不会有日志打印,为啥?因为这个方法不会传sql
Statement stmt = (Statement) method.invoke(connection, params);
stmt = StatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else {
//其他任何方法,不添加额外逻辑.
return method.invoke(connection, params);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
/**
* Creates a logging version of a connection.
* 将生成代理的步骤也在InvocationHandler中封装成一个工具类方法。
* 这样创建代理就仅仅只需要调用这样一个API就完成了。
* @param conn - the original connection
* @return - the connection with logging
*/
public static Connection newInstance(Connection conn, Log statementLog, int queryStack) {
InvocationHandler handler = new ConnectionLogger(conn, statementLog, queryStack);
ClassLoader cl = Connection.class.getClassLoader();
return (Connection) Proxy.newProxyInstance(cl, new Class[]{Connection.class}, handler);
}
/**
* return the wrapped connection.
* @return the connection
*/
public Connection getConnection() {
return connection;
}
}
PreparedStatementLogger的invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
try {
//same
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
//执行sql的方法
if (EXECUTE_METHODS.contains(method.getName())) {
//打印参数的日志
if (isDebugEnabled()) {
debug("Parameters: " + getParameterValueString(), true);
}
//执行了这条sql后,清空对应的参数缓存
clearColumnInfo();
//如果是查询sql,ResultSet也需要被代理
if ("executeQuery".equals(method.getName())) {
ResultSet rs = (ResultSet) method.invoke(statement, params);
return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
} else {
return method.invoke(statement, params);
}
} else if (SET_METHODS.contains(method.getName())) {
//如果是设置参数的方法,先缓存参数,再调用方法
if ("setNull".equals(method.getName())) {
setColumn(params[0], null);
} else {
setColumn(params[0], params[1]);
}
return method.invoke(statement, params);
} else if ("getResultSet".equals(method.getName())) {
ResultSet rs = (ResultSet) method.invoke(statement, params);
return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
} else if ("getUpdateCount".equals(method.getName())) {
int updateCount = (Integer) method.invoke(statement, params);
if (updateCount != -1) {
debug(" Updates: " + updateCount, false);
}
return updateCount;
} else {
return method.invoke(statement, params);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
网友评论