抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

MYBATIS日志模块解析

前言

​ mybatis 没有提供日志的实现类,需要接入第三方的日志组件,但第三方日志组件都有各自的Log级别,且各不相同。而Mybatis统一提供了trace/ debug/ warn/ error四个级别,mybatis使用适配器模式进行日志加载,我们来欣赏下mybatis源码的魅力。

日志接口Log

mybatis 封装了统一的日志接口,其他日志接口接入需要实现该日志接口。

该接口只提供了trace/ debug/ warn/ error四个级别的日志输出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* @author Clinton Begin
* mybatis log日志接口
*/
public interface Log {
//是否启动debug
boolean isDebugEnabled();

//是否启动Trace
boolean isTraceEnabled();

//error日志级别
void error(String s, Throwable e);

void error(String s);

void debug(String s);

void trace(String s);

void warn(String s);

}

日志工厂LogFactory

在这里定义了日志框架的加载顺序

slf4j -> commonsLoging -> Log4J2 -> Log4J -> JdkLog

使得日志框架优雅的嵌入到mybatis中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
/**
* mybatis log 工厂类
* @author Clinton Begin
* @author Eduardo Macarron
*/
public final class LogFactory {

/**
* Marker to be used by logging implementations that support markers.
*/
public static final String MARKER = "MYBATIS";
/**
* 记录正在使用的是那个日志框架的构造方法
*/
private static Constructor<? extends Log> logConstructor;

/**
* 顺序尝试找到一个可用的日志框架
*
* :: 双冒号运算符就是java中的方法引用 方法引用的格式是 类名::方法名。
* person ->person.getAge(); 可以替换为 Person::getAge
* ()-> new HashMap<>(); 可以替换为 HashMap::new
* 双冒号操作符返回的是一个接口的匿名实现
*/
static {
//尝试使用某一种日志框架 第一个不成功到第二个 一直找到一个合适的
tryImplementation(LogFactory::useSlf4jLogging);
tryImplementation(LogFactory::useCommonsLogging);
tryImplementation(LogFactory::useLog4J2Logging);
tryImplementation(LogFactory::useLog4JLogging);
tryImplementation(LogFactory::useJdkLogging);
tryImplementation(LogFactory::useNoLogging);
}

private LogFactory() {
// disable construction
}

/**
* 返回具体实现的实现类
* @param aClass
* @return
*/
public static Log getLog(Class<?> aClass) {
return getLog(aClass.getName());
}

/**
* 返回具体实现的接口
* @param logger 具体需要日志的 类
* @return
*/
public static Log getLog(String logger) {
try {
//使用当前可用的构造方法进行创建对象
return logConstructor.newInstance(logger);
} catch (Throwable t) {
throw new LogException("Error creating logger for logger " + logger + ". Cause: " + t, t);
}
}

public static synchronized void useCustomLogging(Class<? extends Log> clazz) {
setImplementation(clazz);
}

public static synchronized void useSlf4jLogging() {
setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
}

public static synchronized void useCommonsLogging() {
setImplementation(org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.class);
}

public static synchronized void useLog4JLogging() {
setImplementation(org.apache.ibatis.logging.log4j.Log4jImpl.class);
}

public static synchronized void useLog4J2Logging() {
setImplementation(org.apache.ibatis.logging.log4j2.Log4j2Impl.class);
}

public static synchronized void useJdkLogging() {
setImplementation(org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl.class);
}

public static synchronized void useStdOutLogging() {
setImplementation(org.apache.ibatis.logging.stdout.StdOutImpl.class);
}

public static synchronized void useNoLogging() {
setImplementation(org.apache.ibatis.logging.nologging.NoLoggingImpl.class);
}

/**
* 咋一看好像是多线程
* 其实不然,只用用了下Runnable接口的钩子方法
* 不用再自定义接口内部类实现了,用现成的Runnable接口
* @param runnable
*/
private static void tryImplementation(Runnable runnable) {
//如果构造方法为空就调用匿名内部类
if (logConstructor == null) {
try {
//调用具体接口的方法
runnable.run();
} catch (Throwable t) {
// ignore
}
}
}

/**
* 设置日志实现类
* @param implClass
*/
private static void setImplementation(Class<? extends Log> implClass) {
try {
//获取具体实现类的构造方法
Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
//创建一个实现类 并打印日志
Log log = candidate.newInstance(LogFactory.class.getName());
if (log.isDebugEnabled()) {
log.debug("Logging initialized using '" + implClass + "' adapter.");
}
//设置否则方法为当前可用构造方法
logConstructor = candidate;
} catch (Throwable t) {
throw new LogException("Error setting Log implementation. Cause: " + t, t);
}
}

}

