1.mybatis一级缓存
一级缓存: sqlSession缓存 [会话缓存]。默认开启,用户不能关闭(有方法让其失效)。通过同一个sqlSession调用同一个查询方法两次,第二次查询走的缓存。
下面我们就看看一级缓存怎么保存的?
在创建sqlSession的时候会创建Executor
BaseExecutor里有个属性localCache就是保存一级缓存内容的
public abstract class BaseExecutor implements Executor {
protected PerpetualCache localCache;
protected PerpetualCache localOutputParameterCache;
protected Configuration configuration;
打开看PerpetualCache类,看cache熟悉,其实就是个map
public class PerpetualCache implements Cache {
private final String id;
private Map<Object, Object> cache = new HashMap<Object, Object>();
接下来看看是怎么判断缓存命中的?
首先看看在BaseExecutor.query()方法在查询时先生成cachekey
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
cachekey怎么生成的?
@Override
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (closed) {
throw new ExecutorException("Executor was closed.");
}
CacheKey cacheKey = new CacheKey();
cacheKey.update(ms.getId());//statmentid
cacheKey.update(rowBounds.getOffset());//默认是0
cacheKey.update(rowBounds.getLimit());//默认是 Integer.MAX_VALUE
cacheKey.update(boundSql.getSql());//sql语句
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
// mimic DefaultParameterHandler logic 方法的参数
for (ParameterMapping parameterMapping : parameterMappings) {
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);
}
}
if (configuration.getEnvironment() != null) {
// issue #176 环境ID
cacheKey.update(configuration.getEnvironment().getId());
}
return cacheKey;
}
根据statmentid,rowbounds,sql,参数生存cachekey
CacheKey这个类是重写了equals方法的
@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;
}
首先hashcode,checksum,count必须相等,然后分别对比什么生存key的几个参数(statmentid,rowbounds,sql等),这些条件都满足时才出走缓存
接下来看看缓存什么时候放进去的?
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;
}
在从数据库查询的时候,先是放了个占位符EXECUTION_PLACEHOLDER,然后执行查询,再通过key删除占位符,最后就查询的数据放入一级缓存localCache中(我暂时也不知道为什么要这么设计。)
接下来看看在什么地方从缓存中取得?
@SuppressWarnings("unchecked")
@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.");
}
if (queryStack == 0 && ms.isFlushCacheRequired()) {
clearLocalCache();
}
List<E> list;
try {
queryStack++;
//通过cachekey从缓存中取,没取到时才会去查数据库
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();
//当设置localCacheScope=statment时,一级缓存就失效了,每次查询完都会清空一级缓存
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
}
return list;
}
通过cachekey从缓存中取,没取到时才会去查数据库
最后看看什么时候会清空缓存?
1.在进行insert,update,delete时会清空缓存
看看update方法:
@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);
}
2.在调用sqlSession的close()和clearCache()方法的时候,一级缓存本来就是会话缓存。
3.直接使一级缓存失效,
<setting name="localCacheScope" value="STATMENT"/>