一.项目搭建

  1.pom.xml

    <dependencies>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.20.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.3.20.RELEASE</version>
</dependency> <dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.3.20.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.6</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.2</version>
</dependency>
<!-- spring连接池 -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.20.RELEASE</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.30</version>
</dependency>
</dependencies>

  2.配置类

import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.jdbc.datasource.DriverManagerDataSource; import javax.sql.DataSource; @Configuration
@ComponentScan("com.hrh.mybatis")
@MapperScan("com.hrh.mybatis.mapper")
public class MyBatisConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource());
factoryBean.setConfigLocation(new DefaultResourceLoader().getResource("classpath:mybatis-config.xml"));//内置日志工厂配置
return factoryBean;
}
@Bean
public DataSource dataSource(){
DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
driverManagerDataSource.setDriverClassName("com.mysql.jdbc.Driver");
driverManagerDataSource.setUsername("xxx");
driverManagerDataSource.setPassword("xxx");
driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/xxx?useUnicode=true&characterEncoding=utf-8&autoReconnect=true");
return driverManagerDataSource;
} }

  3.pojo类、dao类和service类

import org.springframework.stereotype.Repository;

@Repository
public class Person {
private int id;
private String name;
private int age; public int getId() {
return id;
} public void setId(int id) {
this.id = id;
} public String getName() {
return name;
} public void setName(String name) {
this.name = name;
} public int getAge() {
return age;
} public void setAge(int age) {
this.age = age;
} @Override
public String toString() {
return "Person{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}
import com.hrh.mybatis.bean.Person;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select; import java.util.List;
@Mapper
public interface PersonMapper {
@Select("select *from tab_person;")
List<Person> list();
}
import com.hrh.mybatis.bean.Person;
import com.hrh.mybatis.mapper.PersonMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import java.util.List; @Service
public class PersonService {
@Autowired
PersonMapper personMapper; public List<Person> getList() {
return personMapper.list();
}
}

  4.测试类

    public static void main(String[] args) throws Exception {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyBatisConfig.class);
PersonService bean = context.getBean(PersonService.class);
List<Person> list = bean.getList();
for (Person p : list) {
System.out.println(p.toString());
}
}

二.日志输出

  以下内容是参考官网实现的:https://mybatis.org/mybatis-3/logging.htmlhttps://mybatis.org/mybatis-3/zh/logging.html

  1.使用Mybatis的内置日志工厂输出

  Mybatis 通过使用内置的日志工厂提供日志功能。内置日志工厂将会把日志工作委托给下面的实现之一:

    • SLF4J
    • Apache Commons Logging
    • Log4j 2
    • Log4j
    • JDK logging

  MyBatis 内置日志工厂会基于运行时检测信息选择日志委托实现。它会(按上面罗列的顺序)使用第一个查找到的实现。当没有找到这些实现时,将会禁用日志功能。

  不少应用服务器(如 Tomcat 和 WebShpere)的类路径中已经包含 Commons Logging。注意,在这种配置环境下,MyBatis 会把 Commons Logging 作为日志工具。这就意味着在诸如 WebSphere 的环境中,由于提供了 Commons Logging 的私有实现,你的 Log4J 配置将被忽略。这个时候你就会感觉很郁闷:看起来 MyBatis 将你的 Log4J 配置忽略掉了(其实是因为在这种配置环境下,MyBatis 使用了 Commons Logging 作为日志实现)。

  如果你的应用部署在一个类路径已经包含 Commons Logging 的环境中,而你又想使用其它日志实现,你可以通过在 MyBatis 配置文件 mybatis-config.xml 里面添加一项 setting 来选择其它日志实现。

  可选的值有:SLF4J、LOG4J、LOG4J2、JDK_LOGGING、COMMONS_LOGGING、STDOUT_LOGGING、NO_LOGGING,或者是实现了 org.apache.ibatis.logging.Log 接口,且构造方法以字符串为参数的类完全限定名。

mybatis-config.xml

<?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>
<!-- 打印sql日志 -->
<setting name="logImpl" value="STDOUT_LOGGING" />
</settings>
</configuration>

