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等,我们大概就能猜出具有的功能,这里不再累述。