概述
mybatis的緩存分為一級緩存和二級緩存。
一級緩存是會話級别,也就是session級别。一級緩存預設開啟,使用者不能手動配置(但也不是絕對,可以通過修改源碼或配置插件的方式修改)。
二級緩存是應用級别,也就是application級别,這裡可以細粒度的進行控制,對mapper級别進行控制,可以各個mapper使用各自的緩存,也可以配置多個mapper公用一個緩存。二級緩存預設關閉,需要使用者手動配置開啟。

一級緩存
一級緩存是session級别,在一次會話中,sqlsession會使用executor對象來完成一次會話操作,而一個executor維護一個cache對象,在一級緩存中,主要維護的是perpetualCache.
public interface Cache {
String getId();
void putObject(Object key, Object value);
Object getObject(Object key);
Object removeObject(Object key);
void clear();
int getSize();
ReadWriteLock getReadWriteLock();
}
public class PerpetualCache implements Cache {
private String id;
private Map<Object, Object> cache = new HashMap<Object, Object>();
public PerpetualCache(String id) {
this.id = id;
}
@Override
public String getId() {
return id;
}
@Override
public int getSize() {
return cache.size();
}
@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);
}
@Override
public void clear() {
cache.clear();
}
@Override
public ReadWriteLock getReadWriteLock() {
return null;
}
@Override
public boolean equals(Object o) {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
if (this == o) {
return true;
}
if (!(o instanceof Cache)) {
return false;
}
Cache otherCache = (Cache) o;
return getId().equals(otherCache.getId());
}
@Override
public int hashCode() {
if (getId() == null) {
throw new CacheException("Cache instances require an ID.");
}
return getId().hashCode();
}
}
可以看到PerpetualCache非常簡單,裡面的實作cache的機制就是一個簡單的HashMap
mybatis中最為主要的接口就是sqlsession.這個接口定義了操作資料庫的主要方法。
public interface SqlSession extends Closeable {
<T> T selectOne(String statement);
<T> T selectOne(String statement, Object parameter);
<E> List<E> selectList(String statement);
<E> List<E> selectList(String statement, Object parameter);
<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
<K, V> Map<K, V> selectMap(String statement, String mapKey);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
<T> Cursor<T> selectCursor(String statement);
<T> Cursor<T> selectCursor(String statement, Object parameter);
<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);
void select(String statement, Object parameter, ResultHandler handler);
void select(String statement, ResultHandler handler);
void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
int insert(String statement);
int insert(String statement, Object parameter);
int update(String statement);
int update(String statement, Object parameter);
int delete(String statement);
int delete(String statement, Object parameter);
void commit();
void commit(boolean force);
void rollback();
void rollback(boolean force);
List<BatchResult> flushStatements();
/**
* Closes the session
*/
@Override
void close();
/**
* Clears local session cache
*/
void clearCache();
/**
* Retrieves current configuration
* @return Configuration
*/
Configuration getConfiguration();
/**
* Retrieves a mapper.
* @param <T> the mapper type
* @param type Mapper interface class
* @return a mapper bound to this SqlSession
*/
<T> T getMapper(Class<T> type);
/**
* Retrieves inner database connection
* @return Connection
*/
Connection getConnection();
}
最常用的sqlsession主要是defaultsqlsession,defaultsqlsession實作了sqlsession接口
查詢源碼可以看到selectOne其實調用的就是selectList然後,取第一條資料。我們主要分析下selectList
@Override
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);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
首先根據配置configuration查詢出具體的mappedstatement.真正的查詢,是通過executor對象來執行,而這裡的Executor也是一個接口對象。
可以看下BaseExcutor裡面的方法;
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;
}
上面可以看到,首先會從緩存中去取,如果緩存中沒有,再去資料庫取
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;
}
還是要執行到doQuery方法,這裡BaseExecutor裡面是一個abstract方法,真正的實作,需要具體的executor的來實作
看下SimpleExecutor方法
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
二級緩存
mybatis的二級緩存比較靈活,可以使用mybatis自己定義的二級緩存實作,也可以通過實作cache接口,來自定義緩存。也可以使用第三方緩存庫等。
二級緩存主要是在Executor這裡起作用
二級緩存的開啟
a)設定全局的cacheEnable為true
b)在mapper級别配置<cache>(表示一個mapper配置設定一個緩存)或<cache-ref>(多個mapper公用一個cache緩存)
c)在具體的sql語句上配置useCache="true"
mybatis對二級緩存的實作,都是自己實作了cache接口,它提供了一系列的cache裝飾類,并提供了各種重新整理政策比如FIFO,LRU等
參考:
《深入了解mybatis原理》