一、复习一下二级缓存内容
二级缓存构建在一级缓存之上,在收到查询请求时,MyBatis 首先会查询二级缓存,若二级缓存未命
中,再去查询一级缓存,一级缓存没有,再查询数据库。
二级缓存 -----> 一级缓存 -----> 数据库。
与一级缓存不同,二级缓存和具体的命名空间绑定,一个 Mapper 中有一个 Cache,相同 Mapper 中的 MappedStatement 共用一个 Cache,一级缓存则是和 SqlSession 绑定。
如果要继续了解详情请看 mybatis 缓存 。
1、启动二级缓存
分为三步走:
1)开启全局二级缓存配置
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
2)在需要使用二级缓存的 Mapper 配置文件中配置标签
<cache></cache>
3)在具体 CURD 标签上配置 useCache=true
<select id="findById" resultType="com.demo.pojo.User" useCache="true">
select * from user where id = #{id}
</select>
二、源码刨析
1、配置初始化,标签 的解析
根据之前的 mybatis 源码剖析,xml 的解析工作主要交给 XMLConfigBuilder.parse() 方法来实现。
1)XMLConfigBuilder
// parse()
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
// 在这里
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
}
// parseConfiguration()
// 既然是在 xml 中添加的,那么我们就直接看关于 mappers 标签的解析
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
// 就是这里
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: "
+ e, e);
}
}
// mapperElement()
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
// 按照我们本例的配置,则直接走该 if 判断。
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream,
configuration, resource, configuration.getSqlFragments());
// 生成 XMLMapperBuilder,并执行其 parse 方法。
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream,
configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url,"
+ " resource or class, but not more than one.");
}
}
}
}
}
2)我们来看看解析 Mapper.xml
XMLMapperBuilder
// parse()
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
// configurationElement()
private void configurationElement(XNode context) {
try