示例代码
InputStream inputStream = new FileInputStream(new File("src/main/resources/mybatis-config.xml"));
// 1.加载配置文件创建configuration对象,创建SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 2.创建SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3.获取Mapper接口动态代理
BaseTermMapper mapper = sqlSession.getMapper(BaseTermMapper.class);
// 4.动态代理回调sqlSession中的查询方法,sqlSession将查询方法转发给Executor,
// Executor基于JDBC访问数据库获取数据并通过反射将数据转换成POJO并返回
BaseTerm baseTerm = mapper.selectById(1);
System.out.println(baseTerm.getName());
sqlSession.close();
示例代码打印的日志如下:
Preparing: select * from base_term where id = ?
Parameters: 1(Integer)
Total: 1
MyBatis中加载第三方日志库的优先级顺序:
slf4J → commonsLoging → Log4J2 → Log4J → JdkLog;
public final class LogFactory {
...
//自动扫描日志实现,并且第三方日志插件加载优先级如下:slf4J → commonsLoging → Log4J2 → Log4J → JdkLog
static {
tryImplementation(LogFactory::useSlf4jLogging);
tryImplementation(LogFactory::useCommonsLogging);
tryImplementation(LogFactory::useLog4J2Logging);
tryImplementation(LogFactory::useLog4JLogging);
tryImplementation(LogFactory::useJdkLogging);
tryImplementation(LogFactory::useNoLogging);
}
public static synchronized void useSlf4jLogging() {
//使用Slf4jImpl实现类
setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
}
private static void tryImplementation(Runnable runnable) {
if (logConstructor == null) {//当构造方法不为空才执行方法
try {
runnable.run();
} catch (Throwable t) {
// ignore
}
}
}
//通过指定的log类来初始化构造方法
private static void setImplementation(Class<? extends Log> implClass) {
try {
//获取构造方法
Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
//获取日志对象
Log log = candidate.newInstance(LogFactory.class.getName());
logConstructor = candidate;
} catch (Throwable t) {
throw new LogException("Error setting Log implementation. Cause: " + t, t);
}
}
...
}
这里MyBatis采用适配器模式,通过Log接口的方式实现对不同第三方Log插件的进行适配,具体代码如下:
Log定义了各种日志等级的打印方法:
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);
}
Slf4jImpl类实现Log接口中的打印方法完成日志打印的适配
public class Slf4jImpl implements Log {
private Log log;
public Slf4jImpl(String clazz) {
Logger logger = LoggerFactory.getLogger(clazz);
... ...
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);
}
}
接着我们看一下MyBatis如何使用Log进行日志打印
在MyBatis中,打印的日志主要有:
1.执行的SQL语句
2.参数的类型和值
3.查询的结果数据条数
涉及的类图如下:
ConnectionLogger:负责打印连接信息和SQL语句,并创建PreparedStatementLogger;
PreparedStatementLogger:负责打印参数信息,并创建ResultSetLogger;
ResultSetLogger:负责打印数据结果信息。
接下来看一下涉及ConnectionLogger的相关源码
BaseExecutor.getConnection:
/**
* 获取连接对象
* @param statementLog
* @return
* @throws SQLException
*/
protected Connection getConnection(Log statementLog) throws SQLException {
Connection connection = transaction.getConnection();
if (statementLog.isDebugEnabled()) {
//使用connection连接对象创建ConnectionLogger代理对象并返回
return ConnectionLogger.newInstance(connection, statementLog, queryStack);
} else {
return connection;
}
}
ConnectionLogger采用动态代理的方式对connection的功能进行增强
ConnectionLogger.newInstance:
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);
}
Debug展示的值
ConnectionLogger.invoke方法对Connection方法添加打印日志逻辑
@Override
public Object invoke(Object proxy, Method method, Object[] params)
throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
if ("prepareStatement".equals(method.getName()) || "prepareCall".equals(method.getName())) {
if (isDebugEnabled()) {
//如果是调用prepareStatement、prepareCall打印要执行的sql语句
debug(" Preparing: " + removeExtraWhitespace((String) params[0]), true);
}
PreparedStatement stmt = (PreparedStatement) method.invoke(connection, params);
//返回prepareStatement的代理对象PreparedStatementLogger,让prepareStatement也具备打印日志能力
stmt = PreparedStatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else if ("createStatement".equals(method.getName())) {
Statement stmt = (Statement) method.invoke(connection, params);
//返回Statement的代理对象StatementLogger,让Statement也具备打印日志能力
stmt = StatementLogger.newInstance(stmt, statementLog, queryStack);
return stmt;
} else {
return method.invoke(connection, params);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
Debug展示的值
由上面的代码可以看到如果调用Connection的prepareStatement方法后,Connection的代理对象ConnectionLogger首先会打印sql语句,然后调用invoke方法生成PreparedStatement对象,再用PreparedStatement生成的PreparedStatementLogger代理对象,最后返回出去,因此我们接着看PreparedStatementLogger中的invoke方法,具体如下。
@Override
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
if (EXECUTE_METHODS.contains(method.getName())) {
//execute方法
if (isDebugEnabled()) {
//打印记录的查询参数的参数类型和参数值
debug("Parameters: " + getParameterValueString(), true);
}
clearColumnInfo();
if ("executeQuery".equals(method.getName())) {
//如果是查询语句 返回ResultSet的动态代理对象ResultSetLogger,使ResultSet具有打印日志功能
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())) {
//记录查询参数,在调用execute方法时打印
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的动态代理对象ResultSetLogger
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);
}
}
接着我们继续看ResultSetLogger的invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, params);
}
Object o = method.invoke(rs, params);
if ("next".equals(method.getName())) {// 增强next方法
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);
}
}
可以看到最后也将查询结果总数也打印出来。