Mybatis源码解析之初始化配置文件封装为Configuration源码详解

      接着上文太长的那个文章开始分析http://blog.csdn.net/ccityzh/article/details/71517490

     其实初始化的部分没有什么可以分析的,就是解析Xml文件,不会解析的可以查一下,现在常用的都是JDOM,DOM4J,不过这里不是用的这两种。分析的过程中有初始化某些关键的部分会单独拿出来分析一下。

      注:本文都是根据上一篇中实例为入口的,看到的非mybatis框架代码都是上节例子中的代码

     .首先根据配置文件获取SqlSessionFactory,这个过程也是mybatis初始化的过程,读取配置文件,封装配置文件的过程。

         sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

       根据类及方法的名字就可以看出使用构建模式构建sqlSessionFactory这个对象,一般复杂的对象都是用构建模式来创建对象。

(1)SqlSessionFactoryBuilder.java

 

//首先将配置文件以输入流的方式载入,接着使用一个重载的方法跳转到公共的build方法  
public SqlSessionFactory build(InputStream inputStream) {  
  return build(inputStream, null, null);  
}  
//使用XMLConfigBulder来封装输入流,最终会将输入流转化成一个解析XML大家所熟悉的Document对象  
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {  
  try {  
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);          (1)
    return build(parser.parse());  
  } catch (Exception e) {  
    throw ExceptionFactory.wrapException("Error building SqlSession.", e);  
  } finally {  
    ErrorContext.instance().reset();  
    try {  
      inputStream.close();  
    } catch (IOException e) {  
      // Intentionally ignore. Prefer previous error.  
    }  
  }  
}  

       没什么难以理解的地方,关键代码也就那try中的两句,其他都是安全措施,下面进入到XMLConfigBuilder类中。

(2)XMLConfigBuilder.java

 

//没有什么直接利用另一个构造函数,转移到了XPathParser中了,可以了解一下JDK的XPath,反正我是不会
public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }
//这个类中看到了我们解析XML过程中亲切的document对象了
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
    commonConstructor(validation, variables, entityResolver);
    this.document = createDocument(new InputSource(inputStream));
  }

 (3)  XMLConfigBuilder.java      

         接着会看到更有令人欣喜的东西,慢慢的就会走到咱们mybatis_config文件中各个节点的解析,让你看到熟悉的内容,不要被这些没听过的东西吓到,只是一个层层封

装与转化的过程。下面转化为了document之后,也就这个转化的流程就结束了,接着就返回到了上面(1)出所标记的地方。目前的情况是:XMLConfigBuilder 中包含着XPathParser,XPathParser中包含着document对象。包含着document对象的parser对象在(1)处调用parse方法 。 

