對於一名程序員,緩存真的很重要,而且緩存真的是老生常談的一個話題拉。因爲它在我們的開發過程中真的是無處不在。今天LZ帶大家來看一下。Mybatis是怎麼實現一級緩存和二級緩存的。(自帶的緩存機制)
一級緩存
存在BaseExecutor中,是全局的緩存,每次查詢後將值存入BaseExecutor的localCache中。key是由ms,parameter,rowBounds和boundSql一起生成的一個值。value就是查詢出來的結果。一旦有任何更新變動,就刪除整個localCache。
@Override
//生成一級緩存的key的函數,有興趣的看看,LZ不詳細解釋了,不是重點。
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;
}
//查詢數據庫並存入一級緩存的語句
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List<E> 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
localOutputParameterCache.putObject(key, parameter);
}
return list;
}
@Override
//更新變動等語句刪除緩存
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
//刪除一級緩存
clearLocalCache();
//執行語句
return doUpdate(ms, parameter);
}
@Override
//刪除一級緩存
public void clearLocalCache() {
if (!closed) {
localCache.clear();
localOutputParameterCache.clear();
}
}
所以一級緩存的作用級別是SESSION級別的,因爲一個session中存放一個Executor。而一級緩存放在Executor。
二級緩存
如果開啓了二級緩存的話,你的Executor將會被裝飾成CachingExecutor,二級緩存是MapperStatement級的緩存,也就是一個namespace就會有一個緩存,緩存是通過CachingExecutor來操作的。查詢出來的結果會存在statement中的cache中,若有更新,刪除類的操作默認就會清空該MapperStatement的cache(也可以通過修改xml中的屬性,讓它不執行),不會影響其他的MapperStatement
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
//獲得該MappedStatement的cache
Cache cache = ms.getCache();
if (cache != null) {
//看是否需要清除cache(在xml中可以配置flushCache屬性決定何時清空cache)
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
//若開啓了cache且resultHandler 爲空
ensureNoOutParams(ms, parameterObject, boundSql);
@SuppressWarnings("unchecked")
//從TransactionalCacheManager中取cache
List<E> list = (List<E>) tcm.getObject(cache, key);
//若沒值
if (list == null) {
//訪問數據庫
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
//存入cache
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@Override
public int update(MappedStatement ms, Object parameterObject) throws SQLException {
//看是否需要清除cache(在xml中可以配置flushCache屬性決定何時清空cache)
flushCacheIfRequired(ms);
return delegate.update(ms, parameterObject);
}
private void flushCacheIfRequired(MappedStatement ms) {
//獲得cache
Cache cache = ms.getCache();
if (cache != null && ms.isFlushCacheRequired()) {
//若需要清除,則清除cache
tcm.clear(cache);
}
}
因同一個namespace下的MappedStatement的cache是同一個,而TransactionalCacheManager中統一管理cache是裏面的屬性transactionalCaches,該屬性以MappedStatement中的Cache爲key,TransactionalCache對象爲Value。即一個namespace對應一個TransactionalCache。
public class TransactionalCacheManager {
private Map<Cache, TransactionalCache> transactionalCaches = new HashMap<Cache, TransactionalCache>();
……
}
public class TransactionalCache implements Cache {
private static final Log log = LogFactory.getLog(TransactionalCache.class);
//就是對應的namespace中的cache
private Cache delegate;
//提交的時候清除cache的標誌位
private boolean clearOnCommit;
//待提交的集合
private Map<Object, Object> entriesToAddOnCommit;
//未查到的key存放的集合
private Set<Object> entriesMissedInCache;
}
總結
一級緩存是sqlSession級別的緩存,存放在BaseExecutor中的localCache中。查詢就將結果緩存進去,一旦有更新,刪除,插入類的操作就清空緩存。不同的sqlSession之間的緩存是互相不影響的。
二級緩存是namespace級別的,可以理解爲一個mapper.xml文件對應一個二級緩存(不同的sqlSession之間的緩存是共享的),然後對緩存的操作是在.xml文件中的標籤文件進行控制的。比如下面的代碼
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="mapper.TDemoMapper">
<!-- 一個namespace對應一個緩存 -->
<resultMap id="baseMap" type="entity.TDemo">
<result property="id" column="id" jdbcType="INTEGER"></result>
<result property="name" column="name" jdbcType="VARCHAR"></result>
</resultMap>
<!-- 執行此語句不清空緩存 -->
<select id="getAll" resultType="entity.TDemo" useCache="true" flushCache="false" >
select * from t_demo
</select>
</mapper>