Mybatis日志體系


  承接上一篇關於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為例說明(其余類似):

  1. 執行tryImplementation方法:如果構造器logConstructor(LogFactory中的私有變量)不為空,啟動一個線程去執行其中的run 方法。
  2. 具體是去執行useSlf4jLogging方法中的setImplementation方法,該方法會傳入SLF4J的日志實現類的class對象:class org.apache.ibatis.logging.slf4j.Slf4jImpl。
  3. 試圖去實例化一個實現類的日志對象,由於此時並未加入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'

   可以看到日志打印出的格式以及內容都是我們所想要的。

   

 

 

 

 

 

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM