承接上一篇关于spring 5.x的日志体系,本篇看看Mybatis的日志体系及实现,Mybatis版本基于3.x。
关于mybatis的官方文档比较友好,分门别类,各有论述,如mybatis官方文档详见https://mybatis.org/mybatis-3/#,mybatis与spring的官方文档详见http://mybatis.org/spring/index.html,与springboot相关的参见http://mybatis.org/spring-boot-starter/mybatis-spring-boot-autoconfigure/#,官方文档写的很完备,也适合初学者。
Mybatis环境搭建及日志展示
研究mybatis日志,有必要写一个demo,该有的数据库(Mysql)相关的jar包依赖都应该有,pom文件如下。
1 <dependency>
2 <groupId>org.mybatis</groupId>
3 <artifactId>mybatis</artifactId>
4 <version>3.4.6</version>
5 </dependency>
6
7 <dependency>
8 <groupId>org.mybatis</groupId>
9 <artifactId>mybatis-spring</artifactId>
10 <version>1.3.2</version>
11 </dependency>
12
13 <dependency>
14 <groupId>mysql</groupId>
15 <artifactId>mysql-connector-java</artifactId>
16 version>8.0.13</version>
17 </dependency>
18
19 <dependency>
20 <groupId>org.springframework</groupId>
21 <artifactId>spring-jdbc</artifactId>
22 <version>5.1.3.RELEASE</version>
23 </dependency>
24
25 <dependency>
26 <groupId>org.springframework</groupId>
27 <artifactId>spring-context</artifactId>
28 <version>5.1.3.RELEASE</version>
29 </dependency>
配置mybatis环境:java config风格获取一个SqlSessionFactoryBean和动态动态注入一个数据源dataSource,连接本地的数据库company,表为user。
1 @Configuration
2 @ComponentScan("com.mystyle")
3 @MapperScan("com.mystyle.dao")
4 public class MybatisConfig {
5 @Bean
6 @Autowired
7 public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource) throws Exception {
8 SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
9 sqlSessionFactoryBean.setDataSource(dataSource);
10 // org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
11 // configuration.setLogImpl(Log4jImpl.class);
12 // sqlSessionFactoryBean.setConfiguration(configuration);
13 return sqlSessionFactoryBean;
14 }
15
16 @Bean
17 public DataSource dataSource() {
18 DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
19 driverManagerDataSource.setUsername("root");
20 driverManagerDataSource.setPassword("12345678");
21 driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/company?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC");
22 driverManagerDataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
23 return driverManagerDataSource;
24
25 }
26
27 }
注:注释的代码请先忽略,后续会用到。
写一个UserDao,查询出user表中的user信息
public interface UserDao {
@Select("select * from user")
List<Map<String,String>> queryUsers();
}
运行完成之后,可以看到console成功打印出了user的信息,但是除此之外,并未看到我们想要的打印出sql语句等信息。
Mybatis日志代码跟踪
在MybatisConfig的sqlSessionFactory方法中,new出了一个SqlSessionFactoryBean,在SqlSessionFactoryBean类中可以看到一个关于Logger的私有对象的声明并初始化。
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
private static final Log LOGGER = LogFactory.getLog(SqlSessionFactoryBean.class);
在LogFactory类中,
1 public final class LogFactory {
2
3 /**
4 * Marker to be used by logging implementations that support markers
5 */
6 public static final String MARKER = "MYBATIS";
7
8 private static Constructor<? extends Log> logConstructor;
9
10 static {
//1.启动线程
11 tryImplementation(new Runnable() {
12 @Override
13 public void run() {
//2。加载日志类,返回构造器
14 useSlf4jLogging();
15 }
16 });
17 tryImplementation(new Runnable() {
18 @Override
19 public void run() {
20 useCommonsLogging();
21 }
22 });
23 tryImplementation(new Runnable() {
24 @Override
25 public void run() {
26 useLog4J2Logging();
27 }
28 });
29 tryImplementation(new Runnable() {
30 @Override
31 public void run() {
32 useLog4JLogging();
33 }
34 });
35 tryImplementation(new Runnable() {
36 @Override
37 public void run() {
38 useJdkLogging();
39 }
40 });
41 tryImplementation(new Runnable() {
42 @Override
43 public void run() {
44 useNoLogging();
45 }
46 });
47 }
48
49 private LogFactory() {
50 // disable construction
51 }
52
53 public static Log getLog(Class<?> aClass) {
54 return getLog(aClass.getName());
55 }
56
57 public static Log getLog(String logger) {
58 try {
59 return logConstructor.newInstance(logger);
60 } catch (Throwable t) {
61 throw new LogException("Error creating logger for logger " + logger + ". Cause: " + t, t);
62 }
63 }
64
65 public static synchronized void useCustomLogging(Class<? extends Log> clazz) {
66 setImplementation(clazz);
67 }
68
69 public static synchronized void useSlf4jLogging() {
70 setImplementation(org.apache.ibatis.logging.slf4j.Slf4jImpl.class);
71 }
72
73 public static synchronized void useCommonsLogging() {
74 setImplementation(org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl.class);
75 }
76
77 public static synchronized void useLog4JLogging() {
78 setImplementation(org.apache.ibatis.logging.log4j.Log4jImpl.class);
79 }
80
81 public static synchronized void useLog4J2Logging() {
82 setImplementation(org.apache.ibatis.logging.log4j2.Log4j2Impl.class);
83 }
84
85 public static synchronized void useJdkLogging() {
86 setImplementation(org.apache.ibatis.logging.jdk14.Jdk14LoggingImpl.class);
87 }
88
89 public static synchronized void useStdOutLogging() {
90 setImplementation(org.apache.ibatis.logging.stdout.StdOutImpl.class);
91 }
92
93 public static synchronized void useNoLogging() {
94 setImplementation(org.apache.ibatis.logging.nologging.NoLoggingImpl.class);
95 }
96
97 private static void tryImplementation(Runnable runnable) {
98 if (logConstructor == null) {
99 try {
100 runnable.run();
101 } catch (Throwable t) {
102 // ignore
103 }
104 }
105 }
106
107 private static void setImplementation(Class<? extends Log> implClass) {
108 try {
109 Constructor<? extends Log> candidate = implClass.getConstructor(String.class);
//3. 试图去实例化一个实现类的日志对象
110 Log log = candidate.newInstance(LogFactory.class.getName());
111 if (log.isDebugEnabled()) {
112 log.debug("Logging initialized using '" + implClass + "' adapter.");
113 }
114 logConstructor = candidate;
115 } catch (Throwable t) {
116 throw new LogException("Error setting Log implementation. Cause: " + t, t);
117 }
118 }
119
120 }
LogFactory在加载时,有一个静态代码块,会依次去加载各种日志产品的实现,请其中的顺序是 SLF4J > Apache Commons Logging > Log4j 2 > Log4j > JDK logging。
以SLF4J为例说明(其余类似):
- 执行tryImplementation方法:如果构造器logConstructor(LogFactory中的私有变量)不为空,启动一个线程去执行其中的run 方法。
- 具体是去执行useSlf4jLogging方法中的setImplementation方法,该方法会传入SLF4J的日志实现类的class对象:class org.apache.ibatis.logging.slf4j.Slf4jImpl。
- 试图去实例化一个实现类的日志对象,由于此时并未加入slf4j相关jar包依赖,所以会抛一个异常出去。
在默认情况下,即是用户未指定得情况下,class org.apache.ibatis.logging.commons.JakartaCommonsLoggingImpl也就是Apache Commons Logging会默认实例化成功,spring默认使用此日志实现,由于存在该实现依赖,所以会被实例化成功,并返回其构造器。SqlSessionFactoryBean中调用的getLog方法实际是返回了一个基于JCL实现的日志对象。
用户指定Mybatis日志实现类
假设用户需要使得Mybatis使用的日志是基于log4j的,同样,首先需要在pom中先添加基于log4j的相关依赖,log4j.properties配置文件添加,在sqlSessionFactoryBean实例化时通过configuration去手动指定logimpl为log4J。
1 public void setLogImpl(Class<? extends Log> logImpl) {
2 if (logImpl != null) {
3 this.logImpl = logImpl;
4 LogFactory.useCustomLogging(this.logImpl);
5 }
6 }
实际内部是去调用了LogFactory的useCustomLogging方法。
1 public static synchronized void useCustomLogging(Class<? extends Log> clazz) {
2 setImplementation(clazz);
3 }
其余和上述流程一样,把factory中的日志实现类更新为用户所指定的实现类。
日志打印效果
1 DEBUG - Logging initialized using 'class org.apache.ibatis.logging.log4j.Log4jImpl' adapter.
2 DEBUG - Creating a new SqlSession
3 DEBUG - SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4716be8b] was not registered for synchronization because synchronization is not active
4 DEBUG - JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@3faf2e7d] will not be managed by Spring
5 DEBUG - ==> Preparing: select * from user
6 DEBUG - ==> Parameters:
7 DEBUG - <== Total: 7
8 DEBUG - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@4716be8b]
9 [{eventId=1, count_num=1, name=tom, id=1}, {eventId=2, count_num=2, name=jack, id=2}, {eventId=7, count_num=2, name=david, id=3}, {eventId=3, count_num=2, name=david, id=4}, {eventId=4, count_num=0, name=david, id=5}, {eventId=5, count_num=1, name=david, id=6}, {eventId=6, count_num=0, name=lily, id=7}]
10 Disconnected from the target VM, address: '127.0.0.1:53155', transport: 'socket'
可以看到日志打印出的格式以及内容都是我们所想要的。