到这里是整个日志的加载顺序,尝试找到一个可用的构造方法,找到后返回该日志框架的实例。

这里面用到了Runable接口的钩子方法,也可以叫做接口回调,并没有使用多线程编程。

日志系统转换

这里采用了很多日志框架,使用了适配器模式进行日志的转换,装饰着模式可以查看我的设计模式一节

这里我们就拿比较负责的sl4j来查看源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
/**
* 适配器模式 Sl4j 实现类
* 实现Log 接口
* @author Clinton Begin
* @author Eduardo Macarron
*/
public class Slf4jImpl implements Log {
/**
* 当前类的实现类
*/
private Log log;

/**
* 构造方法 初始化 log
*
* @param clazz 需要打印日志的类
*/
public Slf4jImpl(String clazz) {
//logger的方式创建日志类
Logger logger = LoggerFactory.getLogger(clazz);
//如果返回的是 LocationAwareLogger 对象
if (logger instanceof LocationAwareLogger) {
try {
// check for slf4j >= 1.6 method signature
//检查sl4j 版本是否>=1.6
logger.getClass().getMethod("log", Marker.class, String.class, int.class, String.class, Object[].class, Throwable.class);

//使用 Slf4jLocationAwareLoggerImpl 实例
log = new Slf4jLocationAwareLoggerImpl((LocationAwareLogger) logger);
return;
} catch (SecurityException | NoSuchMethodException e) {
// fail-back to Slf4jLoggerImpl
}
}

// Logger is not LocationAwareLogger or slf4j version < 1.6
//sl4j 版本小于1.6 使用Slf4jLoggerImpl
log = new Slf4jLoggerImpl(logger);
}

@Override
public boolean isDebugEnabled() {
return log.isDebugEnabled();
}

@Override
public boolean isTraceEnabled() {
return log.isTraceEnabled();
}

@Override
public void error(String s, Throwable e) {
log.error(s, e);
}

@Override
public void error(String s) {
log.error(s);
}

@Override
public void debug(String s) {
log.debug(s);
}

@Override
public void trace(String s) {
log.trace(s);
}

@Override
public void warn(String s) {
log.warn(s);
}

}

这里注意下Slf4j版本控制,如果Slf4j版本>=1.6 使用 Slf4jLocationAwareLoggerImpl 否则使用Slf4jLoggerImpl

我们再拿Slf4jLoggerImpl看一下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
* @author Eduardo Macarron
*/
class Slf4jLoggerImpl implements Log {

private final Logger log;

public Slf4jLoggerImpl(Logger logger) {
log = logger;
}

@Override
public boolean isDebugEnabled() {
return log.isDebugEnabled();
}

@Override
public boolean isTraceEnabled() {
return log.isTraceEnabled();
}

@Override
public void error(String s, Throwable e) {
log.error(s, e);
}

@Override
public void error(String s) {
log.error(s);
}

@Override
public void debug(String s) {
log.debug(s);
}

@Override
public void trace(String s) {
log.trace(s);
}

@Override
public void warn(String s) {
log.warn(s);
}

}

到这里就是mybatis日志框架的优雅封装实现,通过依次尝试加载,使用适配器模式进行日志接口的统一

JDBC日志增强

JDBC模板类BaseJdbcLogger

BaseJdbcLogger初始化了日志框架Log,SET_METHODS和jdbc执行方法列表EXECUTE_METHODS

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
/**
* Base class for proxies to do logging.
* 基类 JDBC操作日志
*
* @author Clinton Begin
* @author Eduardo Macarron
*/
public abstract class BaseJdbcLogger {

protected static final Set<String> SET_METHODS;
protected static final Set<String> EXECUTE_METHODS = new HashSet<>();

private final Map<Object, Object> columnMap = new HashMap<>();

private final List<Object> columnNames = new ArrayList<>();
private final List<Object> columnValues = new ArrayList<>();

protected final Log statementLog;
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方法设置进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());
//添加执行方法
EXECUTE_METHODS.add("execute");
EXECUTE_METHODS.add("executeUpdate");
EXECUTE_METHODS.add("executeQuery");
EXECUTE_METHODS.add("addBatch");
}

