在实际mybatis的应用中,我们写好mapper接口之后,需要提供对应的xml文件,这次我们主要分析是如何解析xml文件的。
XmlMappedBuilder
解析的主要函数是:
//是否加载过该映射文件 public void parse() { if (!configuration.isResourceLoaded(resource)) { //处理mapper节点 configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); //注册mapper接口 bindMapperForNamespace(); } //处理configurationElement()中解析失败的节点 parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); }
主要看下configurationElement()方法:
private void configurationElement(XNode context) { try { //获取namespace属性 String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); //解析各个节点 cacheRefElement(context.evalNode("cache-ref")); cacheElement(context.evalNode("cache")); parameterMapElement(context.evalNodes("/mapper/parameterMap")); //解析resultMap节点 resultMapElements(context.evalNodes("/mapper/resultMap")); //解析sql节点 sqlElement(context.evalNodes("/mapper/sql")); //解析select、insert、update、delete节点 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); } }
解析<resultMap>节点
每个<resultmap>节点会解析成一个ResultMap对象,其中每个ResultMapping对象记录了结果集中一列与javaBean中一个属性的映射关系。在XmlMapperBuilder中的resultMapElement处理每一个节点。
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception { ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier()); //获取type属性 String type = resultMapNode.getStringAttribute("type", resultMapNode.getStringAttribute("ofType", resultMapNode.getStringAttribute("resultType", resultMapNode.getStringAttribute("javaType")))); Class<?> typeClass = resolveClass(type); if (typeClass == null) { typeClass = inheritEnclosingType(resultMapNode, enclosingType); } Discriminator discriminator = null; //解析的结果 List<ResultMapping> resultMappings = new ArrayList<>(); resultMappings.addAll(additionalResultMappings); //处理resultMap的子节点 List<XNode> resultChildren = resultMapNode.getChildren(); for (XNode resultChild : resultChildren) { //处理construct节点 if ("constructor".equals(resultChild.getName())) { processConstructorElement(resultChild, typeClass, resultMappings); } else if ("discriminator".equals(resultChild.getName())) { discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings); } else { List<ResultFlag> flags = new ArrayList<>(); if ("id".equals(resultChild.getName())) { flags.add(ResultFlag.ID); } //通过buildResultMappingFromContext创建resultMapping节点 resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags)); } } String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier()); String extend = resultMapNode.getStringAttribute("extends"); Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping"); ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping); try { //得到resultMapping创建resultMap对象,并将对象添加到Configuration的resultMaps集合中 return resultMapResolver.resolve(); } catch (IncompleteElementException e) { configuration.addIncompleteResultMap(resultMapResolver); throw e; } }
解析sql节点
private void sqlElement(List<XNode> list, String requiredDatabaseId) { for (XNode context : list) { //遍历sql节点 String databaseId = context.getStringAttribute("databaseId"); String id = context.getStringAttribute("id"); id = builderAssistant.applyCurrentNamespace(id, false); //检测sql的databaseid是否与configuration记录的databaseId一致 if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) { //记录到集合中保存 sqlFragments.put(id, context); } } }
配置文件中<sql>会解析成SqlSource,而 SQL 语句中定义的动态 SQL 节点,如 <where>,<if> 之类的使用 SqlNode 相关的实现类来表示。DynamicContext 该类主要用来存放解析动态 SQL 语句产生的 SQL 语句片段。
public interface SqlSource { //返回值 BoundSql 对象,它包含了 "?" 占位符的 SQL 语句,以及绑定的实参 BoundSql getBoundSql(Object parameterObject); }
实现sqlSource接口的类有:
- DynamicSqlSource 负责处理动态 SQL 语句,DynamicSqlSource 中的 SQL 还需要进一步解析才能被数据库执行
- RawSqlSource 负责处理静态 SQL 语句
- StaticSqlSource;处理后的 SQL ,StaticSqlSource 包含的 SQL 可能含有 “?” 占位符,可以被数据库直接执行
- providerSqlSource
当当SQL节点经过各个SqlNode解析后,SQL语句会被传到 SqlSourceBuilder 进一步解析。SqlSourceBuilder 主要完成两部:一是解析 #{} 占位符中的属性,二是把SQL中的 #{} 替换为 “?”
XmlStatementBuilder
解析sql语句的节点,使用SqlSource接口表示映射文件中的sql语句。
public void parseStatementNode() { //id 属性和数据库标识 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节点的解析 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); // 解析selectKey节点 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"); //创建 MapperedStatement 对象,添加到 configuration 中,MapperedStatement对象的id是生成cache的一个属性 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }