Mybatis有两级缓存,一级缓存是在同一个SqlSession中有效,两次完全相同的查询,第二次就是从本地缓存中得到上一次的查询结果
Mybatis是默认开启一级缓存的,无法关闭。比如在一个会话中连续两次执行同一个方法, 得到的是同一个对象
一级缓存在一下六种情况会失效:
1.不同的会话
2.同一个会话,两次相同查询中间有增删改操作,因为都会调用clearLocalCache()方法清除缓存
3.同一个会话,查询(条件)参数不同
4.同一个会话,调用清楚会话内容操作 SqlSession.flushCache()
5.同一个会话,SQL相同与条件(参数)相同,但是方法名不同
6.同一个会话,分页信息(偏移量和需要返回的记录数)不同
举例:
public void testCache1() {
SqlSession session = getSqlSession();
CustMapper mapper = session.getMapper(CustMapper.class);
//SQL:select cust_id,cust_name from cust where cust_id=#{custId};
Cust cust1 = mapper.getCustById(1);
//SQL:select cust_id,cust_name from cust where cust_id=#{custId};
//虽然getCustById和getCustByIdNew使用的相同条件和相同SQL,但方法不同不能使用缓存
Cust cust2 = mapper.getCustByIdNew(1);
//如果在这里执行一个增删改操作,那么本地缓存中的 数据将会被清空,下面的getCustByIdNew(1)会再次从数据库中查询数据
//mapper.addCust(new Cust("TonTan"))
//因为和Cust cust2 = mapper.getCustByIdNew(1);方法名(全类名+方法名)、条件、SQL、默认分页信息都一样所以可以使用缓存
Cust cust3 = mapper.getCustByIdNew(1);
}
附上一些源码分析:
一级缓存是使用了名为PerpetualCache的类,变量名为localCache,其内部就是一个Map<Object,Object>。
public class PerpetualCache implements Cache {
private final String id;
private Map<Object, Object> cache = new HashMap<Object, Object>();
}
Mybatis每次执行查询的时候会执行localCache.get(key),这个Key是CacheKey类,他的hashCode值是由如下几个因素决定,只要这几个因素完全一致就可以从localCache中找到之前的查询结果。生成key的方法如下:
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());
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
cacheKey.update(boundSql.getSql());
//其他部分没有复制出来
}
ms.getId是方法的全类名+方法名
rowBounds.getOffset()分页查询时的偏移量
rowBounds.getLimit()分页查询时的查询记录数
boundSql.getSql()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;
}
localCache.putObject(key, list);就是它了。。。。。。。
那为什么调用了增删改缓存就失效了喃,因为他们都底层都是调用update方法,而它调用了clearLocalCache(),所以。。你懂的。
最后提一个全局配置参数:localCacheScope,它可以设置为SESSION或者STATEMENT,默认为SESSION,但是这样的话,可能取到脏数据,假如设置为STATEMENT的话就可以避免这种情况,相当于禁止了一级缓存。