Mybatis源码分析系列之配置文件加载(三)

Mybatis源码分析系列之配置文件加载(三)
也许文中的一些说明能解决你的困惑。也许有表述不清晰的地方。请大家多多指点。欢迎下方留言。本人准备系统的总结一下各种框架的一些源码。也希望大家能给我提出宝贵意见

我们今天继续上文 传送门:Mybatis源码分析系列之配置文件加载(二)

2.11 mapperElement

有很多说刚开始用的小朋友在开发中mappers扫描 配置然后 想要用注解跟xml混合开发但是配置的是resource报错。然后不得解!现在说明 如果你是使用注解开发 应该用包扫描或者class配置,如果是 纯xml则用包或者URL或Resource至于为什么。 在看完这篇文章之后你会豁然开朗的。
private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
    	// 遍历节点
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
        //如果是package标签
          String mapperPackage = child.getStringAttribute("name");
          //从制定包下 加载mepper接口 并根据mapper接口映射
          configuration.addMappers(mapperPackage);
        } else {
        //下面分别是根据URL resource class 方式进行mapper接口查找以及接口映射
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            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());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }
下面我们一次来分析这些源码。首先看到的是通过package标签如何进行mapper加载的以及接口映射
//第一步
  public void addMappers(String packageName) {
    mapperRegistry.addMappers(packageName);
  }
  //第二步
  public void addMappers(String packageName) {
    addMappers(packageName, Object.class);
  }
  //第三步
  public void addMappers(String packageName, Class<?> superType) {
  //通过反射工具类。找到包下所有的类
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
    //继续调用 addMapper方法
      addMapper(mapperClass);
    }
  }
