重要接口
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 | query update flushStatements commit rollback getTransaction close isClosed |
ParameterHandler | getParameterObject setParameters |
ResultSetHandler | handleResultSets handleOutputParameters |
StatementHandler | prepare parameterize batch update query |
自定义
插件实现类:
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)