系列
- mybatis-3.4.6 配置介绍
- mybatis-3.4.6 顶层配置解析
- mybatis-3.4.6 子配置解析
- mybatis-3.4.6 mapper解析
- mybatis-3.4.6 SQL执行流程
- mybatis-3.4.6 SqlSession执行过程
- mybatis-3.4.6 缓存介绍
- mybatis-3.4.6 自增主键
- mybatis-3.4.6 foreach 自增主键
- mybatis-3.4.6 事务管理
开篇
这个系列是基于mybatis-3.4.6版本的源码解析,这篇文章主要是梳理mybatis的一级缓存和二级缓存。
核心主要是分析cache的使用范围以及对应的SQL执行对缓存的使用。
1、一级缓存: 基于PerpetualCache 的 HashMap本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该Session中的所有 Cache 就将清空。
2、二级缓存与一级缓存其机制相同,默认也是采用 PerpetualCache,HashMap存储,不同在于其存储作用域为 Mapper(Namespace),并且可自定义存储源,如 Ehcache。
3、对于缓存数据更新机制,当某一个作用域(一级缓存Session/二级缓存Namespaces)的进行了 C/U/D 操作后,默认该作用域下所有 select 中的缓存将被clear。
4、从Cache的层面来说,二级缓存在一级缓存之前生效。
mybatis一级缓存
public abstract class BaseExecutor implements Executor {
// localCache作为mybatis的一级缓存
protected PerpetualCache localCache;
public List 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 (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List list;
try {
queryStack++;
// 先从localCache查询
list = resultHandler == null ? (List) localCache.getObject(key) : null;
if (list != null) {
// 如果已经存在则直接返回
handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
} else {
// 缓存不存在就查询DB
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
} finally {
queryStack--;
}
return list;
}
private List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
} finally {
localCache.removeObject(key);
}
// 保存到本地缓存
localCache.putObject(key, list);
if (ms.getStatementType() == StatementType.CALLABLE) {
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
}
- mybatis一级缓存在SimpleExecutor级别的,BaseExecutor作为SimpleExecutor的父类。
- BaseExecutor的localCache用来进行缓存,先查询缓存后查询DB,查询完DB保存到localCache当中。
public class PerpetualCache implements Cache {
private final String id;
private Map
- mybatis一级缓存是通过PerpetualCache来进行存储的。
mybatis二级缓存
public class CachingExecutor implements Executor {
public List query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
// 通过MappedStatement来进行查询
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List list = (List) tcm.getObject(cache, key);
// 缓存不存在的情况下由SimpleExecutor来执行query
if (list == null) {
list = delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
// 缓存不允许的情况下由SimpleExecutor来执行query
return delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
}
- 二级缓存在CachingExecutor层被使用。
- 二级缓存是MappedStatement级别的,从MappedStatement当中查询Cache对象。
- 在允许使用二级缓存的场景下优先查询二级缓存,没有的情况下通过SimpleExecutor查询DB。
- 二级缓存是由TransactionalCache进行管理的。
public class TransactionalCache implements Cache {
private static final Log log = LogFactory.getLog(TransactionalCache.class);
private final Cache delegate;
private boolean clearOnCommit;
private final Map entriesToAddOnCommit;
private final Set entriesMissedInCache;
public TransactionalCache(Cache delegate) {
this.delegate = delegate;
this.clearOnCommit = false;
this.entriesToAddOnCommit = new HashMap();
this.entriesMissedInCache = new HashSet();
}
public Object getObject(Object key) {
// issue #116
Object object = delegate.getObject(key);
if (object == null) {
entriesMissedInCache.add(key);
}
// issue #146
if (clearOnCommit) {
return null;
} else {
return object;
}
}
@Override
public void putObject(Object key, Object object) {
entriesToAddOnCommit.put(key, object);
}
}
- TransactionalCache的entriesToAddOnCommit负载保存二级缓存的数据。
mybatis二级缓存的MappedStatement
public final class MappedStatement {
// 省略其他变量
// 二级缓存的cache对象
private Cache cache;
// 是否使用二级缓存
private boolean useCache;
}
- MappedStatement内部保存是否使用二级缓存的配置信息。
public class XMLStatementBuilder extends BaseBuilder {
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
// 针对SELECT的命令行
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
// 针对SELECT的命令默认是不刷新cache的
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
// 针对SELECT的命令默认是使用cache的
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
}
- 在生成MappedStatement对象时候,针对select操作flushCache默认为false,useCache默认是开启。
public class XMLStatementBuilder extends BaseBuilder {
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);
cacheRefElement(context.evalNode("cache-ref"));
// 负责解析cache节点
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
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);
}
}
private void cacheElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type", "PERPETUAL");
Class extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
String eviction = context.getStringAttribute("eviction", "LRU");
Class extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
Long flushInterval = context.getLongAttribute("flushInterval");
Integer size = context.getIntAttribute("size");
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
boolean blocking = context.getBooleanAttribute("blocking", false);
Properties props = context.getChildrenAsProperties();
// 设置是否使用Cache
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
}
public class MapperBuilderAssistant extends BaseBuilder {
public Cache useNewCache(Class extends Cache> typeClass,
Class extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
boolean blocking,
Properties props) {
Cache cache = new CacheBuilder(currentNamespace)
.implementation(valueOrDefault(typeClass, PerpetualCache.class))
.addDecorator(valueOrDefault(evictionClass, LruCache.class))
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.blocking(blocking)
.properties(props)
.build();
configuration.addCache(cache);
currentCache = cache;
return cache;
}
}
- XMLStatementBuilder的cacheElement(context.evalNode("cache"))负责创建二级缓存的cache对象。
public class MapperBuilderAssistant extends BaseBuilder {
public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class> parameterType,
String resultMap,
Class> resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) {
id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
// 设置是否使用cache
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
}
}
- configuration.addMappedStatement(statement)负责保存MappedStatement对象。
参考文章
- 源码参考
- mybatis官网介绍
- 深入理解mybatis原理
- Mybatis3.4.x技术内幕
- mybatis 3.x源码深度解析与最佳实践