//惊不惊喜,这个类里面包含着配置文件中的根节点/configuration,同时也看到了我们最终将配置文件封装在的Configuration类中
public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
//这个就是解析配置文件的核心方法了,每个节点都拿出来单独解析,其中有很多框架运行起来的初始化操作,例如sql语句的封装了,拦截器的初始化了等
//理解这个方法还是比较关键的了
private void parseConfiguration(XNode root) {
    try {
      propertiesElement(root.evalNode("properties")); //issue #117 read properties first
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      settingsElement(root.evalNode("settings"));
      environmentsElement(root.evalNode("environments")); // read it after objectFactory and objectWrapperFactory issue #631
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

 

   (4)  SqlSessionFactoryBuilder.java    

         先把整个流程看完,然后再单独取几个上面关键的节点解析分析一下。接着将各个配置节点解析到了Configuration类,然后(1)处调用 build(parser.parse())方法,也就是构建模式的第二步,利用Configuration构建出复杂的SqlSessionFactory对象,第一步是封装为Configuration的过程。

 

public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

          这样就取到了打开Mybatis大门的钥匙SqlSessionFactory,DefaultSqlSessionFactory是SqlSessionFactory的默认实现类用它就可以随便创建出Mybatis的session,session也就相当于jdbc的connection,拿到session以后就是执行sql的过程了。

(5)下面大体上说下配置文件各个节点的作用,主要分析一下mapper这个节点,这个也是文中最关键的;plugin这个节点,以后会单独写一篇插件(拦截器)的执行流程,毕竟大多数时候分页都是直接使用Mybatis的插件的,这里就不说了;然后typeAlias节点可以看下,别名有时候会用到,也可以看到mybatis中已经有的jdk某些类的默认别名;其他节点感兴趣的可以自己看下。下面按源代码中的解析顺序大体分析一下。

           1.properties   这个到处都在用,配置各种参数,没什么可说的,例如mybatis_config中的    <property name="driver" value="com.mysql.jdbc.Driver" />(略有不同)

           2.typeAliases,别名,这个就是有时候对象分布在各个包中,全路径太长,所以利用全路径注册一个别名,以后mybatis就认识这个别名了,在配置文件中使用起来就方便多了。我的实例中只有一个包,没什么可注册的,直到这个意思就好了,下面看下mybatis中已经注册的别名。

 

public TypeAliasRegistry() {
    registerAlias("string", String.class);

    registerAlias("byte", Byte.class);
    registerAlias("long", Long.class);
    registerAlias("short", Short.class);
    registerAlias("int", Integer.class);
    registerAlias("integer", Integer.class);
    registerAlias("double", Double.class);
    registerAlias("float", Float.class);
    registerAlias("boolean", Boolean.class);

    registerAlias("byte[]", Byte[].class);
    registerAlias("long[]", Long[].class);
    registerAlias("short[]", Short[].class);
    registerAlias("int[]", Integer[].class);
    registerAlias("integer[]", Integer[].class);
    registerAlias("double[]", Double[].class);
    registerAlias("float[]", Float[].class);
    registerAlias("boolean[]", Boolean[].class);

    registerAlias("_byte", byte.class);
    registerAlias("_long", long.class);
    registerAlias("_short", short.class);
    registerAlias("_int", int.class);
    registerAlias("_integer", int.class);
    registerAlias("_double", double.class);
    registerAlias("_float", float.class);
    registerAlias("_boolean", boolean.class);

    registerAlias("_byte[]", byte[].class);
    registerAlias("_long[]", long[].class);
    registerAlias("_short[]", short[].class);
    registerAlias("_int[]", int[].class);
    registerAlias("_integer[]", int[].class);
    registerAlias("_double[]", double[].class);
    registerAlias("_float[]", float[].class);
    registerAlias("_boolean[]", boolean[].class);

    registerAlias("date", Date.class);
    registerAlias("decimal", BigDecimal.class);
    registerAlias("bigdecimal", BigDecimal.class);
    registerAlias("biginteger", BigInteger.class);
    registerAlias("object", Object.class);

    registerAlias("date[]", Date[].class);
    registerAlias("decimal[]", BigDecimal[].class);
    registerAlias("bigdecimal[]", BigDecimal[].class);
    registerAlias("biginteger[]", BigInteger[].class);
    registerAlias("object[]", Object[].class);

    registerAlias("map", Map.class);
    registerAlias("hashmap", HashMap.class);
    registerAlias("list", List.class);
    registerAlias("arraylist", ArrayList.class);
    registerAlias("collection", Collection.class);
    registerAlias("iterator", Iterator.class);

    registerAlias("ResultSet", ResultSet.class);
  }

        没什么可分析的,以后在mapper配置文件中可以方便的用,比如<select id="selectBall" parameterType="string" resultType="map"> ,本来此处的resultType需要写Map全名java.util.Map,但是使用别名只需要小写的map即可。jdk的lang包下的类其实没什么方便的,写String和string差不多,都是可以的。

 

         3.setting,设置一些全局的功能,像缓存了,日志控制了等比较关键,具有全局行,但好像一般也不太用

 

 private void settingsElement(XNode context) throws Exception {
    if (context != null) {
      Properties props = context.getChildrenAsProperties();
      // Check that all settings are known to the configuration class
      MetaClass metaConfig = MetaClass.forClass(Configuration.class);
      for (Object key : props.keySet()) {
        if (!metaConfig.hasSetter(String.valueOf(key))) {
          throw new BuilderException("The setting " + key + " is not known.  Make sure you spelled it correctly (case sensitive).");
        }
      }
      configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
      configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
      configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
      configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
      configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), true));
      configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
      configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
      configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
      configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
      configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
      configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
      configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
      configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
      configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
      configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
      configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
      configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
      configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
      configuration.setLogPrefix(props.getProperty("logPrefix"));
      configuration.setLogImpl(resolveClass(props.getProperty("logImpl")));
      configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
    }
  }

              4. enviroments,配置文件中的功能,一目了然,

 

              5. typeHandler,这个用的比较多,一般也不会自己扩展,有兴趣的可以研究下自己扩展,自定义typeHandler。大多数时候使用比较简单,只是mapper中javaType和jdbcType的一个类型映射关系的支持。

             6.mapper 接下来分析这个最关键的节点

