JDBC Log
logging.jdbc 包下的类,通过 JDK 动态代理的方式,将 JDBC 操作通过指定的日志框架打印出来。这个功能通常在开发阶段使用 ,它可以输出 SQL 语句、用户传入的绑定参数、 SQL 语句影响行数等等信息,对调试程序来说是非常重要的。
日志样例
[DEBUG]2020-08-26 15:02:33.976 c.x.s.d.S.insert:143 - ==> Preparing: INSERT INTO student (id, name, age, address, class_id) VALUES (?,?,?, ?, ?)
[DEBUG]2020-08-26 15:02:33.977 c.x.s.d.S.insert:143 - ==> Parameters: 25(Long), tom(String), 12(Long), china(String), 2(Long)
UML 类图
image.pngBaseJdbcLogger
BaseJdbcLogger 是 JdbcLog 的抽象基类,提供了输出日志需要参数和基本方法,工子类使用。参数包含 用户实参、用户调用的 SQL 类型方法,然后还有一些输出格式化日志的方法。
public abstract class BaseJdbcLogger {
// 记录了 PreparedStatement 接口中定义的常用的 set*() 方法 比如:setBoolean(int parameterIndex, boolean x),用来设置 查询参数绑定的方法
protected static final Set<String> SET_METHODS;
// 记录了 Statement 接口和 PreparedStatement 接口中与执行 SQL 语句相关的方法 比如:ResultSet executeQuery(),是 Statement 执行 SQL 的方法
protected static final Set<String> EXECUTE_METHODS = new HashSet<>();
// 记录了 PreparedStatement.set*()方法设置 键位对
private final Map<Object, Object> columnMap = new HashMap<>();
// 记录了 PreparedStatement.set*() 方法设置的 key
private final List<Object> columnNames = new ArrayList<>();
// 记录了 PreparedStatement.set*() 方法设置的 value
private final List<Object> columnValues = new ArrayList<>();
// 用于输出日志的 Log 对象
protected final Log statementLog;
// 记录了 SQL 的层数,用于格式化输出 SQL
protected final int queryStack;
/*
* Default constructor
*/
public BaseJdbcLogger(Log log, int queryStack) {
this.statementLog = log;
if (queryStack == 0) {
this.queryStack = 1;
} else {
this.queryStack = queryStack;
}
}
static {
// 添加设置参数的 set*() 方法
// PreparedStatement 中的方法,已 set 开头,参数个数 大于一的方法名称 放入 SET_METHODS
SET_METHODS = Arrays.stream(PreparedStatement.class.getDeclaredMethods())
.filter(method -> method.getName().startsWith("set"))
.filter(method -> method.getParameterCount() > 1)
.map(Method::getName)
.collect(Collectors.toSet());
// 添加 SQL 执行的方法
EXECUTE_METHODS.add("execute");
EXECUTE_METHODS.add("executeUpdate");
EXECUTE_METHODS.add("executeQuery");
EXECUTE_METHODS.add("addBatch");
}
protected void setColumn(Object key, Object value) {
columnMap.put(key, value);
columnNames.add(key);
columnValues.add(value);
}
protected Object getColumn(Object key) {
return columnMap.get(key);
}
/**
* 查询实参的参数值以及参数类型
* @return
*/
protected String getParameterValueString() {
List<Object> typeList = new ArrayList<>(columnValues.size());
for (Object value : columnValues) {
if (value == null) {
typeList.add("null");
} else {
typeList.add(objectValueString(value) + "(" + value.getClass().getSimpleName() + ")");
}
}
final String parameters = typeList.toString();
return parameters.substring(1, parameters.length() - 1);
}
protected String objectValueString(Object value) {
if (value instanceof Array) {
try {
return ArrayUtil.toString(((Array) value).getArray());
} catch (SQLException e) {
return value.toString();
}
}
return value.toString();
}
protected String getColumnString() {
return columnNames.toString();
}
/**
* 清空实参相关集合
*/
protected void clearColumnInfo() {
columnMap.clear();
columnNames.clear();
columnValues.clear();
}
protected String removeBreakingWhitespace(String original) {
StringTokenizer whitespaceStripper = new StringTokenizer(original);
StringBuilder builder = new StringBuilder();
while (whitespaceStripper.hasMoreTokens()) {
builder.append(whitespaceStripper.nextToken());
builder.append(" ");
}
return builder.toString();
}
protected boolean isDebugEnabled() {
return statementLog.isDebugEnabled();
}
protected boolean isTraceEnabled() {
return statementLog.isTraceEnabled();
}
protected void debug(String text, boolean input) {
if (statementLog.isDebugEnabled()) {
statementLog.debug(prefix(input) + text);
}
}
protected void trace(String text, boolean input) {
if (statementLog.isTraceEnabled()) {
statementLog.trace(prefix(input) + text);
}
}
private String prefix(boolean isInput) {
char[] buffer = new char[queryStack * 2 + 2];
Arrays.fill(buffer, '=');
buffer[queryStack * 2 + 1] = ' ';
if (isInput) {
buffer[queryStack * 2] = '>';
} else {
buffer[0] = '<';
}
return new String(buffer);
}
}
ConnectionLogger
添加了 logging 的 connection 代理
ConnectionLogger 继承了 BaseJdbcLogger 抽象类,其中封装了 Connection 对象,并同时实现了 InvocationHandler 接口。
ConnectionLogger.newInstance() 方法为会为其封装的 Connection 对象创建相应的代理对象。
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;
}
/**
* 代理对象执行被代理对象方法的调用
* @param proxy
* @param method
* @param params
* @return
* @throws Throwable
*/
@Override
public Object invoke(Object proxy, Method method, Object[] params)
throws Throwable {
try {
// 如果调用的是从 Object 继承的方法,则直接调用,不做任何处理
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
// 调用的是 prepareStatement() 、prepareCall()、createStatement() ,则在创建相应的 Statement 对象后,为其创建代理对象并返回该代理对象
if ("prepareStatement".equals(method.getName())) {
if (isDebugEnabled()) { // 日志输出
debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
}
// 调用底层封装的 Connection 对象 prepareStatement() 方法,得到 PreparedStatement 对象
PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
// 为该 PreparedStatement 对象创建代理对象,添加了 log 功能
// PreparedStatementLogger 中封装了 PreparedStatement 对象,也继承了 BaseJdbcLogger 抽象类并实现了 InvocationHandler 接口
stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else if ("prepareCall".equals(method.getName())) {
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())) {
Statement stmt = (Statement) method.invoke(connection, params);
stmt = StatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else {
// 其他方法则直接调用底层 Connection 对象的相应方法
return method.invoke(connection, params);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
/**
* Creates a logging version of a connection.
*
* @param conn - the original connection
* @return - the connection with logging
*/
public static Connection newInstance(Connection conn, Log statementLog, int queryStack) {
// 使用 JDK 动态代理的方式创建代理对象
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;
}
}
ConnectionLogger 的使用
使用了代理模式,为已经生成的 Connection 对象添加 Log 功能。
/**
* 为 Connection 对象添加 Log 日志功能
* @param statementLog
* @return
* @throws SQLException
*/
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
类似功能
PreparedStatementLogger、StatementLogger、ResultSetLogger 和 ConnectionLogger 实现的方式是一样的,顾不在展开叙述。
网友评论