//第四步
public <T> void addMapper(Class<T> type) {
//如果是接口
    if (type.isInterface()) {
    //hasMapper是 查找已知的knownMapper的Map 如果有就抛出解析异常 说明这个Mapper已经被注册了
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
      //1.注意一点这个是通过构建一个Map 来存放已经注册的Mapper
        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.
        
        /**我用这个区分Mybatis自己的注释。下面这个方法用于
	*是注解解析器 因为可以通过xml以及注解来进行
	*/
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        //解析注解中的信息
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

 public void parse() {
 // 返回这个类的对象的字符串表示形式
    String resource = type.toString()
    //判断是否已经加载过
    if (!configuration.isResourceLoaded(resource)) {
    // **这个方法 是比较重要的方法因为 这个是resource配置mapper  跟url 配置都走的方法** 
      loadXmlResource();
      //把解析的mapper加载 到  configuration中由于是包扫描  所以会加载 xml 以及mapper
     configuration.addLoadedResource(resource);
     //这个是辅助构建工具
      assistant.setCurrentNamespace(type.getName());
      //解析缓存
      parseCache();
      parseCacheRef();
      //获得mapper中的方法
      Method[] methods = type.getMethods();
      for (Method method : methods) {
        try {
          // issue #237
          //判断是否为桥接方法 
          if (!method.isBridge()) {
          //如果是不是桥接方法。。那么我们走下面的
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    parsePendingMethods();
  }

下面继续分析 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
    /**还是判断 例如 namespace:com.ly.mapper.UserMapper 在configuration中是否被加载过了。也就是是否存在*/
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
    //获得xml 路径
      String xmlResource = type.getName().replace('.', '/') + ".xml";
      InputStream inputStream = null;
      try {
      //加载XML为流
        inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
      } catch (IOException e) {
        // ignore, resource is not required
      }
      //如果流不为空
      if (inputStream != null) {
      //xmlMapper构建器 进行构建 我们跟如看一下
        XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
        xmlParser.parse();
      }
    }
  }

XmlMapperBuilder 没什么可说的 主要是看如何解析

parse()下面我们看下 parse()方法 重点重点重点 因为上面 4种解析 其实都走的这一个。。所以这个是最关键的一个点了

 public void parse() {
 // 会从已经解析过的 loadedResources 中去找这个是不是已经解析了。
 //我们第一次跟进来势必不会被解析
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }

下面才是重要的解析步骤 我们依次去分离 剖析

configurationElement方法
private void configurationElement(XNode context) {
//还是把XML中的NameSpace 这个节点 传入
    try {
    //获得namespace标签的属性
      String namespace = context.getStringAttribute("namespace");
      //如果为空 或者是 null 抛出以及构建异常。。所以说。namespace是必须的
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      //这个是一个mybatis的辅助构建器。。
      builderAssistant.setCurrentNamespace(namespace);
      //二级缓存的配置 我后续会讲解。目前不做说明。反正知道这个是干嘛的就好 在后续的缓存种会分析
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
      //parameterMap 已经废弃。。就别看了。没用了。。真的非要看。可以自己看看。我反正不看 O(∩_∩)O
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      //重点说明 这里面就分析 这个 以及 buildStatementFromContext
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      //解析sql 片段的。。
      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 方法处理 resultMap标签
 private void resultMapElements(List<XNode> list) throws Exception {
    for (XNode resultMapNode : list) {
      try {
      //把所有的resultMap都搂出来 你配置了多少ResultMap  这里面循环 遍历 调用resultMapElement
        resultMapElement(resultMapNode);
      } catch (IncompleteElementException e) {
        // ignore, it will be retried
      }
    }
  }
  //中间转折方法。。用于调用最下面ResultMapElement方法
 private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
    return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList());
  }

//核心方法
 private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
 //错误内容 这是利用instance实例 然后给activity赋值。。 例如 rocessing mapper_resultMap[BaseResultMap]
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    //获取id 
    String id = resultMapNode.getStringAttribute("id",
        resultMapNode.getValueBasedIdentifier());
     //获取类型 先找javatype 在去找resultType在找ofType 在找type的顺序 去获得
     //ofType是用来映射到list集合属性中pojo的类型。
    String type = resultMapNode.getStringAttribute("type",
        resultMapNode.getStringAttribute("ofType",
            resultMapNode.getStringAttribute("resultType",
                resultMapNode.getStringAttribute("javaType"))));
     //查找继承标签
    String extend = resultMapNode.getStringAttribute("extends");
    //获得全局配置。。开启后会自动设置嵌套查询中的属性 总开关 即使局部没有写属性 也会映射
    Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
   //这个在之前分析过。。就是通过名字 去找类型注册器 没有就 通过反射加载类
    Class<?> typeClass = resolveClass(type);
    Discriminator discriminator = null;
    List<ResultMapping> resultMappings = new ArrayList<ResultMapping>();
    resultMappings.addAll(additionalResultMappings);
    List<XNode> resultChildren = resultMapNode.getChildren();
    for (XNode resultChild : resultChildren) {
    //查找constructor 
      if ("constructor".equals(resultChild.getName())) {
      //下面我们会说明这个方法中的内容
        processConstructorElement(resultChild, typeClass, resultMappings);
      } else if ("discriminator".equals(resultChild.getName())) {
      //鉴别器
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
      } else {
      // 应该是其他标签 比如 resultMap 然后 Collection 标签 
        List<ResultFlag> flags = new ArrayList<ResultFlag>();
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);
        }
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
    //调用resolve  返回 ResultMap 对象
      return resultMapResolver.resolve();
    } catch (IncompleteElementException  e) {
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }
constructor 标签

这个是有参构造方法。Mybatis 在映射ResultMap时候默认会找无参构造方法。但是有的时候我们可能为实体类生成了有参的构造方法,并且也没有给该实体类生成无参的构造方法,这个时候如果不做特殊配置,resultMap在生成实体类的时候就会报错,因为它没有找到无参构造方法
所以constructor 的作用在于制定 有参构造方法。
里面属性

  1. idArg ID 参数;标记出作为 ID 的结果可以帮助提高整体性能
  2. arg 将被注入到构造方法的一个普通结果
processConstructorElement

这个只是一部分 我们还没有真正的分析最核心的 buildResultMappingFromContext代码。

private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
    List<XNode> argChildren = resultChild.getChildren();
    for (XNode argChild : argChildren) {
      List<ResultFlag> flags = new ArrayList<ResultFlag>();
      flags.add(ResultFlag.CONSTRUCTOR);
      if ("idArg".equals(argChild.getName())) {
        flags.add(ResultFlag.ID);
      }
      resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
    }
  }
//以上代码并不复杂 就是加载节点而已。
buildResultMappingFromContext方法

说明一下:
其实这个方法基本也就是找节点把所有的属性名称都写出来。比如列名|java类型|jdbc类型|
不为空的列才创建子类什么的|通过javaType或者jdbcType找到对应的类。

private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, List<ResultFlag> flags) throws Exception {
    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.<ResultMapping> emptyList()));
    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);
    @SuppressWarnings("unchecked")
    Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resultSet, foreignColumn, lazy);
  }
