😀 这里写文章的前言:
一个简单的开头,简述这篇文章讨论的问题、目标、人物、背景是什么?并简述你给出的答案。
可以说说你的故事:阻碍、努力、结果成果,意外与转折。
📝 题记
MyBatis是如何对 Mapper 文件中的sql进行处理呢? 虽然上篇解析 mybatis-config.xml 是有进行说明的, 但是应该拿出来单独仔细解析下. 因为这个里面涉及到动态sql, 加上mapper文件自身也有很多标签内容,然后MyBatis是怎么读取出这些内容的呢?读取出来后,又是做了怎么样的处理, 然后达到了sql那种执行效果的呢?
意思也就是,Mapper + 动态sql , 内容还是有点多的, 并且也很重要, 是非常有必要拿出来单独的仔细讲解下的.
Target
在之前对标签的进行解析的时候,是有对 标签进行一个初步的解析. 然后里面其实是很多内容还没填补很详细,所以特意记录下对 详细操作的. 那么,下文就开始操作吧.
org.apache.ibatis.builder.xml.XMLMapperBuilder#parse
主要来看这段解析的代码 :
public void parse() {
if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource); bindMapperForNamespace(); }
parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); }
private void configurationElement(XNode context) { try { 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"));
resultMapElements(context.evalNodes("/mapper/resultMap")); sqlElement(context.evalNodes("/mapper/sql")); 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); } }
|
resultMapElements 方法 :
private void resultMapElements(List<XNode> list) { for (XNode resultMapNode : list) { try {
resultMapElement(resultMapNode); } catch (IncompleteElementException e) { } } }
----------
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) { ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
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<>(additionalResultMappings);
List<XNode> resultChildren = resultMapNode.getChildren(); for (XNode resultChild : resultChildren) {
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); } 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 {
return resultMapResolver.resolve(); } catch (IncompleteElementException e) { configuration.addIncompleteResultMap(resultMapResolver); throw e; } }
private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) { String property; if (flags.contains(ResultFlag.CONSTRUCTOR)) { property = context.getStringAttribute("name"); } else { property = context.getStringAttribute("property"); } String column = context.getStringAttribute("column"); String javaType = context.getStringAttribute("javaType"); String jdbcType = context.getStringAttribute("jdbcType"); String nestedSelect = context.getStringAttribute("select"); String nestedResultMap = context.getStringAttribute("resultMap", () -> processNestedResultMappings(context, Collections.emptyList(), resultType)); String notNullColumn = context.getStringAttribute("notNullColumn"); String columnPrefix = context.getStringAttribute("columnPrefix"); String typeHandler = context.getStringAttribute("typeHandler"); String resultSet = context.getStringAttribute("resultSet"); String foreignColumn = context.getStringAttribute("foreignColumn"); boolean lazy = "lazy".equals(context.getStringAttribute("fetchType", configuration.isLazyLoadingEnabled() ? "lazy" : "eager"));
Class<?> javaTypeClass = resolveClass(javaType); Class<? extends TypeHandler<?>> typeHandlerClass = resolveClass(typeHandler); JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy); }
|
SqlElement 方法
该方法可以很明显的感受到是对 标签进行解析的.
private void sqlElement(List<XNode> list) { if (configuration.getDatabaseId() != null) { sqlElement(list, configuration.getDatabaseId()); }
sqlElement(list, null); }
------------------------
private void sqlElement(List<XNode> list, String requiredDatabaseId) { for (XNode context : list) { String databaseId = context.getStringAttribute("databaseId"); String id = context.getStringAttribute("id"); id = builderAssistant.applyCurrentNamespace(id, false);
if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
sqlFragments.put(id, context); } } }
|
最后 解析后的值,是使用 namespace + id 存放在 org.apache.ibatis.builder.xml.XMLMapperBuilder#sqlFragments 的属性中的.
buildStatementFromContext() 方法 :
这里是对 select / insert / update / delete 标签进行解析.
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
private void buildStatementFromContext(List<XNode> list) { if (configuration.getDatabaseId() != null) { buildStatementFromContext(list, configuration.getDatabaseId()); } buildStatementFromContext(list, null); }
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); } } }
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);
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);
processSelectKeyNodes(id, parameterTypeClass, langDriver);
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");
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }
private void processSelectKeyNodes(String id, Class<?> parameterTypeClass, LanguageDriver langDriver) { List<XNode> selectKeyNodes = context.evalNodes("selectKey"); if (configuration.getDatabaseId() != null) { parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId()); } parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null); removeSelectKeyNodes(selectKeyNodes); }
|
🤗 总结归纳
总结下 MyBatis 解析 Mapper的xml 文件流程。 可以感受到,对于Mybatis处理Mapper,对其字段属性都是挨个解析的,还是下了很大的功夫.
先是有一个集合来控制是否已经解析过了,算是一种是否解析的开关配置. 可以看到其先后的解析顺序,
namespace –> cache-ref –> cache —> mapper/parameterMap —> mapper/resultMap —> mapper/sql —> select/insert/update/detele.
当解析这些标签的时候, 又会对标签里面的属性进行解析. 这里,主要看下我们平常使用到最多的标签, MyBatis 对这些标签解析了后,其后有是怎么利用的呢?可以看到目前MyBatis是存放在一些configuration等类信息里面,那么等到真正去查询sql语句的时候, MyBatis 又是怎么用上的呢? 这里目前只讲了如何解析.
解析完了,没异常,那就是解析都ok了,剩下的就是看当 MyBatis 去查询的时候, 是怎么利用上这些资源的呢?所以看接下来的更新.
📎 参考文章
💡 有关文章的问题,欢迎您在底部评论区留言,一起交流~