以前学习了一下MyBatis的一级缓存,主要涉及到BaseExecutor这个类. 如今准备学习记录下MyBatis二级缓存.java
首先二级缓存默认是不开启的,须要本身配置开启.mysql
如上图,须要在configuration里去开启.sql
其次在须要用到二级缓存的Mapper的配置里作一些操做,以下图,增长一个cache节点数据库
至此就能够在UserMapper上开启二级缓存了.apache
当MaBatis初始化的时候,须要解析各类XML配置来生成SQLSessionFactory,解析Mapper的配置也是其中一环.而解析Mapper的配置中,解析Cache节点又是其中的一部分.缓存
这个解析cache会作什么事情呢?app
如上图所示,会往builderAssistant里去新建一个Cache对象(其中的参数是你以前XML里配置的).ide
Cache类的设计也是装饰着模式,层层嵌套.好比咱们new出来的Cache中会包裹一个打印缓存命中信息的LoggingCache,咱们指定的LruCache包裹着PerpetualCache(一级缓存也使用这个Cache)等等.学习
MapperBuilderAssistant是个Builder模式,解析Mapper的最后会调用assistant的addMappedStatement方法.fetch
1 public MappedStatement addMappedStatement( 2 String id, 3 SqlSource sqlSource, 4 StatementType statementType, 5 SqlCommandType sqlCommandType, 6 Integer fetchSize, 7 Integer timeout, 8 String parameterMap, 9 Class<?> parameterType, 10 String resultMap, 11 Class<?> resultType, 12 ResultSetType resultSetType, 13 boolean flushCache, 14 boolean useCache, 15 boolean resultOrdered, 16 KeyGenerator keyGenerator, 17 String keyProperty, 18 String keyColumn, 19 String databaseId, 20 LanguageDriver lang, 21 String resultSets) { 22 23 if (unresolvedCacheRef) { 24 throw new IncompleteElementException("Cache-ref not yet resolved"); 25 } 26 27 id = applyCurrentNamespace(id, false); 28 boolean isSelect = sqlCommandType == SqlCommandType.SELECT; 29 30 MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType) 31 .resource(resource) 32 .fetchSize(fetchSize) 33 .timeout(timeout) 34 .statementType(statementType) 35 .keyGenerator(keyGenerator) 36 .keyProperty(keyProperty) 37 .keyColumn(keyColumn) 38 .databaseId(databaseId) 39 .lang(lang) 40 .resultOrdered(resultOrdered) 41 .resultSets(resultSets) 42 .resultMaps(getStatementResultMaps(resultMap, resultType, id)) 43 .resultSetType(resultSetType) 44 .flushCacheRequired(valueOrDefault(flushCache, !isSelect)) 45 .useCache(valueOrDefault(useCache, isSelect)) 46 .cache(currentCache); 47 48 ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id); 49 if (statementParameterMap != null) { 50 statementBuilder.parameterMap(statementParameterMap); 51 } 52 53 MappedStatement statement = statementBuilder.build(); 54 configuration.addMappedStatement(statement); 55 return statement; 56 }
把刚才new出来的cache设置到MappedStatement.Builder中,最后build出一个MappedStatement,并添加到Configuration里. 以下图
因此其实咱们刚才在Mapper的XML里配置的cache,最后生成对应的java Cache对象,是放在MappedStatement里的,而MappedStatement是什么东西? 看了以下3个截图你们应该就明白了.就是你Mapper XML里配置的一个一个DML对应的节点,固然1个Mapper.XML里会有不少这样的节点,而他们是公用1个Cache的.
MappedStatement是全局惟一的对象,被放到了Configuration里,不一样的Executor须要用到的时候都会从Configuration里去找SQL对应的MappedStatement.(实际上是SqlSession调用Executor的query方法的时候会去根据类.方法名在configuration里查找mappedStatement并传入)
正如以前文章记录的那样,CachingExecutor是包裹在BaseExecutor外的一层Executor,使用的是装饰着模式. 二级缓存主要是在它里面实现的.
先来看一个Demo
2018-10-14 12:04:17,138 DEBUG [main] logging.LogFactory : Logging initialized using 'class org.apache.ibatis.logging.slf4j.Slf4jImpl' adapter. Logging initialized using 'class org.apache.ibatis.logging.stdout.StdOutImpl' adapter. PooledDataSource forcefully closed/removed all connections. PooledDataSource forcefully closed/removed all connections. PooledDataSource forcefully closed/removed all connections. PooledDataSource forcefully closed/removed all connections. Cache Hit Ratio [test.mapper.UserMapper]: 0.0 Opening JDBC Connection Sun Oct 14 12:04:17 CST 2018 WARN: Establishing SSL connection without server's identity verification is not recommended. According to MySQL 5.5.45+, 5.6.26+ and 5.7.6+ requirements SSL connection must be established by default if explicit option isn't set. For compliance with existing applications not using SSL the verifyServerCertificate property is set to 'false'. You need either to explicitly disable SSL by setting useSSL=false, or set useSSL=true and provide truststore for server certificate verification. Created connection 25300561. Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1820e51] ==> Preparing: select id, user_id, user_name, real_name, email, creator_uid, modifier_uid, created_at, updated_at, del from user where id = ? ==> Parameters: 1(Integer) <== Columns: id, user_id, user_name, real_name, email, creator_uid, modifier_uid, created_at, updated_at, del <== Row: 1, 1, test, realName, jyzjyz12@163.com, 1, 1, 2018-09-24 10:10:43.0, 2018-09-24 10:10:46.0, 0 <== Total: 1 User{id=1, userId=1, userName='test', realName='realName', email='jyzjyz12@163.com', creatorUid=1, modifierUid=1, createdAt=Mon Sep 24 10:10:43 CST 2018, updatedAt=Mon Sep 24 10:10:46 CST 2018, del=false} Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@1820e51] Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@1820e51] Returned connection 25300561 to pool. Cache Hit Ratio [test.mapper.UserMapper]: 0.5 User{id=1, userId=1, userName='test', realName='realName', email='jyzjyz12@163.com', creatorUid=1, modifierUid=1, createdAt=Mon Sep 24 10:10:43 CST 2018, updatedAt=Mon Sep 24 10:10:46 CST 2018, del=false}
如上图,从1个SQLSessionFactory里获取了2个SqlSession,分别调用了1次selectByPrimaryKey.
而红色部分输出看到 第一个sqlSession打印了1次SQL, 第二次调用selectByPrimaryKey的时候会打印蓝色的部分,输出缓存命中,几率为1/2,而后没有打印红色的查询语句,而是直接输出返回打印了以前缓存中的对象.
CachingExecutor的query方法以下图
与BaseExecutor的处理方法大体相似,只是先会从MapperedStatement里去寻找是否配置过缓存,有的话就从TransactionalCacheManager中经过Cache作为Key去找TransactionalCache,再从TransactionalCache中根据CacheKey查找结果, TransactionalCache也是Cache的一种实现,目的是为了支持事务回滚提交操做.
TransactionalCache中缓存对象的key为CacheKey,CacheKey以前在一级缓存中已经分享过,value为数据库查询结果,即一个ArrayList.
若是缓存中没有,那就委托内部wrapped的对象,也就是SImpleExecutor去调用query去数据库中找.
总结一下二级缓存如何实现:
1.初始化阶段初始化Cache设置到MappedStatement里,MappedStatement设置到Configuration里.
2.执行查询的时候SqlSession去Configuration里找到MappedStatement传给CachingExecutor.
3.CachingExecutor获取MappedStatement再获取其中的Cache,根据Cache是否null,判断是否有开启二级缓存.
4.若是有开启,那Cache不为null,去TransactionalCacheManager中根据Cache为Key找到TransactionalCache,为null就委托SImpleExecutor继续查数据库.不为null,就在TransactionalCache根据CacheKey找到对应的Value并返回.