Mybatis源码系列文章
手写源码(了解源码整体流程及重要组件)
Mybatis源码解析(一):环境搭建
Mybatis源码解析(二):全局配置文件的解析
Mybatis源码解析(三):映射配置文件的解析
Mybatis源码解析(四):sql语句及#{}、${}的解析
Mybatis源码解析(五):SqlSession会话的创建
Mybatis源码解析(六):缓存执行器操作流程
目录
- 前言
- 一、会话对象selectOne方法
- 二、获取缓存对象key
- 1、获取BoundSql对象
- 2、获取缓存的key值
- 1)CacheKey构造方法
- 2)更新方法
- 三、二级缓存的流程
- 四、一级缓存的流程
- 总结
前言
- 之前篇章讲了配置文件的解析与SqlSession的创建,可以说都是在为执行增删改查操作主流程做铺垫
- 接下来让我们进入SqlSession的selectOne实现方法
一、会话对象selectOne方法
selectOne方法
- SqlSession接口有两个实现类:
- DefaultSqlSessionFactor(通过上一个步骤创建–>Mybatis源码解析(五):SqlSession会话的创建)
- SqlSessionManager(已弃用)
- 方法入参:
- statement:statementId = “namespace.id”
- parameter:方法参数,填充带?的sql
- this.selectList:调用同类的方法,参数传递,从这里可以看出,selectOne方法的实现都交给了selectList,获取到只取第一个值
- 只有一个值,获取返回即可
- 如果是空,则返回空
- 如果大于一个,则报错,因为此方法目的是查询唯一的一个值,结果多个
@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;
}
}
selectList方法
- 三个selectList方法依次向下调用
- 第一个方法添加默认分页对象调用第二个方法
- 第二个方法添加结果集处理器空对象调用第三个方法
- 第三个核心方法:
- 通过statementId获取MappedStatement(由每个<select><insert><update><delete>内属性构建而成的对象)
- executor:默认无配置情况下,是简单执行器外面包一层缓存执行器。具体这里又讲:Mybatis源码解析(五):SqlSession会话的创建
// 第一个
@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) {
// 参数4:结果集处理器
return selectList(statement, parameter, rowBounds, Executor.NO_RESULT_HANDLER);
}
// 第三个
private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
// 根据传入的statementId,获取MappedStatement对象
MappedStatement ms = configuration.getMappedStatement(statement);
// 调用执行器的查询方法
// wrapCollection(parameter)是用来装饰集合或者数组参数
return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
二、获取缓存对象key
1. 从MappedStatement对象中获取BoundSql对象
2. 获取一级或二级缓存Map的key,value是查询的结果
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 获取绑定的SQL语句,比如 "SELECT * FROM user WHERE id = ? "
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 生成缓存Key
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
1、获取BoundSql对象
- 通过MappedStatement对象中的sqlSource属性对象(Mybatis源码解析(四):sql语句及#{}、${}的解析-讲解了如何构建SqlSource)
- getBoundSql方法:动态sql或${}则动态构建,#{}则获取以前准备好的数据构建对象(Mybatis源码解析(四)有讲)
public BoundSql getBoundSql(Object parameterObject) {
BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings == null || parameterMappings.isEmpty()) {
boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
}
//
for (ParameterMapping pm : boundSql.getParameterMappings()) {
String rmId = pm.getResultMapId();
if (rmId != null) {
ResultMap rm = configuration.getResultMap(rmId);
if (rm != null) {
hasNestedResultMaps |= rm.hasNestedResultMaps();
}
}
}
return boundSql;
}
进入getBoundSql
- 这里只展示非动态sql(#{}样式),动态的sql需要动态组装sql,而非动态带?的sql已经准备好了
- BoundSql的组成:
- 带?的sql
- #{}或${}中的属性参数
- 入参属性值
@Override
public BoundSql getBoundSql(Object parameterObject) {
return new BoundSql(configuration, sql, parameterMappings, parameterObject);
}
2、获取缓存的key值
进入CachingExecutor的createCacheKey方法
- delegate:简单执行器、批量执行器、重用执行器其中的一种,这里使用他们的父类Executor接受
- Mybatis源码解析(五):SqlSession会话的创建-默认情况下,缓存开启,则以上的三种执行器会在外面包一层(创建一个缓存执行器,构造函数传入以上执行器,jdbc操作由他们处理,缓存操作由缓存执行器处理)
- 由于创建key是不同执行器的公共操作,则方法在他们三的抽象父类BaseExecutor中
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
}
进入BaseExecutor的createCacheKey方法
- 创建CachKey对象(一二级缓存都用这个当key)
- update方法更新
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 创建cacheKey对象
CacheKey cacheKey = new CacheKey();
// id
cacheKey.update(ms.getId());
// 分页参数
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
// sql
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic
for (ParameterMapping parameterMapping : parameterMappings) {
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
// 参数的值
cacheKey.update(value);
}
}
if (configuration.getEnvironment() != null) {
// issue #176
// 当前环境的值也会设置
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
1)CacheKey构造方法
- 初始化一些字段值,为之后重写hashCode和equals方法做准备
...
private static final int DEFAULT_MULTIPLIER = 37;
private static final int DEFAULT_HASHCODE = 17;
// 参与hash运算的乘数
private final int multiplier;
// cachekey的hash值,在update函数中实时算出来
private int hashcode;
// 校验和,hash值的和
private long checksum;
// updateList的中的元素个数
private int count;
// 根据该集合中的元素判断两个cacheKey是否相同
private List<Object> updateList;
public CacheKey() {
this.hashcode = DEFAULT_HASHCODE;
this.multiplier = DEFAULT_MULTIPLIER;
this.count = 0;
this.updateList = new ArrayList<>();
}
...
2)更新方法
- 通过入参计算hashcode值,将入参添加到更新集合
- 入参:statementId、分页参数、带?的sql、参数值、环境id
- 比较CacheKey对象就是比较上述参数的hashcode值,相同则返回以前存储的value,不相同则查询数据库
public void update(Object object) {
// 获取参数的object的hash值
int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
// 更新count 、checksum以及hashcode的值
count++;
checksum += baseHashCode;
baseHashCode *= count;
hashcode = multiplier * hashcode + baseHashCode;
// 将对象添加到list集合中
updateList.add(object);
}
equals和hashCode方法
@Override
public boolean equals(Object object) {
if (this == object) { // 比较是不是同一个对象
return true;
}
if (!(object instanceof CacheKey)) { // 是否类型相同
return false;
}
final CacheKey cacheKey = (CacheKey) object;
if (hashcode != cacheKey.hashcode) { // hashcode是否相同
return false;
}
if (checksum != cacheKey.checksum) { // checksum是否相同
return false;
}
if (count != cacheKey.count) { // count是否相同
return false;
}
// 按顺序比较updateList中元素的hash值是否相同
for (int i = 0; i < updateList.size(); i++) {
Object thisObject = updateList.get(i);
Object thatObject = cacheKey.updateList.get(i);
if (!ArrayUtil.equals(thisObject, thatObject)) {
return false;
}
}
return true;
}
@Override
public int hashCode() {
return hashcode;
}
三、二级缓存的流程
- 回到第二章节的开头;获取完缓存的CachKey对象后,进入缓存执行器重载方法query
- ms.getCache():namespace启动二级标签;MappedStatement对象中属性,所以在映射配置文件中配置,如下
- flushCacheIfRequired:<select>标签中的flushCache属性,刷新二级缓存,默认false
- ms.isUseCache:<select>标签中的useCache属性,此标签启用二级缓存,默认true
- 先从二级缓存tcm中获取,没有则委托BaseExecutor去查询一级缓存或数据库
- 再讲结果放入二级缓存tcm,其实只是放入一个map(非二级缓存的map),因为只有提交事务才会真正添加到二级缓存(二级缓存的get和put后面篇章单独讲)
@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) {
// 刷新二级缓存 (存在缓存且flushCache为true时)
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
// 从二级缓存中查询数据
List<E> list = (List<E>) tcm.getObject(cache, key);
// 如果二级缓存中没有查询到数据,则查询一级缓存及数据库
if (list == null) {
// 委托给BaseExecutor执行
list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
// 将查询结果 要存到二级缓存中(注意:此处只是存到map集合中,没有真正存到二级缓存中)
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 委托给BaseExecutor执行
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
四、一级缓存的流程
- 第三章的delegate.query方法,delegate此时为简单执行器SimpleExecutor,而query方法在他的父类BaseExecutor
- queryStack为0:表示是当前会话只有本次查询而没有其他的查询了
- ms.isFlushCacheRequired():与二级缓存中的<select>标签中的flushCache属性功能一样
- 先从一级缓存localCache中获取,没有则从数据库查询
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
// 如果该执行器已经关闭,则抛出异常
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 1. 如果配置了flushCacheRequired为true,则会在执行器执行之前就清空本地一级缓存
if (queryStack == 0 && ms.isFlushCacheRequired()) {
// 1.1. 清空缓存
clearLocalCache();
}
List<E> list;
try {
// 2. 查询堆栈 + 1
queryStack++;
// 从一级缓存中获取数据
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
// 3.1. 已有缓存结果,则处理本地缓存结果输出参数(存储过程)
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 3.2. 没有缓存结果,则从数据库查询结果
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
// 查询堆栈数 -1
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
进入queryFromDatabase方法
- 因为延迟加载的原因,这里一级缓存先添加一个占位符
- 查询结果以后,移除然后再put
- 具体的查询数据库操作doQuery方法,下一篇文章专门来讲
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> list;
// 1. 首先向本地缓存中存入一个ExecutionPlaceholder的枚举类占位value
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 2. 执行doQuery方法
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
// 3. 执行完成移除这个key
localCache.removeObject(key);
}
// 4. 查询结果存入缓存中
localCache.putObject(key, list);
// 5. 如果MappedStatement的类型为CALLABLE,则向localOutputParameterCache缓存中存入value为parameter的缓存
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
总结
- 一级缓存和二级缓存就是一个Map集合对象:
- key:Cache对象(statementId、分页参数、带?的sql、参数值、环境id)
- value:数据库查询结果
- 一级缓存默认开启,二级缓存需要添加 <cache>标签开启
- 都开启情况下:先从二级缓存获取,没有则从一级缓存获取,还没有则查询数据库(查询结果后先添加到一级缓存,再添加到二级缓存)