-----------------------------------------------------------------------感觉排版比阅读代码都麻烦,真是好懒啊---------------------------------------------------------------------------------

mapperElement(root.evalNode("mappers"));

        这句可以说是整个初始化最关键的一句,过程也是最复杂的,包括mapper文件中单个节点(select|delete|update|insert)的抽象过程,dao层接口的注册等,复杂又复杂,下面一层一层揭开面纱,尽量详细点讲述。

 

/** 
 *      <mappers>
		<mapper resource="ball.xml"/>
	</mappers>
 * 
 */
private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
    	//这个分支我一般走不到,还没用过mapper下的package属性
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          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);
            //显然和mybatis_config的初始化解析一样,也是这么个过程,输入流,抽象document
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            //关键又是这一句parse
            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.");
          }
        }
      }
    }
  }

看起来也没什么,又是一个xml文件的解析。

 

接着看mapperParser.parse()方法:

 

public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
  }

看起来流程也比较清晰,如果这个xml资源没有被载入过,解析这个xml的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"));
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
    }
  }

            这个方法里全是熟悉的面孔,说一下常用的,不常用的就不说了,有兴趣的可以继续跟一下。

 

            namespace,必须有,这里也可以看得到,不然报异常,也可以想的到,没有namespace怎么知道sql对应的dao方法呢。

           cache相关还没有研究,等后面单独研究下mybatis的一级二级缓存。

           resultMap,对应实体类和数据库字段的一个关系准则

          sql好像是个sql的缓存一样的,此处定义了,后面可以用include直接使用,

         select|insert|update|delete又是最关键的过程。

1.首先看resultMap的处理过程,这里面使用到了经典的构建者模式构建对象

 

//一个xml文件可以定义多个resultMap
private void resultMapElements(List<XNode> list) throws Exception {
    for (XNode resultMapNode : list) {
      try {
    	//处理单个resultMap
        resultMapElement(resultMapNode);
      } catch (IncompleteElementException e) {
        // ignore, it will be retried
      }
    }
  }
//什么也没干
private ResultMap resultMapElement(XNode resultMapNode) throws Exception {
    return resultMapElement(resultMapNode, Collections.<ResultMapping> emptyList());
  }
 //部分熟悉的内容,一般只用的着id,唯一标记这个resultMap;type 实体类,resutlMap的内容就是这个实体类和数据库表字段的一个映射关系
  private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings) throws Exception {
    ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());
    String id = resultMapNode.getStringAttribute("id",
        resultMapNode.getValueBasedIdentifier());
    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) {
      if ("constructor".equals(resultChild.getName())) {
        processConstructorElement(resultChild, typeClass, resultMappings);
      } else if ("discriminator".equals(resultChild.getName())) {
        discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
      } else {
        ArrayList<ResultFlag> flags = new ArrayList<ResultFlag>();
        if ("id".equals(resultChild.getName())) {
          flags.add(ResultFlag.ID);
        }
        //单条映射的最终处理方法,每条映射为一个单独的resultMapping
        resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
      }
    }
    ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);
    try {
      return resultMapResolver.resolve();
    } catch (IncompleteElementException  e) {
      configuration.addIncompleteResultMap(resultMapResolver);
      throw e;
    }
  }

