mybatis缓存创建过程

2023-09-11,,

带着 上篇 的问题,再来看看mybatis的创建过程

1.从SqlSessionFactoryBuilder解析mybatis-config.xml开始

对文件流解析

XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());//等同于return new DefaultSqlSessionFactory(config);

关键是parser.parse()里面

public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
} private void parseConfiguration(XNode root) {
try {
propertiesElement(root.evalNode("properties")); //issue #117 read properties first
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
settingsElement(root.evalNode("settings"));
environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));//mapper配置文件的位置
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}

关键看这:mapperElement(root.evalNode("mappers")); mapperElement中看

XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();

2.XMLMapperBuilder的parse方法如下:

public void parse() {
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
} parsePendingResultMaps();
parsePendingChacheRefs();
parsePendingStatements();
}

进入红色部分,代码如下:

private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));//找到<cache />
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));//<select>...标签
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}

2.1 处理cache标签

private void cacheElement(XNode context) throws Exception {
if (context != null) {
String type = context.getStringAttribute("type", "PERPETUAL");
Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
String eviction = context.getStringAttribute("eviction", "LRU");
Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
Long flushInterval = context.getLongAttribute("flushInterval");
Integer size = context.getIntAttribute("size");
boolean readWrite = !context.getBooleanAttribute("readOnly", false);
Properties props = context.getChildrenAsProperties();
builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, props);
}
}

进入MapperBuilderAssistant的useNewCache,

public Cache useNewCache(Class<? extends Cache> typeClass,
Class<? extends Cache> evictionClass,
Long flushInterval,
Integer size,
boolean readWrite,
Properties props) {
typeClass = valueOrDefault(typeClass, PerpetualCache.class);
evictionClass = valueOrDefault(evictionClass, LruCache.class);
Cache cache = new CacheBuilder(currentNamespace)
.implementation(typeClass)
.addDecorator(evictionClass)
.clearInterval(flushInterval)
.size(size)
.readWrite(readWrite)
.properties(props)
.build();
configuration.addCache(cache);
currentCache = cache;
return cache;
}

创建了一个二级cache,并记录在configuration对象中,并赋值给了currentCache

2.2回到2.1,再看对 buildStatementFromContext(context.evalNodes("select|insert|update|delete"));的处理

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}

对每个标签parseStatementNode(),进入XMLStatementBuilder.parseStatementNode(),最终有句

builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
  fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
  resultSetTypeEnum, flushCache, useCache, resultOrdered,
  keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);

还记得上面的MapperBuilderAssistant的useNewCache么,一个对象

public MappedStatement addMappedStatement(
String id,
SqlSource sqlSource,
StatementType statementType,
SqlCommandType sqlCommandType,
Integer fetchSize,
Integer timeout,
String parameterMap,
Class<?> parameterType,
String resultMap,
Class<?> resultType,
ResultSetType resultSetType,
boolean flushCache,
boolean useCache,
boolean resultOrdered,
KeyGenerator keyGenerator,
String keyProperty,
String keyColumn,
String databaseId,
LanguageDriver lang,
String resultSets) { if (unresolvedCacheRef) throw new IncompleteElementException("Cache-ref not yet resolved"); id = applyCurrentNamespace(id, false);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT; MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType);
statementBuilder.resource(resource);
statementBuilder.fetchSize(fetchSize);
statementBuilder.statementType(statementType);
statementBuilder.keyGenerator(keyGenerator);
statementBuilder.keyProperty(keyProperty);
statementBuilder.keyColumn(keyColumn);
statementBuilder.databaseId(databaseId);
statementBuilder.lang(lang);
statementBuilder.resultOrdered(resultOrdered);
statementBuilder.resulSets(resultSets);
setStatementTimeout(timeout, statementBuilder); setStatementParameterMap(parameterMap, parameterType, statementBuilder);
setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder);
setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder); //等同于mappedStatement.cache = cache;
    MappedStatement statement = statementBuilder.build(); 
configuration.addMappedStatement(statement); //存于configuration对象!!!!
return statement;
}

ps:二级缓存是针对namespace的

<mapper namespace="***">

若有namespace1和namespace2,namespace2有对namespace1表的操作,namespace1刷新了缓存,而这时namespace2中的缓存仍然有效就是脏数据

参考文章:

1. 深入了解MyBatis二级缓存

mybatis缓存创建过程的相关教程结束。

《mybatis缓存创建过程.doc》

下载本文的Word格式文档,以方便收藏与打印。