debug补充示例图
起始debug源码:
首先搞清楚那些地方需要打印日志?通过对日志的观察,如下几个位置需要打日志:
- 在创建prepareStatement 时,打印执行的 SQL 语句;
- 访问数据库时,打印参数的类型和值
- 查询出结构后,打印结果数据条数
因此在日志模块中有 BaseJdbcLogger 、ConnectionLogger 、PreparedStatementLogger
ResultSetLogge 通过动态代理负责在不同的位置打印日志;几个相关类的类图如下:
BaseJdbcLogger:所有日志增强的抽象基类,用于记录 JDBC 那些方法需要增强,保存运
行期间sql 参数信息;
ConnectionLogger:负责打印连接信息和 SQL 语句。通过动态代理,对 connection 进行
增强,如果是调用 prepareStatement、prepareCall、createStatement 的方法,打印要执
行的 sql 语句并返回 prepareStatement 的代理对象(PreparedStatementLogger),让
prepareStatement 也具备日志能力,打印参数;
PreparedStatementLogger:对prepareStatement 对象增强,增强的点如下:
增强 PreparedStatement 的 setxxx 方法将参数设置到 columnMap、columnNames、
columnValues,为打印参数做好准备;
增强 PreparedStatement 的 execute 相关方法
参数,返回动态代理能力的resultSet;
如果是查询,增强 PreparedStatement
resultSet;如果是更新,直接打印影响的行数
ResultSetLogge:负责打印数据结果信息;
最后一个问题:上面讲这么多,都是日志功能的实现,那日志功能是怎么加入主体功能的?
答:既然在Mybatis 中Executor 才是访问数据库的组件,日志功能是在Executor 中被嵌入的,
具体代码在org.apache.ibatis.executor.SimpleExecutor.prepareStatement(StatementHandler, Log)
方法中,如下图所示:
//创建Statement
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
//获取connection对象的动态代理,添加日志能力;
Connection connection = getConnection(statementLog);
//通过不同的StatementHandler,利用connection创建(prepare)Statement
stmt = handler.prepare(connection, transaction.getTimeout());
//使用parameterHandler处理占位符
handler.parameterize(stmt);
return stmt;
}
//重点关注这个方法:
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
//通过动态代理为我们的对象提供打印日志的功能
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
ConnectionLogger.newInstance(connection, statementLog, queryStack);
这个方法也就是调用ConnectionLogger 的invoke方法
@Override
//对连接的增强
public Object invoke(Object proxy, Method method, Object[] params)
throws Throwable {
try {
//如果是从Obeject继承的方法直接忽略
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
//如果是调用prepareStatement、prepareCall、createStatement的方法,打印要执行的sql语句
//并返回prepareStatement的代理对象,让prepareStatement也具备日志能力,打印参数
//生成具有打印功能的
if ("prepareStatement".equals(method.getName())) {
if (isDebugEnabled()) {
debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);//打印sql语句
}
PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);//创建代理对象
return stmt;
} else if ("prepareCall".equals(method.getName())) {
if (isDebugEnabled()) {
debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);//打印sql语句
}
PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);//创建代理对象
stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else if ("createStatement".equals(method.getName())) {
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);
}
}
**这个方法中关注 PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);这个方法,开头已经对它做了介绍 这个也是框架自带的方法它里面有一个代理方法 **
//1,增强PreparedStatement的setxxx方法将参数设置到columnMap、columnNames、columnValues,为打印参数做好准备
//2. 增强PreparedStatement的execute相关方法,当方法执行时,通过动态代理打印参数,返回动态代理能力的resultSet
//3. 如果是查询,增强PreparedStatement的getResultSet方法,返回动态代理能力的resultSet
// 如果是更新,直接打印影响的行数
@Override
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
//增强PreparedStatement的execute相关方法
if (EXECUTE_METHODS.contains(method.getName())) {
if (isDebugEnabled()) {
//当方法执行时,通过动态代理打印参数
debug("Parameters: " + getParameterValueString(), true);
}
clearColumnInfo();
//返回动态代理能力的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);
}
//将参数设置到columnMap、columnNames、columnValues,为打印参数做好准备
} 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);
//返回动态代理能力的resultSet
} 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);
}
}
关注 return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);这个方法
@Override
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
//执行result.next方法,判断是否还有数据
Object o = method.invoke(rs, params);
//如果还有数据,计数器rows加一
if ("next".equals(method.getName())) {
if (((Boolean) o)) {
rows++;
if (isTraceEnabled()) {
ResultSetMetaData rsmd = rs.getMetaData();
final int columnCount = rsmd.getColumnCount();
if (first) {
first = false;
printColumnHeaders(rsmd, columnCount);
}
printColumnValues(columnCount);
}
} else {
//如果没有数据了,打印rows,打印查询出来的数据条数
debug(" Total: " + rows, false);
}
}
clearColumnInfo();
return o;
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
如图:
mybatis就是通过有这几个代理方法来增强对jdbc的方法