MyBatis中使用緩存來提高其性能。
MyBatis中的緩存分爲兩種:一級緩存和二級緩存。使用過MyBatis的可能聽到過這樣一句話“一級緩存是sqlSession級別的,二級緩存是mapper級別的”。這也說明了,當使用同一個sqlSession時,查詢到的數據可能是一級緩存;而當使用同一個mapper是,查詢到的數據可能是二級緩存。
MyBatis中的一級緩存
由前面的文章可以知道,執行查詢時,SqlSession是將任務交給Executor來完成對數據庫的各種操作。
之前的文章中關於Executor的介紹。可以知道,Executor執行查詢前,會先去查詢緩存。
我們看一看Executor的實現BaseExecutor。
BaseExecutor中擁有一個PerpetualCache,它是Cache接口的實現,則對於BaseExecutor對象而言,它將使用PerpetualCache對象維護緩存,Cache是一個緩存接口。
PerpetualCache是如何實現對緩存的維護的?
public class PerpetualCache implements Cache {
private String id;
//使用一個Map對象,作爲緩存內容的容器
private Map<Object, Object> cache = new HashMap<Object, Object>();
public PerpetualCache(String id) {
this.id = id;
}
public String getId() {
return id;
}
public int getSize() {
return cache.size();
}
public void putObject(Object key, Object value) {
cache.put(key, value);
}
public Object getObject(Object key) {
return cache.get(key);
}
public Object removeObject(Object key) {
return cache.remove(key);
}
public void clear() {
cache.clear();
}
public ReadWriteLock getReadWriteLock() {
return null;
}
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());
}
public int hashCode() {
if (getId() == null) throw new CacheException("Cache instances require an ID.");
return getId().hashCode();
}
}
PerpetualCache內部就是通過一個簡單的HashMap
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
//創建cacheKey。
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
進入方法createCacheKey
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) throw new ExecutorException("Executor was closed.");
CacheKey cacheKey = new CacheKey();
//獲得statementId
cacheKey.update(ms.getId());
//獲得rowBounds.offset
cacheKey.update(rowBounds.getOffset());
//獲得rowBounds.Limit()
cacheKey.update(rowBounds.getLimit());
//獲得boundSql.ql()
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
for (int i = 0; i < parameterMappings.size(); i++) { // mimic DefaultParameterHandler logic
ParameterMapping parameterMapping = parameterMappings.get(i);
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);
}
}
return cacheKey;
}
此時進入update方法:
CacheKey.java
public void update(Object object) {
if (object != null && object.getClass().isArray()) {
int length = Array.getLength(object);
for (int i = 0; i < length; i++) {
Object element = Array.get(object, i);
doUpdate(element);
}
} else {
doUpdate(object);
}
}
private void doUpdate(Object object) {
int baseHashCode = object == null ? 1 : object.hashCode();
count++;
checksum += baseHashCode;
baseHashCode *= count;
//產生hashcode
hashcode = multiplier * hashcode + baseHashCode;
updateList.add(object);
}
到這裏,cacheKey的構建終於真相大白:根據 statementId 、 rowBounds 、傳遞給JDBC的SQL 和 rowBounds.limit決定key中的hashcode。因此,相同的操作就會有相同的hashcode,來保證一個cacheKey對應一個操作。
MyBatis二級緩存
當我們的配置文件配置了cacheEnabled=true時,就會開啓二級緩存,二級緩存是mapper級別的,也就說不同的sqlsession使用同一個mapper查詢是,查詢到的數據可能是另一個sqlsession做相同操作留下的緩存。
之前說一級緩存是Executor執行操作時會去PerpetualCache中的HashMap中根據cacheKey查詢緩存。
而二級緩存的是這樣的:SqlSession對象創建Executor對象時,會對Executor對象加上一個裝飾者:CachingExecutor,然後將操作數據庫的任務交給CachingExecutor,此時CachingExecutor會查找二級緩存是否有需要的數據,如果沒有則將任務交給Executor對象。
這裏使用的裝飾器模式,CachingExecutor包裝了Executor接口,對於裝飾器模式不瞭解的同學可看我設計模式類別下的文章。
由此刻見,如果你配置了二級緩存,那麼查詢數據的順序應該爲:二級緩存→一級緩存→數據庫。
存放二級緩存數據的地方在哪裏?
答:Configration中。
二級緩存的實現(Cache接口的裝飾器)
MyBatis自身提供了豐富的並且強大的二級緩存的實現。
根據名字,如FIFO,LRU,BLOCKING等,我們大概就能猜出具有的功能,這裏不再累述。