mybatis源码 (一) —— SqlSessionFactory创建和mapper的解析

这篇主要分析SqlSessionFactory的构建过程,以及mybatis mapper文件的解析
先来看SqlSessionFactory的创建过程

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

org.apache.ibatis.session.SqlSessionFactoryBuilder#build(java.io.Reader,java.lang.String,java.util.Properties)

  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
        //委托XMLConfigBuilder来解析xml文件,并构建
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
        //这里是捕获异常,包装成自己的异常并抛出的idiom?,最后还要reset ErrorContext
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

可以看到SqlSessionFactoryBuilder委托XMLConfigBuilder来解析xml文件,下面我们跟进去查看解析过程

  //构造函数,转换成XPathParser再去调用构造函数
  public XMLConfigBuilder(Reader reader, String environment, Properties props) {
    //构造一个需要验证,XMLMapperEntityResolver的XPathParser
    this(new XPathParser(reader, true, props, new XMLMapperEntityResolver()), environment, props);
  }

最终调用的构造方法是:

  //上面6个构造函数最后都合流到这个函数,传入XPathParser
  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    //首先调用父类初始化Configuration
    super(new Configuration());
    //错误上下文设置成SQL Mapper Configuration(XML文件配置),以便后面出错了报错用吧
    ErrorContext.instance().resource("SQL Mapper Configuration");
    //将Properties全部设置到Configuration里面去
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

创建一个Configuration对象,
然后会调用父类的构造org.apache.ibatis.builder.BaseBuilder#BaseBuilder

  //需要配置,类型别名注册,类型处理器注册3个东西
  protected final Configuration configuration;
  protected final TypeAliasRegistry typeAliasRegistry;
  protected final TypeHandlerRegistry typeHandlerRegistry;

  public BaseBuilder(Configuration configuration) {
    this.configuration = configuration;
    this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
  }

  //映射的语句,存在Map里
  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
  //缓存,存在Map里
  protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");

这是Configuration对象的成员变量,mapper中配置的sql解析出来之后会存在mappedStatements 中
org.apache.ibatis.builder.xml.XMLConfigBuilder#parse

 //解析配置
  public Configuration parse() {
    //如果已经解析过了,报错
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
//  <?xml version="1.0" encoding="UTF-8" ?> 
//  <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" 
//  "http://mybatis.org/dtd/mybatis-3-config.dtd"> 
//  <configuration> 
//  <environments default="development"> 
//  <environment id="development"> 
//  <transactionManager type="JDBC"/> 
//  <dataSource type="POOLED"> 
//  <property name="driver" value="${driver}"/> 
//  <property name="url" value="${url}"/> 
//  <property name="username" value="${username}"/> 
//  <property name="password" value="${password}"/> 
//  </dataSource> 
//  </environment> 
//  </environments>
//  <mappers> 
//  <mapper resource="org/mybatis/example/BlogMapper.xml"/> 
//  </mappers> 
//  </configuration>

    //根节点是configuration
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

调用parse方法返回Configuration,调用XPathParser解析configuration节点
org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration

  //解析配置
  private void parseConfiguration(XNode root) {
    try {
      //分步骤解析
      //issue #117 read properties first
      //1.properties
      propertiesElement(root.evalNode("properties"));
      //2.类型别名
      typeAliasesElement(root.evalNode("typeAliases"));
      //3.插件
      pluginElement(root.evalNode("plugins"));
      //4.对象工厂
      objectFactoryElement(root.evalNode("objectFactory"));
      //5.对象包装工厂
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      //6.设置
      settingsElement(root.evalNode("settings"));
      // read it after objectFactory and objectWrapperFactory issue #631
      //7.环境
      environmentsElement(root.evalNode("environments"));
      //8.databaseIdProvider
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      //9.类型处理器
      typeHandlerElement(root.evalNode("typeHandlers"));
      //10.映射器
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

我们关心的怎么解析mapper中的sql,对于其他的配置解析暂且不关注
org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement

  /**
   * 
   * 
   *    //10.映射器
   *    10.1使用类路径
   *    <mappers>
   *      <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
   *      <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
   *      <mapper resource="org/mybatis/builder/PostMapper.xml"/>
   *    </mappers>
   *
   *    10.2使用绝对url路径
   *    <mappers>
   *      <mapper url="file:///var/mappers/AuthorMapper.xml"/>
   *      <mapper url="file:///var/mappers/BlogMapper.xml"/>
   *      <mapper url="file:///var/mappers/PostMapper.xml"/>
   *    </mappers>
   *
   *    10.3使用java类名
   *    <mappers>
   *      <mapper class="org.mybatis.builder.AuthorMapper"/>
   *      <mapper class="org.mybatis.builder.BlogMapper"/>
   *      <mapper class="org.mybatis.builder.PostMapper"/>
   *    </mappers>
   *
   *    10.4自动扫描包下所有映射器
   *    <mappers>
   *      <package name="org.mybatis.builder"/>
   *    </mappers>
   */

  private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          //10.4自动扫描包下所有映射器
          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) {
            //10.1使用类路径
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            //映射器比较复杂,调用XMLMapperBuilder
            //注意在for循环里每个mapper都重新new一个XMLMapperBuilder,来解析
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            //10.2使用绝对url路径
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            //映射器比较复杂,调用XMLMapperBuilder
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            //10.3使用java类名
            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.");
          }
        }
      }
    }
  }

