作者简介:大家好,我是码炫码哥,前中兴通讯、美团架构师,现任某互联网公司CTO,兼职码炫课堂主讲源码系列专题
代表作:《jdk源码&多线程&高并发》,《深入tomcat源码解析》,《深入netty源码解析》,《深入dubbo源码解析》,《深入springboot源码解析》,《深入spring源码解析》,《深入redis源码解析》等
联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬。码炫课堂的个人空间-码炫码哥个人主页-面试,源码等
回答
Mybatis 内置了强大了事务性查询缓存机制。默认情况下,Mybatis 定义了两级缓存,并且提供Cache接口扩展,提高了框架的扩展性。
- 一级缓存:也叫“本地缓存”,默认情况下开启(SqlSession 级别的缓存)
- 二级缓存:也叫“全局缓存”,基于 namespace 级别的缓存,需要手动开启和配置。
实现原理
一级缓存
Mybatis 一级缓存由PerpetualCache实现,采用 Map 结构存储数据。其作用域是 SqlSession,各个 SqlSession 之间的缓存相互隔离。每次查询后,结果会被缓存到PerpetualCache中,后续相同查询直接取缓存数据。

核心实现类和方法
- BaseExecutor:负责执行 SQL,管理一级缓存。
public abstract class BaseExecutor implements Executor {
// ... 其他代码省略
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
// 生成缓存 key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
// 查询结果
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
@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;
}
// ... 其他代码省略
}
- PerpetualCache:缓存实现类,采用 Map 结构存储数据。
public class PerpetualCache implements Cache {
// ... 其他代码省略
private final String id;
// Map 结果本地缓存结果
private final Map<Object, Object> cache = new HashMap<>();
public PerpetualCache(String id) {
this.id = id;
}
@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);
}
// ... 其他代码省略
}
二级缓存
Mybatis 二级缓存是基于命名空间(namespace)的缓存,存储在CachingExecutor。它在每次执行查询之前,先从二级缓存中查找,如果找到则返回缓存结果,否则执行查询并将结果缓存。

默认不打开二级缓存,要开启二级缓存,需通过配置打开,并且使用二级缓存的属性类需要实现 Serializable 序列化接口(可用来保存对象的状态)。配置如下:
<configuration>
<!-- 其他配置 -->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
</configuration>
<mapper namespace="com.damingge.mapper.EmployeeMapper">
<!-- 启用二级缓存 -->
<cache
eviction="LRU"
flushInterval="60000"
size="512"
readOnly="true"/>
</mapper>
核心实现类和方法
- 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);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 生成缓存key
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 从映射声明器获取 Cache
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);
}
// ... 其他代码省略
}
- Cache:缓存接口,Mybatis 默认提供了多种缓存实现,如 PerpetualCache、LruCache、FifoCache、BlockingCache 等。
扩展
一级缓存的 cache key 如何生成?
Mybatis 一级缓存键值通过 CacheKey 实现,生成 Cache Key 包含 SQL 语句ID、查询参数、分页参数等。
- CacheKey:CacheKey 类通过 update 方法将各个属性添加到缓存键中。
public class CacheKey implements Cloneable, Serializable {
// ... 其他代码省略
public CacheKey(Object[] objects) {
this();
updateAll(objects);
}
public int getUpdateCount() {
return updateList.size();
}
//核心方法
public void update(Object object) {
int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
count++;
checksum += baseHashCode;
baseHashCode *= count;
hashcode = multiplier * hashcode + baseHashCode;
// 所有属性存放至一个List集合对象
updateList.add(object);
}
public void updateAll(Object[] objects) {
for (Object o : objects) {
update(o);
}
}
@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) {
return false;
}
if (checksum != cacheKey.checksum) {
return false;
}
if (count != cacheKey.count) {
return false;
}
// 对象比较
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;
}
// ... 其他代码省略
}
- BaseExecutor:BaseExecutor 类通过 createCacheKey 方法生成 CacheKey
public abstract class BaseExecutor implements Executor {
// ... 其他代码省略
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
CacheKey cacheKey = new CacheKey();
//SQL语句ID
cacheKey.update(ms.getId());
// 分页参数
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
// SQL
cacheKey.update(boundSql.getSql());
// 入参
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
Configuration configuration = ms.getConfiguration();
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
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);
}
}
return cacheKey;
}
// ... 其他代码省略
}
Mybatis缓存的实现原理及扩展
629

被折叠的 条评论
为什么被折叠?



