Mybatis源码学习-日志模块
所用到的设计模式:
- 适配器模式 (对外提供的日志功能)
- 代理模式 (将日志功能添加到业务中)
代理模式 (静态代理 动态代理)
定义:给目标对象提供一个代理对象,并由代理对象控制目标对象的引用
目的:
1.通过代理对象来访问目标对象,防止直接访问目标对象给系统带来的不必要的复杂性
2.通过代理对象增强原有的业务逻辑(eg:给业务增加日志功能) - 装饰器模式 (真正获取connection对象的时候)
1.Mybatis源码的基本模块
模块如下图:一共16个模块,这16个模块可以分为3层
基础支撑层:专注于底层实现,无业务含义(通用性比较强)
核心处理层:专门处理Mybatis的业务流程实现,依赖于基础支撑层
接口层:对外提供的访问接口,也就是面向SqlSession编程
日志模块
- 如何提供统一的日志级别
前提:Mybatis本身没有日志的实现类,而是接入的第三方日志组件
提供了统一的(log)接口,让客户端面向接口编程,不用关系具体的实现
package org.apache.ibatis.logging;
/**
* @author Clinton Begin
*/
public interface Log {
boolean isDebugEnabled();
boolean isTraceEnabled();
void error(String s, Throwable e);
void error(String s);
void debug(String s);
void trace(String s);
void warn(String s);
}
- 自动扫描日志,并按顺序加载第三方日志 (顺序slf4j->commomsLogging->log4j2->log4j->jdkLog)
在LogFactory中,有下面的代码块
static {
tryImplementation(LogFactory::useSlf4jLogging);
tryImplementation(LogFactory::useCommonsLogging);
tryImplementation(LogFactory::useLog4J2Logging);
tryImplementation(LogFactory::useLog4JLogging);
tryImplementation(LogFactory::useJdkLogging);
tryImplementation(LogFactory::useNoLogging);
}
该静态代码块的功能是,扫描第三方的日志组件,将他们的构造方法赋给LogFactory,让LogFactory使用该构造方法来实例化日志对象
private static Constructor<? extends Log> logConstructor;
// 真正创建Log实现类的方法
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);
}
}
// 获取第三方日志的构造方法
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);
}
}
- 将日志功能优雅的加入到相应的功能里面(使用动态代理模式)
执行mybatis的时候,一共打印了3行日志,分别是:
1.执行的sql (ConnectionLogger)
2.参数值跟参数类型 (PreparedStatementLogger)
3.查询的结果条数 (ResultSetLogger)
主要由以下几个类来提供日志的打印
BaseJdbcLogger:所有日志增强的抽象基类,用于记录jdbc哪些方法需要增强,保存运行期间的sql参数信息
ConnectionLogger:对Connection对象进行增强。负责打印连接信息和sql语句。
public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler{
//真正的连接对象
private final Connection connection;
}
如果是调用prepareStatement、prepareCall、createStatement的方法,打印要执行的sql语句,并创建PreparedStatementLogger代理对象,让PreparedStatement对象也具有打印的功能
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);
}
PreparedStatementLogger:对prepareStatement 进行增强
1.如果是excute相关方法,对excute相关方法进行参数的日志打印
if (EXECUTE_METHODS.contains(method.getName())) {//增强PreparedStatement的execute相关方法
if (isDebugEnabled()) {
debug("Parameters: " + getParameterValueString(), true);//当方法执行时,通过动态代理打印参数
}
clearColumnInfo();
if ("executeQuery".equals(method.getName())) {//返回动态代理能力的resultSet
ResultSet rs = (ResultSet) method.invoke(statement, params);
return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
} else {
return method.invoke(statement, params);
}
}
2.如果是set方法,将参数设置到columnMap、columnNames、columnValues中
PreparedStatementLogger:
else if (SET_METHODS.contains(method.getName())) {//将参数设置到columnMap、columnNames、columnValues,为打印参数做好准备
if ("setNull".equals(method.getName())) {
setColumn(params[0], null);
} else {
setColumn(params[0], params[1]);
}
return method.invoke(statement, params);
}
BaseJdbcLogger:
//保存preparestatment中set方法的键值对
private final Map<Object, Object> columnMap = new HashMap<>();
//保存preparestatment中set方法的key值
private final List<Object> columnNames = new ArrayList<>();
//保存preparestatment中set方法的value值
private final List<Object> columnValues = new ArrayList<>();
protected void setColumn(Object key, Object value) {
columnMap.put(key, value);
columnNames.add(key);
columnValues.add(value);
}
3.如果是查询,增强 PreparedStatement 的 getResultSet 方法,返回动态代理能力的
resultSet
PreparedStatementLogger:
else if ("getResultSet".equals(method.getName())) {//返回动态代理能力的resultSet
ResultSet rs = (ResultSet) method.invoke(statement, params);
return rs == null ? null : ResultSetLogger.newInstance(rs, statementLog, queryStack);
}
如果是更新,直接打印影响的行数
PreparedStatementLogger:
else if ("getUpdateCount".equals(method.getName())) {// 如果是更新,直接打印影响的行数
int updateCount = (Integer) method.invoke(statement, params);
if (updateCount != -1) {
debug(" Updates: " + updateCount, false);
}
return updateCount;
ResultSetLogger:负责打印数据结果信息(总行数)。
原生jdbc,获取数据是执行resultSet.next()方法来获取。mybatis也是用.next()来获取,并设置一个变量,记录.next()的次数,这个次数就是查询结果的总行数
ResultSetLogger:
private int rows;
private final ResultSet rs;
Object o = method.invoke(rs, params);//执行result.next方法,判断是否还有数据
if ("next".equals(method.getName())) {//如果还有数据,计数器rows加一
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);//如果没有数据了,打印rows,打印查询出来的数据条数
}
}
到目前为止,只体现了mybatis如何实现日志增强的,那真正加入到业务里的是在SimpleExecutor
Mybatis中Executor才是访问数据库的真正组件,所以找到SimpleExecutor类中,能找到prepareStatement()这个方法
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;
}