看下resultMappings add方法里的buildResultMappingFromContext(resultChild, typeClass, flags)方法:

 

 

//这个方法就是把每条的映射属性一个个拿出来,最终注册成为一个resultMapping
  private ResultMapping buildResultMappingFromContext(XNode context, Class<?> resultType, ArrayList<ResultFlag> flags) throws Exception {
	    String 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 resulSet = 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);
	    //这里是一个非常典型的构造者模式,不太清楚的可以研究下构造者模式应用在对象构建的优势(effective java说的挺清晰)
	    return builderAssistant.buildResultMapping(resultType, property, column, javaTypeClass, jdbcTypeEnum, nestedSelect, nestedResultMap, notNullColumn, columnPrefix, typeHandlerClass, flags, resulSet, foreignColumn, lazy);
	  }
  //构建者模式的全貌, 值得借鉴学习,当一个对象比较复杂的时候,可以使用这种内部类构建对象。
  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);
	    if (composites.size() > 0) column = null;
	    ResultMapping.Builder builder = new ResultMapping.Builder(configuration, property, column, javaTypeClass);
	    builder.jdbcType(jdbcType);
	    builder.nestedQueryId(applyCurrentNamespace(nestedSelect, true));
	    builder.nestedResultMapId(applyCurrentNamespace(nestedResultMap, true));
	    builder.resultSet(resultSet);
	    builder.typeHandler(typeHandlerInstance);
	    builder.flags(flags == null ? new ArrayList<ResultFlag>() : flags);
	    builder.composites(composites);
	    builder.notNullColumns(parseMultipleColumnNames(notNullColumn));
	    builder.columnPrefix(columnPrefix);
	    builder.foreignColumn(foreignColumn);
	    builder.lazy(lazy);
	    return builder.build();
	  }

           当每一条映射关系构建成一个resultMapping,并且都add到resultMappings之后,做了统一处理,最终肯定是要把这些内容都封装在configuration类的。resultMapElement方法的下面这一步就是做这件事的:

 

         ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);

 

//没什么说的
public ResultMapResolver(MapperBuilderAssistant assistant, String id, Class<?> type, String extend, Discriminator discriminator, List<ResultMapping> resultMappings, Boolean autoMapping) {
    this.assistant = assistant;
    this.id = id;
    this.type = type;
    this.extend = extend;
    this.discriminator = discriminator;
    this.resultMappings = resultMappings;
    this.autoMapping = autoMapping;
  }

  public ResultMap resolve() {
    return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
  }
  //看到没有又是一个构建者模式,细节就不解释了,看到最终放到了configuration类中了
  public ResultMap addResultMap(
	      String id,
	      Class<?> type,
	      String extend,
	      Discriminator discriminator,
	      List<ResultMapping> resultMappings,
	      Boolean autoMapping) {
	    id = applyCurrentNamespace(id, false);
	    extend = applyCurrentNamespace(extend, true);

	    ResultMap.Builder resultMapBuilder = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping);
	    if (extend != null) {
	      if (!configuration.hasResultMap(extend)) {
	        throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");
	      }
	      ResultMap resultMap = configuration.getResultMap(extend);
	      List<ResultMapping> extendedResultMappings = new ArrayList<ResultMapping>(resultMap.getResultMappings());
	      extendedResultMappings.removeAll(resultMappings);
	      // Remove parent constructor if this resultMap declares a constructor.
	      boolean declaresConstructor = false;
	      for (ResultMapping resultMapping : resultMappings) {
	        if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
	          declaresConstructor = true;
	          break;
	        }
	      }
	      if (declaresConstructor) {
	        Iterator<ResultMapping> extendedResultMappingsIter = extendedResultMappings.iterator();
	        while (extendedResultMappingsIter.hasNext()) {
	          if (extendedResultMappingsIter.next().getFlags().contains(ResultFlag.CONSTRUCTOR)) {
	            extendedResultMappingsIter.remove();
	          }
	        }
	      }
	      resultMappings.addAll(extendedResultMappings);
	    }
	    resultMapBuilder.discriminator(discriminator);
	    ResultMap resultMap = resultMapBuilder.build();
	    configuration.addResultMap(resultMap);
	    return resultMap;
	  }

          到此为止,resultMap放到了configuraion类中,resultMap的初始化也就结束了。sql标签就不讲了,相对比较简单,下面看更加关键也更加复杂的select|insert|update|delete。

 

      2.其次,select|insert|update|delete标签的解析

