衆所周知mybatis緩存體系分爲一級緩存和二級緩存,因此今天就分別聊聊這兩級緩存。spring
一級緩存的使用是不須要任何配置的,直接使用session就可使用一級緩存。代碼以下:sql
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
}...
}
}
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
...
list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
...
return list;
}
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.putObject(key, parameter);
}
return list;
}
複製代碼
localCache就是所謂的一級緩存。數據庫
this.localCache = new PerpetualCache("LocalCache");
public class PerpetualCache implements Cache {
private final String id;
private Map<Object, Object> cache = new HashMap<Object, Object>();
public PerpetualCache(String id) {
this.id = id;
}
}
複製代碼
從代碼中能夠看出localCache是一個PerpetualCache的實現類。在該類裏面,存了一個hashmap,這即是一級緩存。看到這兒你們會發現一級緩存是如此簡單,以致於沒有任何的淘汰,過時策略! 若是一直執行查詢的話,一級緩存會不斷增長。那麼何時一級緩存會清空呢?兩種狀況:緩存
看到這兒你們會發現,一級緩存並很差用。bash
這邊還有個細節就是當mybatis與spring整合的時候,mybatis在SqlSessionTemplate類中給session封裝了一層SqlSessionInterceptor,而這個類中有這樣一個邏輯。session
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
...
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
sqlSession.commit(true);
}
return result;
...
}
}
複製代碼
若是該線程沒有開啓事務則執行sqlSession.commit(true)。也就是清空一級緩存。因此你們在spring中使用mybatis的話,是不用擔憂一級緩存的問題的,由於每次操做後都會刪掉。固然在spring中你也別想使用一級緩存。mybatis
二級緩存使用須要開啓一下配置,首先在mapper文件中添加cache配置。app
<mapper namespace="***">
<cache/>
</mapper>
複製代碼
其次數據bean還須要實現Serializable接口。這樣就能夠開啓二級緩存了。二級緩存代碼以下:ide
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} ...
}
}
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
...
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.<E> query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
tcm.putObject(cache, key, list);
}
return list;
}
...
}
複製代碼
這個地方cache就是二級緩存。固然二級緩存在使用上和一級緩存稍有區別。一級緩存執行session.commit()以後,緩存就清空了。二級緩存則必須執行session.commit()數據纔會被真正的緩存下來。這邊能夠看一下 tcm.putObject(cache, key, list); 這個方法post
public void putObject(Cache cache, CacheKey key, Object value) {
getTransactionalCache(cache).putObject(key, value);
}
public void putObject(Object key, Object object) {
數據僅僅是被存到了entriesToAddOnCommit這個裏面
entriesToAddOnCommit.put(key, object);
}
session.commit();
public void commit(boolean force) {
try {
executor.commit(isCommitOrRollbackRequired(force));
dirty = false;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
public void commit(boolean required) throws SQLException {
delegate.commit(required);
//這個方法纔是真正存數據的方法
tcm.commit();
}
public void commit() {
for (TransactionalCache txCache : transactionalCaches.values()) {
txCache.commit();
}
}
public void commit() {
if (clearOnCommit) {
delegate.clear();
}
flushPendingEntries();
reset();
}
private void flushPendingEntries() {
for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
delegate.putObject(entry.getKey(), entry.getValue());
}
for (Object entry : entriesMissedInCache) {
if (!entriesToAddOnCommit.containsKey(entry)) {
delegate.putObject(entry, null);
}
}
}
複製代碼
二級緩存是帶有過時和淘汰策略的。進入XMLMapperBuilder的cacheElement方法:
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();
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
}
}
複製代碼
這個地方能夠看到和緩存相關的配置以及默認值。這邊邏輯比較簡單你們本身看看就好。
那麼相比於一級緩存而言,擁有了過時,淘汰等策略,還能夠自定義的二級緩存是否是就好用一點了呢?至少我以爲不是,由於翻看代碼能夠發現,在默認狀況下,每次執行增刪改等操做的時候,二級緩存也會被清空。這等於手動觸發緩存雪崩啊。固然你能夠本身定義,可是由於CacheKey是固定生成模式,想要本身定義,得本身解析相應的key或者結果集,仍是比較麻煩的。
因此綜上所述,我我的認爲mybatis的緩存體系比較雞肋,不實用。