Mybatis 源码分析(二) | MyBatis 配置文件解析过程

前言

如何调试源码请参考这一篇文章《如何调试Mybatis源码 》。

注意:如果你还没有调试过mybatis,那么不建议你往下阅读,因为你会看的稀里糊涂-。-

本篇我们就一块Debug源码,看看Mybatis到底是如何读取到 mybatis-config.xml 文件中的一系列参数的呢?

首先我们看张图:这是我们JDBC时对数据库的一系列操作,先获取数据源中一系列的参数,然后执行SQL语句,最后对数据库进行操作,获取到结果。

本章节就先说说第一部分,mybatis如何获取到数据源。

在这里插入图片描述

Mybatis如何获取数据源

在 MyBatis 初始化过程中,会加载 mybatis-config.xml 配置文件、映射配置文件以及 Mapper 接口中的注解信息,解析后的配置信息会形成相应的对象并保存到 Configuration 对象中。利用该 Configuration 对象创建 SqlSessionFactory对象。待 MyBatis 初始化之后,开发人员可以通过初始化得到 SqlSessionFactory 创建 SqlSession 对象并完成数据库操作。

  • 对应 builder 模块,为配置解析过程。主要的xml文件的解析都在这个模块下进行。
    在这里插入图片描述
    第一步要做的事情就是根据配置文件构建SqlSessionFactory对象。,使用Resources 工具类加载了mybatis-config.xml 文件,得到输入流,然后通过 SqlSessionFactoryBuilderbuild() 方法,构建出sqlSessionFactory 对象。
public static void main(String[] args) throws IOException {
    
    
        // 获取配置文件
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        // 构建SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        try {
    
    
            User user = sqlSession.selectOne("com.mybatis.read.UserDao.selectUser", 1);
        } finally {
    
    
            sqlSession.close();
        }
    }

SqlSessionFactoryBuilder

可以看出这里调用了 XMLConfigBuilder 对象的 parse() 方法,对 xml 文件进行了解析,那么如何解析的呢?进入 XMLConfigBuilder 类中一探究竟吧。

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    
    
        try {
    
    
            // 创建 XMLConfigBuilder 对象
            XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
            // 执行 XML 解析
            // DefaultSqlSessionFactory对象
            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.
            }
        }
    }

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

XMLConfigBuilder

  1. Configuration 这个对象贯穿了整个配置文件初始化的过程。
  2. 对xml文件中的标签做了解析,从源码中可以看出几个关键的标签有 configurationpropertiesenvironmentsdataSourcemapper
  3. 通过configuration.setVariables()configuration.setEnvironment() 将配置文件中的propertiesenvironments 下的参数放到了 Configuration对象中供全局使用
public Configuration parse() {
    
    
    // 只加载一次xml文件,否则抛出异常
    if (parsed) {
    
    
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    // 解析xml文件中configuration标签
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

  /**
   * 功能描述: 解析 configuration 标签下的所有属性配置
   * @date 2020/2/27
   */
  private void parseConfiguration(XNode root) {
    
    
    try {
    
    
      // 读取mybatis-config.xml中的properties,加载config.properties文件中的参数
      propertiesElement(root.evalNode("properties"));
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(settings);
      loadCustomLogImpl(settings);
      typeAliasesElement(root.evalNode("typeAliases"));
      pluginElement(root.evalNode("plugins"));
      objectFactoryElement(root.evalNode("objectFactory"));
      objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
      reflectorFactoryElement(root.evalNode("reflectorFactory"));
      settingsElement(settings);
      // 加载environments节点
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      // 加载mapper文件
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
    
    
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

解析 properties 配置

properties 的解析过程比较简单,由代码中可以看出,属性配置是从resource或者url中获取的,即文件系统或者网络地址。

项目中配置的是resource<properties resource = "config.properties"/>。将 config.properties 文件中的内容读取出来,然后设置到 XPathParserConfiguration 对象中。

config.properties文件内容如下:

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis
username=root
password=root
/***
   * 功能描述:解析properties节点,获取url,driver,username,password
   * @date 2020/2/27
   */
  private void propertiesElement(XNode context) throws Exception {
    
    
    if (context != null) {
    
    
      Properties defaults = context.getChildrenAsProperties();
      String resource = context.getStringAttribute("resource");
      String url = context.getStringAttribute("url");
      // properties 标签里的属性,只能设置resource或者url其中一个,否则抛出异常
      if (resource != null && url != null) {
    
    
        throw new BuilderException("The properties element cannot specify both a URL and a resource based property file reference.  Please specify one or the other.");
      }
      // 加载了resource的文件,把里面的参数放到defaults中
      if (resource != null) {
    
    
        defaults.putAll(Resources.getResourceAsProperties(resource));
      } else if (url != null) {
    
    
        defaults.putAll(Resources.getUrlAsProperties(url));
      }
      Properties vars = configuration.getVariables();
      if (vars != null) {
    
    
        defaults.putAll(vars);
      }
      parser.setVariables(defaults);
      configuration.setVariables(defaults);
    }
  }

defaults 中的参数如下:

在这里插入图片描述

解析 environments 配置

在 MyBatis 中,事务管理器和数据源是配置在 environments 中的。它们的配置大致如下:

<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>

解析 environments 标签,之后解析了内部的 dataSource 标签。

 /***
   * 功能描述:解析 environments 标签
   * @date 2020/2/27
   */
  private void environmentsElement(XNode context) throws Exception {
    
    
    if (context != null) {
    
    
      if (environment == null) {
    
    
        environment = context.getStringAttribute("default");
      }
      for (XNode child : context.getChildren()) {
    
    
        String id = child.getStringAttribute("id");
        if (isSpecifiedEnvironment(id)) {
    
    
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
          // 解析environment下的dataSource标签
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          DataSource dataSource = dsFactory.getDataSource();
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
          configuration.setEnvironment(environmentBuilder.build());
        }
      }
    }
  }

  /**
   * 功能描述:解析 dataSource 标签
   * @date 2020/2/27
   */
  private DataSourceFactory dataSourceElement(XNode context) throws Exception {
    
    
    if (context != null) {
    
    
      String type = context.getStringAttribute("type");
      Properties props = context.getChildrenAsProperties();
      DataSourceFactory factory = (DataSourceFactory) resolveClass(type).getDeclaredConstructor().newInstance();
      factory.setProperties(props);
      return factory;
    }
    throw new BuilderException("Environment declaration requires a DataSourceFactory.");
  }

dataSource 中的参数如下:
在这里插入图片描述

以上代码的流程简单的花了一个图,可以大致明白是怎样的一个流程:
在这里插入图片描述

小结

至此,我们已经可以看出Mybatis是如何从配置文件中加载出数据源了。

在解析 Configuration 标签时我们只关注了和数据源参数相关的 propertiesenvironments 标签,还有一些同样重要的标签没有展开说明,有兴趣的同学同样可以自行debug到代码中,看看其他参数是设置,并产生作用的。

下一节说一说 Mapper 映射配置文件 是如何加载的。

猜你喜欢

转载自blog.csdn.net/u012294515/article/details/104541814