重要接口
SqlSessionFactoryBuilder
用于创建 SqlSessionFactory对象。
SqlSessionFactory
用于创建 SqlSession对象,每一个数据库最好只对应一个 SqlSessionFactory,以避免过多消耗。
类图如下:

SqlSession
代表一次会话,相当于 JDBC中的 Connection。
作用:
- 获取映射器,让映射器通过命名空间和方法名称找到对应的SQL,发送给数据库后返回结果;
- 直接通过命名信息去执行SQL返回结果(ibatis版本的方式)。
类图如下:

Mapper
映射器是由java接口和XML文件(或注解)共同组成的。
Executor
执行器,用来调度 StatementHandler、ParameterHandler、ResultHandler等来执行对应的 SQL。
类图如下:

| 类 | 说明 |
|---|---|
SimpleExecutor | 简单执行器 |
ReuseExecutor | 重用预处理语句的执行器 |
BatchExecutor | 针对批量操作的执行器 |
CachingExecutor | 若开启了缓存,则对以上三种执行器做封装 |
配置方式:
<settings>
<setting name="defaultExecutorType" value="SIMPLE"/>
</settings>
defaultExecutorType默认 SIMPLE,可取值如下:
| 值 | 对应类 |
|---|---|
SIMPLE | SimpleExecutor |
REUSE | ReuseExecutor |
BATCH | BatchExecutor |
StatementHandler
数据库会话器,执行 CRUD等操作。
类图如下:

| 类 | 说明 |
|---|---|
SimpleStatementHandler | 对应 JDBC的 Statement;执行简单的 SQL |
PreparedStatementHandler | 对应 JDBC的 PreparedStatement;执行预编译的 SQL |
CallableStatementHandler | 对应 JDBC的 CallableStatement;执行存储过程 |
RoutingStatementHandler | 负责创建和调用以上三种会话器 |
配置方式:
<select id="queryById" resultType="user" statementType="PREPARED">
SELECT `id`,`name`,`age` FROM `user` WHERE `id` = #{id}
</select>
statementType默认 PREPARED,可取值如下:
| 值 | 对应类 |
|---|---|
STATEMENT | SimpleExecutor |
PREPARED | PreparedStatementHandler |
CALLABLE | CallableStatementHandler |
主要方法:
| 方法名 | 说明 |
|---|---|
prepare | 对 SQL预编译 |
parameterize | 设置参数 |
query | 执行查询 |
update | 执行更新 |
ParameterHandler
参数处理器,调用类型处理器 TypeHandler做类型转换。
类图如下:

ResultHandler
结果处理器,调用类型处理器 TypeHandler做类型转换。
类图如下:

Configuration
SqlSource
BoundSql
MappedStatement
示例
User.java
package mncode.entity;
@Data
@Alias("user")
public class User {
private Integer id;
private String name;
private Integer age;
}
UserMapper.java
package mncode.mapper;
public interface UserMapper {
User queryById(Integer id);
}
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>
<typeAliases>
<package name="mncode.entity"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/test"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mapper/UserMapper.xml"/>
</mappers>
</configuration>
UserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mncode.mapper.UserMapper">
<select id="queryById" resultType="user">
SELECT `id`,`name`,`age` FROM `user` WHERE `id` = #{id}
</select>
</mapper>
测试代码:
@Test
public void test1() throws Exception {
SqlSession sqlSession = null;
try {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.queryById(1);
System.out.println(user);
} finally {
sqlSession.close();
}
}
原理
使用流程,大概如下:
- 使用
SqlSessionFactoryBuilder创建SqlSessionFactory; - 使用
SqlSessionFactory创建SqlSession; - 使用
SqlSession获取映射器; - 通过映射器做
CRUD操作; - 操作完成后,关闭
SqlSession;
创建SqlSessionFactory
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
主要逻辑在 build方法中,时序图如下:

