MyBatis源码解析 第二章 加载和解析配置文件以及xxMapper.xml

113 阅读4分钟

解析mybatis-config.xml和mapper/*.xml文件

解析xml配置是mybatis实例化SqlSessionFactory的开始
mybatis解析xml文件时有一个共同的实现,就是先根据xml文件流创建此文件的创建者
然后再根据其创建者调用本身的 parse()方法完成解析,最后都会放入Configuration对象中供全局使用

  1. 创建SqlSessionFactory
public SqlSessionFactory getSqlSessionFactory() throws IOException {
    //声明配置类名称
    String resource = "mybatis-config.xml";
    // 获取配置文件的输入流
    InputStream inputStream = Resources.getResourceAsStream(resource);
    // 用输入流创建SqlSessionFactory
    return new SqlSessionFactoryBuilder().build(inputStream);
}
  1. new SqlSessionFactoryBuilder().build(inputStream);

new SqlSessionFactoryBuilder().build(inputStream);有多个实现类,其中输入流传参就是其中一种

// SqlSessionFactoryBuilder::build()
public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
}
  1. build(inputStream, null, null);
// SqlSessionFactoryBuilder::build(),继续调用build方法的另外一种实现方式
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
       // 获取XMLConfig文件的创建者,使用此创建者对象可以解析config.xml对象
        /**
         * inputStream:流文件
         * environment:环境配置,mysql服务器地址,用户名,密码等信息
         * properties:属性
         */
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        // xml配置文件解析
      return build(parser.parse());
    } catch (Exception e) {
      ...
    } finally {
      // 关闭输入流
      ...
    }
}

4.build(parser.parse());

parser.parse() 对文件进行解析