buildResultMapping方法

这个没什么可讲的。。就是通过建造者模式的设计模式返回ResultMapping对象

 public ResultMapping buildResultMapping(
      Class<?> resultType,
      String property,
      String column,
      Class<?> javaType,
      JdbcType jdbcType,
      String nestedSelect,
      String nestedResultMap,
      String notNullColumn,
      String columnPrefix,
      Class<? extends TypeHandler<?>> typeHandler,
      List<ResultFlag> flags,
      String resultSet,
      String foreignColumn,
      boolean lazy) {
    Class<?> javaTypeClass = resolveResultJavaType(resultType, property, javaType);
    TypeHandler<?> typeHandlerInstance = resolveTypeHandler(javaTypeClass, typeHandler);
    List<ResultMapping> composites = parseCompositeColumnName(column);
    return new ResultMapping.Builder(configuration, property, column, javaTypeClass)
        .jdbcType(jdbcType)
        .nestedQueryId(applyCurrentNamespace(nestedSelect, true))
        .nestedResultMapId(applyCurrentNamespace(nestedResultMap, true))
        .resultSet(resultSet)
        .typeHandler(typeHandlerInstance)
        .flags(flags == null ? new ArrayList<ResultFlag>() : flags)
        .composites(composites)
        .notNullColumns(parseMultipleColumnNames(notNullColumn))
        .columnPrefix(columnPrefix)
        .foreignColumn(foreignColumn)
        .lazy(lazy)
        .build();
  }
discriminator 标签

discriminator既不是一对多也不是一对一,这个我们称之为鉴别器级联,使用它我们可以在不同的条件下执行不同的查询匹配不同的实体类
里面的属性为:

  1. case – 基于某些值的结果映射
    嵌套结果映射 – 一个 case 也是一个映射它本身的结果,因此可以包含很多相 同的元素,或者它可以参照一个外部的 resultMap。
    我们可以通过鉴别器 来获得 我们要返回的对应的类
processDiscriminatorElement方法
private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType, List<ResultMapping> resultMappings) throws Exception {
	//解析column标签
    String column = context.getStringAttribute("column");
    //解析javaType标签
    String javaType = context.getStringAttribute("javaType");
    //解析jdbcType标签
    String jdbcType = context.getStringAttribute("jdbcType");
    //解析typpeHandler标签
    String typeHandler = context.getStringAttribute("typeHandler");
    
    Class<?> javaTypeClass = resolveClass(javaType);
    @SuppressWarnings("unchecked")
    Class<? extends TypeHandler<?>> typeHandlerClass = (Class<? extends TypeHandler<?>>) resolveClass(typeHandler);
    JdbcType jdbcTypeEnum = resolveJdbcType(jdbcType);
    Map<String, String> discriminatorMap = new HashMap<String, String>();
    for (XNode caseChild : context.getChildren()) {
    //遍历节点 拿到 value 属性。以及resultMap 然后放到鉴别器的Map中
      String value = caseChild.getStringAttribute("value");
      String resultMap = caseChild.getStringAttribute("resultMap", processNestedResultMappings(caseChild, resultMappings));
      discriminatorMap.put(value, resultMap);
    }
    return builderAssistant.buildDiscriminator(resultType, column, javaTypeClass, jdbcTypeEnum, typeHandlerClass, discriminatorMap);
  }
buildDiscriminator 方法

这个方法也是通过建造者模式。返回对应的鉴别器类。

 public Discriminator buildDiscriminator(
      Class<?> resultType,
      String column,
      Class<?> javaType,
      JdbcType jdbcType,
      Class<? extends TypeHandler<?>> typeHandler,
      Map<String, String> discriminatorMap) {
    ResultMapping resultMapping = buildResultMapping(
        resultType,
        null,
        column,
        javaType,
        jdbcType,
        null,
        null,
        null,
        null,
        typeHandler,
        new ArrayList<ResultFlag>(),
        null,
        null,
        false);
    Map<String, String> namespaceDiscriminatorMap = new HashMap<String, String>();
    for (Map.Entry<String, String> e : discriminatorMap.entrySet()) {
      String resultMap = e.getValue();
      resultMap = applyCurrentNamespace(resultMap, true);
      namespaceDiscriminatorMap.put(e.getKey(), resultMap);
    }
    return new Discriminator.Builder(configuration, resultMapping, namespaceDiscriminatorMap).build();
  }

