Mybatis源码解析之核心类分析
Mybatis源码解析之初始化分析
Mybatis源码解析之执行流程解析
Mybatis源码解析之数据库连接和连接池
Mybatis源码解析之事务管理
CacheKey是每次查询操作的特征值抽象而成的类,用于区别查询是否相同,从而从cache中找到对应的结果。
当下列元素相同时,我们认为是相同的查询。
(1)statementId
(2)要求的查询结果集的范围(RowBounds的offset和limit)
(3)传给statement的sql语句
(4)传给statement的参数集
但是Cachekey并不是简单的直接由这四个元素作为成员变量组成的一个类,而是进一步抽象,并没有限制特征值的数量和类型。
CacheKey的本质是判断相等性,也就是equals方法,而equals方法相等的前提是hashcode相等,CacheKey核心是其经典的hashcode算法。
CacheKey通过update方法加入一个特征值对象。
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;
}
再看equal方法,判断特征集集合的相等性只是最后一步,如果两个对象不等,只有极小的可能需要对特征值集合进行比较。
一级缓存使用PerpetualCache对象进行表示。
PerpetualCache除了一个id外,保存缓存的属性cache只是一个HashMap,private Map
CachKey作为map的key值,查询结果作为map的value值。 所有对缓存的操作实际上就是对HashMap的操作。
@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();
}
一级缓存又称为本地缓存。
一级缓存不需要任何配置,而是强制开启的,无法取消。不过动态sql的flushc参数可以强制清除所有一级缓存。
@Override
public List 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 list;
try {
queryStack++;
//从缓存中进行查找
list = resultHandler == null ? (List) 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();
}
// issue #601
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
private List queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
List 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;
}
@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);
}
可以看到,执行update方法会清空本地缓存。
@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();
}
}
可以看到,执行commit方法也会清空本地缓存。
@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;
}
}
可以看到,close方法也会将缓存删除。
从前面的分析可以看到,一级缓存是一个粗粒度的缓存,设计的也比较简单。仅仅只是一个HashMap,也没有对HashMap的大小进行管理,也没有缓存更新和过期的概念。
这是因为一级缓存的生命周期很短,不会存活多长时间。
(1)每次调用update方法(insert、delete、update的sql),都会将一级缓存清空。
(2)一级缓存是SqlSession级别的,SqlSession一旦关闭,对应的一级缓存也就不会存在。
(3)可以通过BaseExecutor#clearLocalCache()手动清空缓存。
因此,一下情况时要控制好SqlSession的生存时间,必要时手动清除一级缓存
(1)对于数据的时效性准确性要求比较高的查询,防止一级缓存的长时间存活导致脏数据的读取。
(2)对于频率且大数据量的查询,防止一级缓存占用的内存过大。