Mybatis中的多级缓存
一级缓存
一、Cachekey
要了解一级缓存,先要了解一个类:CacheKey,它是查询条件的抽象封装体,也就是说,判断一个条件是否是之前查过的,那么就是通过判断Cachekey的相等性也就是Equals方法了
当下列特征值相同时,我们认为是相同的查询。
- statementId
- 要求的查询结果集的范围(RowBounds的offset和limit)
- 传给statement的sql语句
- 传给statement的参数集
特征值是通过update方法来添加到CacheKey对象内的
public void update(Object object) {
int baseHashCode = object == null ? 1 : ArrayUtil.hashCode(object);
//特征值数量
count++;
//特征值的hashcode之和
checksum += baseHashCode;
baseHashCode *= count;
//hashcode = 原来的hashcode * 扩展因子(37) + 新特征值的hashcode
hashcode = multiplier * hashcode + baseHashCode;
updateList.add(object);
}
可以看出,不同CacheKey对象的hashcode的碰撞率可以控制在一个极小的概率上。
@Override
public boolean equals(Object object) {
if (this == object) {
return true;
}
if (!(object instanceof CacheKey)) {
return false;
}
final CacheKey cacheKey = (CacheKey) object;
if (hashcode != cacheKey.hashcode) {
return false;
}
if (checksum != cacheKey.checksum) {
return false;
}
if (count != cacheKey.count) {
return false;
}
for (int i = 0; i < updateList.size(); i++) {
Object thisObject = updateList.get(i);
Object thatObject = cacheKey.updateList.get(i);
if (!ArrayUtil.equals(thisObject, thatObject)) {
return false;
}
}
return true;
}
二、PerpetualCache
PerpetualCache除了一个id外,保存缓存的属性cache只是一个HashMap
private Map<Object, Object> cache = new HashMap<Object, Object>();
CachKey作为map的key值,查询结果作为map的value值。 所有对缓存的操作实际上就是对HashMap的操作。
@Override
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.");
}
//flush为true,强制清除缓存
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
//从缓存中进行查找
list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
if (list != null) {
//callable时的参数处理
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();
}
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
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;
}
那么什么时候会需要清除缓存呢?首先从上面的源码看来,如果已经用数据库查询了,那么就会把之前的缓存清除
其他情况是否还能出发缓存清除呢?
update方法(实际上delete、insert都会清除缓存)
@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);
}
commit方法
@Override
public void commit(boolean required) throws SQLException {
if (closed) {
throw new ExecutorException("Cannot commit, transaction is already closed");
}
clearLocalCache();
flushStatements();
if (required) {
transaction.commit();
}
}
close方法
@Override
public void close(boolean forceRollback) {
try {
try {
rollback(forceRollback);
} finally {
if (transaction != null) {
transaction.close();
}
}
} catch (SQLException e) {
// Ignore. There's nothing that can be done at this point.
log.warn("Unexpected exception on closing transaction. Cause: " + e);
} finally {
transaction = null;
deferredLoads = null;
localCache = null;
localOutputParameterCache = null;
closed = true;
}
}
三、一级缓存的性能
从前面的分析可以看到,一级缓存是一个粗粒度的缓存,设计的也比较简单。仅仅只是一个HashMap,也没有对HashMap的大小进行管理,也没有缓存更新和过期的概念。
这是因为一级缓存的生命周期很短,不会存活多长时间。
- 每次调用update方法(insert、delete、update的sql),都会将一级缓存清空。
- 一级缓存是SqlSession级别的,SqlSession一旦关闭,对应的一级缓存也就不会存在。(会话结束,事务提交或回滚)
- 可以通过BaseExecutor#clearLocalCache()手动清空缓存。
因此,一下情况时要控制好SqlSession的生存时间,必要时手动清除一级缓存
(1)对于数据的时效性准确性要求比较高的查询,防止一级缓存的长时间存活导致脏数据的读取。
(2)对于频率且大数据量的查询,防止一级缓存占用的内存过大。