[置顶] MyBatis(三) xml文件解析流程 动态SQL解析
2017-05-23 16:34
417 查看
1.MyBatis将整个系统串联起来的就是Configure对象这个需要牢记。在前面MyBatis xml文件解析流程(二) Mapper解析中介绍了Mapper的解析入口,以及Mapper文件中各个节点的解析方法。里面有几个类需要总结下
BaseBuilder:作为其他Builder类的基类
2.XMLConfigBuilder:在Configuration解析时介绍过,主要用来解析config配置文件下的Configuration节点,内部会使用XMLMapperBuilder用于解析xml文件
3.XMLMapperBuilder:在Mapper解析中也介绍过。主要用来解析Mapper文件的,里面用,内部会使用XMLStatementBuilder来处理节点
4.XMLStatementBuilder:解析select|insert|update|delete节点,内部会使用XMLScriptBuilder解析xml节点
5.XMLScriptBuilder:解析sql中各个其他的节点并把解析结果保存到SqlNode中。
6.MapperBuilderAssistant:这是mapper解析中的关键,他作为mapper解析的助理类,负责将解析出来的结果保存下来,并且通过configuration.addMappedStatement方法保存到conguration对象中,最终在调用dao是根据传入的参数动态生成sql。所以说configuration是MyBatis调用的调配中心。
2.动态sql解析流程分析
2.1 从XMLMapperBuilder类中configurationElement方法是mapper文件解析开始。
2.2调用buildStatementFromContext方法,将配置文件中的insert|select|update|delete保存到list集合中,并且通过buildStatementFromContext方法进行循环解析所有节点
2.3 此处XMLStatementBuilder(configuration, builderAssistant, context,requiredDatabaseId)引出来XMLStatementBuilder , MapperBuilderAssistant类,上面介绍过这两个类的作用,XMLStatementBuilder 的parseStatementNode方法负责即解析sql节点。
2.4 其中 SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
很重要,我们看下这个SqlSouse是如何生成的。
langDriver我们看其子类XMLLanguageDriver。在XMLLanguageDriver使用XMLScriptBuilder并传入了Configuration ,以及XNode 节点parameterType
2.5 XMLScriptBuilder的parseScriptNode方法
2.6 SqlNode介绍
如上图所示所有的“sqlNode”都是实现自SqlNode接口并实现它的apply方法
TrimSqlNode: insert|update|select|delete,mapper配置下的trim节点对象
IfSqlNode: insert|update|select|delete,mapper配置下的if节点对象
ChooseSqlNode: insert|update|select|delete,mapper配置下的Choose节点对象.
ForEachSqlNode: insert|update|select|delete,mapper配置下的ForEach节点对象。
WhereSqlNode,SetSqlNode 集成自trim节点,其实这两个完全可以用trim的形式写出来。等等。
2.7XMLScriptBuilder parseDynamicTags方法
获取不同的NodeHandler
以上就完成了sql的解析操作
2.8 介绍MixedSqlNode ,其中apply用于遍历所有sqlnode节点
BaseBuilder:作为其他Builder类的基类
2.XMLConfigBuilder:在Configuration解析时介绍过,主要用来解析config配置文件下的Configuration节点,内部会使用XMLMapperBuilder用于解析xml文件
3.XMLMapperBuilder:在Mapper解析中也介绍过。主要用来解析Mapper文件的,里面用,内部会使用XMLStatementBuilder来处理节点
4.XMLStatementBuilder:解析select|insert|update|delete节点,内部会使用XMLScriptBuilder解析xml节点
5.XMLScriptBuilder:解析sql中各个其他的节点并把解析结果保存到SqlNode中。
6.MapperBuilderAssistant:这是mapper解析中的关键,他作为mapper解析的助理类,负责将解析出来的结果保存下来,并且通过configuration.addMappedStatement方法保存到conguration对象中,最终在调用dao是根据传入的参数动态生成sql。所以说configuration是MyBatis调用的调配中心。
2.动态sql解析流程分析
2.1 从XMLMapperBuilder类中configurationElement方法是mapper文件解析开始。
private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute("namespace"); if (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")); //sql insert|select|update|delete节点解析入口 buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e); } }
2.2调用buildStatementFromContext方法,将配置文件中的insert|select|update|delete保存到list集合中,并且通过buildStatementFromContext方法进行循环解析所有节点
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) { //创建XMLStatementBuilder 实例 final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { //进行解析 statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } } }
2.3 此处XMLStatementBuilder(configuration, builderAssistant, context,requiredDatabaseId)引出来XMLStatementBuilder , MapperBuilderAssistant类,上面介绍过这两个类的作用,XMLStatementBuilder 的parseStatementNode方法负责即解析sql节点。
public void parseStatementNode() { String id = context.getStringAttribute("id"); String databaseId = context.getStringAttribute("databaseId"); if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) return; Integer fetchSize = context.getIntAttribute("fetchSize"); Integer timeout = context.getIntAttribute("timeout"); String parameterMap = context.getStringAttribute("parameterMap"); String parameterType = context.getStringAttribute("parameterType"); Class<?> parameterTypeClass = resolveClass(parameterType); String resultMap = context.getStringAttribute("resultMap"); String resultType = context.getStringAttribute("resultType"); String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); Class<?> resultTypeClass = resolveClass(resultType); String resultSetType = context.getStringAttribute("resultSetType"); StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); 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()); // Parse selectKey after includes, // in case if IncompleteElementException (issue #291) List<XNode> selectKeyNodes = context.evalNodes("selectKey"); if (configuration.getDatabaseId() != null) { parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, configuration.getDatabaseId()); } parseSelectKeyNodes(id, selectKeyNodes, parameterTypeClass, langDriver, null); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); String resultSets = context.getStringAttribute("resultSets"); String keyProperty = context.getStringAttribute("keyProperty"); String keyColumn = context.getStringAttribute("keyColumn"); 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)) ? new Jdbc3KeyGenerator() : new NoKeyGenerator(); } //MapperBuilderAssistant将解析信息保存并且与configuration关联起来,在后面调用mapper接口时会根据这里保存的信息,以及用户传入的参数来进行动态生成sql后面会说到。 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }
2.4 其中 SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
很重要,我们看下这个SqlSouse是如何生成的。
langDriver我们看其子类XMLLanguageDriver。在XMLLanguageDriver使用XMLScriptBuilder并传入了Configuration ,以及XNode 节点parameterType
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) { XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script); return builder.parseScriptNode(); }
2.5 XMLScriptBuilder的parseScriptNode方法
public SqlSource parseScriptNode() { //进行解析节点并返回sqlnode集合 List<SqlNode> contents = parseDynamicTags(context); //MixedSqlNode 负责对所有不同的SqlNode进行遍历并且调用他们的apply方法此时并没有调用只是将其保存到sqlSource 中,等待将来用户调用时执行MixedSqlNode apply方法 MixedSqlNode rootSqlNode = new MixedSqlNode(contents); //返回的是动态DynamicSqlSource SqlSource sqlSource = new DynamicSqlSource(configuration, rootSqlNode); return sqlSource; }
2.6 SqlNode介绍
如上图所示所有的“sqlNode”都是实现自SqlNode接口并实现它的apply方法
TrimSqlNode: insert|update|select|delete,mapper配置下的trim节点对象
IfSqlNode: insert|update|select|delete,mapper配置下的if节点对象
ChooseSqlNode: insert|update|select|delete,mapper配置下的Choose节点对象.
ForEachSqlNode: insert|update|select|delete,mapper配置下的ForEach节点对象。
WhereSqlNode,SetSqlNode 集成自trim节点,其实这两个完全可以用trim的形式写出来。等等。
2.7XMLScriptBuilder parseDynamicTags方法
private List<SqlNode> parseDynamicTags(XNode node) { List<SqlNode> contents = new ArrayList<SqlNode>(); NodeList children = node.getNode().getChildNodes(); for (int i = 0; i < children.getLength(); i++) { XNode child = node.newXNode(children.item(i)); String nodeName = child.getNode().getNodeName(); //如果节点类型是CDATA类型或者是TEXT_NODE则创建TextNode if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) { String data = child.getStringBody(""); contents.add(new TextSqlNode(data)); } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE && !"selectKey".equals(nodeName)) { // issue #628 //如果是ELEMENT_NODE 类型则根据nodeName,根据nodename获取不同的NodeHandler NodeHandler handler = nodeHandlers.get(nodeName); if (handler == null) { throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement."); } handler.handleNode(child, contents); } } return contents; }
获取不同的NodeHandler
private Map<String, NodeHandler> nodeHandlers = new HashMap<String, NodeHandler>() { private static final long serialVersionUID = 7123056019193266281L; { put("trim", new TrimHandler()); put("where", new WhereHandler()); put("set", new SetHandler()); put("foreach", new ForEachHandler()); put("if", new IfHandler()); put("choose", new ChooseHandler()); put("when", new IfHandler()); put("otherwise", new OtherwiseHandler()); put("bind", new BindHandler()); } };
以上就完成了sql的解析操作
2.8 介绍MixedSqlNode ,其中apply用于遍历所有sqlnode节点
public MixedSqlNode(List<SqlNode> contents) { this.contents = contents; } public boolean apply(DynamicContext context) { for (SqlNode sqlNode : contents) { sqlNode.apply(context); } return true; }
相关文章推荐
- [置顶] MyBatis(一) xml文件解析流程 Configuration
- [置顶] MyBatis(二) xml文件解析流程 Mapper解析
- Mybatis 代码流程及实现原理解析(一)
- Mybatis 代码流程及实现原理解析(三)
- [置顶] 【Mybatis】深入浅出Mybatis(八)——动态SQL
- mybatis源码之解析xml文件
- mybatis的mapper.xml文件的动态sql编辑
- js中加载并解析xml文件, 动态添加标签,控件
- [置顶] MyBatis(四) sql执行流程
- [置顶] java中采用Pull解析器对XML文件进行解析
- MyBatis xml文件动态生成对象,网上找的自己进行了优化。
- Mybatis解析动态sql原理分析
- mybatis+spring源码解析(动态代理 spring初始化)
- Mybatis中xml文件的时间段动态查询
- Mybatis解析动态sql原理分析
- Mybatis高级映射、动态SQL及获得自增主键的解析
- Mybatis 代码流程及实现原理解析(二)
- [置顶]xml文件解析方式详解、 pull方式解析xml文件实现代码
- Mybatis解析动态sql原理分析
- MyBatis源码解析(一)——执行流程