private Resource[] mapperLocations;
mybatis.mapper-locations=classpath:/mapper/*.xml
public void parse() {
//这里首先判断该资源是否已经加载过,已加载则不处理。
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
// XPathParser 类 parser.evalNode("/mapper") 构造一个XNode 节点对象。
public XNode evalNode(String expression) {
return evalNode(document, expression);
}
public XNode evalNode(Object root, String expression) {
Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
if (node == null) {
return null;
}
return new XNode(this, node, variables);
}
// configurationElement 方法
private void configurationElement(XNode context) {
try {
// 获取命名空间,可见mapper文件的命名空间不可为空也不能是空字符串
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
// 设置当前命名空间
builderAssistant.setCurrentNamespace(namespace);
// 解析cache-ref 相关配置
cacheRefElement(context.evalNode("cache-ref"));
// 解析cache配置
cacheElement(context.evalNode("cache"));
// 解析mapper 标签下parameterMap 配置 参数配置
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
// 解析 mapper/resultMap 标签,放入到configuration 的属性resultMaps 。key 是由我们当前mapper文件的命名空间和标签定义里的id 拼接而来的。这样不同的命名空间就可以只用相同的id定义,由此命名空间起到了隔离的作用。
// protected final Map resultMaps = new StrictMap<>("Result Maps collection");
resultMapElements(context.evalNodes("/mapper/resultMap"));
// 解析sql标签 放入到 configuration 的属性sqlFragments 参数里,sql片段信息
// protected final Map sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");
sqlElement(context.evalNodes("/mapper/sql"));
// 这个方法是我们解析SQL语句的关键地方,这里包含一些我们的if 等标签的解析。解析完成后存放在
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
}
}
// 获取参数配置后放入到 builderAssistant 对象的
private void parameterMapElement(List list) {
for (XNode parameterMapNode : list) {
String id = parameterMapNode.getStringAttribute("id");
String type = parameterMapNode.getStringAttribute("type");
Class> parameterClass = resolveClass(type);
List parameterNodes = parameterMapNode.evalNodes("parameter");
List parameterMappings = new ArrayList<>();
for (XNode parameterNode : parameterNodes) {
String property = parameterNode.getStringAttribute("property");
String javaType = parameterNode.getStringAttribute("javaType");
String jdbcType = parameterNode.getStringAttribute("jdbcType");
String resultMap = parameterNode.getStringAttribute("resultMap");
String mode = parameterNode.getStringAttribute("mode");
String typeHandler = parameterNode.getStringAttribute("typeHandler");
Integer numericScale = parameterNode.getIntAttribute("numericScale");
ParameterMode modeEnum = resolveParameterMode(mode);
Class> javaTypeClass = resolveClass(javaType);
JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
Class extends TypeHandler>> typeHandlerClass = resolveClass(typeHandler);
ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property, javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);
parameterMappings.add(parameterMapping);
}
//看此方法源码可以知道参数对象放入到configuration 对象的属性里,由此可见configuration是一个容器
builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
}
}
// 这里是添加mapper文件的配置参数以Map的形式放入到configuration 的属性 parameterMaps .
// protected final Map parameterMaps = new StrictMap<>("Parameter Maps collection");
public ParameterMap addParameterMap(String id, Class> parameterClass, List parameterMappings) {
id = applyCurrentNamespace(id, false);
ParameterMap parameterMap = new ParameterMap.Builder(configuration, id, parameterClass, parameterMappings).build();
configuration.addParameterMap(parameterMap);
return parameterMap;
}
// 该方法是处理mapper 文件下的 insert|update|delete|select 标签的主要方法。
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
String parameterType = context.getStringAttribute("parameterType");
Class> parameterTypeClass = resolveClass(parameterType);
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// Parse selectKey after includes and remove them.
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: and were parsed and removed)
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
String resultType = context.getStringAttribute("resultType");
Class> resultTypeClass = resolveClass(resultType);
String resultMap = context.getStringAttribute("resultMap");
String resultSetType = context.getStringAttribute("resultSetType");
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
if (resultSetTypeEnum == null) {
resultSetTypeEnum = configuration.getDefaultResultSetType();
}
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
String resultSets = context.getStringAttribute("resultSets");
// 经过一系列处理完毕后。这里将会构建MappedStatement 对象用于存储这些解析出来的信息。
//
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
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)
.resource(resource)
.fetchSize(fetchSize)
.timeout(timeout)
.statementType(statementType)
.keyGenerator(keyGenerator)
.keyProperty(keyProperty)
.keyColumn(keyColumn)
.databaseId(databaseId)
.lang(lang)
.resultOrdered(resultOrdered)
.resultSets(resultSets)
.resultMaps(getStatementResultMaps(resultMap, resultType, id))
.resultSetType(resultSetType)
.flushCacheRequired(valueOrDefault(flushCache, !isSelect))
.useCache(valueOrDefault(useCache, isSelect))
.cache(currentCache);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
MappedStatement statement = statementBuilder.build();
// 最后是放入configuration 对象的 mappedStatements
// protected final Map mappedStatements = new StrictMap("Mapped Statements collection")
.conflictMessageProducer((savedValue, targetValue) ->
". please check " + savedValue.getResource() + " and " + targetValue.getResource());
// mappedStatements 使用的Map实现类是StrictMap ,是由mybatis自定义的,这里的put 方法可以看下。
configuration.addMappedStatement(statement);
return statement;
}
// 这个put 方法会检查key是否包含.号,包含则会获取一个短key,即. 分隔符分割成数组后取最后一个
// 所以这里会放入两个key value 。 key不同,value 相同。
// 短 key 的存储这里会有一个问题,因为短key会重复,那么重复的话
public V put(String key, V value) {
if (containsKey(key)) {
throw new IllegalArgumentException(name + " already contains value for " + key
+ (conflictMessageProducer == null ? "" : conflictMessageProducer.apply(super.get(key), value)));
}
if (key.contains(".")) {
final String shortKey = getShortName(key);
if (super.get(shortKey) == null) {
super.put(shortKey, value);
} else {
// 短key 重复,则短key对应的value值是短key本身封装的一个对象。
// Ambiguity是一个静态内部类对象。这里简单的封装而不是使用字符串,可以便于以后的识别。
super.put(shortKey, (V) new Ambiguity(shortKey));
}
}
return super.put(key, value);
}
// Configuration 的内部类
protected static class Ambiguity {
final private String subject;
public Ambiguity(String subject) {
this.subject = subject;
}
public String getSubject() {
return subject;
}
}