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 #482clearLocalCache();}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中被清理掉了
@Overridepublic void close(boolean forceRollback) {...localCache = null;closed = true;}
2、二级缓存,如果采用mybatis提供的默认设置,Configuration#cacheEnabled为true,默认开启二级缓存,但同时需要开启 xml标签才能开启,查看源代码 MappedStatement#getCache() 二级缓存的代码仅在 CachingExecutor 中可
@Overridepublic <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 其次刷新缓存不能为trueCache 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
