MyBatis没有提供日志的实现类,需要接入第三方的日志组件,但第三方的日志组件有各自的log级别,各不相同,MyBatis统一提供了trace、debug、warn、error四个级别。
MyBatis源码依赖的开源日志组件有:
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-apiartifactId>
<version>1.7.30version>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.slf4jgroupId>
<artifactId>slf4j-log4j12artifactId>
<version>1.7.30version>
<optional>trueoptional>
dependency>
<dependency>
<groupId>log4jgroupId>
<artifactId>log4jartifactId>
<version>1.2.17version>
<optional>trueoptional>
dependency>
<dependency>
<groupId>org.apache.logging.log4jgroupId>
<artifactId>log4j-coreartifactId>
<version>2.13.0version>
<optional>trueoptional>
dependency>
<dependency>
<groupId>commons-logginggroupId>
<artifactId>commons-loggingartifactId>
<version>1.2version>
<optional>trueoptional>
dependency>
optional均为true,让使用者可选择哪一种依赖。
Mybatis配置文件中配置使用哪一种日志的地方是:
<settings>
<setting name="logImpl" value="SLF4J"/>
settings>
可选项共有:SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING
XMLConfigBuilder的loadCustomLogImpl方法加载MyBatis配置文件的时候,读取logImpl配置的value:
private void loadCustomLogImpl(Properties props) {
Class<? extends Log> logImpl = resolveClass(props.getProperty("logImpl"));
configuration.setLogImpl(logImpl);
}
在resolveClass方法中,MyBatis的保存了一系列的别名映射,其中就有:
slf4j -> {Class@2218} "class org.apache.ibatis.logging.slf4j.Slf4jImpl"
log4j -> {Class@2265} "class org.apache.ibatis.logging.log4j.Log4jImpl"
log4j2 -> {Class@2279} "class org.apache.ibatis.logging.log4j2.Log4j2Impl"
jdk_logging -> {Class@2267} "class org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl"
commons_logging -> {Class@2259} "class org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl"
stdout_logging -> {Class@2284} "class org.apache.ibatis.logging.stdout.StdOutImpl"
no_logging -> {Class@2249} "class org.apache.ibatis.logging.nologging.NoLoggingImpl"
然后找到xml中配置的日志别名(解析过程会将配置文件中的日志别名转换成小写)对应的class。
例如我们在配置文件中配置
<settings>
<setting name="logImpl" value="SLF4J"/>
settings>
效果相同。
找到slf4j对应的class之后,通过Resource类初始化org.apache.ibatis.logging.slf4j.Slf4jImpl类。
Resources.classForName(org.apache.ibatis.logging.slf4j.Slf4jImpl)
然后将org.apache.ibatis.logging.slf4j.Slf4jImpl赋予Configuration类的logImpl属性。
Configuration尤其重要,基本所有重要的MyBatis配置都在里面。
Configuration.setLogImpl()方法如下:
public void setLogImpl(Class<? extends Log> logImpl) {
if (logImpl != null) {
this.logImpl = logImpl;
LogFactory.useCustomLogging(this.logImpl);
}
}
在进入LogFactory.useCustomLogging之前,先看MyBatis的日志接口和实现类(Slf4jImpl为例):
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);
}
public class Slf4jImpl implements Log {
private Log log;
public Slf4jImpl(String clazz) {
Logger logger = LoggerFactory.getLogger(clazz);
if (logger instanceof LocationAwareLogger) {
try {
// check for slf4j >= 1.6 method signature
logger.getClass().getMethod("log", Marker.class, String.class, int.class, String.class, Object[].class, Throwable.class);
log = new Slf4jLocationAwareLoggerImpl((LocationAwareLogger) logger);
return;
} catch (SecurityException | NoSuchMethodException e) {
// fail-back to Slf4jLoggerImpl
}
}
// Logger is not LocationAwareLogger or slf4j version < 1.6
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);
}
}
其他日志组件log4j等的实现类类似。
继续回到LogFactory.useCustomLogging方法中:
public final class LogFactory {
public static final String MARKER = "MYBATIS";
private static Constructor<? extends Log> logConstructor;
static {
//MyBatis对于第三方日志加载的优先级是:slf4j->commonsloging->log4j2->log4j->jdklog
tryImplementation(LogFactory::useSlf4jLogging);
tryImplementation(LogFactory::useCommonsLogging);
tryImplementation(LogFactory::useLog4J2Logging);
tryImplementation(LogFactory::useLog4JLogging);
tryImplementation(LogFactory::useJdkLogging);
tryImplementation(LogFactory::useNoLogging);
}
private LogFactory() {
// disable construction
}
public static Log getLog(Class<?> aClass) {
return getLog(aClass.getName());
}
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);
}
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);
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);
}
}
}
根据class的类型,通过反射,获取构造方法对象Constructor,并使用构造方法(传一个String参数,值就是日志的class名称)初始化日志类,尝试着输出测试一下日志(debug级别)。
然后将构造方法对象赋予logConstructor变量。然后通过下面的方法在MyBatis源码各个地方被四处调用:
public static Log getLog(String logger) {
return logConstructor.newInstance(logger);
}
其中就包括LoggingCache日志缓存类。二级缓存命中率就是在此类中输出的。
public class LoggingCache implements Cache {
private final Log log;
private final Cache delegate;
protected int requests = 0;
protected int hits = 0;
public LoggingCache(Cache delegate) {
this.delegate = delegate;
this.log = LogFactory.getLog(getId());
}
@Override
public String getId() {
return delegate.getId();
}
@Override
public int getSize() {
return delegate.getSize();
}
@Override
public void putObject(Object key, Object object) {
delegate.putObject(key, object);
}
@Override
public Object getObject(Object key) {
requests++;
final Object value = delegate.getObject(key);
if (value != null) {
hits++;
}
if (log.isDebugEnabled()) {
log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio());
}
return value;
}
@Override
public Object removeObject(Object key) {
return delegate.removeObject(key);
}
@Override
public void clear() {
delegate.clear();
}
@Override
public int hashCode() {
return delegate.hashCode();
}
@Override
public boolean equals(Object obj) {
return delegate.equals(obj);
}
private double getHitRatio() {
return (double) hits / (double) requests;
}
}
由于用到了装饰模式,下面代码中的TransactionalCache二级缓存对象,它有一个属性是Cache,实际运行的真身就是LoggingCache。
/**
* The 2nd level cache transactional buffer.
*
* This class holds all cache entries that are to be added to the 2nd level cache during a Session.
* Entries are sent to the cache when commit is called or discarded if the Session is rolled back.
* Blocking cache support has been added. Therefore any get() that returns a cache miss
* will be followed by a put() so any lock associated with the key can be released.
*/
public class TransactionalCache implements Cache {
private final Cache delegate;
@Override
public Object getObject(Object key) {
// issue #116
Object object = delegate.getObject(key);
if (object == null) {
entriesMissedInCache.add(key);
}
// issue #146
if (clearOnCommit) {
return null;
} else {
return object;
}
}
查询缓存的时候,如果开启了二级缓存,通过装饰模式间接调用LoggingCache统计命中率。命中率即hits次数/请求的次数。
https://blog.csdn.net/smith789/article/details/104207786
MyBatis日志模块用到了适配器模式。例如:
class Slf4jLoggerImpl implements Log {
private final Logger log;
Slf4jLoggerImpl是针对slf4j的适配器,log变量是真正提供日志能力的类。