mybatis 一级缓存 源码解析
程序员文章站
2022-04-12 19:36:07
mybatis内置一级缓存(session会话级别的缓存),默认情况下是开启的。官方文档说明如下:cache-- 默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:By default, just local session caching is enabled that is used solel......
mybatis 内置一级缓存(session 会话级别的缓存), 默认情况下是开启的。官方文档说明如下:
cache
-- 默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行:
By default, just local session caching is enabled that is used solely to cache data for the duration of a session.
To enable a global second level of caching you simply need to add one line to your SQL Mapping file:
接下来我们通过源码来看看mybatis 一级缓存具体实现。下图是简单的查询时序图 (画的不好,将就看看)
如上图知道mybatis的SQL执行最后是交给了Executor执行器来完成的,我们看下BaseExecutor的源码
@SuppressWarnings("unchecked")
@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.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
// 从本地缓存中取数据
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
// 处理本地缓存的输出参数 如果是存储过程,把输出参数保存起来
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 查询数据库数据
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
if (queryStack == 0) {
for (DeferredLoad deferredLoad : deferredLoads) {
deferredLoad.load();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
protected BaseExecutor(Configuration configuration, Transaction transaction) {
this.transaction = transaction;
this.deferredLoads = new ConcurrentLinkedQueue<>();
this.localCache = new PerpetualCache("LocalCache");
this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
this.closed = false;
this.configuration = configuration;
this.wrapper = this;
}
/**
* @author Clinton Begin
*/
public class PerpetualCache implements Cache {
private final String id;
private Map<Object, Object> cache = new HashMap<>();
public PerpetualCache(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
@Override
public int getSize() {
return cache.size();
}
@Override
public void putObject(Object key, Object value) {
cache.put(key, value);
}
@Override
public Object getObject(Object key) {
return cache.get(key);
}
@Override
public Object removeObject(Object key) {
return cache.remove(key);
}
@Override
public void clear() {
cache.clear();
}
@Override
public ReadWriteLock getReadWriteLock() {
return null;
}
@Override
public boolean equals(Object o) {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
if (this == o) {
return true;
}
if (!(o instanceof Cache)) {
return false;
}
Cache otherCache = (Cache) o;
return getId().equals(otherCache.getId());
}
@Override
public int hashCode() {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
return getId().hashCode();
}
}
以上:
一级缓存存储在BaseExecutor对象中的localCache属性中,localCache的实现类是perpetualCache,其底层是用HashMap存储缓存对象,CacheKey对象作为HashMap的key,缓存对象作为HashMap作为value;因此CacheKey对象的hashcode将决定存储位置;
我们可以看一下CacheKey的生成规则:
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
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;
}
创建cacheKey主要依据以下几个条件:
- MappedStatement的id也就是select标签所在mapper文件的namespace+select的id相同;
- RowBounds的offset属性和limit()属性相同;
- BoundSql中的sql语句相同;
- 遍历输入参数列表必须满足每个参数相同;
- 获取Environment的id,保证数据源相同;
本文地址:https://blog.csdn.net/qq_16708327/article/details/85784475
推荐阅读
-
Mybaits 源码解析 (十)----- 全网最详细,没有之一:Spring-Mybatis框架使用与源码解析
-
Mybaits 源码解析 (九)----- 全网最详细,没有之一:一级缓存和二级缓存源码分析
-
MyBatis一级缓存的笔记及记录
-
持久层Mybatis3底层源码分析,原理解析
-
Mybatis源码解析,一步一步从浅入深(七):执行查询
-
Mybaits 源码解析 (一)----- 搭建一个mybatis框架(MyBatis HelloWorld)
-
MyBatis缓存实现原理及代码实例解析
-
MyBatis源码解析 - 反射模块
-
springboot mybatis 项目框架源码 shiro 集成代码生成器 ehcache缓存
-
mybatis中的一级缓存深入剖析