  在 MyBatisConfig 配置类中或xml中添加 mybatis-config.xml 配置

    @Bean
public SqlSessionFactoryBean sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource());
factoryBean.setConfigLocation(new DefaultResourceLoader().getResource("classpath:mybatis-config.xml"));//内置日志工厂配置
return factoryBean;
}
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
</bean>

  PS:经测试(测试背景有 log4j、logback),其实不用 mybatis-config.xml 这个配置也可以实现 log4j 或 logback 打印出 SQL 日志;

  2.在实现方法中调用指定日志:

  你应该在调用其它 MyBatis 方法之前调用以上的某个方法。另外,仅当运行时类路径中存在该日志实现时,日志实现的切换才会生效。如果你的环境中并不存在 Log4J,你却试图调用了相应的方法,MyBatis 就会忽略这一切换请求,并将以默认的查找顺序决定使用的日志实现。

org.apache.ibatis.logging.LogFactory.useSlf4jLogging();
org.apache.ibatis.logging.LogFactory.useLog4JLogging();
org.apache.ibatis.logging.LogFactory.useLog4J2Logging();
org.apache.ibatis.logging.LogFactory.useJdkLogging();
org.apache.ibatis.logging.LogFactory.useCommonsLogging();
org.apache.ibatis.logging.LogFactory.useStdOutLogging();

  

  PS:经测试(测试背景有 log4j、log4j2、logback 等),发现 org.apache.ibatis.logging.LogFa

ctory.useXXXX 语句无效,无法实现指定日志打印出 SQL;

  3.log4j的其他设置

  当你想打印 SQL 的详细信息,比如执行查询时,控制台输出包括查询语句、字段名称、参数、每条数据都打印出来和总数时,可以在 log4j.properties 配置文件中添加如下配置:

log4j.logger.com.hrh.mybatis.mapper.ManagerMapper=TRACE

  

  其中 com.hrh.mybatis.mapper.ManagerMapper 是 ManagerMapper 接口的详细路径(对应XML的命名空间),当配置完上面的信息,执行 ManagerMapper 接口的方法时会输出详细的信息,也可以只针对特定方法进行详细输出,配置是

log4j.logger.com.hrh.mybatis.mapper.ManagerMapper.list=TRACE

  以此类推,当配置下面信息时,会对 mapper 包下的所有类进行详细输出:

log4j.logger.com.hrh.mybatis.mapper=TRACE

  某些查询可能会返回庞大的结果集。这时,你可能只想查看 SQL 语句,而忽略返回的结果集。为此,SQL 语句将会在 DEBUG 日志级别下记录(JDK 日志则为 FINE)。返回的结果集则会在 TRACE 日志级别下记录(JDK 日志则为 FINER)。因此,只要将日志级别调整为 DEBUG 即可:

log4j.logger.com.hrh.mybatis.mapper.ManagerMapper=DEBUG