//XMLConfigBuilder::parse()
public Configuration parse() {
    // 文件是否解析过,每个实例化XMLConfigBuilder对象里面都有一个布尔值,parsed,标记此实例化对象
    // 中的config.xml文件是否已经解析,默认值是未解析,解析完成后设置成已解析
    if (parsed) {
        throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    // 设置成已解析(这里可能不在解析完成后设置,可能是因为解析需要花费时间,而在等待过程中,可能又有代码进来解析)
    // 写在这里可以防止这种情况发生,保障每次在解析的配置文件只有一个
    parsed = true;
    // 解析xml文件
    //parser.evalNode("/configuration") 获取流文件中,<configuration>标签包裹的所有值
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
}
  1. 解析配置文件 parseConfiguration(parser.evalNode("/configuration"));

mybatis-config.xml文件中没有的标签先略过

<!--environments 标签的值-->
<environments default="mysql">
     <environment id="mysql">
         <transactionManager type="JDBC"/>
         <dataSource type="POOLED">
             <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
             <!--数据库地址-->
             <property name="url" value="jdbc:mysql://localhost:3306/path2java?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai&amp;verifyServerCertificate=false"/>
             <!--数据库账号-->
             <property name="username" value="root"/>
             <!--数据库密码-->
             <property name="password" value="root"/>
         </dataSource>
     </environment>
 </environments>
<!--mappers标签的值-->
<mappers>
    <!--映射文件路径-->
    <mapper resource="mapper/AdvertiseSiteMapper.xml"/>
</mappers>
private void parseConfiguration(XNode root) {
    try {
      ...
      // read it after objectFactory and objectWrapperFactory issue #631
      // 获取<environments>标签没所有值
      // 并在解析完成后,生成一个Environment对象,并且放入Configruation对象的environment对象中,以后每次创建sqlSession都会携带里面的数据库信息
      environmentsElement(root.evalNode("environments"));
      // 获取<mappers>标签的所有属性
      // 拿到此处的信息,相当于在yml文件中配置的mapper.xml文件地址:mapper/*.xml
      // 此对象解析完成后会将mapper.xml文件的内容生成sql对象,存放到Configruation中
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }
  1. 解析mapper/AdvertiseSiteMapper.xml文件

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

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          // 获取文件xml文件路径 resource="mapper/AdvertiseSiteMapper.xml"
          String resource = child.getStringAttribute("resource");
          // 获取url地址
          String url = child.getStringAttribute("url");
          // 获取class类
          String mapperClass = child.getStringAttribute("class");
          //解析只有resource存在时的情况
          if (resource != null && url == null && mapperClass == null) {
            // 获取 mapper/AdvertiseSiteMapper.xml 文件的文件流
            InputStream inputStream = Resources.getResourceAsStream(resource);
            // 根据mapper/AdvertiseSiteMapper.xml文件常见其mapper创建者
            // 这一步做的主要功能就是将文件流放入mapperParser对象中
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            // 根据其创建者信息,解析mapper/AdvertiseSiteMapper.xml文件
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ...
          } else if (resource == null && url == null && mapperClass != null) {
            ...
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }
  1. 开始解析 mapper/AdvertiseSiteMapper.xml

XMLMapperBuilder::parse();

public void parse() {
    // 根据文件全路径名,判断是否已经解析过此xml文件
    if (!configuration.isResourceLoaded(resource)) {
      // 获取文件流中的 <mapper>标签的所有信息
      configurationElement(parser.evalNode("/mapper"));
      configuration.addLoadedResource(resource);
      bindMapperForNamespace();
    }
    parsePendingResultMaps();
    parsePendingCacheRefs();
    parsePendingStatements();
  }
  1. 解析mapper/AdvertiseSiteMapper.xml的标签下的所有属性
<mapper namespace="cn.kgm.mapper.MyBatisMapper">
    <!-- 通用查询映射结果 -->
    <resultMap id="BaseResultMap" type="cn.kgm.entity.MyBatis">
        <id column="id" property="id"/>
        <id column="content" property="content"/>
        <result column="create_time" property="createTime"/>
    </resultMap>
    <!-- 通用查询结果列 -->
    <sql id="BaseColumnList">
        id,content,create_time
    </sql>
    <select id="getOne" resultType="cn.kgm.entity.MyBatis" parameterType="java.lang.Long">
        select
        <include refid="BaseColumnList"/>
        from my_batis where id = #{id}
    </select>
</mapper>
  1. configurationElement(parser.evalNode("/mapper"));

解析mapper标签对象

//XMLMapperBuilder::configurationElement();
private void configurationElement(XNode context){
        try{
        // 对应的Mapper接口 namespace="cn.kgm.mapper.MyBatisMapper"
        String namespace=context.getStringAttribute("namespace");
        if(namespace==null||namespace.equals("")){
        throw new BuilderException("Mapper's namespace cannot be empty");
        }
        // 设置Mapper对象地址
        builderAssistant.setCurrentNamespace(namespace);
        // 缓存相关配置
        cacheRefElement(context.evalNode("cache-ref"));
        // 缓存
        cacheElement(context.evalNode("cache"));
        // 参数映射关系
        parameterMapElement(context.evalNodes("/mapper/parameterMap"));
        // 返回值映射关系
        resultMapElements(context.evalNodes("/mapper/resultMap"));
        // SQL 预设模板
        sqlElement(context.evalNodes("/mapper/sql"));
        // 获取select,insert,update,delete四种标签的内容并解析
        buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
        }catch(Exception e){
        throw new BuilderException("Error parsing Mapper XML. Cause: "+e,e);
        }
        }

10.buildStatementFromContext(context.evalNodes("select|insert|update|delete"));

获取SQL标签内容,此处只有 select标签有值


<select id="getOne" resultType="cn.kgm.entity.MyBatis" parameterType="java.lang.Long">
    select
    <include refid="BaseColumnList"/>
    from my_batis where id = #{id}
</select>
// XMLMapperBuilder::buildStatementFromContext();
private void buildStatementFromContext(List<XNode> list){
        if(configuration.getDatabaseId()!=null){
        buildStatementFromContext(list,configuration.getDatabaseId());
        }
        // 此时走这里
        buildStatementFromContext(list,null);
        }

private void buildStatementFromContext(List<XNode> list,String requiredDatabaseId){
        // 获取四种标签的列表
        for(XNode context:list){
// 根据每个标签创建XMLStatementBuilder
final XMLStatementBuilder statementParser=new XMLStatementBuilder(configuration,builderAssistant,context,requiredDatabaseId);
        try{
        // 解析标签,类似之前各种builder类的parse()方法
        statementParser.parseStatementNode();
        }catch(IncompleteElementException e){
        configuration.addIncompleteStatement(statementParser);
        }
        }
        }
  1. statementParser.parseStatementNode();

获取select|delete|insert|update四种标签内的所有信息,分别解析

//每一个标签的内容最后都会生成一个MappedStatement对象,最后放入全局配置Configuration中的mappedStatements对象中
//StrictMap 强校验的Map,mybatis为了防止值和键为null而重写的Map
protected final Map<String, MappedStatement> mappedStatements=new StrictMap<MappedStatement>("Mapped Statements collection");