Mybatis筆記
一.項目搭建
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>
View Code
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 +
'}';
}
}
View Code
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.html、https://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();