/**
* 设置参数
* @param key
* @param value
*/
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);
}

/**
* 将Object转换为String
* @param value
* @return
*/
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();
}

/**
* 获取参数名称列表
* @return
*/
protected String getColumnString() {
return columnNames.toString();
}

/**
* 清空参数map
*/
protected void clearColumnInfo() {
columnMap.clear();
columnNames.clear();
columnValues.clear();
}

/**
* 清除换行符以及制表符
* @param original
* @return
*/
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();
}

/**
* 是否启用debug
* @return
*/
protected boolean isDebugEnabled() {
return statementLog.isDebugEnabled();
}

/**
* 是否启用Trace
* @return
*/
protected boolean isTraceEnabled() {
return statementLog.isTraceEnabled();
}

/**
* 日志打印
* @param text
* @param input
*/
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);
}
}

/**
* 获取输出的前缀
* @param isInput
* @return
*/
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);
}
}

Connection日志增强

Connection日志增加是基于动态代理实现的,具有很高的参考价值,他创建预处理等对象的时候并返回预处理对象的代理方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
/**
* Connection proxy to add logging.
* 基于动态代理的日志增强
*
* @author Clinton Begin
* @author Eduardo Macarron
*
*/
public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler {
/**
* 数据库连接
*/
private final Connection connection;

/**
* 构造方法
* @param conn 连接
* @param statementLog 日志接口
* @param queryStack
*/
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 {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
//如果是预处理方法 打印debug信息
if ("prepareStatement".equals(method.getName())) {
if (isDebugEnabled()) {
debug(" Preparing: " + removeBreakingWhitespace((String) params[0]), true);
}
//调用prepareStatement方法
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);
}
//调用prepareCall 方法
PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
//链式创建预处理代理对象
stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
//并返回预处理代理对象
return stmt;
//如果创建createStatement 方法
} else if ("createStatement".equals(method.getName())) {
//调用 createStatement 方法
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.
* 创建 连接的代理对象
* @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预处理日志增强

预处理日志增强,对其中一些方法进行增强,对于查询以及获取结果集,返回结果集的日志动态代理增强。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
/**
* PreparedStatement proxy to add logging.
* 基于动态代理的预处理日志增强
*
* @author Clinton Begin
* @author Eduardo Macarron
*/
public final class PreparedStatementLogger extends BaseJdbcLogger implements InvocationHandler {
/**
* 原始的预处理对象
*/
private final PreparedStatement statement;

/**
* 私有的构造方法
*
* @param stmt 预处理对象
* @param statementLog 日志接口
* @param queryStack
*/
private PreparedStatementLogger(PreparedStatement stmt, Log statementLog, int queryStack) {
super(statementLog, queryStack);
this.statement = stmt;
}

/**
* 动态代理和核心方法
*
* @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);
}
//如果包含定义的可执行方法 则打印日志 以及值列表
if (EXECUTE_METHODS.contains(method.getName())) {
if (isDebugEnabled()) {
debug("Parameters: " + getParameterValueString(), true);
}
//清空 参数
clearColumnInfo();
//如果是查询方法
if ("executeQuery".equals(method.getName())) {
//调用 executeQuery 方法 并返回结果集
ResultSet rs = (ResultSet) method.invoke(statement, params);
//返回结果集的 动态代理日志增强
return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
} else {
//其他方法直接调用不进行日志增强
return method.invoke(statement, params);
}
//如果方法在SET_METHODS列表中
} else if (SET_METHODS.contains(method.getName())) {
//如果是 setNull 的方法 则设置为null
if ("setNull".equals(method.getName())) {
setColumn(params[0], null);
} else {
//其他设置具体参数的值
setColumn(params[0], params[1]);
}
//调用set方法
return method.invoke(statement, params);
//如果是getResultSet方法
} else if ("getResultSet".equals(method.getName())) {
//调用具体的getResultSet方法
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);
//如果是不等于-1 打印结果日志
if (updateCount != -1) {
debug(" Updates: " + updateCount, false);
}
//返回更新条数
return updateCount;
} else {
//不进行增加的方法
return method.invoke(statement, params);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}

/**
* Creates a logging version of a PreparedStatement.
*
* 创建动态代理的实例
* @param stmt - the statement
* @param statementLog - the statement log
* @param queryStack - the query stack
* @return - the proxy
*/
public static PreparedStatement newInstance(PreparedStatement stmt, Log statementLog, int queryStack) {
InvocationHandler handler = new PreparedStatementLogger(stmt, statementLog, queryStack);
ClassLoader cl = PreparedStatement.class.getClassLoader();
return (PreparedStatement) Proxy.newProxyInstance(cl, new Class[]{PreparedStatement.class, CallableStatement.class}, handler);
}

/**
* Return the wrapped prepared statement.
*
* @return the PreparedStatement
*/
public PreparedStatement getPreparedStatement() {
return statement;
}

}

StatementLogger日志增加和PreparedStatementLogger这个类基本上一样,大家可以看一下。

ResultSetLogger结果集日志增强

结果集打印没有什么可说的了,基本上就是判断是否是blob类型,特殊处理,其他情况进行字符串拼接

这里使用了JDK1.8的StringJoiner 进行字符串拼接,可以简单的方式进行包含有分隔符的字符串拼接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/**
* ResultSet proxy to add logging.
* 结果集处理日志动态代理
* @author Clinton Begin
* @author Eduardo Macarron
*
*/
public final class ResultSetLogger extends BaseJdbcLogger implements InvocationHandler {
/**
* Bolb 字段类型列表
*/
private static final Set<Integer> BLOB_TYPES = new HashSet<>();
private boolean first = true;
private int rows;
//结果集原始对象
private final ResultSet rs;
//blob参数
private final Set<Integer> blobColumns = new HashSet<>();

static {
//设置blob数据类型
BLOB_TYPES.add(Types.BINARY);
BLOB_TYPES.add(Types.BLOB);
BLOB_TYPES.add(Types.CLOB);
BLOB_TYPES.add(Types.LONGNVARCHAR);
BLOB_TYPES.add(Types.LONGVARBINARY);
BLOB_TYPES.add(Types.LONGVARCHAR);
BLOB_TYPES.add(Types.NCLOB);
BLOB_TYPES.add(Types.VARBINARY);
}

/**
* 私有构造方法
* @param rs 结果集
* @param statementLog 日志接口
* @param queryStack
*/
private ResultSetLogger(ResultSet rs, Log statementLog, int queryStack) {
super(statementLog, queryStack);
this.rs = rs;
}

/**
* 动态代理的核心方法
* @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);
}
//调用原始的方法
Object o = method.invoke(rs, params);
//如果调用next方法
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 {
debug(" Total: " + rows, false);
}
}
clearColumnInfo();
return o;
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}

/**
* 打印结果集日志
* @param rsmd 结果集元数据
* @param columnCount 列行数
* @throws SQLException
*/
private void printColumnHeaders(ResultSetMetaData rsmd, int columnCount) throws SQLException {
StringJoiner row = new StringJoiner(", ", " Columns: ", "");
for (int i = 1; i <= columnCount; i++) {
if (BLOB_TYPES.contains(rsmd.getColumnType(i))) {
blobColumns.add(i);
}
row.add(rsmd.getColumnLabel(i));
}
trace(row.toString(), false);
}

/**
* 打印结果集数据
* @param columnCount
*/
private void printColumnValues(int columnCount) {
//StringJoiner 进行字符串拼接
StringJoiner row = new StringJoiner(", ", " Row: ", "");
for (int i = 1; i <= columnCount; i++) {
try {
//如果包是blob类型 添加BLOB 标识
if (blobColumns.contains(i)) {
row.add("<<BLOB>>");
} else {
//将值添加到字符串中
row.add(rs.getString(i));
}
} catch (SQLException e) {
// generally can't call getString() on a BLOB column
row.add("<<Cannot Display>>");
}
}
trace(row.toString(), false);
}

/**
* Creates a logging version of a ResultSet.
* 创建结果集代理类
* @param rs - the ResultSet to proxy
* @return - the ResultSet with logging
*/
public static ResultSet newInstance(ResultSet rs, Log statementLog, int queryStack) {
InvocationHandler handler = new ResultSetLogger(rs, statementLog, queryStack);
ClassLoader cl = ResultSet.class.getClassLoader();
return (ResultSet) Proxy.newProxyInstance(cl, new Class[]{ResultSet.class}, handler);
}

/**
* Get the wrapped result set.
*
* @return the resultSet
*/
public ResultSet getRs() {
return rs;
}

}

总结

​ mybatis 日志模块使用适配器模式进行不同类型的日志框架的统一,使用动态代理对连接执行sql,结果集进行日志的增强,使其无缝的与mybatis结合起来,你们的日志是怎么打印的呢?

评论