mybatis源码(十)mybatis二级缓存的使用
mybatis的二级缓存是mapper级别的缓存
1.mybatis中如何使用二级缓存
a.mybatis的主配置文件的settings中设置cacheEnabled=true
b.mybatis的mapper的配置文件中,配置缓存策略、缓存刷新时间、缓存容量等属性.这个有专门的cache标签。
c.在配置mapper时,通过usecache属性指定是否使用缓存、通过flushCache指定mapper执行后是否刷新缓存。
2.mybatis二级缓存的实现原理
2.1 mybatis二级缓存开启的地方
当mybatis的主配置文件的settings中设置cacheEnabled=true时,创建Executor的时候,默认是创建简单的执行器,这里替换成CacheExecutor,是执行器接口的一个实现类
newExecutor 方法是在sqlSessionFactory.openSession()是调用的
public Executor newExecutor(Transaction transaction) { return newExecutor(transaction, defaultExecutorType); } public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; // 根据executor类型创建对象的Executor对象 if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } // 如果cacheEnabled属性为ture,这使用CachingExecutor对上面创建的Executor进行装饰 if (cacheEnabled) { executor = new CachingExecutor(executor); } // 执行拦截器链的拦截逻辑 executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
代码执行到这里,我们的执行器已经不是默认的simple执行器了,而是CachingExecutor. 后续执行代码的时候,就是通过该执行器执行代码
2.2 mybatis二级缓存的创建过程
二级缓存的创建是在解析mapper配置文件的时候创建的。
流程如下:
XMLConfigBuilder:解析mybatis主配置文件--->解析mybatis的mapper配置文件---->解析cache标签---->创建缓存对象
public class XMLConfigBuilder extends BaseBuilder { // 解析主配置文件 public Configuration parse() { // 防止parse()方法被同一个实例多次调用 if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; // 调用XPathParser.evalNode()方法,创建表示configuration节点的XNode对象。 // 调用parseConfiguration()方法对XNode进行处理 parseConfiguration(parser.evalNode("/configuration")); return configuration; } // 解析mybatis主配置文件下configuration对象 private void parseConfiguration(XNode root) { try { //issue #117 read properties first propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } } // 解析mapper标签 private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { // 通过<package>标签指定包名 if ("package".equals(child.getName())) { String mapperPackage = child.getStringAttribute("name"); configuration.addMappers(mapperPackage); } else { String resource = child.getStringAttribute("resource"); String url = child.getStringAttribute("url"); String mapperClass = child.getStringAttribute("class"); // 通过resource属性指定XML文件路径 if (resource != null && url == null && mapperClass == null) { ErrorContext.instance().resource(resource); InputStream inputStream = Resources.getResourceAsStream(resource); XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url != null && mapperClass == null) { // 通过url属性指定XML文件路径 ErrorContext.instance().resource(url); InputStream inputStream = Resources.getUrlAsStream(url);
// 解析具体的mapper文件 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse(); } else if (resource == null && url == null && mapperClass != null) { // 通过class属性指定接口的完全限定名 Class<?> mapperInterface = Resources.classForName(mapperClass); configuration.addMapper(mapperInterface); } else { throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one."); } } } } }
// 解析具体的mapper文件 XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments()); mapperParser.parse();
从这里进去XMLMapperBuilder,解析具体的mapper文件,代码如下:
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();
// 通过MapperBuilderAssiatant创建Cache builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props); } }
MapperBuilderAssistant
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; }
如上面的代码所示,在获取<cache>标签的所有属性信息后,调用MapperBuilderAssistant对象的userNewCache()方法创建二级缓存实例,然后通过MapperBuilderAssistant的currentCache属性保存二级
缓存对象的引用。在调用MapperBuilderAssistant对象的addMappedStatement()方法创建MappedStatement对象时会将当前命名空间对应的二级缓存对象的引用添加到MappedStatement对象中。
下面是创建MappedStatement对象的关键代码:
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) { if (unresolvedCacheRef) { throw new IncompleteElementException("Cache-ref not yet resolved"); } 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)) .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; }
2.3 mybaits的二级缓存的原理
CacheExcutor的query操作源码如下:
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); // 调用createCacheKey()方法创建缓存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 { // 获取MappedStatement对象中维护的二级缓存对象 Cache cache = ms.getCache(); if (cache != null) { // 判断是否需要刷新二级缓存 flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, boundSql); // 从MappedStatement对象对应的二级缓存中获取数据 @SuppressWarnings("unchecked") List<E> list = (List<E>) tcm.getObject(cache, key); if (list == null) { // 如果缓存数据不存在,则从数据库中查询数据 list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); // 將数据存放到MappedStatement对象对应的二级缓存中 tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); } }
如上面的代码所示,在CachingExecutor的query()方法中, 首先调用createCacheKey()方法创建缓存Key对象,然后调用MappedStatement对象的getCache()方法获取MappedStatement对象
中维护的二级缓存对象。然后尝试从二级缓存对象中获取结果,如果获取不到,则调用目标Executor对象的query()方法从数据库获取数据,再将数据添加到二级缓存中。
由上代码可知,二级缓存是从TransactionalCacheManager中获取的,该类的源码如下:
public class TransactionalCacheManager { // 通过HashMap对象维护二级缓存对应的TransactionalCache实例 private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>(); public void clear(Cache cache) { getTransactionalCache(cache).clear(); } public Object getObject(Cache cache, CacheKey key) { // 获取二级缓存对应的TransactionalCache对象,然后根据缓存Key获取缓存对象 return getTransactionalCache(cache).getObject(key); } public void putObject(Cache cache, CacheKey key, Object value) { getTransactionalCache(cache).putObject(key, value); } public void commit() { for (TransactionalCache txCache : transactionalCaches.values()) { txCache.commit(); } } public void rollback() { for (TransactionalCache txCache : transactionalCaches.values()) { txCache.rollback(); } } private TransactionalCache getTransactionalCache(Cache cache) { // 获取二级缓存对应的TransactionalCache对象 TransactionalCache txCache = transactionalCaches.get(cache); if (txCache == null) { // 如果获取不到则创建,然后添加到Map中 txCache = new TransactionalCache(cache); transactionalCaches.put(cache, txCache); } return txCache; } }
2.4 mybaits的二级缓存删除地方
当执行update更新操作的时候,同一命名空间下的二级缓存会被清空,下面是CachingExecutor的update方法
@Override public int update(MappedStatement ms, Object parameterObject) throws SQLException { // 如果需要刷新,则更新缓存 flushCacheIfRequired(ms); return delegate.update(ms, parameterObject); } public boolean isFlushCacheRequired() { return flushCacheRequired; } private void flushCacheIfRequired(MappedStatement ms) { Cache cache = ms.getCache(); if (cache != null && ms.isFlushCacheRequired()) { tcm.clear(cache); } }