小言_互联网的博客

mybatis 源码分析之 解析mapper.xml文件

301人阅读  评论(0)
  • mybatis 是如何解析我们定义的mapper.xml文件?解析后如何存储?存在哪里?
  • 想要解析必然先找到mapper.xml文件,mapperLocations  就是通过读取我们的配置获取到的xml Resource数组。

  
  1. private Resource[] mapperLocations;
  2. mybatis.mapper-locations=classpath:/mapper /*.xml
  • 循环读取mapper文件,进行解析。
  • 接下来我们看下解析是如何进行的?
  • 首先构建了一个xmlMapperBuilder  对象,该对象包含了mapper.xml文件流,全路径文件名,Configuration 对象,以及当前对象的sqlFragments .即 sql 片段。
  • 接下来调用XMLMapperBuilder的parse方法进行具体的解析工作。具体方法如下。
    
        
    1. public void parse() {
    2. //这里首先判断该资源是否已经加载过,已加载则不处理。
    3. if (!configuration.isResourceLoaded(resource)) {
    4. configurationElement(parser.evalNode( "/mapper"));
    5. configuration.addLoadedResource(resource);
    6. bindMapperForNamespace();
    7. }
    8. parsePendingResultMaps();
    9. parsePendingCacheRefs();
    10. parsePendingStatements();
    11. }
    12. // XPathParser 类 parser.evalNode("/mapper") 构造一个XNode 节点对象。
    13. public XNode evalNode(String expression) {
    14. return evalNode(document, expression);
    15. }
    16. public XNode evalNode(Object root, String expression) {
    17. Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
    18. if (node == null) {
    19. return null;
    20. }
    21. return new XNode( this, node, variables);
    22. }
    23. // configurationElement 方法
    24. private void configurationElement(XNode context) {
    25. try {
    26. // 获取命名空间,可见mapper文件的命名空间不可为空也不能是空字符串
    27. String namespace = context.getStringAttribute( "namespace");
    28. if (namespace == null || namespace.equals( "")) {
    29. throw new BuilderException( "Mapper's namespace cannot be empty");
    30. }
    31. // 设置当前命名空间
    32. builderAssistant.setCurrentNamespace(namespace);
    33. // 解析cache-ref 相关配置
    34. cacheRefElement(context.evalNode( "cache-ref"));
    35. // 解析cache配置
    36. cacheElement(context.evalNode( "cache"));
    37. // 解析mapper 标签下parameterMap 配置 参数配置
    38. parameterMapElement(context.evalNodes( "/mapper/parameterMap"));
    39. // 解析 mapper/resultMap 标签,放入到configuration 的属性resultMaps 。key 是由我们当前mapper文件的命名空间和标签定义里的id 拼接而来的。这样不同的命名空间就可以只用相同的id定义,由此命名空间起到了隔离的作用。
    40. // protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
    41. resultMapElements(context.evalNodes( "/mapper/resultMap"));
    42. // 解析sql标签 放入到 configuration 的属性sqlFragments 参数里,sql片段信息
    43. // protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");
    44. sqlElement(context.evalNodes( "/mapper/sql"));
    45. // 这个方法是我们解析SQL语句的关键地方,这里包含一些我们的if 等标签的解析。解析完成后存放在
    46. buildStatementFromContext(context.evalNodes( "select|insert|update|delete"));
    47. } catch (Exception e) {
    48. throw new BuilderException( "Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    49. }
    50. }
    51. // 获取参数配置后放入到 builderAssistant 对象的
    52. private void parameterMapElement(List<XNode> list) {
    53. for (XNode parameterMapNode : list) {
    54. String id = parameterMapNode.getStringAttribute( "id");
    55. String type = parameterMapNode.getStringAttribute( "type");
    56. Class<?> parameterClass = resolveClass(type);
    57. List<XNode> parameterNodes = parameterMapNode.evalNodes( "parameter");
    58. List<ParameterMapping> parameterMappings = new ArrayList<>();
    59. for (XNode parameterNode : parameterNodes) {
    60. String property = parameterNode.getStringAttribute( "property");
    61. String javaType = parameterNode.getStringAttribute( "javaType");
    62. String jdbcType = parameterNode.getStringAttribute( "jdbcType");
    63. String resultMap = parameterNode.getStringAttribute( "resultMap");
    64. String mode = parameterNode.getStringAttribute( "mode");
    65. String typeHandler = parameterNode.getStringAttribute( "typeHandler");
    66. Integer numericScale = parameterNode.getIntAttribute( "numericScale");
    67. ParameterMode modeEnum = resolveParameterMode(mode);
    68. Class<?> javaTypeClass = resolveClass(javaType);
    69. JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    70. Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler);
    71. ParameterMapping parameterMapping = builderAssistant.buildParameterMapping(parameterClass, property, javaTypeClass, jdbcTypeEnum, resultMap, modeEnum, typeHandlerClass, numericScale);
    72. parameterMappings.add(parameterMapping);
    73. }
    74. //看此方法源码可以知道参数对象放入到configuration 对象的属性里,由此可见configuration是一个容器
    75. builderAssistant.addParameterMap(id, parameterClass, parameterMappings);
    76. }
    77. }
    78. // 这里是添加mapper文件的配置参数以Map的形式放入到configuration 的属性 parameterMaps .
    79. // protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
    80. public ParameterMap addParameterMap(String id, Class<?> parameterClass, List<ParameterMapping> parameterMappings) {
    81. id = applyCurrentNamespace(id, false);
    82. ParameterMap parameterMap = new ParameterMap.Builder(configuration, id, parameterClass, parameterMappings).build();
    83. configuration.addParameterMap(parameterMap);
    84. return parameterMap;
    85. }
    86. // 该方法是处理mapper 文件下的 insert|update|delete|select 标签的主要方法。
    87. public void parseStatementNode() {
    88. String id = context.getStringAttribute( "id");
    89. String databaseId = context.getStringAttribute( "databaseId");
    90. if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
    91. return;
    92. }
    93. String nodeName = context.getNode().getNodeName();
    94. SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    95. boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    96. boolean flushCache = context.getBooleanAttribute( "flushCache", !isSelect);
    97. boolean useCache = context.getBooleanAttribute( "useCache", isSelect);
    98. boolean resultOrdered = context.getBooleanAttribute( "resultOrdered", false);
    99. // Include Fragments before parsing
    100. XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    101. includeParser.applyIncludes(context.getNode());
    102. String parameterType = context.getStringAttribute( "parameterType");
    103. Class<?> parameterTypeClass = resolveClass(parameterType);
    104. String lang = context.getStringAttribute( "lang");
    105. LanguageDriver langDriver = getLanguageDriver(lang);
    106. // Parse selectKey after includes and remove them.
    107. processSelectKeyNodes(id, parameterTypeClass, langDriver);
    108. // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    109. KeyGenerator keyGenerator;
    110. String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    111. keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    112. if (configuration.hasKeyGenerator(keyStatementId)) {
    113. keyGenerator = configuration.getKeyGenerator(keyStatementId);
    114. } else {
    115. keyGenerator = context.getBooleanAttribute( "useGeneratedKeys",
    116. configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
    117. ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    118. }
    119. SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    120. StatementType statementType = StatementType.valueOf(context.getStringAttribute( "statementType", StatementType.PREPARED.toString()));
    121. Integer fetchSize = context.getIntAttribute( "fetchSize");
    122. Integer timeout = context.getIntAttribute( "timeout");
    123. String parameterMap = context.getStringAttribute( "parameterMap");
    124. String resultType = context.getStringAttribute( "resultType");
    125. Class<?> resultTypeClass = resolveClass(resultType);
    126. String resultMap = context.getStringAttribute( "resultMap");
    127. String resultSetType = context.getStringAttribute( "resultSetType");
    128. ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    129. if (resultSetTypeEnum == null) {
    130. resultSetTypeEnum = configuration.getDefaultResultSetType();
    131. }
    132. String keyProperty = context.getStringAttribute( "keyProperty");
    133. String keyColumn = context.getStringAttribute( "keyColumn");
    134. String resultSets = context.getStringAttribute( "resultSets");
    135. // 经过一系列处理完毕后。这里将会构建MappedStatement 对象用于存储这些解析出来的信息。
    136. //
    137. builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
    138. fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
    139. resultSetTypeEnum, flushCache, useCache, resultOrdered,
    140. keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
    141. }
    142. public MappedStatement addMappedStatement(
    143. String id,
    144. SqlSource sqlSource,
    145. StatementType statementType,
    146. SqlCommandType sqlCommandType,
    147. Integer fetchSize,
    148. Integer timeout,
    149. String parameterMap,
    150. Class<?> parameterType,
    151. String resultMap,
    152. Class<?> resultType,
    153. ResultSetType resultSetType,
    154. boolean flushCache,
    155. boolean useCache,
    156. boolean resultOrdered,
    157. KeyGenerator keyGenerator,
    158. String keyProperty,
    159. String keyColumn,
    160. String databaseId,
    161. LanguageDriver lang,
    162. String resultSets) {
    163. if (unresolvedCacheRef) {
    164. throw new IncompleteElementException( "Cache-ref not yet resolved");
    165. }
    166. id = applyCurrentNamespace(id, false);
    167. boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    168. MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
    169. .resource(resource)
    170. .fetchSize(fetchSize)
    171. .timeout(timeout)
    172. .statementType(statementType)
    173. .keyGenerator(keyGenerator)
    174. .keyProperty(keyProperty)
    175. .keyColumn(keyColumn)
    176. .databaseId(databaseId)
    177. .lang(lang)
    178. .resultOrdered(resultOrdered)
    179. .resultSets(resultSets)
    180. .resultMaps(getStatementResultMaps(resultMap, resultType, id))
    181. .resultSetType(resultSetType)
    182. .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
    183. .useCache(valueOrDefault(useCache, isSelect))
    184. .cache(currentCache);
    185. ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    186. if (statementParameterMap != null) {
    187. statementBuilder.parameterMap(statementParameterMap);
    188. }
    189. MappedStatement statement = statementBuilder.build();
    190. // 最后是放入configuration 对象的 mappedStatements
    191. // protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
    192. .conflictMessageProducer((savedValue, targetValue) ->
    193. ". please check " + savedValue.getResource() + " and " + targetValue.getResource());
    194. // mappedStatements 使用的Map实现类是StrictMap ,是由mybatis自定义的,这里的put 方法可以看下。
    195. configuration.addMappedStatement(statement);
    196. return statement;
    197. }
    198. // 这个put 方法会检查key是否包含.号,包含则会获取一个短key,即. 分隔符分割成数组后取最后一个
    199. // 所以这里会放入两个key value 。 key不同,value 相同。
    200. // 短 key 的存储这里会有一个问题,因为短key会重复,那么重复的话
    201. public V put(String key, V value) {
    202. if (containsKey(key)) {
    203. throw new IllegalArgumentException(name + " already contains value for " + key
    204. + (conflictMessageProducer == null ? "" : conflictMessageProducer.apply( super.get(key), value)));
    205. }
    206. if (key.contains( ".")) {
    207. final String shortKey = getShortName(key);
    208. if ( super.get(shortKey) == null) {
    209. super.put(shortKey, value);
    210. } else {
    211. // 短key 重复,则短key对应的value值是短key本身封装的一个对象。
    212. // Ambiguity是一个静态内部类对象。这里简单的封装而不是使用字符串,可以便于以后的识别。
    213. super.put(shortKey, (V) new Ambiguity(shortKey));
    214. }
    215. }
    216. return super.put(key, value);
    217. }
    218. // Configuration 的内部类
    219. protected static class Ambiguity {
    220. final private String subject;
    221. public Ambiguity(String subject) {
    222. this.subject = subject;
    223. }
    224. public String getSubject() {
    225. return subject;
    226. }
    227. }

     

  • 通过以上的源码跟踪和分析我们可以得到:
  • 解析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
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场