一.项目搭建

  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. 如何使用 js 实现一个 debounce 函数

    如何使用 js 实现一个 debounce 函数 原理 防抖: 是指在指定的单位时间内,如果重复触发了相同的事件,则取消上一次的事件,重新开始计时! 实现方式 "use strict&quo ...

  2. js console.log all in one

    js console.log all in one this & arguments "use strict"; /** * * @author xgqfrms * @li ...

  3. WebXR All in One

    WebXR All in One VR / WebVR WebXR https://www.w3.org/TR/webxr/ WebXR Device API https://immersiveweb ...

  4. You Don't Know Chrome Features

    You Don't Know Chrome Features URL auto convert to QR Code click the tab URL address click QRCode ic ...

  5. shit 牛客网

    shit 牛客网 为什么,只可以 log 一次,什么垃圾逻辑呀! https://www.nowcoder.com/test/question/e46437833ddc4c5bb79f7af7a1b7 ...

  6. iOS effect & swiper delete components

    iOS effect & swiper delete components mint-ui & cell-swipe https://elemefe.github.io/mint-ui ...

  7. asm FPU 寄存器

    TOP-- TOP++ 顶部 ST(0) ST(1) ST(2) ST(3) ST(4) ST(5) ST(6) ST(7) 底部 指令后的注释通常是执行后的结果 push section .data ...

  8. c++ DWORD和uintptr_t

    x86模式 DWORD 是4字节 x86模式 uintptr_t 是4字节 x64模式 DWORD 是4字节 x64模式 uintptr_t 是8字节 std::cout << sizeo ...

  9. 25_MySQL 数据操作语言:UPDATE语句

    -- UPDATE 把每个员工的编号和上司的编号都加1,用 ORDER BY 完成 UPDATE t_emp SET empno=empno+1,mgr=mgr+1 ORDER BY empno DE ...

  10. javascript 十大经典排序

    首先生成一个数字数组: let arr = Array.from({length:20},x=>{return Math.ceil(Math.random()*10**2)}) console. ...