- mybatis 是如何解析我们定义的mapper.xml文件?解析后如何存储?存在哪里?
- 想要解析必然先找到mapper.xml文件,mapperLocations 就是通过读取我们的配置获取到的xml Resource数组。
-
private Resource[] mapperLocations;
-
-
mybatis.mapper-locations=classpath:/mapper
/*.xml
- 循环读取mapper文件,进行解析。
- 接下来我们看下解析是如何进行的?
- 首先构建了一个xmlMapperBuilder 对象,该对象包含了mapper.xml文件流,全路径文件名,Configuration 对象,以及当前对象的sqlFragments .即 sql 片段。
- 接下来调用XMLMapperBuilder的parse方法进行具体的解析工作。具体方法如下。
-
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<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
-
resultMapElements(context.evalNodes( "/mapper/resultMap"));
-
// 解析sql标签 放入到 configuration 的属性sqlFragments 参数里,sql片段信息
-
// protected final Map<String, XNode> 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<XNode> list) {
-
for (XNode parameterMapNode : list) {
-
String id = parameterMapNode.getStringAttribute( "id");
-
String type = parameterMapNode.getStringAttribute( "type");
-
Class<?> parameterClass = resolveClass(type);
-
List<XNode> parameterNodes = parameterMapNode.evalNodes( "parameter");
-
List<ParameterMapping> 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<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
-
-
public ParameterMap addParameterMap(String id, Class<?> parameterClass, List<ParameterMapping> 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: <selectKey> and <include> 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<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("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;
-
}
-
}
-
- 通过以上的源码跟踪和分析我们可以得到:
- 解析mapper.xml文件的结果使用mapper存储,放在Configuration 生成的对象的属性里。
- 通过对不同mapper下的标签的解析,分别生成了不同的对象存在自定义的map里。如参数对象 ParameterMap,映射结果对象:ResultMap sql 标签解析对象:XNode key生成策略:protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");。
- 通过源码的分析跟踪: 这里我们可以学习到 mybatis 的设计概念,比如短key的添加,不同标签的解析到不同对象。
- 使用自定义map实现一些自己框架的逻辑。设计模式的灵活运用,比如建造者模式构建复杂对象。比如模板方法模式:抽出公共处理逻辑方法,定义整个处理流程。不同的处理由子类来实现。
- 比如内部类的使用,内部类的使用一般都是在主类下或者主类的同一个包下面,或者及其子类。
,
转载:https://blog.csdn.net/m0_37948170/article/details/104923608
查看评论