---------------------------------------------------------------------------排版第二次好麻烦啊,都过了凌晨0点了,明天继续----------------------------------------------------------------------------

 这句开始解析:buildStatementFromContext(context.evalNodes("select|insert|update|delete"));其中mapper.xml文件中的每个sql节点都会解析一次,其实都会解析成为一个MappedStatement,当让最终又会将这个MappedStatement存储在Configuration中。

 

//简单判断,什么也没做
	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) {
	    //这里看到,对一个mapper文件的每个增删改查都执行一次
		  for (XNode context : list) {
	      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
	      try {
	    	  ///关键部分
	        statementParser.parseStatementNode();
	      } catch (IncompleteElementException e) {
	        configuration.addIncompleteStatement(statementParser);
	      }
	    }
	  }

       接着继续看parseStatementNode()方法,这个方法就是最关键的最后一步了,最终会构建出一个MappedStatement,这里包含单个sql语句节点里所需要的所有东西,这个方法有点复杂,里面套了很多抽象流程。

 

 

 //只注释一些必须用得着的关键字段
	public void parseStatementNode() {
		//取到sql语句的id,也就是我们dao层方法的方法名
	    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");
	    //关键字段,dao方法中传入的参数,最终会封装在ParameterMapping
	    String parameterType = context.getStringAttribute("parameterType");
	   //根据上面的路径,找到这个类的.class文件,框架中都是用反射做一些事情,这里会经常注册为别名
	    Class<?> parameterTypeClass = resolveClass(parameterType);
	    //这里就是前面解析的resultMap,会在这里用到,从数据库查出来数据以后,和实体类的映射关系,
	    //或者相反方向,插入数据库映射关系
	    String resultMap = context.getStringAttribute("resultMap");
	    //和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;
	    }
        //构建者模式构建mappedstatement类,后面分析
	    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
	        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
	        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
	        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
	  }

 

   一些在使用中必须用到的字段都做了注释分析,几个关键的点单独分析。SqlSource这个类的生成,里面包含了sql语句的具体内容,configutation ,parameterMapping等非常关键的信息。下面跟踪SqlSource类的创建过程。

 

createSqlSource(configuration, context, parameterTypeClass);

