MyBatis源码分析

分析完了MyBatis的架构和执行流程,终于到了源码分析的章节,估计很多小伙伴的大刀都已经饥渴难耐了,好了接下来咱么就要开始 “ Double Kill ” 了。
这篇文章咱么主要根据MyBatis的执行流程,通过Debug的方式,来一步步非常详细的带着大家看下MyBatis的从加载配置文件、解析配置文件、创建四大核心对象(ExecutorParameterHandlerResultSetHandlerStatementHandler)的详细过程,以及在这个过程中是如何的设置参数?如何执行查询?如何封装结果集?同时会给大家介绍下Mybatis中常用的几个设计模如:工厂模式、建造者模式等。最终可以达到从模仿到超越
好了,废话不多说,开始模仿的Debug之旅吧

兵马未动,粮草先行(环境准备)

兵家有云:兵马未动,粮草先行;即使打仗(杀人)这么高大上的职业,也不能免俗也是要吃饭的,所以我们搭建一个最简单的MyBatis的工程,只需要在pom文件中添加Mybatis的依赖就可以,为了更好的分析源码的执行流程细节,我使用最原始的方式来分析,Debug也会跟着下面的步骤一步一个脚印分析

/**
     * mybatis源码分析
     * @throws Exception
     */
    @Test
    public void test1() throws Exception {
        // 1.加载配置文件
        InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
        // 2. 创建SqlSessionFactory对象实际创建的是DefaultSqlSessionFactory对象
        SqlSessionFactory builder = new SqlSessionFactoryBuilder().build(inputStream);
        // 3. 创建SqlSession对象实际创建的是DefaultSqlSession对象
        SqlSession sqlSession = builder.openSession();
        // 4. 创建代理对象
        UserMapper mapper = sqlSession.getMapper(UserMapper.class);
        // 5. 执行查询语句
        List<User> users = mapper.selectUserList();
        for (User user : users) {
            System.out.println(user);
        }
    }
第一步:加载配置文件

首先,先来看第一步加载配置文件篇,这里主要介绍配置文件加载的过程,以及用的类

// 1.加载配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");

Debug一直往下更,会进到一个ClassLoaderWrapper类中,并找到配置文件最终返回一个BufferedInput Stream带有缓冲的输入流,它继承于FilterInputStream,第一步就只加载配置文件

第二步:创建 SqlSessionFactory

接着进入第二步,这一步通过解析加载的配置文件,创建一个配置类Configuration,而且这一步中也把映射文件一并解析,最终返回DefaultSqlSessionFactory对象,是一个工厂对象,工厂模式在Myabtis中用的是相当多,以及建造者模式

 // 2. 创建SqlSessionFactory对象实际创建的是DefaultSqlSessionFactory对象
SqlSessionFactory builder = new SqlSessionFactoryBuilder().build(inputStream);

接着往下中走进入

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
        // 创建XMLConfigBuilder,用来解析xml配置文件,使用的是建造这模式
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
        // 解析配置文件,构建SqlSessionFactory
      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.
      }
    }
  }

最终要的是parser.parse()这个方法,这里面试真正来解析核心配置文件的,进到这个方法中

public Configuration parse() {
    // 判断配置文件是否被解析过,一个配置文件呢只能别解析一次
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // 解析核心配置文件的configuration节点
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

最终返回的是一个Configuration对象,这个对象封装了所有的配置相关的信息,后面还会在看,这里可以看到

parseConfiguration(parser.evalNode("/configuration"));

这段代码已经开始解析配置文件的configuration节点,并且从上往下开始解析,我们进到这个方法中

 private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
        // 解析properties节点
      propertiesElement(root.evalNode("properties"));
        // 解析settings并将其转换为 Properties 对象
      Properties settings = settingsAsProperties(root.evalNode("settings"));
          // // 加载 vfs虚拟文件系统
      loadCustomVfs(settings);
          // 解析settings
      loadCustomLogImpl(settings);
          // 解析typeAliases
      typeAliasesElement(root.evalNode("typeAliases"));
          // 解析plugins
      pluginElement(root.evalNode("plugins"));
          // 解析objectFactory
      objectFactoryElement(root.evalNode("objectFactory"));
          // 解析objectWrapperFactory
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
         // 解析reflectorFactory
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
        // settings 中的信息设置到 Configuration 对象中
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
        // 解析environments
      environmentsElement(root.evalNode("environments"));
       // 解析 databaseIdProvider
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
          // 解析mappers
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

至此,我们就看明白了,mybatis-config.xml核心配置文件的解析过程,其他的节点好比较好理解,跟所有的xml配置文件的解析都是一样的,一层一层的解析里面的子节点或属性,所以这里我们略过;重点要看的是关于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");
            // 扫描包里的映射文件,最终放到一个map中备用,type作为key,创建的代理对象作为value
          configuration.addMappers(mapperPackage);
            // 其他配置方方式resource、url、class
        } else {
            // 解析reource的配置方式
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
              // 解析映射文件
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            ErrorContext.instance().resource(url);
            InputStream inputStream = Resources.getUrlAsStream(url);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url == null && mapperClass != null) {
            Class<?> mapperInterface = Resources.classForName(mapperClass);
            configuration.addMapper(mapperInterface);
          } else {
            throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
          }
        }
      }
    }
  }

这里就可以看出来这个方法主是更具不同的配置方式用来解析映射文件的,进到解析映射文件的方法mapperParser.parse()

  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
        // 解析mapper节点
      configurationElement(parser.evalNode("/mapper"));
        // 添加的configura中的集合中
      configuration.addLoadedResource(resource);
        // 绑定命名空间
      bindMapperForNamespace();
    }

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

跟进去,看下是怎么解析映射文件的内容

  private void configurationElement(XNode context) {
    try {
        // 拿到命名空间
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.equals("")) {
        throw new BuilderException("Mapper's namespace cannot be empty");
      }
      builderAssistant.setCurrentNamespace(namespace);
        // 处理缓存的配置标签
      cacheRefElement(context.evalNode("cache-ref"));
      cacheElement(context.evalNode("cache"));
        // 解析parameterMap
      parameterMapElement(context.evalNodes("/mapper/parameterMap"));
         // 解析resultMap
      resultMapElements(context.evalNodes("/mapper/resultMap"));
       // 解析 sql标签
      sqlElement(context.evalNodes("/mapper/sql"));
        // 解析增删改查标签
      buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
    }
  }

这个放的作用是解析映射文件的,和之前的解析核心配置文件的逻辑是一样的。不再赘述,重要的一个是解析增删改查标签的方法,跟着往下走

  private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
    for (XNode context : list) {
        // 创建 XMLStatementBuilder
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
          // 解析sql标签
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

进来之后

public void parseStatementNode() {
    String id = context.getStringAttribute("id");
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    // 解析