1 Mybatis中集成日志框架示例
1.1 Mybatis使用log4j示例(推荐方式)
第一步:pom.xml引入log4j依赖
<!-- slf4j日志门面 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.36</version>
</dependency>
<!-- 引入slf4j对应log4j的桥接器 -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>1.7.36</version>
</dependency>
<!-- log4j日志框架 -->
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.17</version>
</dependency>
第二步:全局配置文件中配置logImpl的值为"SLF4J"
第三步:在resource目录下创建一个log4j.properties
的配置文件
log4j.rootLogger=debug,console,stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.err
log4j.appender.stdout.layout=org.apache.log4j.SimpleLayout
log4j.appender.logfile=org.apache.log4j.FileAppender
log4j.appender.logfile.File=d:/msb.log
log4j.appender.logfile.layout=org.apache.log4j.PatternLayout
log4j.appender.logfile.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %l %F %p %m%n
log4j.appender.console=org.apache.log4j.ConsoleAppender
log4j.appender.console.Target=System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout=org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %l %F %p %m%n
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
OK, 日志框架集成完成, 可以使用了,重新运行Mybatis测试,控制台输出如下:
1.2 Mybatis使用logback示例
第一步:pom.xml引入logback依赖
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.11</version>
</dependency>
第二步:全局配置文件中配置logImpl的值为"SLF4J"
第三步:在resource目录下创建一个logback.xml
的配置文件
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<!-- encoder 默认配置为PatternLayoutEncoder -->
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
</pattern>
</encoder>
</appender>
<logger name="com.kkarma" level="DEBUG"/>
<logger name="com.kkarma" level="INFO"/>
<root level="DEBUG">
<appender-ref ref="STDOUT"/>
</root>
</configuration>
OK, 日志框架集成完成, 可以使用了,重新运行Mybatis测试,控制台输出如下:
更多日志框架的集成请参考官方文档:https://mybatis.org/mybatis-3/zh/logging.html
2 Mybatis中集成日志框架原理解析
2.1 关于全局配置文件中日志相关配置的解析过程
2.2 Myabtis中关于日志框架的设计
2.2.1 Log接口
/*
* Copyright 2009-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.logging;
/**
* @author Clinton Begin
* Mybatis框架重定义的日志接口
*/
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);
}
2.2.2 LogFactory
/*
* Copyright 2009-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.ibatis.logging;
import java.lang.reflect.Constructor;
/**
* @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;
static {
// 支持Slf4j日志框架
tryImplementation(LogFactory::useSlf4jLogging);
// 支持common-logging日志框架
tryImplementation(LogFactory::useCommonsLogging);
// 支持log4j2日志框架
tryImplementation(LogFactory::useLog4J2Logging);
// 支持log4j日志框架
tryImplementation(LogFactory::useLog4JLogging);
// 支持jdk自带日志框架
tryImplementation(LogFactory::useJdkLogging);
tryImplementation(LogFactory::useNoLogging);
}
private LogFactory() {
// disable construction
}
public static Log getLog(Class<?> clazz) {
return getLog(clazz.getName());
}
public static Log getLog(String logger) {
try {
// 通过反射根据传递进来的日志实现类创建Log的动态代理对象
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);
}
/**
* @deprecated Since 3.5.9 - See https://github.com/mybatis/mybatis-3/issues/1223. This method will remove future.
*/
@Deprecated
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);
}
private static void tryImplementation(Runnable runnable) {
if (logConstructor == null) {
try {
runnable.run();
} catch (Throwable t) {
// ignore
}
}
}
private static void setImplementation(Class<? extends Log> implClass) {
try {
Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
// 通过解析我们在全局配置文件中配置的logImpl的值, 获取到日志框架框架的全限定路径名, 通过动态代理创建出Log接口的代理类对象, 配置的logImpl的实现类是什么类型,最后的log对象就是对应类型的日志框架的实现类对象,这里使用了适配器模式
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);
}
}
}
2.2.3 项目中的应用
2.2.3.1 概述
看到这张图片,大家在项目中使用Mybatis框架进行数据库操作的时候可能都在控制台看到过类似的日志打印, 大家有没有去深究一下, 这些功能到底是怎么实现的呢, 下面我主要通过源码分析一下在Mybatis框架在执行JDBC操作的时候是如何完成相关的操作信息和SQL语句从控制台输出的?
这里我们聊的是围绕着JDBC相关的操作, 所以我们只分析跟JDBC相关的日志实现。
我们知道JDBC关键的几个实现, 所以要打印日志, 最好的方式就是通过动态代理创建这些关键接口的代理类, 在我们的代理类中完成我们需要的日志打印输出相关的增强功能,Mybatis框架也的确是这样进行设计的。
- Connection
- Statement
- ResultSet
BaseJdbcLogger是一个抽象类,它是jdbc包下其他Logger的父类,因为需要通过动态代理创建实现类对象, 这里的实现类都实现了Invocationhandler接口。继承关系如下:
2.2.3.2 ConnectionLogger的创建时机和创建过程
- Connection的代理对象ConnectionLogger的创建时机和创建过程, 如下图
2.2.3.3 PreparedStatementLogger的创建时机和创建过程
-PreparedStatement的代理对象PreparedStatementLogger的创建时机和创建过程, 如下图
这里就是日志增强的体现, 我们可以在控制台看到预处理的SQL语句输出。
2.2.3.4 ResultSetLogger的创建时机和创建过程
- ResultSet的代理对象ResultSetLogger对象创建的实际和创建过程如下
从ResultSetWrapper中获取到ResultSetLogger对象
对ResultSet进行循环处理,解析ResultSet中的每一个行数据对象
真正处理数据库列字段映射到java实体类的属性值得处理方法
当最后一次执行ResultSet.next()方法时, 游标移动到最后一行数据行的下一行, 没有数据返回, foundValues的结果就为false, 说明数据解析映射已经完成了, 控制台输出查询结果数据的总数。