resultMapResolver.resolve()

public ResultMap resolve() {
    return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
  }

public ResultMap addResultMap(
      String id,
      Class<?> type,
      String extend,
      Discriminator discriminator,
      List<ResultMapping> resultMappings,
      Boolean autoMapping) {
    id = applyCurrentNamespace(id, false);
    extend = applyCurrentNamespace(extend, true);
	//判断是否有继承关系
    if (extend != null) {
    //判断是否已经解析过继承的resultMap
      if (!configuration.hasResultMap(extend)) {
        throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
      }
      //有的话 获得继承的resulMap
      ResultMap resultMap = configuration.getResultMap(extend);
      //获得已经继承的ResultMap节点
      List<ResultMapping> extendedResultMappings = new ArrayList<ResultMapping>(resultMap.getResultMappings());
      //从继承的ResultMapping中删除 儿子的节点
      extendedResultMappings.removeAll(resultMappings);
      // Remove parent constructor if this resultMap declares a constructor.
      boolean declaresConstructor = false;
      //遍历儿子节点
      for (ResultMapping resultMapping : resultMappings) {
      //如果含有constructor 节点的标记
        if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
          declaresConstructor = true;
          break;
        }
      }
      //跳出循环
      if (declaresConstructor) {
      //遍历父ResultMap 节点
        Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
        while (extendedResultMappingsIter.hasNext()) {
        //如果父节点中 也有 就删除
          if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
            extendedResultMappingsIter.remove();
          }
        }
      }
      //然后加载所有的父 进ResultMap
      resultMappings.addAll(extendedResultMappings);
    }
    ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping)
        .discriminator(discriminator)
        .build();
        //把resultMap加载到configuration中
    configuration.addResultMap(resultMap);
    return resultMap;
  }

sqlElement

用于解析sql 片段

private void sqlElement(List<XNode> list) throws Exception {
//首先 先判断是不是配置了多sql方言。。比如 mysql oracle 等等
    if (configuration.getDatabaseId() != null) {
    // 如果有的话。。传入
      sqlElement(list, configuration.getDatabaseId());
    }
    //没有走这个。。都是一个方法 传递参数不一样而已
    sqlElement(list, null);
  }

private void sqlElement(List<XNode> list, String requiredDatabaseId) throws Exception {
    for (XNode context : list) {
    // 下面俩个 一个是databasedid 一个id 分别获得属性
      String databaseId = context.getStringAttribute("databaseId");
      String id = context.getStringAttribute("id");
      //调用下面的applyCurrentNamespace 方法。获得名称
      id = builderAssistant.applyCurrentNamespace(id, false);
      //判断数据库方言 是否匹配 下面有详细说明
      if (databaseIdMatchesCurrent(id, databaseId, requiredDatabaseId)) {
        //qlFragment存储于Configuration内部。
        sqlFragments.put(id, context);
      }
    }
  }
  
 public String applyCurrentNamespace(String base, boolean isReference) {
    if (base == null) {
      return null;
    }
//上面代码isReference 是false 所以走else方法
    if (isReference) {
      // is it qualified with any namespace yet?
      if (base.contains(".")) {
        return base;
      }
    } else {
      // is it qualified with this namespace yet?
      //判断字符串是不是从currentNamespace  +“.”开始 如果是直接返回
      if (base.startsWith(currentNamespace + ".")) {
        return base;
      }
      //如果包含点 抛出异常。。等于就是没有 mapper方法的路径
      if (base.contains(".")) {
        throw new BuilderException("Dots are not allowed in element names, please remove it from " + base);
      }
    }
    //返回 一个 例如 com.ly.mapper.SysRolePrivilegeMapper.BaseResultMap 
    return currentNamespace + "." + base;
  }
  //MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。 MyBatis 会加载不带 databaseId 属性和带有匹配当前数据库 databaseId 属性的所有语句。 如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则后者会被舍弃。 为支持多厂商特性只要像下面这样在 mybatis-config.xml 文件中加入 databaseIdProvider 即可:

