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

来源:互联网 发布:精密减速机 知乎 编辑:程序博客网 时间:2024/06/01 07:44

      接着上文太长的那个文章开始分析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文件可以定义多个resultMapprivate 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

要什么有什么类

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

最后QQ交流群(341252823),新建的,目前没有人,期望你的加入,可以一起讨论一些问题