MyBatis SQL日志源码分析

源码

BaseJdbcLogger

打印日志的抽象类。

PreparedStatementLogger

PreparedStatement的日志。

ResultSetLogger

ResultSet的日志。

打印debug的日志

1
2
3
4
5
protected void debug(String text, boolean input) {
if (statementLog.isDebugEnabled()) {
statementLog.debug(prefix(input) + text);
}
}

字符串前缀

1
2
3
4
5
6
7
8
9
10
11
private String prefix(boolean isInput) {
char[] buffer = new char[queryStack * 2 + 2];
Arrays.fill(buffer, '=');
buffer[queryStack * 2 + 1] = ' ';
if (isInput) {
buffer[queryStack * 2] = '>';
} else {
buffer[0] = '<';
}
return new String(buffer);
}

MyBatis基于接口编程的原理分析

源码

UserMapper

1
2
3
4
5
6
7
8
9
10
11
package org.denger.mapper;  

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.denger.po.User;

public interface UserMapper {

@Select("select * from tab_uc_account where id=#{userId}")
User getUser(@Param("userId") Long userId);
}

application-context.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
<?xml version="1.0" encoding="UTF-8"?>  
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-2.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.0.xsd">


<!-- Provided by annotation-based configuration -->
<context:annotation-config/>

<!--JDBC Transaction Manage -->
<bean id="dataSourceProxy" class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy">
<constructor-arg>
<ref bean="dataSource" />
</constructor-arg>
</bean>

<!-- The JDBC c3p0 dataSource bean-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="com.mysql.jdbc.Driver" />
<property name="jdbcUrl" value="jdbc:mysql://127.0.0.1:3306/noah" />
<property name="user" value="root" />
<property name="password" value="123456" />
</bean>

<!--MyBatis integration with Spring as define sqlSessionFactory -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
<property name="basePackage" value="org.denger.mapper"></property>
</bean>
</beans>

test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package org.denger.mapper;  

import org.junit.Assert;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.AbstractJUnit4SpringContextTests;

@ContextConfiguration(locations = { "/application-context.xml"})
public class UserMapperTest extends AbstractJUnit4SpringContextTests{

@Autowired
public UserMapper userMapper;

@Test
public void testGetUser(){
Assert.assertNotNull(userMapper.getUser(300L));
}
}

原理分析

对于以上极其简单代码看上去并无特殊之处,主要亮点在于 UserMapper 居然不用实现类,而且在调用 getUser 的时候,也是使用直接调用了UserMapper实现类,那么Mybatis是如何去实现 UserMapper的接口的呢?
可能你马上能想到的实现机制就是通过动态代理方式,看看MyBatis整个的代理过程吧。
首先在Spring的配置文件中看到下面的Bean。

1
2
3
4
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">  
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
<property name="basePackage" value="org.denger.mapper"></property>
</bean>

1
2
3
4
5
/**
* BeanDefinitionRegistryPostProcessor that searches recursively starting from a base package for
* interfaces and registers them as {@code MapperFactoryBean}. Note that only interfaces with at
* least one method will be registered; concrete classes will be ignored.
* <p>

以上的MapperScannerConfigurer的注释中描述道。
从base包中搜索所有下面所有interface,并将其注册到Spring Bean容器中,其注册的class bean是MapperFactoryBean
看看它的注册过程,从MapperScannerConfigurer#Scanner类中抽取,在初始化以上application-content.xml文件时就会进行调用。
主要用于是搜索base packages下的所有mapper.class,并将其注册至spring的benfinitionHolder中。

Mybatis一级缓存和二级缓存

一级缓存

一级缓存,又叫本地缓存,是PerpetualCache类型的永久缓存,保存在执行器中(BaseExecutor),而执行器又在SqlSessionDefaultSqlSession)中,所以一级缓存的生命周期与SqlSession是相同的。
MyBatis的一级缓存指的是在一个Session域内,Session为关闭的时候执行的查询会根据SQL为key被缓存,单独使用MyBatis而不继承Spring,使用原生的MyBatis的SqlSessionFactory来构造sqlSession查询,是可以使用以及缓存的。
当参数不变的时候只进行一次查询,参数变更以后,则需要重新进行查询,而清空缓存以后,参数相同的查询过的SQL也需要重新查询,当执行SQL时两次查询中间发生增删改操作,则SqlSession的缓存清空。
如果集成Spring是没有使用一级缓存。原因是一个sqlSession,但是实际上因为我们的dao继承SqlSessionDaoSupport,而SqlSessionDaoSupport内部sqlSession的实现是使用用动态代理实现的,这个动态代理sqlSessionProxy使用一个模板方法封装select()等操作,每一次select()查询都会自动先执行openSession(),执行完后调用close()方法,相当于生成一个新的session实例,所以我们无需手动的去关闭这个session(),当然也无法使用MyBatis的一级缓存,也就是说MyBatis的一级缓存在Spring中是没有作用的。

官方文档

MyBatis SqlSession provides you with specific methods to handle transactions programmatically. But when using MyBatis-Spring your beans will be injected with a Spring managed SqlSession or a Spring managed mapper. That means that Spring will always handle your transactions.
You cannot call SqlSession.commit(), SqlSession.rollback() or SqlSession.close() over a Spring managed SqlSession. If you try to do so, a UnsupportedOperationException exception will be thrown. Note these methods are not exposed in injected mapper classes.

二级缓存

二级缓存,又叫自定义缓存,实现Cache接口的类都可以作为二级缓存,所以可配置如encache等的第三方缓存。二级缓存以namespace名称空间为其唯一标识,被保存在Configuration核心配置对象中。

1
2
3
4
5
public class Configuration {
// ...
protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
// ...
}

每次构建SqlSessionFactory对象时都会创建新的Configuration对象,因此,二级缓存的生命周期与SqlSessionFactory是相同的。在创建每个MapperedStatement对象时,都会根据其所属的namespace名称空间,给其分配Cache缓存对象。
二级缓存同样执行增删查改操作,会清空缓存。
二级缓存就是global caching,它超出session范围之外,可以被所有sqlSession共享,它的实现机制和MySQL的缓存一样,开启它只需要在MyBatis的配置文件开启settings里。

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×