private boolean databaseIdMatchesCurrent(String id, String databaseId, String requiredDatabaseId) {
   //从configuration去除配置的 databasedid
   //判断传入的databasedid 与这当前数据库databaseId 是否一致
     if (requiredDatabaseId != null) {
      if (!requiredDatabaseId.equals(databaseId)) {
        return false;
      }
    } else {
    //如果没有配置 但是下面写了。。那么这个废弃
      if (databaseId != null) {
        return false;
      }
      // skip this fragment if there is a previous one with a not null databaseId
      if (this.sqlFragments.containsKey(id)) {
      //如果没有配置 那么 相同的俩句话。。肯定要没有配置的。。有配置的舍弃
        XNode context = this.sqlFragments.get(id);
        if (context.getStringAttribute("databaseId") != null) {
          return false;
        }
      }
    }
    return true;
  }

重点重点 buildStatementFromContext

用于加载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);
      }
    }
  }


parseStatementNode

解析语句节点 主要是那些sql语句 映射成Statement对象 然后加载到configuration中

 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 and remove them.
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
    // 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))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }

    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

重点重点 langDriver.createSqlSource(configuration, context, parameterTypeClass)

这个是LanguageDriver接口。。然后对应的实现类有 XMLLanguageDriver
SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
createSqlSource 创建sqlSource
public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
    //创建XmlScriptBuilder
    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
    //解析成SqlSource
    return builder.parseScriptNode();
  }
parseScriptNode 方法解析节点
public SqlSource parseScriptNode() {
	//解析出最小的sql节点的集合
    MixedSqlNode rootSqlNode = parseDynamicTags(context);
    SqlSource sqlSource = null;
   //判断是否为动态sql
    if (isDynamic) {
    //1.是动态sql
      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
    } else {
    //2.不是动态sql
      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
    }
    return sqlSource;
  }
  1. parseDynamicTags 方法
protected MixedSqlNode parseDynamicTags(XNode node) {
  // 创建一个 sqlNode节点的contents 集合
    List<SqlNode> contents = new ArrayList<SqlNode>();
    //获得NodeList //是
    NodeList children = node.getNode().getChildNodes();
    //其实就是看节点中有多少。。然后判断长度。遍历 也就是把所有的sql 中的节点都搂出来。去循环。比如
    		/**select * from user where 
		*<if test="id!=null and id!=''">
		*id=#{id}
		*</if>
		*/
    // 这个节点是多少个呢。。也就是length。。我们这个是3个。。分别是#text  | if |#text 
    for (int i = 0; i < children.getLength(); i++) {
      XNode child = node.newXNode(children.item(i));
      //这个是俩个 一个是cdata 一个是普通文本。。我们这个 第一次循环进来的时候肯定去走
      if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
      //获得sql语句
        String data = child.getStringBody("");
        TextSqlNode textSqlNode = new TextSqlNode(data);
        //判断是动态sql
        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();
        //节点判断是if where ...等
        NodeHandler handler = nodeHandlerMap.get(nodeName);
        //如果为空证明这个节点 并没有 是错误的节点。。
        if (handler == null) {
          throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
        }
        //加入到目标节点contents中。动态sql 
        handler.handleNode(child, contents);
        isDynamic = true;
      }
    }
    return new MixedSqlNode(contents);
  }

说明:我用几句话简单说明下他的运行原理

  • 第一步 通过 parseDynamicTags 方法解析出最小的sql节点。比如 刚才上面写的。。一共有三个 并且是动态sql
  • 第二步:判断是否为动态sql. 是通过isDynamic 来实现的。 在parseDynamicTags 判断节点为1 也就是标签节点。
  • 第三部。返回不同的sqlSource
    上面的方法很好理解 handleNode 方法是处理动态sql 比如 if标签吧
    就把if标签 处理过的IfSqlNode 加入到contents中。。因为他们都是sqlNode接口的实现类。
  builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);

最终由MapperBuilderAssistant完成MappedStatement对象的封装,并且将MappedStatement对象放入Configuration对象的mappedStatements容器中

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值