本文以mybatis-3.5.11版本为基础,对mybatis缓存进行较详细的解析。
注意,本文说明的情况,适用于mybatis单独使用的情况,即,不与spring或其他容器框架结合使用的情况。
缓存概念说明
mybatis官方文档中,没有对缓存的明确定义,但引用到了两个缓存概念:
在官方文档中翻找一遍,只有两个概念:本地缓存,二级缓存。并没有一级缓存的概念。本文沿用官方文档说明,使用本地缓存与二级缓存的概念。
本地缓存
mybatis中有SqlSession接口,所有数据库操作,均由该接口完成。使用mybatis时,也需要通过该接口进行数据库操作。
本地缓存,由BaseExecutor的localCache实现,并且是在构造方法中进行初始化,因此,本地缓存,是mybatis所有执行器都具备的缓存功能。
1 protected BaseExecutor(Configuration configuration, Transaction transaction) { 2 ... 3 this.localCache = new PerpetualCache("LocalCache"); // 初始化本地缓存 4 ... 5 }
本地缓存的清空:
- 在进行update、commit、rollback操作时,会被清空。“为什么delete的时候没被清空呢?”,因为delete是使用update实现的;
- 在显示设置了flushCache=true的时候,执行语句后清空缓存;
- 缓存生命周期设置为statement的时候(默认为session),执行完语句会清空;
- 超时后会被清空(默认貌似是60秒);
- 显示调用clearCache的时候。
统计未必全面,源码中调用路径较多,没有进行全面的检查。
本地缓存key的组成部分中,包含mapper方法签名。例如,org.messi.dao.TestMapper 接口中,有一个 getUser 方法,则签名中包含 org.messi.dao.TestMapper.getUser 串。
因此,本地缓存,仅对同一mapper中的同一方法有效。
二级缓存
相对于本地缓存,二级缓存的实现更有必要进行说明。本地缓存的实现非常直白,操作简单。二级缓存功能强大了一些,实现也绕了一点。
二级缓存的适用范围,不再有同一sqlSession的局限。二级缓存保存在mapperStatement中,但是使用的仍然是与一级缓存相同的key,因此,二级缓存跨sqlSession,但是无法跨mapper。当一个流程中使用一个sqlSession调用多个mapper方法时,这多个mapper的查询语句,都会被缓存起来,并且是在一个缓存之中。下次再调用这个流程时(缓存超时前),多个mapper的方法,都可以走缓存。这是二级缓存支持的重点。
下面用一张图来表示二级缓存的实现原理:
如上图,左边两个mapper,表示XxxMapper.xml映射文件。右边的事务性缓存管理器,是CachingExecutor中的一个属性,因此二级缓存,必须手动开启,才会生效。手动开启后,会使用CachingExecutor对指定的执行器进行装饰,从而获得二级缓存功能。
二级缓存也支持自定义的cache,可以通过自定义cache,实现自己想要的结果。例如使用redis缓存,从而实现分布式部署下的缓存同步。二级缓存的实现原理,到此就描述完毕了。
以下是二级缓存实现的截取相关代码,可以不看。
以下是二级缓存实现的截取相关代码,可以不看。
以下是二级缓存实现的截取相关代码,可以不看。
XxxMapper.xml映射文件的解析代码(XmlConfigBuilder):
1 public Configuration parse() { 2 if (parsed) { 3 throw new BuilderException("Each XMLConfigBuilder can only be used once."); 4 } 5 parsed = true; 6 // 解析配置。这里是对所有配置的解析入口 7 parseConfiguration(parser.evalNode("/configuration")); 8 return configuration; 9 }
解析方法展开:
1 private void parseConfiguration(XNode root) { 2 try { 3 // issue #117 read properties first 4 propertiesElement(root.evalNode("properties")); 5 Properties settings = settingsAsProperties(root.evalNode("settings")); 6 loadCustomVfs(settings); 7 loadCustomLogImpl(settings); 8 typeAliasesElement(root.evalNode("typeAliases")); 9 pluginElement(root.evalNode("plugins")); 10 objectFactoryElement(root.evalNode("objectFactory")); 11 objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); 12 reflectorFactoryElement(root.evalNode("reflectorFactory")); 13 settingsElement(settings); 14 // read it after objectFactory and objectWrapperFactory issue #631 15 environmentsElement(root.evalNode("environments")); 16 databaseIdProviderElement(root.evalNode("databaseIdProvider")); 17 typeHandlerElement(root.evalNode("typeHandlers")); 18 // 解析xml映射文件 19 mapperElement(root.evalNode("mappers")); 20 } catch (Exception e) { 21 throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); 22 } 23 }
最终解析代码(XMLMapperBuilder):
1 private void configurationElement(XNode context) { 2 try { 3 String namespace = context.getStringAttribute("namespace"); 4 if (namespace == null || namespace.isEmpty()) { 5 throw new BuilderException("Mapper's namespace cannot be empty"); 6 } 7 builderAssistant.setCurrentNamespace(namespace); 8 cacheRefElement(context.evalNode("cache-ref")); 9 cacheElement(context.evalNode("cache")); 10 parameterMapElement(context.evalNodes("/mapper/parameterMap")); 11 resultMapElements(context.evalNodes("/mapper/resultMap")); 12 sqlElement(context.evalNodes("/mapper/sql")); 13 buildStatementFromContext(context.evalNodes("select|insert|update|delete")); 14 } catch (Exception e) { 15 throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); 16 } 17 }
cache标签解析代码(XMLMapperBuilder):
1 private void cacheElement(XNode context) { 2 if (context != null) { 3 String type = context.getStringAttribute("type", "PERPETUAL"); 4 // 解析指定的缓存类型。如果没有指定,默认为mybatis的永久缓存类型。 5 Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type); 6 // 解析缓存剔除策略,默认是LRU。 7 String eviction = context.getStringAttribute("eviction", "LRU"); 8 Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction); 9 Long flushInterval = context.getLongAttribute("flushInterval"); 10 Integer size = context.getIntAttribute("size"); 11 boolean readWrite = !context.getBooleanAttribute("readOnly", false); 12 boolean blocking = context.getBooleanAttribute("blocking", false); 13 Properties props = context.getChildrenAsProperties(); 14 // 创建cache对象 15 builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props); 16 } 17 }cache标签解析
缓存对象创建代码(MapperBuilderAssistant):
1 public Cache useNewCache(Class<? extends Cache> typeClass, 2 Class<? extends Cache> evictionClass, 3 Long flushInterval, 4 Integer size, 5 boolean readWrite, 6 boolean blocking, 7 Properties props) { 8 Cache cache = new CacheBuilder(currentNamespace) 9 .implementation(valueOrDefault(typeClass, PerpetualCache.class)) 10 .addDecorator(valueOrDefault(evictionClass, LruCache.class)) 11 .clearInterval(flushInterval) 12 .size(size) 13 .readWrite(readWrite) 14 .blocking(blocking) 15 .properties(props) 16 .build(); 17 configuration.addCache(cache); 18 // 注意这里 19 currentCache = cache; 20 return cache; 21 } public Cache useNewCache(Class<? extends Cache> typeClass, 22 Class<? extends Cache> evictionClass, 23 Long flushInterval, 24 Integer size, 25 boolean readWrite, 26 boolean blocking, 27 Properties props) { 28 Cache cache = new CacheBuilder(currentNamespace) 29 .implementation(valueOrDefault(typeClass, PerpetualCache.class)) 30 .addDecorator(valueOrDefault(evictionClass, LruCache.class)) 31 .clearInterval(flushInterval) 32 .size(size) 33 .readWrite(readWrite) 34 .blocking(blocking) 35 .properties(props) 36 .build(); 37 configuration.addCache(cache); 38 // 注意这里 39 currentCache = cache; 40 return cache; 41 }缓存对象创建
标签:缓存,--,cache,二级缓存,context,mybatis,root,evalNode From: https://www.cnblogs.com/xujq/p/16725554.html