可以看到这个函数的三个参数,每一个都是重量级的,第一个不用说了,有你需要的一切, 第二个context,代表这条增删改查节点的所有信息,第三个是补充这条增删改查节点中的未知参数的空的参数。

 

	//将三个重量级参数注入到XMLScriptBuilder类中,辅助后期的sqlsource生成
	public SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType) {
	    XMLScriptBuilder builder = new XMLScriptBuilder(configuration, script, parameterType);
	    //每次都是先用关键参数生成一个builder类,然后用这个类构建想要的对象
	    return builder.parseScriptNode();
	  }
	//这个跟踪起来有点乱
	public SqlSource parseScriptNode() {
		//首先用原始的增删改查节点封装为一个SqlNode类,必须具体分析下这个封装过程才知道作什么,见下面(比较关键,先看下)
	    List<SqlNode> contents = parseDynamicTags(context);
	    //返回一个封装原始sql语句的StaticTextSqlNode的list,接着对list转存了下
	    MixedSqlNode rootSqlNode = new MixedSqlNode(contents);
	    SqlSource sqlSource = null;
	    if (isDynamic) {
	    //大多数还是走这个分支,这个类里有一个函数getBoundSql,在执行dao接口函数的时候
	    //会调用getBoundSql,生成boundSql类,组装sql等,后面分析执行流程的时候分析
	      sqlSource = new DynamicSqlSource(configuration, rootSqlNode);
	    } else {
	      //生成sqlSource的关键流程,参数原始sql语句,占位参数类型,具体看下面这个构造函数分析,这种只是简单的sql语句,没有其他
	     //where set if等子节点
	      sqlSource = new RawSqlSource(configuration, rootSqlNode, parameterType);
	    }
	    return sqlSource;
	  }
	//封装为SqlNode的过程
	private List<SqlNode> parseDynamicTags(XNode node) {
	    List<SqlNode> contents = new ArrayList<SqlNode>();
	    //xml节点的处理,具体就是将各条原始sql语句分别处理成类的形式,然后随变取哪个信息
	    NodeList children = node.getNode().getChildNodes();
	    for (int i = 0; i < children.getLength(); i++) {
	      //取出其中一条sql包装成类的节点
	      XNode child = node.newXNode(children.item(i));
	      if (child.getNode().getNodeType() == Node.CDATA_SECTION_NODE || child.getNode().getNodeType() == Node.TEXT_NODE) {
	          //data就是从节点中取到的具体sql语句,#{}这种表示方式还没换成jdbc的?占位符
	    	  String data = child.getStringBody("");
	    	  //对sql做了一次封装
	        TextSqlNode textSqlNode = new TextSqlNode(data);
	        //里面有对sql做判断,分析什么情况的sql是动态的,大多是走else分支
	        if (textSqlNode.isDynamic()) {
	          contents.add(textSqlNode);
	          isDynamic = true;
	        } else {
	         //将sql抽象为StaticTextSqlNode添加到contents中,最终返回。
	          contents.add(new StaticTextSqlNode(data));
	        }
	    //mapper中的where,set,if等字节点走这个分支,最终也会将子节点中的内容存储在contents中
	      } else if (child.getNode().getNodeType() == Node.ELEMENT_NODE) { // issue #628
	        String nodeName = child.getNode().getNodeName();
	        NodeHandler handler = nodeHandlers.get(nodeName);
	        if (handler == null) {
	          throw new BuilderException("Unknown element <" + nodeName + "> in SQL statement.");
	        }
	        //这里就是拿着子节点where set if和被存储的list,执行handleNode方法,里面
	        //有具体的分支,接口方法,会根据传入的节点选择具体哪个handler,解析后也是存到了contents里
	        handler.handleNode(child, contents);
	        isDynamic = true;
	      }
	    }
	    return contents;
	  }


因为本文实例就是一个简单的sql,所以执行下面的RawSqlSource,继续看关键流程:RawSqlSource的构造流程,这个处理结果会将原始sql的#{}替换为jdbc的占位符?,把关键的参数信息存储起来。

 

因为这个分支sql比较简单,在初始化的时候基本就把工作做好了,

上面一个动态分支,比较复杂,此处只是将该有的参数注入到类中,等用到的时候在执行相应的逻辑

 

public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) {
    this(configuration, getSql(configuration, rootSqlNode), parameterType);
  }
