由于上篇博客,介绍了主配置文件的加载过程,至于mapper的文件的解析,也是讲了但是不够详细,这篇博客我决定重写梳理一遍,首先我们先看我们运行代码的以及对应的mapper文件,具体的内容如下:
public class Test { public static void main(String[] args) throws Exception { String resource = "mybatis.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); //xml解析完成 //其实我们mybatis初始化方法 除了XML意外 其实也可以0xml完成 //new SqlSessionFactoryBuilder().b SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); Configuration configuration = sqlSessionFactory.getConfiguration(); //使用者可以随时使用或者销毁缓存 //默认sqlsession不会自动提交 //从SqlSession对象打开开始 缓存就已经存在 SqlSession sqlSession = sqlSessionFactory.openSession(); sqlSession.close(); } } <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <environments default="development"> <environment id="development"> <!-- 使用jdbc事务管理 --> <transactionManager type="JDBC" /> <!-- 数据库连接池 --> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver" /> <property name="url" value="jdbc:mysql://localhost:3306/bookstore?characterEncoding=utf-8" /> <property name="username" value="root" /> <property name="password" value="5201314ysX@" /> </dataSource> </environment> </environments> <mappers> <mapper resource="mapper/DemoMapper.xml"></mapper> </mappers> </configuration> <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="com.DemoMapper"> <select id="selectAll" parameterType="String" resultType="Map"> select * from test <where> 1=1 <if test="param2 != null and param2 != ''"> and age = #{param2} </if> and id =${param2} </where> </select> </mapper> public interface DemoMapper { List<Map<String,Object>> selectAll(String name,String age); }上面的mapper文件基本上涵盖了所有应该有的情况了,废话不多说,我们直接上代码,具体的代码如下:
public class XMLMapperBuilder extends BaseBuilder { public void parse() { if (!configuration.isResourceLoaded(resource)) { configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); //绑定Namespace里面的Class对象 bindMapperForNamespace(); } //重新解析之前解析不了的节点 parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); } }上面的代码走来会判断Set<String> loadedResources集合中又没有mapper/DemoMapper.xml这个值,这个是主配置文件中配置,判断这个mapper文件有没有加载,如果加载了这儿就不会执行if中代码,由于我们这儿是第一次执行所以这个值返回的true,我们继续看后买呢重要的方法configurationElement(parser.evalNode("/mapper"));这个方法就是解析DemoMapper.xml文件,同时将mapper这个节点传入进去,我们跟进对应代码,具体的代码如下:
public class XMLMapperBuilder extends BaseBuilder { 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); } } }走来先获取namespace的属性,这儿的namespace是com.DemoMapper,然后设置到builderAssistant的currentNamespace属性中去。后面的两个方法都是解析缓存的节点,后面我会着重说明一下。然后解析parameterMap节点,这个时候这个节点中内容会创建一个ParameterMapping对象,最终会添加到configuration中的parameterMaps Map<String, ParameterMap>变量中去,然后再看解析resultMap节点,最终会创建一个ResultMap对象,然后加他加入到configuration中的resultMaps Map<String, ResultMap>,然后再解析sql节点,最终是将这个节点下的内容填充到configuration的sqlFragments Map<String, XNode>。 这儿上篇博客,已经讲了,但是下面的buildStatementFromContext(context.evalNodes("select|insert|update|delete"));方法,讲的不够详细,今天着重讲这个方法。
public class XMLMapperBuilder extends BaseBuilder { private void buildStatementFromContext(List<XNode> list) { if (configuration.getDatabaseId() != null) { buildStatementFromContext(list, configuration.getDatabaseId()); } buildStatementFromContext(list, null); } }上面的databaseId是在配置多数据源的时候有用,根据对应的databaseId调用不同的数据源。这儿只有一个数据源,就直接传null,默认的就可以了。我们继续跟进 buildStatementFromContext(list, null);方法,具体的代码如下:
public class XMLMapperBuilder extends BaseBuilder { private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { for (XNode context : list) { final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { //解析xml节点 statementParser.parseStatementNode(); } catch (IncompleteElementException e) { //xml语句有问题时 存储到集合中 等解析完能解析的再重新解析 configuration.addIncompleteStatement(statementParser); } } }这儿做了一个如果解析不了直接将其添加到incompleteStatements变量中去,这个是一个链表,因为有些东西可能是先使用,后定义的,就会导致报错,这儿直接添加到这个链表中去,然后后面再解析一次,我们继续跟进 statementParser.parseStatementNode();方法。具体的代码如下:
public class XMLStatementBuilder extends BaseBuilder { public void parseStatementNode() { //这儿获取的selectAll String id = context.getStringAttribute("id"); //空的 String databaseId = context.getStringAttribute("databaseId"); //不会进入此判断 if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } //select String nodeName = context.getNode().getNodeName(); //SELECT SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); //true boolean isSelect = sqlCommandType == SqlCommandType.SELECT; //是否刷新缓存 默认值:增删改刷新 查询不刷新 false boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); //是否使用二级缓存 默认值:查询使用 增删改不使用 true boolean useCache = context.getBooleanAttribute("useCache", isSelect); // 三组数据 分成一个嵌套的查询结果 false boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); // Include Fragments before parsing XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); //替换Includes标签为对应的sql标签里面的值 includeParser.applyIncludes(context.getNode()); //解析parameterType属性 String parameterType = context.getStringAttribute("parameterType"); //然后从typeAliasRegistry中取,如果没有直接从工作空间加载 Class<?> parameterTypeClass = resolveClass(parameterType); //解析配置的自定义脚本语言驱动 mybatis plus String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); // Parse selectKey after includes and remove them. //解析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; } //解析Sql 根据sql文本来判断是否需要动态解析 如果没有动态sql语句且 只有#{}的时候 直接静态解析使用?占位 当有 ${} 不解析 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); } }上面的代码代码都是解析对应的属性,然后进行设置,但是最核心的方法还是解析sql语句,对应的方法是langDriver.createSqlSource(configuration, context, parameterTypeClass);,具体的代码如下:
public class XMLLanguageDriver implements LanguageDriver { @Override public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) { XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType); return builder.parseScriptNode(); } public SqlSource parseScriptNode() { //# $ MixedSqlNode rootSqlNode = parseDynamicTags(context); SqlSource sqlSource; if (isDynamic) { //不解析 sqlSource = new DynamicSqlSource(configuration, rootSqlNode); } else { //用占位符方式来解析 sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType); } return sqlSource; } }上面的代码会调用parseDynamicTags(context);方法,也是解析的核心方法,我们跟进去看看,具体的代码如下:
public class XMLScriptBuilder extends BaseBuilder { protected MixedSqlNode parseDynamicTags(XNode node) { List<SqlNode> contents = new ArrayList<>(); NodeList children = node.getNode().getChildNodes(); for (int i = 0; i < children.getLength(); i++) { XNode child = node.newXNode(children.item(i)); if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) { String data = child.getStringBody(""); TextSqlNode textSqlNode = new TextSqlNode(data); if (textSqlNode.isDynamic()) { contents.add(textSqlNode); isDynamic = true; } else { contents.add(new StaticTextSqlNode(data)); } } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628 String nodeName = child.getNode().getNodeName(); NodeHandler handler = nodeHandlerMap.get(nodeName); if (handler == null) { throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement."); } handler.handleNode(child, contents); isDynamic = true; } } return new MixedSqlNode(contents); } }这时候取出来的节点类型Node.TEXT_NODE类型然后会取出data的值,data的值是select * from test,然后又是核心的方法,就是调用textSqlNode.isDynamic()方法,具体的代码如下:
public class TextSqlNode implements SqlNode { public boolean isDynamic() { DynamicCheckerTokenParser checker = new DynamicCheckerTokenParser(); //创建后面匹配的匹配字符 GenericTokenParser parser = createParser(checker); parser.parse(text); return checker.isDynamic(); } private GenericTokenParser createParser(TokenHandler handler) { return new GenericTokenParser("${", "}", handler); } public String parse(String text) { //如果上面传进来的值为空,直接返回 if (text == null || text.isEmpty()) { return ""; } // search open token 这个时候由于上面创建的所以这儿是${ int start = text.indexOf(openToken); //没有匹配到,直接返回 if (start == -1) { return text; } char[] src = text.toCharArray(); int offset = 0; final StringBuilder builder = new StringBuilder(); StringBuilder expression = null; //遍历里面所有的#{} select ? ,#{id1} ${} while (start > -1) { if (start > 0 && src[start - 1] == '\\') { // this open token is escaped. remove the backslash and continue. builder.append(src, offset, start - offset - 1).append(openToken); offset = start + openToken.length(); } else { // found open token. let's search close token. if (expression == null) { expression = new StringBuilder(); } else { expression.setLength(0); } builder.append(src, offset, start - offset); offset = start + openToken.length(); int end = text.indexOf(closeToken, offset); while (end > -1) { if (end > offset && src[end - 1] == '\\') { // this close token is escaped. remove the backslash and continue. expression.append(src, offset, end - offset - 1).append(closeToken); offset = end + closeToken.length(); end = text.indexOf(closeToken, offset); } else { expression.append(src, offset, end - offset); break; } } if (end == -1) { // close token was not found. builder.append(src, start, src.length - start); offset = src.length; } else { builder.append(handler.handleToken(expression.toString())); offset = end + closeToken.length(); } } start = text.indexOf(openToken, offset); } if (offset < src.length) { builder.append(src, offset, src.length - offset); } return builder.toString(); } }先是创建了一个GenericTokenParser对象并指定"${", "}"为匹配到字符,这儿如果没有找到直接返回,很明显这儿没有找到,直接返回text,这儿就是select * from test,然后调用checker.isDynamic();的值,这儿就是false。然后返回到原来的代码的执行的地方。具体的代码如下:
protected MixedSqlNode parseDynamicTags(XNode node) { List<SqlNode> contents = new ArrayList<>(); NodeList children = node.getNode().getChildNodes(); for (int i = 0; i < children.getLength(); i++) { XNode child = node.newXNode(children.item(i)); if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) { String data = child.getStringBody(""); TextSqlNode textSqlNode = new TextSqlNode(data); if (textSqlNode.isDynamic()) { contents.add(textSqlNode); isDynamic = true; } else { contents.add(new StaticTextSqlNode(data)); } } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628 String nodeName = child.getNode().getNodeName(); NodeHandler handler = nodeHandlerMap.get(nodeName); if (handler == null) { throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement."); } handler.handleNode(child, contents); isDynamic = true; } } return new MixedSqlNode(contents); }这儿返回的值是false,所以会执行else中代码,创建一个StaticTextSqlNode对象,然后添加到contents集合中去。这个时候会遍历第二个标签标签,这个时候上面的那个if就不会进去了。而是直接会执行else if中的代码。这个时候获取到nodeName的值是where。这个时候会从nodeHandlerMap中取出where键对应的值,nodeHandlerMap中有九个键,分别是otherwise,foreach,set,bind,trim,where,choose,if,when。这个时候取出来的类就是WhereHandler,然后执行WhereHandler中的handleNode的方法,具体的代码如下:
private class WhereHandler implements NodeHandler { @Override public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) { MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle); WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode); targetContents.add(where); } }你会发现又执行到原来的代码,这儿是递归调用,做好准备,和我一起绕。具体执行下面的代码,传入的参数为:and age = #{param2}
protected MixedSqlNode parseDynamicTags(XNode node) { List<SqlNode> contents = new ArrayList<>(); NodeList children = node.getNode().getChildNodes(); for (int i = 0; i < children.getLength(); i++) { XNode child = node.newXNode(children.item(i)); if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) { String data = child.getStringBody(""); TextSqlNode textSqlNode = new TextSqlNode(data); if (textSqlNode.isDynamic()) { contents.add(textSqlNode); isDynamic = true; } else { contents.add(new StaticTextSqlNode(data)); } } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628 String nodeName = child.getNode().getNodeName(); NodeHandler handler = nodeHandlerMap.get(nodeName); if (handler == null) { throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement."); } handler.handleNode(child, contents); isDynamic = true; } } return new MixedSqlNode(contents); }这个时候由于where标签中1=1所以直接将这个值加入contents,然后继续解析if标签,这个时候第一个if判断又是不会进,而是直接进入else if中的判断,然后取出nodeName的值为if,然后取出的handler的值为IfHandler,这个时候会调用IfHandler中的handleNode方法,具体的代码如下:
private class IfHandler implements NodeHandler { @Override public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) { MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle); String test = nodeToHandle.getStringAttribute("test"); IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test); targetContents.add(ifSqlNode); } }你会发现又进行了一层俄罗斯套娃,这个时候将 and age = #{parame2} 直接添加到contents中去。这个这个for到循环已经结束了,这个时候返回MixedSqlNode对象,只是将contens的值赋值给MixedSqlNode,这个时候返回上面的代码的执行的地方,然后获取test的属性,这儿的test的值等于param2 != null and param2 != ''这个时候会创建IfSqlNode对象,这个对象中有三个值,分别是test,contents,evaluator=ExpressionEvaluator,然后将这个对象添加到上一个调用他的contents中。这个返回到原来的调用地方,具体的代码如下:
protected MixedSqlNode parseDynamicTags(XNode node) { List<SqlNode> contents = new ArrayList<>(); NodeList children = node.getNode().getChildNodes(); for (int i = 0; i < children.getLength(); i++) { XNode child = node.newXNode(children.item(i)); if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) { String data = child.getStringBody(""); TextSqlNode textSqlNode = new TextSqlNode(data); if (textSqlNode.isDynamic()) { contents.add(textSqlNode); isDynamic = true; } else { contents.add(new StaticTextSqlNode(data)); } } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628 String nodeName = child.getNode().getNodeName(); NodeHandler handler = nodeHandlerMap.get(nodeName); if (handler == null) { throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement."); } //从if进去的,这个时候if执行结束,从这返回 handler.handleNode(child, contents); isDynamic = true; } } return new MixedSqlNode(contents); }从if的标签执行结束返回到,这儿的contents的值应该是两个,一个是StaticTextSqlNode的对象,中间的值是1=1,然后还有一个是IfSqlNode,就是刚才添加的。这个时候将isDynamic的值改成true,这个时候where标签中的值还没有执行结束,这个时候取出来的值是and id =${param2},注意这儿是$符号,所以我们要跟进去看看parse方法,具体的代码如下:
public String parse(String text) { //如果上面传进来的值为空,直接返回 if (text == null || text.isEmpty()) { return ""; } // search open token 这个时候由于上面创建的所以这儿是${ int start = text.indexOf(openToken); //没有匹配到,直接返回 if (start == -1) { return text; } char[] src = text.toCharArray(); int offset = 0; final StringBuilder builder = new StringBuilder(); StringBuilder expression = null; //遍历里面所有的#{} select ? ,#{id1} ${} while (start > -1) { if (start > 0 && src[start - 1] == '\\') { // this open token is escaped. remove the backslash and continue. builder.append(src, offset, start - offset - 1).append(openToken); offset = start + openToken.length(); } else { // found open token. let's search close token. if (expression == null) { expression = new StringBuilder(); } else { expression.setLength(0); } builder.append(src, offset, start - offset); offset = start + openToken.length(); int end = text.indexOf(closeToken, offset); while (end > -1) { if (end > offset && src[end - 1] == '\\') { // this close token is escaped. remove the backslash and continue. expression.append(src, offset, end - offset - 1).append(closeToken); offset = end + closeToken.length(); end = text.indexOf(closeToken, offset); } else { expression.append(src, offset, end - offset); break; } } if (end == -1) { // close token was not found. builder.append(src, start, src.length - start); offset = src.length; } else { builder.append(handler.handleToken(expression.toString())); offset = end + closeToken.length(); } } start = text.indexOf(openToken, offset); } if (offset < src.length) { builder.append(src, offset, src.length - offset); } return builder.toString(); }最终变成了and id =null,等你发现,你返回的时候,这个返回值没有东西接收,你会发现刚才的修改是无效,有点搞不懂。这个时候这个动态的值,就是true,因为前面再if执行的时候,已经替换过了。这个时候会将and id =${param2}的值添加到contents中去。然后将isDynamic的值设置为true,这个时候这个循环会结束。这个时候会返回MixedSqlNode对象,MixedSqlNode中的contents现在是三个值分别是一个是StaticTextSqlNode,它的值是1=1,然后就是IfSqlNode里面存的是test和contents,还有一个值是TextSqlNode存的值是and id =${param2}。这个时候执行完了where标签了,然后就是下面的代码
private class WhereHandler implements NodeHandler { @Override public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) { MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle); WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode); targetContents.add(where); } }这个时候会执行创建WhereHandler对象的方法,具体的代码如下:
public class WhereSqlNode extends TrimSqlNode { private static List<String> prefixList = Arrays.asList("AND ","OR ","AND\n", "OR\n", "AND\r", "OR\r", "AND\t", "OR\t"); public WhereSqlNode(Configuration configuration, SqlNode contents) { super(configuration, contents, "WHERE", prefixList, null, null); } }然后调用的是父类的方法,具体的代码如下:
protected TrimSqlNode(Configuration configuration, SqlNode contents, String prefix, List<String> prefixesToOverride, String suffix, List<String> suffixesToOverride) { this.contents = contents; this.prefix = prefix; this.prefixesToOverride = prefixesToOverride; this.suffix = suffix; this.suffixesToOverride = suffixesToOverride; this.configuration = configuration; }然后将这个where对象添加到contents中返回。具体的代码如下:
protected MixedSqlNode parseDynamicTags(XNode node) { List<SqlNode> contents = new ArrayList<>(); NodeList children = node.getNode().getChildNodes(); for (int i = 0; i < children.getLength(); i++) { XNode child = node.newXNode(children.item(i)); if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) { String data = child.getStringBody(""); TextSqlNode textSqlNode = new TextSqlNode(data); if (textSqlNode.isDynamic()) { contents.add(textSqlNode); isDynamic = true; } else { contents.add(new StaticTextSqlNode(data)); } } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628 String nodeName = child.getNode().getNodeName(); NodeHandler handler = nodeHandlerMap.get(nodeName); if (handler == null) { throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement."); } //从if进去的,这个时候if执行结束,从这返回 handler.handleNode(child, contents); isDynamic = true; } } return new MixedSqlNode(contents); }注意这儿的contents中只有两个对象,一个是StaticTextSqlNode其中的值是 select * from test,然后就是WhereSqlNode对象,这个对象中contents中有三个对象,一个StaticTextSqlNode,它的值是1=1,然后就是IfSqlNode对象,它其中也有一个contents集合,它的值是and age = #{param2},然后就是TextSqlNode对象,它其中的值是and id =${param2},记住只要是动态的标签中都有一个contents集合。然后将isDynamic设置为true,最后再将这个总的contents包装到MixedSqlNode对象中去,然后返回,然后执行如下的代码
public class XMLScriptBuilder extends BaseBuilder { public SqlSource parseScriptNode() { //# $ MixedSqlNode rootSqlNode = parseDynamicTags(context); SqlSource sqlSource; if (isDynamic) { //不解析 sqlSource = new DynamicSqlSource(configuration, rootSqlNode); } else { //用占位符方式来解析 sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType); } return sqlSource; } }这个时候isDynamic的值为true,所以会创建DynamicSqlSource对象,具体的代码如下:
public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) { this.configuration = configuration; this.rootSqlNode = rootSqlNode; }可以发现我们就是将这两个属性赋值给DynamicSqlSource对象。然后返回复制给sqlSource变量,最后再返回。上面的代码执行是isDynamic的值为true的情况,那什么时候isDynamic的值为false?为false又会执行什么代码呢?解答第一个问题,我们看了解析的代码当出现$和动态标签时候都会将这个isDynamic变成true,其他的情况都是false。为false的时候执行的代码如下:
public class RawSqlSource implements SqlSource { public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) { this(configuration, getSql(configuration, rootSqlNode), parameterType); } public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) { SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration); Class<?> clazz = parameterType == null ? Object.class : parameterType; sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>()); } public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) { ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters); GenericTokenParser parser = new GenericTokenParser("#{", "}", handler); String sql = parser.parse(originalSql); return new StaticSqlSource(configuration, sql, handler.getParameterMappings()); } }上面的代码就会将sql中的#{}转成?,同时将返回StaticSqlSource对象。
最后执行的代码如下:
public class XMLStatementBuilder extends BaseBuilder { public void parseStatementNode() { //这儿获取的selectAll String id = context.getStringAttribute("id"); //空的 String databaseId = context.getStringAttribute("databaseId"); //不会进入此判断 if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } //select String nodeName = context.getNode().getNodeName(); //SELECT SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); //true boolean isSelect = sqlCommandType == SqlCommandType.SELECT; //是否刷新缓存 默认值:增删改刷新 查询不刷新 false boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); //是否使用二级缓存 默认值:查询使用 增删改不使用 true boolean useCache = context.getBooleanAttribute("useCache", isSelect); // 三组数据 分成一个嵌套的查询结果 false boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); // Include Fragments before parsing XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); //替换Includes标签为对应的sql标签里面的值 includeParser.applyIncludes(context.getNode()); //解析parameterType属性 String parameterType = context.getStringAttribute("parameterType"); //然后从typeAliasRegistry中取,如果没有直接从工作空间加载 Class<?> parameterTypeClass = resolveClass(parameterType); //解析配置的自定义脚本语言驱动 mybatis plus String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); // Parse selectKey after includes and remove them. //解析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; } //解析Sql 根据sql文本来判断是否需要动态解析 如果没有动态sql语句且 只有#{}的时候 直接静态解析使用?占位 当有 ${} 不解析 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); } }然后将所有的属性读出来,最终创建一个MappedStatement对象,添加到configuration的mappedStatements变量中去,至此整个mapper文件解析已经讲完了。后面会讲mybatis的执行流程。