三.源码探究

  我们从依赖 mybatis-3.4.6.jar 的logging包可以看出,它提供了很多日志框架依赖的实现类,那么它是怎么确定使用哪种日志呢?我们可以从 LogFactory 这个类来探究下它的实现原理。

  从 LogFactory 类我们可以看到它有一些静态代码,这表示当 Spring Framework 加载这个类时,会执行这些静态代码,一个个按顺序执行,同样这些静态代码验证了前面日志输出的第一点讲到了使用Mybatis的内置日志工厂输出时的日志查找顺序:

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 {
tryImplementation(new Runnable() {
@Override
public void run() {
useSlf4jLogging();
}
});
tryImplementation(new Runnable() {
@Override
public void run() {
useCommonsLogging();
}
});
tryImplementation(new Runnable() {
@Override
public void run() {
useLog4J2Logging();
}
});
tryImplementation(new Runnable() {
@Override
public void run() {
useLog4JLogging();
}
});
tryImplementation(new Runnable() {
@Override
public void run() {
useJdkLogging();
}
});
tryImplementation(new Runnable() {
@Override
public void run() {
useNoLogging();
}
});
}
.........
}

  下面我们看 tryImplementation 方法:第一次进来肯定等于空,表示 runnable 会执行 run 方法,后面会执行 useXXLogging() 方法,当执行 useXXLogging() 方法后  logConstructor 变量就不等于空了,后面的tryImplementation 不会再执行 run 方法了

  private static void tryImplementation(Runnable runnable) {
if (logConstructor == null) {
try {
runnable.run();
} catch (Throwable t) {
// ignore
}
}
}

  接下来我们看看 useXXLogging() 方法:从中我们可以看到它是给 setImplementation 方法传递了一个日志实现类的class对象,然后通过获取 class 的构造方法,再通过构造方法创建出日志实现类的实例给 logConstructor 这个全局变量(如果创建实例失败,则抛出异常,然后再执行后面的 tryImplementation 方法),后面通过 LogFactory#getLog() 方法就可以得到 Log 对象

  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);
}
}

  LogFactory#getLog() :

  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);
}
}

  当如果项目依赖了 log4j,这时我们通过 debug 可以得知项目是经过 useCommonsLogging() 加载了 jcl (commons-logging.jar,从前文Spring笔记(10) - 日志体系可以得知 Spring Framework 是包含jcl日志的,案例项目中使用的是 Spring Framework 4.x)。因此 MyBatisConfig#sqlSessionFactory() 中的 SqlSessionFactoryBean 的 logger 对象是:

public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {

  private static final Log LOGGER = LogFactory.getLog(SqlSessionFactoryBean.class);

}

  Spring Framework 4.x 的日志框架默认为 jul,其中 log4j>jul,所以此时控制台打印的是 log4j 日志框架的日志信息。

  当然我们可以通过mybatis-config.xml 配置文件对 logConstructor 变量进行再赋值,比如配置信息是  <setting name="logImpl" value="LOG4J" />,而且配置文件也加载到了项目中,这时虽然 logConstructor 的值因Spring Framework 加载而是 jul,但当加载执行到 MyBatisConfig#sqlSessionFactory() 的

factoryBean.setConfigLocation(new DefaultResourceLoader().getResource("classpath:mybatis-config.xml"));

语句时,会重新进入 setImplementation 给 logConstructor 重新赋值,如下图所示:

当然,你同样可以通过如下配置来实现指定日志框架:(推荐使用)

    @Bean
public SqlSessionFactoryBean sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource());
org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
configuration.setLogImpl(Log4jImpl.class);
factoryBean.setConfiguration(configuration);
return factoryBean;
}

四.问题探究

  前面中讲到在实现方法中调用指定日志实现日志切换,但却发现失效,无法实现,这是为什么呢?

  这时我们可以从下图得知:

  由此当代码变为下面时,就可以实现日志切换了:

        org.apache.ibatis.logging.LogFactory.useStdOutLogging();
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyBatisConfig.class);
PersonService bean = context.getBean(PersonService.class);
List<Person> list = bean.getList();