private static String getSql(Configuration configuration, SqlNode rootSqlNode) {
    DynamicContext context = new DynamicContext(configuration, null);
    //sqlNode中的sql语句在context中构建了一份
    rootSqlNode.apply(context);
    //去除这条sql语句
    return context.getSql();
  }
  public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) {
	//SqlSource类中注册进了configuration,后面使用
    SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration);
    //取到参数.class文件,
    Class<?> clazz = parameterType == null ? Object.class : parameterType;
    //继续解析
    sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<String, Object>());
  }

  public SqlSource parse(String originalSql, Class<?> parameterType, Map<String, Object> additionalParameters) {
	  //注册进需要的参数 ,准备ParameterMapping生成的handler
	    ParameterMappingTokenHandler handler = new ParameterMappingTokenHandler(configuration, parameterType, additionalParameters);
	    //把原始sql中的#{}占位符拿出来了
	    GenericTokenParser parser = new GenericTokenParser("#{", "}", handler);
	    //将原始sql的#{}占位符替换成jdbc需要的‘?’,这个过程还有一个关键的操作是
	    //builder.append(handler.handleToken(content));构建ParameterMapping的过程,别名注册过程等
	    String sql = parser.parse(originalSql);
	    return new StaticSqlSource(configuration, sql, handler.getParameterMappings());
	  }
  //暂时将ParameterMapping存在集合中
  public String handleToken(String content) {
	  //add函数中的函数又是一个构建者方式构建出ParameterMapping
      parameterMappings.add(buildParameterMapping(content));
      return "?";
    }
    //填充各个字段
    private ParameterMapping buildParameterMapping(String content) {
      Map<String, String> propertiesMap = parseParameterMapping(content);
      String property = propertiesMap.get("property");
      Class<?> propertyType;
      if (metaParameters.hasGetter(property)) { // issue #448 get type from additional params
        propertyType = metaParameters.getGetterType(property);
      } else if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
        propertyType = parameterType;
      } else if (JdbcType.CURSOR.name().equals(propertiesMap.get("jdbcType"))) {
        propertyType = java.sql.ResultSet.class;
      } else if (property != null) {
        MetaClass metaClass = MetaClass.forClass(parameterType);
        if (metaClass.hasGetter(property)) {
          propertyType = metaClass.getGetterType(property);
        } else {
          propertyType = Object.class;
        }
      } else {
        propertyType = Object.class;
      }
      ParameterMapping.Builder builder = new ParameterMapping.Builder(configuration, property, propertyType);
      Class<?> javaType = propertyType;
      String typeHandlerAlias = null;
      for (Map.Entry<String, String> entry : propertiesMap.entrySet()) {
        String name = entry.getKey();
        String value = entry.getValue();
        if ("javaType".equals(name)) {
          javaType = resolveClass(value);
          builder.javaType(javaType);
        } else if ("jdbcType".equals(name)) {
          builder.jdbcType(resolveJdbcType(value));
        } else if ("mode".equals(name)) {
          builder.mode(resolveParameterMode(value));
        } else if ("numericScale".equals(name)) {
          builder.numericScale(Integer.valueOf(value));
        } else if ("resultMap".equals(name)) {
          builder.resultMapId(value);
        } else if ("typeHandler".equals(name)) {
          typeHandlerAlias = value;
        } else if ("jdbcTypeName".equals(name)) {
          builder.jdbcTypeName(value);
        } else if ("property".equals(name)) {
          // Do Nothing
        } else if ("expression".equals(name)) {
          throw new BuilderException("Expression based parameters are not supported yet");
        } else {
          throw new BuilderException("An invalid property '" + name + "' was found in mapping #{" + content + "}.  Valid properties are " + parameterProperties);
        }
      }
      if (typeHandlerAlias != null) {
        builder.typeHandler(resolveTypeHandler(javaType, typeHandlerAlias));
      }
      return builder.build();
    }

上面整个分析的过程就是返回一个SqlSource,终于把SqlSource的创建完成了,最终返回的是一new StaticSqlSource(configuration, sql, handler.getParameterMappings());

 

看下参数,处理过的sql语句,参数映射关系类和什么都含有类。

