mybatis缓存和设计模式
1、mybatis使用了设计模式 装饰器模式
引入了二级缓存,装饰器模式是为了在原先的 事情
上添加一些装饰,而在 mybatis
中则是在 Executor 中对simple,reuse,batch引入了cache这个装饰,通过使用装饰器mybatis引入了二级缓存
2、mybatis 一级缓存是每一个SqlSession都存在的,存在sqlSession和statement2个级别,sqlSession级别在同一个sqlSession执行期间,默认对select操作的结果会进行缓存,缓存的key是cacheKey使用的,具体的比较可参见 CacheKey#equals 方法,总的来时key = statemtnId + rowBounds.getOffset() + rowBounds.getLimit()+boundSql.getSql()+参数 + 可选的environment标识符
mybatis装饰器模式源代码如下
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
....
////解释为什么mybatis的一级缓存是sqlSession级别和statement , 每一个sqlSession都有一个executor(缓存),sqlSession关闭时executor清空缓存,被回收(sqlSession级别)
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);}
}
private SqlSession openSessionFromConnection(ExecutorType execType, Connection connection) {
....
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
}
Configuration#newExecutor
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;//默认的执行类型
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
//settings的缓存设置开启了,那么就采用CachingExecutor对executor进行缓存装饰,典型的装饰器模式运用,加入了缓存功能,但是加入的缓存功能还需要mapper.xml加入<cache/> //标签才能打开
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
1、一级缓存的statement级别 BaseExecutor#query
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
...省略查询过程,查询缓存,不中查询数据,放入缓存的过程
//可见如果手动设置成STATEMENT级别,那么每次查询完之后都会清空缓存,导致实际上的功能时关闭了缓存
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
// issue #482
clearLocalCache();
}
return list;
}
mybatis的sqlSession的推荐使用是方法级别的
public List<PerformData> selectPerformDataByKey(Integer key) {
try (SqlSession sqlSessionTemplate = this.getSqlSessionTemplate()) {
return sqlSessionTemplate.selectList("PerformData.selectPerformDataByKey", key);
}
}
try(){} 的语法会自动调用close方法,可猜想sqlSession的缓存肯定是在close中被清理掉了
@Override
public void close(boolean forceRollback) {
...
localCache = null;
closed = true;
}
2、二级缓存,如果采用mybatis提供的默认设置,Configuration#cacheEnabled为true,默认开启二级缓存,但同时需要开启 xml标签才能开启,查看源代码 MappedStatement#getCache()
二级缓存的代码仅在 CachingExecutor
中可
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
//1 要求mapper开启了 <cache /> 不然 ms.getCache()==null
//2 其次刷新缓存不能为true
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
# if (ms.isUseCache() && resultHandler == null) {
List<E> list = (List<E>) tcm.getObject(cache, key);
if (list == null) {
list = delegate.query(ms, parameterObject, rowBounds, null, key, boundSql);
tcm.putObject(cache, key, list); // issue #578 and #116
}
return list;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
到这一步一级缓存和二级缓存都已经讲到差不多了,接下来就是实际演练一下,配置cacheEnable=true,打开Cache标签
- 同sqlSession依次查询2次,观察结果
- 不同sqlSesion依次查询2次,观察结果
- 使用sqlSession1查询一次,sqlSession2更新,sqlSession1再次查询,观察结果
- 同sqlSession依次查询2次,更新或者插入一次,再查询一次,观察结果
测试的时候可以引入我的agent监控sql语句是否执行或者加入log日志去查看是否真正的发生了sql查询而不是命中缓存
缓存的戏份不多,毕竟在实际中却用不到,实际的生产缓解中,比较常见的是采用多点tomcat/容器+nginx做负载均衡,而mybatis缓存是本地缓存,也就是说可能造成A机器修改了,但是B机器还是用缓存的脏数据问题
总结
写的不多,也很粗糙
2018-10-18