Mybatis日志源码探究的更多相关文章

  1. Spring Framework自动装配setAutowireMode和Mybatis案例的源码探究

    由前文可得知, Spring Framework的自动装配有两种方式:xml配置和注解配置: 自动装配的类型有: (1)xml配置中的byType根据类型查找(@Autowired注解是默认根据类型查 ...

  2. spring-boot-2.0.3之quartz集成,数据源问题,源码探究

    前言 开心一刻 着火了,他报警说:119吗,我家发生火灾了. 119问:在哪里? 他说:在我家. 119问:具体点. 他说:在我家的厨房里. 119问:我说你现在的位置. 他说:我趴在桌子底下. 11 ...

  3. Sharding-Jdbc源码探究-读写分离

    1. Sharding-Jdbc源码探究-读写分离 1.1. 主入口 找到源码入口 这一个类围绕了springboot配置属性的加载,加载了spring.shardingsphere.datasour ...

  4. ConcurrentHashMap源码探究 (JDK 1.8)

    很早就知道在多线程环境中,HashMap不安全,应该使用ConcurrentHashMap等并发安全的容器代替,对于ConcurrentHashMap也有一定的了解,但是由于没有深入到源码层面,很多理 ...

  5. CyclicBarrier源码探究 (JDK 1.8)

    CyclicBarrier也叫回环栅栏,能够实现让一组线程运行到栅栏处并阻塞,等到所有线程都到达栅栏时再一起执行的功能."回环"意味着CyclicBarrier可以多次重复使用,相 ...

  6. 【狂神说】JAVA Mybatis 笔记+源码

    简介 自学的[狂神JAVA]MyBatis GitHub源码: https://github.com/Donkequan/Mybatis-Study 分享自写源码和笔记 配置用的 jdk13.0.2 ...

  7. mybatis缓存源码分析之浅谈缓存设计

    本文是关于mybatis缓存模块设计的读后感,关于缓存的思考,关于mybatis的缓存源码详细分析在另一篇文章:https://www.cnblogs.com/gmt-hao/p/12448896.h ...

  8. spring-cloud-sleuth+zipkin源码探究

    1. spring-cloud-sleuth+zipkin源码探究 1.1. 前言   粗略看了下spring cloud sleuth core源码,发现内容真的有点多,它支持了很多类型的链路追踪, ...

  9. mybatis generator 源码学习

    mybatis/generator 源码地址mybatis/parent 源码地址1. 分别点击Download ZIP下载到本地. 2. 解压generator-master.zip中的core到g ...

随机推荐

  1. HTML5 drag & drop & H5 DnD

    HTML5 drag & drop H5 DnD https://html5demos.com/ demos https://html5demos.com/dnd-upload https:/ ...

  2. Interview Questions All In One

    Interview Questions All In One web fullstack System Design Operating System Object-Oriented Design O ...

  3. React Learning Paths

    React Learning Paths React Expert React in Action The assessment may cover: Components Events and Bi ...

  4. Puppeteer: 虚拟键盘

    文档 main.js const pptr = require('puppeteer'); const gotoUrl = 'http://127.0.0.1:5500/index.html'; (a ...

  5. CSS绘制三角形和箭头

    <html> <head> <meta charset="utf-8"> <title>CSS绘制三角形和箭头</title& ...

  6. ES6 声明变量的六种方法

    ES5 只有两种声明变量的方法: var 命令和 function 命令. ES6 除了添加 let 和 const 命令, 后面章节还会提到, 另外两种声明变量的方法: import 命令和 cla ...

  7. js中函数调用时,对参数个数和类型没有要求

    因为js是一种弱类型的编程语言,对数据类型的要求没有其他编程语言的要求严格,所以在定义函数的时候不需要像java一样对其传入参数的类型进行定,也对传入参数的个数没有要求. js函数的参数与大多数其他语 ...

  8. Java基础语法:包机制

    为了更好地组织类,Java 提供了包(package)机制. 这种机制是为了防止命名冲突,访问控制,提供搜索和定位类(class).接口(interface).枚举(enumerations)和注释( ...

  9. Redis 使用入门

    NoSql概述 NoSQL(NoSQL = Not Only SQL ),意即"不仅仅是SQL",它泛指非关系型的数据库, Redis 是一个高性能的开源的.C语言写的Nosql( ...

  10. bootstrap日期范围选择插件daterangepicker详细使用方法

    插件官方网站地址 bootstrap-daterangepicker是个很方便的插件,但是对我这种菜鸟来说,文档不够详细,摆弄了好久才整好.记录下来供以后参考,也希望能帮到有需要的朋友. 目前版本是2 ...