网上挺多文章是通过mybatis的拦截器来实现的,但是测试发现,sql条数如果过多的话,相比于mybatis原生的输出方式,通过拦截器方式输出日志会对性能有影响。当然,因为mybatis原生的输出级别是debug,在有些情况下可能不适用(比如有的系统只保存INFO、ERROR级别的日志,但又想要保留SQL日志方便排查问题),所以这里通过自定义日志来达到不通过拦截器且能输出日志的目的。
mybatis的xml方式配置(spring中properties等其他方式也是类似的,都是logImpl
属性,源码也差不多)
建了一张hobby表来做测试,大家可以用自己的表
Hobby实体类、mapper和xml,都是很常用的,方便起见都放在一个文件夹下面,大家可以使用自己项目中的
mybatis-config配置内容
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<!-- 指定使用SLF4J框架-->
<setting name="logImpl" value="SLF4J"/>
</settings>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<!-- 这些都要改成自己的-->
<property name="url" value="jdbc:mysql://xxx.xxx.xxx.xxx:3306/test1?serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="root/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="HobbyMapper.xml"/>
</mappers>
</configuration>
首先我们看一下原生的mybatis输出(配置使用了SLF4J日志框架),这边我已经自己建了一个测试项目,大家可以用自己的项目,表也可以随便用。
public class MybatisTest {
@SneakyThrows
public static void main(String[] args) {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
HobbyMapper mapper = sqlSession.getMapper(HobbyMapper.class);
System.out.println(mapper.queryAll());
System.out.println(mapper.getById(1));
sqlSession.close();
}
}
可以看到,未自定义日志输出之前,都是DEBUG级别的日志,如果把mybatis-config.xml中的<setting name="logImpl" value="SLF4J"/>
去掉,是没有这些日志输出的,所以,可以发现,通过mybatis的logImpl
属性,我们是可以来指定日志对象的。
mybatis中文网原话:可选的值有:SLF4J、LOG4J、LOG4J2、JDK_LOGGING、COMMONS_LOGGING、STDOUT_LOGGING、NO_LOGGING,或者是实现了 org.apache.ibatis.logging.Log 接口,且构造方法以字符串为参数的类完全限定名
所以我们只需要自定义一个类,实现org.apache.ibatis.logging.Log
接口,重写其中的方法就行,可以看到,里面对应了4种级别的日志输出方法
这里我们自定义一个CsLog类
import org.apache.ibatis.logging.Log;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CsLog implements Log {
/**
* 定义一个log属性,在构造器中初始化,方便后续的使用
*/
private final Logger log;
/**
* 定义一个带参构造函数,拿到全类名之后就可以通过日志工厂拿到log对象了,我这里使用的slf4j,其他的日志框架也差不多用法
*
* @param className 类名称
* @see org.apache.ibatis.logging.LogFactory#setImplementation(Class)
*/
public CsLog(String className) {
log = LoggerFactory.getLogger(className);
}
@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) {
// 为了区别之前的,这里加个前缀,并且改成info级别,大家可根据需要修改
log.info("自定义的logImpl" + s);
}
@Override
public void trace(String s) {
log.trace("自定义的logImpl" + s);
}
@Override
public void warn(String s) {
log.warn("自定义的logImpl:" + s);
}
}
然后在mybatis-config.xml中配置,之前我配置的是SLF4J,把SLF4J改成自定义日志类的全类名就行
<setting name="logImpl" value="mybatis.test.CsLog"/>
改完之后运行截图,可以发现输出的日志已经是自定义的了
mybatis-plus方式配置
如果使用的是mybatis-plus框架,也可以指定这个属性
mybatis-plus:
configuration:
log-impl: mybatis.test.CsLog
原理
在org.apache.ibatis.builder.xml.XMLConfigBuilder
类中parseConfiguration
方法中会调用loadCustomLogImpl
方法
获取logImpl属性值,然后拿到日志实现类的Class对象,拿到之后再通过configuration对象的方法去设置
resolveClass方法里面会根据这个属性值去已经注册的别名里面查询是否存在别名,存在就返回别名map集合中的value(所以为什么前面写一个SLF4J就可以使用slf4j输出日志了,就是因为mybatis内部已经定义好了别名),不存在就通过Class.forName返回一个Class对象
前面一定会拿到Class对象,如果拿不到会报错,拿到之后,就会调用org.apache.ibatis.session.Configuration
对象中的setLogImpl方法,方法里面主要是调用日志工厂的useCustomLogging方法,顾名思义就是使用自定义的日志实现
最终会调用到LogFactory中的setImplementation
方法,其他几个UseSlf4JLogging()类似的都是调用会setImplementation方法。这个方法里面会拿到自定义日志的构造器,这就是为什么前面要自定义一个有参构造器的原因了。拿到构造器之后会赋值给logConstructor,这是因为后续其他mapper的log也要初始化(通过此类的getLog方法),所以为什么我们输出sql的时候,显示的都是sql所在的全类名,而不是固定的类名。
题外话:LogFactory里面static代码块会去找存不存在日志框架,里面会先判断logConstructor属性是否为空,这就是为什么不配置logImpl属性的时候,也会输出日志了原因了