mybatis 源码简单探究
一 、初始化环境
前提
- maven环境
- jdk1.8
- mysql8.0
pom.xml
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.11</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- https://mvnrepository.com/artifact/junit/junit -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
</dependencies>
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="com.wuxin.mybatis.read.mapper.UserMapper">
<insert id="addUser" parameterType="java.lang.String">
insert into `user` (`username`, `address`)
values (#{username}, #{address})
</insert>
<update id="updateId" >
update `user` set `username`=#{username} ,address=#{address} where userId=#{userId}
</update>
<select id="selectOne" resultType="com.wuxin.mybatis.read.pojo.User" parameterType="java.lang.Long">
select *
from user
where userId = #{userId}
</select>
<select id="list" resultType="com.wuxin.mybatis.read.pojo.User">
select * from user;
</select>
</mapper>
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>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://localhost:3306/mybatis-test?serverTimezone=UTC&character=utf8&useSSL=true"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mappers/UserMapper.xml"/>
</mappers>
</configuration>
二、执行流程
1、配置环境初始化
// main
// ...
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// ...
通过 org.apache.ibatis.builder.XMLConfigBuilder
类初始化环境,解析 xxx.xml文件生成对应Java对象
// org.apache.ibatis.builder.XMLConfigBuilder
private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
super(new Configuration());
// ...
this.parser = parser;
}
// 配置文件解析只能调用一次,下次调用抛出异常!!!
public Configuration parse() {
if (this.parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
} else {
this.parsed = true;
this.parseConfiguration(this.parser.evalNode("/configuration"));
return this.configuration;
}
}
// 该方法通过
private void parseConfiguration(XNode root) {
try {
this.propertiesElement(root.evalNode("properties"));
Properties settings = this.settingsAsProperties(root.evalNode("settings"));
this.loadCustomVfs(settings);
this.loadCustomLogImpl(settings);
this.typeAliasesElement(root.evalNode("typeAliases"));
this.pluginElement(root.evalNode("plugins"));
this.objectFactoryElement(root.evalNode("objectFactory"));
this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
this.reflectorFactoryElement(root.evalNode("reflectorFactory"));
this.settingsElement(settings);
this.environmentsElement(root.evalNode("environments"));
this.databaseIdProviderElement(root.evalNode("databaseIdProvider"));
this.typeHandlerElement(root.evalNode("typeHandlers"));
this.mapperElement(root.evalNode("mappers"));
} catch (Exception var3) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3);
}
}
// 通过上面节点解析顺序知道,节点位置顺序不能乱,最好参考上面这个配置的节点顺序
由 SqlSessionFactoryBuilder
调用 XMLConfigBuilder
的 parse 方法,解析生成 config 对象配置
// org.apache.ibatis.session.SqlSessionFactoryBuilder build();
// ...
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
var5 = this.build(parser.parse());
Configuration
,只有两个构造器
// 有参构造
public Configuration(Environment environment) {
this();
// 可以通过xml配置指定环境
this.environment = environment;
}
// 无参构造
// 默认初始化环境 通过xml配置读取过程中,如果没有指定对应配置,采用如下默认配置
// 关于下面配置详情参考 https://mybatis.net.cn/configuration.html
public Configuration() {
// 初始化值
// ... 详细配置参考 org.apache.ibatis.session.Configuration 类的构造函数
this.typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
// ... 默认配置文件
this.typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
this.typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
// ...
}
更多信息参考下图,
执行流程如下:
XMLConfigBuilder
处理配置类 集中管理XMLMapperBuilder
处理xxxxMapper.xml
扫描 xxxxMapper.xml 等MapperBuilderAssistant
mapper辅助类XMLStatementBuilder
statement生成XMLScriptBuilder
处理 xml 中的 脚本SqlSourceBuilder
生成sql 语句ParameterMappingTokenHandler
将参数 #{} 替换为 ?,并保存参数信息
2、整体执行流程
3、查询详细执行流程
4、关于各个处理器,执行器说明
1、SqlSession
会话管理,默认是 DefaultSqlSession
2、Executor
执行器:
- CachingExecutor
- 二级缓存
- BaseExecutor 一级缓存,如果没有一级缓存,执行数据库操作,如果是修改操作,删除缓存
- SimpleExecutor 一般查询或者修改处理器
- BatchExecutor 批处理
3、StatementHandlder
注意
RoutingStatementHandler 最终还是生成 PreparedStatementHandler 、SimpleStateHandler 和
CallableStatementHandler三个中的一个
4、ParameterHandlder
参数解析,将?替换为输入的参数,然后通过 Preparement 执行exqute() 执行操作
三 、缓存
1、二级缓存
注意二级缓存执行在一级缓存之前
二级缓存实现类是 org.apache.ibatis.executor.CachingExecutor
二级缓存以实现核心部分
public class CachingExecutor implements Executor {
private final Executor delegate;
private final TransactionalCacheManager tcm = new TransactionalCacheManager();
public CachingExecutor(Executor delegate) {
this.delegate = delegate;
delegate.setExecutorWrapper(this);
}
// ....
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
Cache cache = ms.getCache();
// 获取 mapper 的二级缓存
if (cache != null) {
// ...
// 获取内容
List<E> list = (List)this.tcm.getObject(cache, key);
if (list == null) {
// 如果结果为空,从执行器中获取 一般是 BaseExecutor
list = this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
this.tcm.putObject(cache, key, list);
}
// 如果获取到内容直接返回
return list;
}
// 如果没有获取到内容 交给下一个执行器执行 一般是 BaseExecutor 责任链模式
return this.delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
// ....
}
2、一级缓存
PerpetualCache
继承了 cache
并且作为 一级缓存,存储方式是 HashMap
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
// ....
try {
++this.queryStack;
// 一级缓存
list = resultHandler == null ? (List)this.localCache.getObject(key) : null;
if (list != null) {
// 缓存校验
this.handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 从数据库中获取
list = this.queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
--this.queryStack;
}
// ...
return list;
}
}
3、缓存 key 是怎么来的?
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
// 走缓存
return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
添加 cachekey
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (this.closed) {
throw new ExecutorException("Executor was closed.");
} else {
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
// ....
return cacheKey;
}
}
默认org.apache.ibatis.cache.CacheKey
重写了toString
public String toString() {
StringJoiner returnValue = new StringJoiner(":");
returnValue.add(String.valueOf(this.hashcode));
returnValue.add(String.valueOf(this.checksum));
Stream var10000 = this.updateList.stream().map(ArrayUtil::toString);
Objects.requireNonNull(returnValue);
// lambda表达式 执行 StringJoiner.add()
// toString() 拼装之后返回
// StringJoiner add 实际上执行的是 StringBuilder.append(xxxx)
var10000.forEach(returnValue::add);
return returnValue.toString();
}
// ...
关于id的部分源代码
org.apache.ibatis.binding.MapperMetho$SqlCommand
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName, Class<?> declaringClass, Configuration configuration) {
String statementId = mapperInterface.getName() + "." + methodName;
// ....
}
例如:
实例key
-8238651:-329230814:com.wuxin.mybatis.read.mapper.UserMapper.list:0:2147483647:select * from user;:development
-
MappedStatement
- id ,为 namespace + idName, 实际上就是mapper的命名空间和 id名
- 例如
com.wuxin.mybatis.read.mapper.UserMapper
+list
-
Object
-
RowBounds
分页对象 -
默认为
RowBounds.DEFAULT
-
ResultHandler
结果处理器 -
默认为
Executor.NO_RESULT_HANDLER
4、修改和查询
- 查询
- 执行 executor 中 doQuery()
- 修改
- 添加
- 删除
- 修改
- 执行 executor 中 doUpdate()
查询
如果走到这一步了说明缓存中没有结果
// org.apache.ibatis.executor.BaseExecutor
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
this.localCache.putObject(key, ExecutionPlaceholder.EXECUTION_PLACEHOLDER);
List list;
try {
// 从数据库中执行查询操作
// 该方法在 BaseExecutor为抽象方法 ,交给实现类执行(四种)如下图
list = this.doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
// 删除之前对应的key
this.localCache.removeObject(key);
}
// 存入 缓存中
this.localCache.putObject(key, list);
// ...
return list;
}
交给下一个执行器执行
最终执行的是
// 处理多个结果
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement)statement;
ps.execute();
return this.resultSetHandler.handleResultSets(ps);
}
// 处理单个结果
public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement)statement;
ps.execute();
return this.resultSetHandler.handleCursorResultSets(ps);
}
修改
修改包括添加、删除和修改操作
// org.apache.ibatis.executor.BaseExecutor
public int update(MappedStatement ms, Object parameter) throws SQLException {
// ...
// 每次执行修改操作之前,删除本地缓存
this.clearLocalCache();
return this.doUpdate(ms, parameter);
}
修改操作最终执行的是 PreparedStatementHandler
的 update() 方法
public int update(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement)statement;
ps.execute();
int rows = ps.getUpdateCount();
// ...
return rows;
}
四、插件实现
默认查询分页的经过 DefaultSqlSession
方法 分页设置默认参数 RowBounds.DEFAULT
public <E> List<E> selectList(String statement, Object parameter) {
// 如果没有 RowBounds 对象,系统默认 RowBounds.DEFAULT
// RowBounds.DEFAULT ======> offset=0 ,limit= Integer.MAX_VALUE
return this.selectList(statement, parameter, RowBounds.DEFAULT); //
}
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
return this.selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
}
有关 RowBounds
Public class RowBounds {
public static final int NO_ROW_OFFSET = 0;
public static final int NO_ROW_LIMIT = Integer.MAX_VALUE;
public static final RowBounds DEFAULT = new RowBounds();
private final int offset;
private final int limit;
public RowBounds() {
this.offset = 0;
this.limit = Integer.MAX_VALUE;
}
public RowBounds(int offset, int limit) {
this.offset = offset;
this.limit = limit;
}
// .... getter methods
}
所以list 最多能够查询结果 Integer.MAX_VALUE
,经过拼接 的sql语句
select c1,c2,...,cn from tbName xxxxx limit 0,Integer.MAX_VALUE
更多详情参考 :https://mybatis.net.cn/configuration.html#plugins
五、SQL生成
1、条件处理
mybatis 中 封装了许多对应 mapper中各种各样的条件
核心 SqlNode是 TrimSqlNode
public class TrimSqlNode implements SqlNode {
// ...
}
2、"#{ }"是如何 替换成 "?"
通过 GenericTokenParser
的方法 parse()
public String parse(String text) {
// ...
do {
// ...
// 关键替换
// 参考下面步骤 SqlSourceBuilder 类 的 ParameterMappingTokenHandler
builder.append(this.handler.handleToken(expression.toString()))
} while(start > -1);
// ...
return builder.toString();
}
循环查找#{
和 }
通过 字符串length 方法找到偏移量
GenericTokenParser
对象是通过 SqlSourceBuilder
构造函数生成的
核心部分如下
public SqlSource parse(/*...*/) {
ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(this.configuration, parameterType, additionalParameters);
GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
String sql;
// 该过程 将#{}替换为 ?
if (this.configuration.isShrinkWhitespacesInSql()) {
sql = parser.parse(removeExtraWhitespaces(originalSql));
} else {
sql = parser.parse(originalSql);
}
return new StaticSqlSource(this.configuration, sql, handler.getParameterMappings());
}
// ParameterMappingTokenHandler 类是 `SqlSourceBuilder` 类的一个内部类
// 生成 "?"
//
private final List<ParameterMapping> parameterMappings = new ArrayList();
public String handleToken(String content) {
// 保存 参数 名称 比如 #{username} 替换为 ? ,但是将 username 保存到
this.parameterMappings.add(this.buildParameterMapping(content));
return "?";
}
六、mybaits中的设计模式
-
工厂模式
- mybatis的会话是通过会话工厂创建。
SqlSessionFactory
、ObjectFactory
、MapperProxyFactory
- 一般情况下如果不指定会话,系统调用的是默认的
SqlSessionFactory
- mybatis的会话是通过会话工厂创建。
-
建造者模式
- 这个就不多介绍了,常见的 xxxBuilder类都是建造者模式。
-
代理模式。
- mybatis中有
cglib
和jdk
自带的代理工具Reflect
类实现。 - 关于
代理模式
、装饰者模式
和适配器模式
之间区别可百度,这里简单说下- 代理模式: 补充功能的同时,更多是为了增加访问控制权限
- 适配器模式: 更多是为了兼容性,同一个接口方法使用多种功能
- 装饰器模式:在原来基础功能上添加一些功能。
- mybatis中有
-
适配器模式
- 日志功能实现
-
装饰者模式
- 例如Cache包中的cache.decorators子包中等各个装饰者的实现
-
策略模式
- 执行
sqlNode
, 决定使用哪个sqlNode
器执行过程 - 执行
BaseExecutor
,mybatis中有个四个类继承了(具体可看上面关系图)BaseExecutor
如果没有指定话,有一个默认的兜底。
- 执行
-
单例模式
ErrorContext
LogFactory
,PS : 这个不是工厂模式 🤣
-
迭代器模式
- 将
#{}
替换为?
过程就是用了迭代器模式,具体细节参考GenericTokenParser
- 将