Mybatis源码学习(三)

Mybatis源码分析(三)

上回我们分析了Mybatis解析核心配置文件的代码,我们继续上次的分析,开始mapper映射文件的解析,话不多说咱们上代码!!!!!!

Mapper映射文件的前奏

上次我们说到调用mapperElement(root.evalNode("mappers")),解析核心配置文件中的“mappers”标签

/**
   * 解析<mappers>标签
   * @param parent  mappers标签对应的XNode对象
   * @throws Exception
   */
  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      // 获取<mappers>标签的子标签
      for (XNode child : parent.getChildren()) {
    	// <package>子标签
        if ("package".equals(child.getName())) {
          // 获取mapper接口和mapper映射文件对应的package包名
          String mapperPackage = child.getStringAttribute("name");
          // 将包下所有的mapper接口以及它的代理对象存储到一个Map集合中,key为mapper接口类型,value为代理对象工厂
          configuration.addMappers(mapperPackage);
        } else {// <mapper>子标签
          // 获取<mapper>子标签的resource属性
          String resource = child.getStringAttribute("resource");
          // 获取<mapper>子标签的url属性
          String url = child.getStringAttribute("url");
          // 获取<mapper>子标签的class属性
          String mapperClass = child.getStringAttribute("class");
          // 它们是互斥的
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            // 专门用来解析mapper映射文件
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            // 通过XMLMapperBuilder解析mapper映射文件
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            // 通过XMLMapperBuilder解析mapper映射文件
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            // 将指定mapper接口以及它的代理对象存储到一个Map集合中,key为mapper接口类型,value为代理对象工厂
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

我们这里的Mybatis核心配置文件是通过package(包扫描的方式)配置mapper映射文件

<mappers>
    <package name="com.li.mapper"/>
</mappers>

所以我们这里会走到这个if判断中;
在这里插入图片描述

在这里通过configuration的addMappers()方法将该包下的所有的mapper接口以及他的代理对象存储到一个map集合中,key为mapper接口类型,value为代理对象工厂;

调用configuration.addMappers(mapperPackage)方法

// configuration 中的addMappers方法
public void addMappers(String packageName) {
	mapperRegistry.addMappers(packageName);
}

// MapperRegistry 的addMappers 方法
public void addMappers(String packageName) {
    addMappers(packageName, Object.class);
}

// 调用上面的方法addMappers()
public void addMappers(String packageName, Class<?> superType) {
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
    // 根据package名称,加载该包下Mapper接口文件(不是映射文件)
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    // 获取加载的Mapper接口
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
      // 将Mapper接口添加到MapperRegistry中
      addMapper(mapperClass);
    }
}

这里将扫描包,加载接口类,将Mapper接口添加MapperRegistry中,调用MapperRegistry中addMapper方法;

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      // 如果Map集合中已经有该mapper接口的映射,就不需要再存储了
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
    	// 将mapper接口以及它的代理对象存储到一个Map集合中,key为mapper接口类型,value为代理对象工厂
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.

        // 用来解析注解方式的mapper接口
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        // 解析注解方式的mapper接口
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

将mapper接口的class作为key,对应的代理工厂作为value保存在MapperRegistry中的knownMappers内;

创建MapperAnnotationBuilder ,顾名思义该类用来解析注解配置的Mapper接口,在解析注解配置的Mapper之前,也解析了package方式配置的mapper映射文件,调用MapperAnnotationBuilder的parse()方法;

public void parse() {
	// 获取mapper接口的全路径
    String resource = type.toString();
    // 是否解析过该mapper接口
    if (!configuration.isResourceLoaded(resource)) {
      // 先解析mapper映射文件
      loadXmlResource();      //做主要分析
      // 设置解析标识
      configuration.addLoadedResource(resource);
      // Mapper构建者助手
      assistant.setCurrentNamespace(type.getName());
      // 解析CacheNamespace注解
      parseCache();
      // 解析CacheNamespaceRef注解
      parseCacheRef();
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
          // issue #237
          if (!method.isBridge()) {
        	// 每个mapper接口中的方法,都解析成MappedStatement对象
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }

因为我们这里主要分析Mybatis映射文件方式。所以注解不做过多分析,我们来看loadXmlResource()方法;

private void loadXmlResource() {
    // Spring may not know the real resource name so we check a flag
    // to prevent loading again a resource twice
    // this flag is set at XMLMapperBuilder#bindMapperForNamespace
    // 是否解析过该mapper映射文件
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
        // 获取映射文件的全路径
      String xmlResource = type.getName().replace('.', '/') + ".xml";
      // #1347
      InputStream inputStream = type.getResourceAsStream("/" + xmlResource);
      if (inputStream == null) {
        // Search XML mapper that is not in the module but in the classpath.
        try {
          inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
        } catch (IOException e2) {
          // ignore, resource is not required
        }
      }
      if (inputStream != null) {
        XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
        xmlParser.parse();
      }
    }
  }

获取到mapper映射文件的全路径后加载映射文件,创建XMLMapperBuilder类,调用parse()方法解析mapper映射文件;

开始解析Mapper映射文件
public void parse() {
	// mapper映射文件是否已经加载过
    if (!configuration.isResourceLoaded(resource)) {
      // 从映射文件中的<mapper>根标签开始解析,直到完整的解析完毕
      configurationElement(parser.evalNode("/mapper"));
      // 标记已经解析
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }
  • 调用configurationElement(parser.evalNode("/mapper"))开始解析mapper映射文件
    /**
       * 解析映射文件
       * @param context 映射文件根节点<mapper>对应的XNode
       */
      private void configurationElement(XNode context) {
        try {
          // 获取<mapper>标签的namespace值,也就是命名空间
          String namespace = context.getStringAttribute("namespace");
          // 命名空间不能为空
          if (namespace == null || namespace.equals("")) {
            throw new BuilderException("Mapper's namespace cannot be empty");
          }
    
          // 设置当前的命名空间为namespace的值
          builderAssistant.setCurrentNamespace(namespace);
          // 解析<cache-ref>子标签
          cacheRefElement(context.evalNode("cache-ref"));
          // 解析<cache>子标签
          cacheElement(context.evalNode("cache"));
    
          // 解析<parameterMap>子标签
          parameterMapElement(context.evalNodes("/mapper/parameterMap"));
          // 解析<resultMap>子标签
          resultMapElements(context.evalNodes("/mapper/resultMap"));
          // 解析<sql>子标签,也就是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);
        }
      }
    

    在这个方法中我们可以看出解析mapper映射文件时和解析mybatis核心配置文件一样,也是一层一层的去解析,我们在这里是主要分析解析<select><insert><update><delete>子标签

  • 解析<select><insert><update><delete>子标签
    //
    private void buildStatementFromContext(List<XNode> list) {
        if (configuration.getDatabaseId() != null) {
          buildStatementFromContext(list, configuration.getDatabaseId());
        }
        // 构建MappedStatement
        buildStatementFromContext(list, null);
      }
    
      private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
        for (XNode context : list) {
          // MappedStatement解析器
          final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
          try {
        	// 解析select等4个标签,创建MappedStatement对象
            statementParser.parseStatementNode();
          } catch (IncompleteElementException e) {
            configuration.addIncompleteStatement(statementParser);
          }
        }
      }
    

    这里创建了一个XMLStatementBuilder用来构建MappedStatement,在mybatis中一个MappedStatement就代表了一个<select><insert><update><delete>其中一个标签对应的SQL节点。

  • 调用statementParser.parseStatementNode()开始解析<select><insert><update><delete>标签
    /**
       * 解析<select>\<insert>\<update>\<delete>子标签
       */
      public void parseStatementNode() {
    	// 获取statement的id属性(特别关键的值)
        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");
        // 别名处理,获取入参对应的Java类型
        Class<?> parameterTypeClass = resolveClass(parameterType);
        // 获取ResultMap
        String resultMap = context.getStringAttribute("resultMap");
        // 获取结果映射类型
        String resultType = context.getStringAttribute("resultType");
        String lang = context.getStringAttribute("lang");
        LanguageDriver langDriver = getLanguageDriver(lang);
    
        // 别名处理,获取返回值对应的Java类型
        Class<?> resultTypeClass = resolveClass(resultType);
        String resultSetType = context.getStringAttribute("resultSetType");
    
        // 设置默认StatementType为Prepared,该参数指定了后面的JDBC处理时,采用哪种Statement
        StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
        ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    
        String nodeName = context.getNode().getNodeName();
        // 解析SQL命令类型是什么?确定操作是CRUD中的哪一种
        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
        // <include>标签解析
        XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
        includeParser.applyIncludes(context.getNode());
    
        // Parse selectKey after includes and remove them.
        // 解析<selectKey>标签
        processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
        // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
        // 创建SqlSource,解析SQL,封装SQL语句(未参数绑定)和入参信息
        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))
              ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
        }
    
        // 通过构建者助手,创建MappedStatement对象
        builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
            fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
            resultSetTypeEnum, flushCache, useCache, resultOrdered,
            keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
      }
    

    在这个方法里,对<select><insert><update><delete>标签进行解析,获取到了标签中配置的值,比如parameterType(入参类型)、resultType(出参类型)等做了获取,最重要的是创建SqlSource,解析SQL,封装SQL语句(未参数绑定)和入参信息并且通过构建者助手创建MappedStatement对象;

  • 创建SqlSource、创建MappedStatement对象
  • 创建SqlSource
    @Override
    public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    	// 初始化了动态SQL标签处理器
    	XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    	// 解析动态SQL
    	return builder.parseScriptNode();
    }
    
    //解析动态标签
    public SqlSource parseScriptNode() {
    	// 解析select\insert\ update\delete标签中的SQL语句,最终将解析到的SqlNode封装到MixedSqlNode中的List集合中
    	// ****将带有${}号的SQL信息封装到TextSqlNode
    	// ****将带有#{}号的SQL信息封装到StaticTextSqlNode
    	// ****将动态SQL标签中的SQL信息分别封装到不同的SqlNode中
    	MixedSqlNode rootSqlNode = parseDynamicTags(context);
    	SqlSource sqlSource = null;
    	// 如果SQL中包含${}和动态SQL语句,则将SqlNode封装到DynamicSqlSource
    	if (isDynamic) {
    		sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    	} else {
    		// 如果SQL中包含#{},则将SqlNode封装到RawSqlSource中,并指定parameterType
    		sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    	}
    	return sqlSource;
    }
    
    //
    protected MixedSqlNode parseDynamicTags(XNode node) {
    		List<SqlNode> contents = new ArrayList<>();
    		//获取<select>\<insert>等4个标签的子节点,子节点包括元素节点和文本节点
    		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("");
    				// 将文本内容封装到SqlNode中
    				TextSqlNode textSqlNode = new TextSqlNode(data);
    				// SQL语句中带有${}的话,就表示是dynamic的
    				if (textSqlNode.isDynamic()) {
    					contents.add(textSqlNode);
    					isDynamic = true;
    				} else {
    					// SQL语句中(除了${}和下面的动态SQL标签),就表示是static的
    					// StaticTextSqlNode的apply只是进行字符串的追加操作
    					contents.add(new StaticTextSqlNode(data));
    				}
    
    				//处理元素节点
    			} else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
    				String nodeName = child.getNode().getNodeName();
    				// 动态SQL标签处理器
    				// 思考,此处使用了哪种设计模式?---策略模式
    				NodeHandler handler = nodeHandlerMap.get(nodeName);
    				if (handler == null) {
    					throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
    				}
    				handler.handleNode(child, contents);
    				// 动态SQL标签是dynamic的
    				isDynamic = true;
    			}
    		}
    		return new MixedSqlNode(contents);
    	}
    

    通过循环标签内子节点,将每个节点进行分类封装,最终封装成一个MixedSqlNode(混合的sqlNode);

    这边进行一个简单的讲解:

  • SqlNode:是一个接口,有一个apply的方法,将解析完的语句append到结果中;

    ​ 实现类:

    • MixedSqlNode:是一个混合的SqlNode,用来封装sql解析的上下文;

    • StaticTextSqlNode:最简单的SqlNode,功能仅仅就是将自身记录的text拼接到context上下文中;

    • TextSqlNode

      • 表示包含 “${}” 占位符的动态SQL节点;
      • TextSqlNode.apply()方法会使用GenericTokenParser解析“${}”占位符,并直接替换成传入的实际参数值;
      • 实际参数值获取逻辑在内部BindingTokenParser.handleToken中;
    • IfSqlNode:if标签平常用的最多,在处理对应节点逻辑时,其主要工作就是通过Ognl表达式和传入的参数进行判断,看传入的参数值是否有满足if test里的表达式,满足将SQL片段合并到context上下文中。若不满足test则过滤掉这一部分SQL片段,不添加到context中;

    • WhereSqlNode:<where /> 标签的 SqlNode 实现类,继承至TrimSqlNode。WhereSqlNode会传入prefix=WHERE和 prefixesToOverride=["AND ","OR ",“AND\n”, “OR\n”, “AND\r”, “OR\r”, “AND\t”, “OR\t”]`;

    • SetSqlNode:<set /> 标签的 SqlNode 实现类,继承至TrimSqlNode。SetSqlNode会传入prefix=WHEREprefixesToOverride=[“,”];

    • TrimSqlNode:<trim /> 标签的 SqlNode 实现类,继承至TrimSqlNode;

    • ForEachSqlNode:<foreach /> 标签的 SqlNode 实现类;

    • VarDeclSqlNode:<bind /> 标签的 SqlNode 实现类;

  • NodeHandler:
    在这里插入图片描述

      //在XMLScriptBuilder的构造方法中初始化了SQL中的节点处理器集合
      public XMLScriptBuilder(Configuration configuration, XNode context, Class<?> parameterType) {
      		super(configuration);
      		this.context = context;
      		this.parameterType = parameterType;
      		// 初始化动态SQL中的节点处理器集合
      		initNodeHandlerMap();
      	}
      
      	private void initNodeHandlerMap() {
      		nodeHandlerMap.put("trim", new TrimHandler());
      		nodeHandlerMap.put("where", new WhereHandler());
      		nodeHandlerMap.put("set", new SetHandler());
      		nodeHandlerMap.put("foreach", new ForEachHandler());
      		nodeHandlerMap.put("if", new IfHandler());
      		nodeHandlerMap.put("choose", new ChooseHandler());
      		nodeHandlerMap.put("when", new IfHandler());
      		nodeHandlerMap.put("otherwise", new OtherwiseHandler());
      		nodeHandlerMap.put("bind", new BindHandler());
      }
  1. BindHandler用于解析<bind /> 标签节点

    private class BindHandler implements NodeHandler {
    		public BindHandler() {
    			// Prevent Synthetic Access
    		}
    
    		@Override
    		public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {      
          		//获取name和value属性
    			final String name = nodeToHandle.getStringAttribute("name");
    			final String expression = nodeToHandle.getStringAttribute("value");
                //包装成简单的VarDeclSqlNode类
    			final VarDeclSqlNode node = new VarDeclSqlNode(name, expression);
    			targetContents.add(node);
    		}
    	}
    
    1. TrimHandler用于解析<trim />标签节点

      private class TrimHandler implements NodeHandler {
      		public TrimHandler() {
      			// Prevent Synthetic Access
      		}
      
      		@Override
      		public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
                  //trim标签下可包含where/set/if/when等标签,将之封装成MixedSqlNode
      			MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
                  // read prefix/preffixOverrides/suffix/suffixOverrides properties
      			String prefix = nodeToHandle.getStringAttribute("prefix");
      			String prefixOverrides = nodeToHandle.getStringAttribute("prefixOverrides");
      			String suffix = nodeToHandle.getStringAttribute("suffix");
      			String suffixOverrides = nodeToHandle.getStringAttribute("suffixOverrides");
                  // 委托TrimSqlNode处理SQL
      			TrimSqlNode trim = new TrimSqlNode(configuration, mixedSqlNode, prefix, prefixOverrides, suffix,
      					suffixOverrides);
      			targetContents.add(trim);
      		}
      	}
      
    2. WhereHandler用于解析<where />标签节点

      private class WhereHandler implements NodeHandler {
      		public WhereHandler() {
      			// Prevent Synthetic Access
      		}
      
      		@Override
      		public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      			MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
                  // 与TrimSqlNode相同,包装成简单的WhereSqlNode类
      			WhereSqlNode where = new WhereSqlNode(configuration, mixedSqlNode);
      			targetContents.add(where);
      		}
      	}
      
    3. SetHandler用于解析<set />标签节点

      private class SetHandler implements NodeHandler {
      		public SetHandler() {
      			// Prevent Synthetic Access
      		}
      
      		@Override
      		public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      			MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
                  // 与TrimSqlNode相同,包装成简单的SetSqlNode类
      			SetSqlNode set = new SetSqlNode(configuration, mixedSqlNode);
      			targetContents.add(set);
      		}
      	}
      
    4. ForEachHandler用于解析<foreach />标签节点

      private class ForEachHandler implements NodeHandler {
      		public ForEachHandler() {
      			// Prevent Synthetic Access
      		}
      
      		@Override
      		public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      			MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
            		// read collection/item/index/open/close/separator properties
      			String collection = nodeToHandle.getStringAttribute("collection");
      			String item = nodeToHandle.getStringAttribute("item");
      			String index = nodeToHandle.getStringAttribute("index");
      			String open = nodeToHandle.getStringAttribute("open");
      			String close = nodeToHandle.getStringAttribute("close");
      			String separator = nodeToHandle.getStringAttribute("separator");
                  // 独立的 SqlNode
      			ForEachSqlNode forEachSqlNode = new ForEachSqlNode(configuration, mixedSqlNode, collection, index, item,
      					open, close, separator);
      			targetContents.add(forEachSqlNode);
      		}
      	}
      
    5. IfHandler用于解析<if />标签节点

      private class IfHandler implements NodeHandler {
      		public IfHandler() {
      			// Prevent Synthetic Access
      		}
      
      		@Override
      		public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
      			MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
            		// read test properties
      			String test = nodeToHandle.getStringAttribute("test");
      			IfSqlNode ifSqlNode = new IfSqlNode(mixedSqlNode, test);
      			targetContents.add(ifSqlNode);
      		}
      	}
      
    6. OtherwiseHandler/ChooseHandler用于解析otherwise/choose/when节点,这三者一般搭配使用

      1. OtherwiseHandler

        private class OtherwiseHandler implements NodeHandler {
        		public OtherwiseHandler() {
        			// Prevent Synthetic Access
        		}
        
        		@Override
        		public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
        			MixedSqlNode mixedSqlNode = parseDynamicTags(nodeToHandle);
        			targetContents.add(mixedSqlNode);
        		}
        	}
        
      2. ChooseHandler,内含choose/when的解析

        private class ChooseHandler implements NodeHandler {
        		public ChooseHandler() {
        			// Prevent Synthetic Access
        		}
        
        		@Override
        		public void handleNode(XNode nodeToHandle, List<SqlNode> targetContents) {
        			List<SqlNode> whenSqlNodes = new ArrayList<>();
        			List<SqlNode> otherwiseSqlNodes = new ArrayList<>();
                    //解析choose...when..otherwise结构
        			handleWhenOtherwiseNodes(nodeToHandle, whenSqlNodes, otherwiseSqlNodes);
                    //检查otherwise标签是否只有一个,大于一个则报错
        			SqlNode defaultSqlNode = getDefaultSqlNode(otherwiseSqlNodes);
        			ChooseSqlNode chooseSqlNode = new ChooseSqlNode(whenSqlNodes, defaultSqlNode);
        			targetContents.add(chooseSqlNode);
        		}
            
        		// when标签使用IfHandler解析,otherwise标签使用OtherwiseHandler
        		private void handleWhenOtherwiseNodes(XNode chooseSqlNode, List<SqlNode> ifSqlNodes,
        				List<SqlNode> defaultSqlNodes) {
        			List<XNode> children = chooseSqlNode.getChildren();
        			for (XNode child : children) {
        				String nodeName = child.getNode().getNodeName();
        				NodeHandler handler = nodeHandlerMap.get(nodeName);
        				if (handler instanceof IfHandler) {
        					handler.handleNode(child, ifSqlNodes);
        				} else if (handler instanceof OtherwiseHandler) {
        					handler.handleNode(child, defaultSqlNodes);
        				}
        			}
        		}
        
        		private SqlNode getDefaultSqlNode(List<SqlNode> defaultSqlNodes) {
        			SqlNode defaultSqlNode = null;
        			if (defaultSqlNodes.size() == 1) {
        				defaultSqlNode = defaultSqlNodes.get(0);
        			} else if (defaultSqlNodes.size() > 1) {
        				throw new BuilderException("Too many default (otherwise) elements in choose statement.");
        			}
        			return defaultSqlNode;
        		}
        	}
        
      3. ChooseSqlNode:choose…when…otherwise结构类似于java的switch…case结构

        public class ChooseSqlNode implements SqlNode {
          private SqlNode defaultSqlNode;
          private List<SqlNode> ifSqlNodes;
         
          public ChooseSqlNode(List<SqlNode> ifSqlNodes, SqlNode defaultSqlNode) {
            this.ifSqlNodes = ifSqlNodes;
            this.defaultSqlNode = defaultSqlNode;
          }
         
          public boolean apply(DynamicContext context) {
            for (SqlNode sqlNode : ifSqlNodes) {
              if (sqlNode.apply(context)) {
                return true;
              }
            }
            if (defaultSqlNode != null) {
              defaultSqlNode.apply(context);
              return true;
            }
            return false;
          }
        

    通过上述的解析,如果包含子标签,比如<if />标签,会通过策略模式使用对应的sql标签处理器进行解析,调用IfHandler.handleNode()方法,进行递归调用一步一步解析<if />标签,最终封装为一个MixedSqlNode,再将MixedSqlNode封装到SqlSource中;

    回到之前的地方封装SqlSource后,接下来是将刚才解析到的各种信息通过builderAssistant(构建者助手)创建MappedStatement对象;

  2. 创建MappedStatement对象

    public MappedStatement addMappedStatement(
          String id,
          SqlSource sqlSource,
          StatementType statementType,
          SqlCommandType sqlCommandType,
          Integer fetchSize,
          Integer timeout,
          String parameterMap,
          Class<?> parameterType,
          String resultMap,
          Class<?> resultType,
          ResultSetType resultSetType,
          boolean flushCache,
          boolean useCache,
          boolean resultOrdered,
          KeyGenerator keyGenerator,
          String keyProperty,
          String keyColumn,
          String databaseId,
          LanguageDriver lang,
          String resultSets) {
    
        if (unresolvedCacheRef) {
          throw new IncompleteElementException("Cache-ref not yet resolved");
        }
    
        id = applyCurrentNamespace(id, false);
        boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    
        //利用构建者模式,去创建MappedStatement.Builder,用于创建MappedStatement对象
        MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
            .resource(resource)
            .fetchSize(fetchSize)
            .timeout(timeout)
            .statementType(statementType)
            .keyGenerator(keyGenerator)
            .keyProperty(keyProperty)
            .keyColumn(keyColumn)
            .databaseId(databaseId)
            .lang(lang)
            .resultOrdered(resultOrdered)
            .resultSets(resultSets)
            .resultMaps(getStatementResultMaps(resultMap, resultType, id))
            .resultSetType(resultSetType)
            .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
            .useCache(valueOrDefault(useCache, isSelect))
            .cache(currentCache);
    
        ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
        if (statementParameterMap != null) {
          statementBuilder.parameterMap(statementParameterMap);
        }
    
        // 通过MappedStatement.Builder,构建一个MappedStatement
        MappedStatement statement = statementBuilder.build();
        // 将MappedStatement对象存储到Configuration中的Map集合中,key为statement的id,value为MappedStatement对象
        configuration.addMappedStatement(statement);
        return statement;
      }
    

    创建MappedStatement对象后将MappedStatement对象存储到Configuration中的Map集合中,key为statement的id,value为MappedStatement对象。

    以上就是已经mapper映射文件全部解析完毕了,方法继续往后执行就是去解析注解方式的mapper接口,全部解析工作完成后创建了SqlSessionFactory并返回

    Mybatis的解析流程已经全部完成了,Mybatis的执行sql 的流程请听下回分解;

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值