主要是针对不同的mapper映射方式做的处理,我们就看<mapper resource="org/mybatis/builder/PostMapper.xml"/>这种映射怎么完成解析的

//10.1使用类路径
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
//映射器比较复杂,调用XMLMapperBuilder
//注意在for循环里每个mapper都重新new一个XMLMapperBuilder,来解析
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream,configuration,resource,configuration.getSqlFragments());
mapperParser.parse();

创建一个XMLMapperBuilder 然后调用parse方法,XMLMapperBuilder 构造需要一个configuration,那么可以肯定解析完成的数据是放到configuration中的
org.apache.ibatis.builder.xml.XMLMapperBuilder#parse

  //解析
  public void parse() {
    //如果没有加载过再加载,防止重复加载
    if (!configuration.isResourceLoaded(resource)) {
      //解析mapper节点下的数据
      configurationElement(parser.evalNode("/mapper"));
      //标记一下,已经加载过了
      configuration.addLoadedResource(resource);
      //绑定映射器到namespace
      bindMapperForNamespace();
    }

    //还有没解析完的东东这里接着解析?  
    parsePendingResultMaps();
    parsePendingChacheRefs();
    parsePendingStatements();
  }
  1. 解析mapper节点下的数据,可能有resultmap statement这些节点是只能等全部解析完再去完成映射
  2. 把上一步解析出的 resultmap 等信息和没有完成的sql来完成对应关系

org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement

    //配置mapper元素
//  <mapper namespace="org.mybatis.example.BlogMapper">
//    <select id="selectBlog" parameterType="int" resultType="Blog">
//      select * from Blog where id = #{id}
//    </select>
//  </mapper>
  private void configurationElement(XNode context) {
    try {
      //1.配置namespace
      String namespace = context.getStringAttribute("namespace");
      if (namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
      //2.配置cache-ref
      cacheRefElement(context.evalNode("cache-ref"));
      //3.配置cache
      cacheElement(context.evalNode("cache"));
      //4.配置parameterMap(已经废弃,老式风格的参数映射)
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
      //5.配置resultMap(高级功能)
      resultMapElements(context.evalNodes("/mapper/resultMap"));
      //6.配置sql(定义可重用的 SQL 代码段)
      sqlElement(context.evalNodes("/mapper/sql"));
      //7.配置select|insert|update|delete TODO
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
    }
  }
  1. 解析namespace属性
  2. 解析parameterMap
  3. 解析resultMap
  4. 解析自定义sql代码片
  5. 解析select节点
    org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext
  //7.配置select|insert|update|delete
  private void buildStatementFromContext(List<XNode> list) {
    //调用7.1构建语句
    if (configuration.getDatabaseId() != null) {
      buildStatementFromContext(list, configuration.getDatabaseId());
    }
    buildStatementFromContext(list, null);
  }

org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List

  //7.1构建语句
  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
      //构建所有语句,一个mapper下可以有很多select
      //语句比较复杂,核心都在这里面,所以调用XMLStatementBuilder
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
          //核心XMLStatementBuilder.parseStatementNode
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
          //如果出现SQL语句不完整,把它记下来,塞到configuration去
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

委托XMLStatementBuilder去解析
org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode

  //解析语句(select|insert|update|delete)
//<select
//  id="selectPerson"
//  parameterType="int"
//  parameterMap="deprecated"
//  resultType="hashmap"
//  resultMap="personResultMap"
//  flushCache="false"
//  useCache="true"
//  timeout="10000"
//  fetchSize="256"
//  statementType="PREPARED"
//  resultSetType="FORWARD_ONLY">
//  SELECT * FROM PERSON WHERE ID = #{id}
//</select>
  public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    //如果databaseId不匹配,退出
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    .......

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

前面省略的是具体解析select节点下resultMap等属性代码
解析完成又调助手类的addMappedStatement方法

  //增加映射语句
  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加上namespace前缀
    id = applyCurrentNamespace(id, false);
    //是否是select语句
    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);

    //1.参数映射
    setStatementParameterMap(parameterMap, parameterType, statementBuilder);
    //2.结果映射
    setStatementResultMap(resultMap, resultType, resultSetType, statementBuilder);
    setStatementCache(isSelect, flushCache, useCache, currentCache, statementBuilder);

    MappedStatement statement = statementBuilder.build();
    //建造好调用configuration.addMappedStatement
    configuration.addMappedStatement(statement);
    return statement;
  }
  1. 为id加上namespace前缀,现在id就是 namespace + id
  2. 创建MappedStatement.Builder对象设置各种属性
  3. statementBuilder.build()产生MappedStatement对象
  4. 添加到configuration对象中去
    org.apache.ibatis.session.Configuration#addMappedStatement
  public void addMappedStatement(MappedStatement ms) {
    mappedStatements.put(ms.getId(), ms);
  }

那么我们可以清晰的看到mapper中配置的select语句是通过key:namespace+id value:sql语句放到map中去的


上述的基本解析流程完成之后把{key:namespace+id value:sql}放到Configuration的map中去,完成的事情:

  1. 解析数据源datasource
  2. 解析resultmap
  3. 解析select update等节点
  //最后一个build方法使用了一个Configuration作为参数,并返回DefaultSqlSessionFactory
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }

最后返回一个DefaultSqlSessionFactory对象

猜你喜欢

转载自blog.csdn.net/u011702633/article/details/82147344