说明:
- 第
2步以configuration为根节点解析xml文件,返回XNode对象; - 第
4步对XNode对象做进一步处理,parseConfiguration源码如下:
private void parseConfiguration(XNode root) {
try {
//issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
- 第
4步会对每个标签做处理,并把处理结果封装进Configuration类型的对象中; - 第
6步build源码如下:
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
代码很简单,创建 DefaultSqlSessionFactory对象,并传入 Configuration对象的引用。
Mapper文件处理
上一小节第 4步会对每个标签做处理,先暂时只关注对 <mappers>标签的处理:
完整源码
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
截取需要关注的代码如下:
// 处理 mappers 标签下的所有 mapper 标签
for (XNode child : parent.getChildren()) {
// 获取<mapper>标签的 resource属性
String resource = child.getStringAttribute("resource");
// 加载对应的文件
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
// 解析
mapperParser.parse();
}
parse源码如下:
public void parse() {
// 判断文件是否已经加载过
if (!configuration.isResourceLoaded(resource)) {
// 解析
configurationElement(parser.evalNode("/mapper"));
// 解析成功后,放入loadedResources集合中,下次就不需要再解析了
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
configurationElement源码如下:
private void configurationElement(XNode context) {
try {
// 获取命名空间
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
// 对 <cache-ref> 节点处理
cacheRefElement(context.evalNode("cache-ref"));
// 对 <cache> 节点处理
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 对 <parameterMap> 节点处理
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 对 <sql> 节点处理
sqlElement(context.evalNodes("/mapper/sql"));
// 对 select|insert|update|delete 4种节点处理
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
暂时只关注对 <select>节点的处理,对应代码如下:
源码
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
调用 parseStatementNode做解析,时序图如下:

说明:
1:获取节点的各个属性值(如id、parameterType、resultMap等);2:先创建建造器(MappedStatement.Builder类型);3:再使用建造器的build方法创建MappedStatement,内部封装了第1步解析出的所有信息;5:数据保存到mappedStatements中,mappedStatements结构如下:
protected final Map<String, MappedStatement> mappedStatements;
key为 《 命名空间 + "." + id 》,例如:mncode.mapper.UserMapper.queryById;
value为 MappedStatement对象。
创建 SqlSession
sqlSession = sqlSessionFactory.openSession();
openSession会调用 openSessionFromDataSource,时序图如下:

说明:
1:根据配置文件的配置生成TransactionFactory;2 ~ 3:创建Transaction对象;4 ~ 5:创建Executor对象;- 最后创建
DefaultSqlSession对象并返回;
获取映射器
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
getMapper时序图如下:

MapperRegistry类的 getMapper方法源码如下:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
入参 type为 mncode.entity.User.UserMapper(自定义的 Mapper接口);
先从 knownMappers缓存中获取对应的 mapperProxyFactory,knownMappers结构如下:
Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>()
然后调用 MapperProxyFactory的 newInstance方法:
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
这里使用JDK的动态代理机制生成代理对象(动态代理详解)
自动生成的代理类,内容如下:
代码
package com.sun.proxy;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;
import mncode.entity.User;
import mncode.mapper.UserMapper;
public final class $Proxy2 extends Proxy implements UserMapper {
private static Method m1;
private static Method m2;
private static Method m3;
private static Method m0;
public $Proxy2(InvocationHandler var1) throws {
super(var1);
}
public final boolean equals(Object var1) throws {
try {
return (Boolean)super.h.invoke(this, m1, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final String toString() throws {
try {
return (String)super.h.invoke(this, m2, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
public final User queryById(Integer var1) throws {
try {
return (User)super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
public final int hashCode() throws {
try {
return (Integer)super.h.invoke(this, m0, (Object[])null);
} catch (RuntimeException | Error var2) {
throw var2;
} catch (Throwable var3) {
throw new UndeclaredThrowableException(var3);
}
}
static {
try {
m1 = Class.forName("java.lang.Object").getMethod("equals", Class.forName("java.lang.Object"));
m2 = Class.forName("java.lang.Object").getMethod("toString");
m3 = Class.forName("mncode.mapper.UserMapper").getMethod("queryById", Class.forName("java.lang.Integer"));
m0 = Class.forName("java.lang.Object").getMethod("hashCode");
} catch (NoSuchMethodException var2) {
throw new NoSuchMethodError(var2.getMessage());
} catch (ClassNotFoundException var3) {
throw new NoClassDefFoundError(var3.getMessage());
}
}
}
可以看到代理类继承了 Proxy类,实现了 UserMapper接口。
查询数据
User user = userMapper.queryById(1);
queryById方法的代码如下:
public final User queryById(Integer var1) throws {
try {
return (User)super.h.invoke(this, m3, new Object[]{var1});
} catch (RuntimeException | Error var3) {
throw var3;
} catch (Throwable var4) {
throw new UndeclaredThrowableException(var4);
}
}
调用的 invoke方法在 MapperProxy中:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
最后调用 MapperMethod类的 execute方法,源码如下:
execute源码
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
这里 CRUD不同的操作对应不同的代码片段,先暂时只关注查询相关的代码:
result = sqlSession.selectOne(command.getName(), param);
调用的是 DefaultSqlSession类的 selectOne方法:
selectOne源码
@Override
public <T> T selectOne(String statement, Object parameter) {
// Popular vote was to return null on 0 results and throw exception on too many.
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
@Override
public <E> List<E> selectList(String statement, Object parameter) {
return this.selectList(statement, parameter, RowBounds.DEFAULT);
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
说明:
先从 mappedStatements缓存中根据 statement获取 MappedStatement对象;statement为接口名+方法名(如:mncode.mapper.UserMapper.queryById);而 MappedStatement对象 是在前面的 《Mapper 文件处理》小节中放入到缓存中的;mybatis通过这种机制将 mapper接口与配置文件中的语句关联了起来;最后会执行 CachingExecutor类的 query方法。
query源码如下:
query源码
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
flushCacheIfRequired(ms);
return delegate.queryCursor(ms, parameter, rowBounds);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
紧接着把请求转发给 SimpleExecutor的 query,时序图如下:

说明:
3:创建会话器RoutingStatementHandler;6:获取数据库连接Connection;7:预编译SQL;9:设置参数;10:执行查询;
其中第 7步,RoutingStatementHandler会把请求委派给 PreparedStatementHandler来处理,时序图如下:

说明:
3:调用JDBC的API创建preparedStatement对象;
最后执行查询,RoutingStatementHandler会把请求委派给 PreparedStatementHandler来处理,时序图如下:

说明:
2:调用JDBC的API执行查询;3:委派结果处理器DefaultResultSetHandler对查询结果做处理;5:调用JDBC的API获取结果集;7:结果集转换为List;
插件
原理
Mybatis的插件相当于是个拦截器。使用了动态代理模式和责任链模式。
- 需要实现以下接口:
public interface Interceptor {
// 拦截后要执行的代码
Object intercept(Invocation invocation) throws Throwable;
// 利用动态代理返回代理对象
Object plugin(Object target);
// 参数设置
void setProperties(Properties properties);
}
-
注解说明:
@Intercepts:标记这是个拦截器@Signature:拦截点type:要拦截的类method:要拦截的方法args:方法参数类型
-
Mybatis支持被拦截的方法如下:
| 类 | 方法 |
|---|---|
Executor | queryupdateflushStatementscommitrollbackgetTransactioncloseisClosed |
ParameterHandler | getParameterObjectsetParameters |
ResultSetHandler | handleResultSetshandleOutputParameters |
StatementHandler | prepareparameterizebatchupdatequery |
自定义
插件实现类:
package mncode.plugin;
@Slf4j
@Intercepts({@Signature(type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class})})
public class FirstPlugin implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
log.info("第一个插件...");
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
// 修改sql语句
BoundSql boundSql = statementHandler.getBoundSql();
Field field = boundSql.getClass().getDeclaredField("sql");
field.setAccessible(true);
field.set(boundSql, boundSql.getSql() + " limit 1");
return invocation.proceed();
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
}
在 mybatis的配置文件中添加:
<plugins>
<plugin interceptor="mncode.plugin.FirstPlugin"/>
</plugins>
测试:
@Test
public void test1() throws Exception {
SqlSession sqlSession = null;
try {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
User user = userMapper.queryById(1);
System.out.println(user);
} finally {
sqlSession.close();
}
}
输出:
11:24:09.264 [main] INFO mncode.plugin.FirstPlugin - 第一个插件...
11:24:09.264 [main] DEBUG mncode.mapper.UserMapper.queryById - ==> Preparing: SELECT `id`,`name`,`age` FROM `user` WHERE `id` = ? limit 1
11:24:09.295 [main] DEBUG mncode.mapper.UserMapper.queryById - ==> Parameters: 1(Integer)
11:24:09.342 [main] DEBUG mncode.mapper.UserMapper.queryById - <== Total: 1
User(id=1, name=Jone, age=25)