再接着最上面创建完成SqlSource类往下分析,创建完SqlSource其实基本上解析就结束了,剩下就是将Sqlsource构建到MappedStatement这个代表一条sql语句节点的类中,

然后将MappedStatement添加到Configuration中。

 

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 statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType);
    statementBuilder.resource(resource);
    statementBuilder.fetchSize(fetchSize);
    statementBuilder.statementType(statementType);
    statementBuilder.keyGenerator(keyGenerator);
    statementBuilder.keyProperty(keyProperty);
    statementBuilder.keyColumn(keyColumn);
    statementBuilder.databaseId(databaseId);
    statementBuilder.lang(lang);
    statementBuilder.resultOrdered(resultOrdered);
    statementBuilder.resulSets(resultSets);
    setStatementTimeout(timeout, statementBuilder);

    setStatementParameterMap(parameterMap, parameterType, statementBuilder);
    setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder);
    setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder);

    MappedStatement statement = statementBuilder.build();
    configuration.addMappedStatement(statement);
    return statement;
  }

确实是预测的这么一个流程,

 

到此处 configurationElement(parser.evalNode("/mapper"));这个节点的解析流程就结束了,下面还有一处需要分析的地方,就是Dao接口的注册缓存,以后执行接口方法的时候是直接把接口作为键取的。

 

public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }

    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
  }

再把这个方法拿出来,解析完成/mapper以后,configuration做下标记,下次不用再初始化

 

接着注册注册dao接口

 

private void bindMapperForNamespace() {
	//namespace,忘了没有就是dao接口的全名称
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
    	  //加载.class文件
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        //ignore, bound type is not required
      }
      if (boundType != null) {
        if (!configuration.hasMapper(boundType)) {
          // Spring may not know the real resource name so we set a flag
          // to prevent loading again this resource from the mapper interface
          // look at MapperAnnotationBuilder#loadXmlResource
          configuration.addLoadedResource("namespace:" + namespace);
          //要说的就是这个addMapper,把传过来的Dao接口注册进去
          configuration.addMapper(boundType);
        }
      }
    }
  }
public <T> void addMapper(Class<T> type) {
    mapperRegistry.addMapper(type);
  }
public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        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.
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }


留意一下这个knownMappers,此处将dao接口的.class存了缓存,同时对接口封装了一下,封装为了MapperProxyFactory

 

parse下面的三个函数是对解析不充分情况的一个补充,应该没什么用。

此后就一路void返回到SqlSessionFactoryBuilder类的build方法,SqlSessionFactory的最后一步,用一路返回的Confiugration构建出真实可用的SqlSessionFactory。

一路艰辛,终于看到了庐山真面目,领略了庐山风光。路漫漫其修远兮。

最后的最后对整个解析的过程做一个关键类的总结

--------------------------------------------------------------------------好累-----------------------------20170803---------------0:43------------------------------

1.SqlSessionFactoryBuilder类

名字就可以看出,构建sqlSessionFactory的类,使用的也是构建者模式,分两步,第一步初始化配置文件封装为Configuration,第二步用Configuration build出一个DefaultSqlSessionFactory。

2.sqlSessionFactory类

创造SqlSession的地方,执行sql语句的大门,SqlSession类似于jdbc的connection,用它来操作它下面的四大护法完成sql执行。

3.XML...ConfigBuilder

这种类型的类就是用传入的xml节点,来解析出具体内容,最后构建出一个...对象,这里的构建者模式和1中的有一点不一样的感觉,显然这里的更常用

4.SqlSource

  这个类也是够复杂的类,感觉上面完全没有分析清楚,组装成完整sql的资源库,基本上parameterObject,parameterMapping,sql都能从这个类中以某种方式获得

5,MappedStatement

比SqlSource还要高一个层次,是一个增删改查的资源库,后面的执行流程到处都是他的身影

6. Configuration

要什么有什么类

设计模式也主要就是构建者模式令人影响深刻些。

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值