mybatis源码分析-mybatis-config配置文件解析

mybatis源码分析-配置文件解析(1)

配置⽂件解析过程分析

第一步要做的事情一般是根据配置文件构建 SqlSessionFactory 对象。相关代码大致如下:
InputStream inputStream = Resources.getResourceAsStream("MyBatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

我们首先会使用 MyBatis 提供的工具类 Resources 加载配置文件,得到 一个输入流。然后再通过 SqlSessionFactoryBuilder 对象的 build 方法构建 SqlSessionFactory 对象。这里的 build 方法是我们分析配置文件解析过程的入口方法。

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
  try {
    //inputStream xml输入流
    // environment为Null ,properties为null,
    //配置文件的加载以及解析全部委托给了XPathParser。最终使用的是JDK的xml解析器,而不是在这里使用第三方的dom4j等,底层还是使用的xpath的当时来进行的节点解析。
    //这边我们不去了解怎么解析xml里面的节点的
    XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
    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);
}

parser.parse()解析xml配置

build(parser.parse());这个方法就是把parser.parse()继续出来的结果当成mybatis的上下文Configuration放到DefaultSqlSessionFactory的工厂里面。
public Configuration parse() {
  if (parsed) {
    throw new BuilderException("Each XMLConfigBuilder can only be used once.");
  }
  parsed = true;
  //解析/configuration,就是我们的mybatis-config.xml里面的configuration的节点
  parseConfiguration(parser.evalNode("/configuration"));
  return configuration;
}

//解析节点,我们这边就看几个比较关键的解析,其他可以看完主流程再去看
private void parseConfiguration(XNode root) {
  try {
    //      <properties>
    //        <property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
    //        <property name="username" value="root"/>
    //        <property name="password" value="123456"/>
    //      </properties>
    //解析properties
    //会放在 variables中
    //解析properties节点
    propertiesElement(root.evalNode("properties"));
    /*      <settings>
        <setting name="cacheEnabled" value="true"/>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="useGeneratedKeys" value="true"/>
        <setting name="mapUnderscoreToCamelCase" value="true"/>
      </settings>*/
    /*"mapUnderscoreToCamelCase" -> "true"
      "useGeneratedKeys" -> "true"
      "lazyLoadingEnabled" -> "true"
      "cacheEnabled" -> "true"*/
    // 解析 settings 配置,并将其转换为 Properties 对象
    Properties settings = settingsAsProperties(root.evalNode("settings"));
    //这个方法判断settins里面有咩有vfsImpl,这边先不看
    loadCustomVfs(settings);
    //解析typeAliases节点
    //      <typeAliases>
    //<typeAlias alias="User" type="com.wjk.entity.User"/>
    //</typeAliases>
    //类是typeAliasRegistry 中的TypeAliasRegistry中别名的注册
    typeAliasesElement(root.evalNode("typeAliases"));
    // 解析 plugins 配置
    pluginElement(root.evalNode("plugins"));
    objectFactoryElement(root.evalNode("objectFactory"));
    objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
    reflectorFactoryElement(root.evalNode("reflectorFactory"));
    // settings 中的信息设置到 Configuration 对象中
    settingsElement(settings);
    // 解析 environments 配置
    environmentsElement(root.evalNode("environments"));
    databaseIdProviderElement(root.evalNode("databaseIdProvider"));
    // 解析 typeHandlers 配置
    typeHandlerElement(root.evalNode("typeHandlers"));
    //<mappers>
    //<mapper resource="UserMapper.xml"/>
    //</mappers>
    //      <mappers>
    //<package name="com.wjk.entity"/>
    //</mappers>
		// 解析 mappers 配置
    mapperElement(root.evalNode("mappers"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
  }
}

解析配置文件,这边先看比较关键的配置解析

1.解析properties

private void propertiesElement(XNode context) throws Exception {
  if (context != null) {
    //Properties的chird节点
    Properties defaults = context.getChildrenAsProperties();
    //password -> 123456
    //url -> jdbc:mysql://localhost:3306/mybatis
    //username -> root
    String resource = context.getStringAttribute("resource");
    String url = context.getStringAttribute("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.");
    }
    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);
    //password -> 123456
    //url -> jdbc:mysql://localhost:3306/mybatis
    //username -> root
    //设置到Properties variables里面
    configuration.setVariables(defaults);
  }
}

2.解析setting节点

private Properties settingsAsProperties(XNode context) {
  if (context == null) {
    return new Properties();
  }
  //mapUnderscoreToCamelCase -> true  大小写转换
  //    "useGeneratedKeys" -> "true"      自增主键
  //    "lazyLoadingEnabled" -> "true"    懒加载
  //    "cacheEnabled" -> "true"          二级缓存打开
  Properties props = context.getChildrenAsProperties();
  //metaClass是啥就是我们的元数据类,里面存储的是比如类的成员变量,getter/setter 方法等,这个解析有点复杂,我们就直接认为我们可以从这个里面获得我们的构造方法,getter,setter方法就行
  MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
  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).");
    }
  }
  return props;
}

3.解析environments节点

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");
      //判断当前的environment是不是一样的
      if (isSpecifiedEnvironment(id)) {
        //事务解析<transactionManager type="JDBC"/>
        //Jdbc事务工厂
        //事务类型  JDBC 这个配置直接简单使用了 JDBC 的提交和回滚设置。 它依赖于从数据源得 到的连接来管理事务范围。
        //MANAGED从来不回滚或提交一个连接而它会让 容器来管理事务的整个生命周期(比如 Spring 或 JEE 应用服务器的上下文) 默认 情况下它会关闭连接。
        // 然而一些容器并不希望这样, 因此如果你需要从连接中停止 它,将 closeConnection 属性设置为 false。
        TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
        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());
      }
    }
  }
}

4.解析typeHandlers的节点

在 MyBatis 中,数据库类型和 Java 类型之间的转换任务是委托给类型处理器 TypeHandler 去处理的,MyBatis 提供了一些常见类型的类型处理器,除此之外,我们还可以 自定义类型处理器以非常见类型转换的需求,TypeHandlerRegistry初始化的时候,就会初始化一些基础的映射

private void typeHandlerElement(XNode parent) throws Exception {
  if (parent != null) {
    for (XNode child : parent.getChildren()) {
      if ("package".equals(child.getName())) {
        String typeHandlerPackage = child.getStringAttribute("name");
        //注册进去
        typeHandlerRegistry.register(typeHandlerPackage);
      } else {
        String javaTypeName = child.getStringAttribute("javaType");
        String jdbcTypeName = child.getStringAttribute("jdbcType");
        String handlerTypeName = child.getStringAttribute("handler");
        Class<?> javaTypeClass = resolveClass(javaTypeName);
        JdbcType jdbcType = resolveJdbcType(jdbcTypeName);
        Class<?> typeHandlerClass = resolveClass(handlerTypeName);
        if (javaTypeClass != null) {
          if (jdbcType == null) {
            typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
          } else {
            typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
          }
        } else {
          typeHandlerRegistry.register(typeHandlerClass);
        }
      }
    }
  }
}

解析mapper节点里面的内容比较多,我们下个文章见

  • 10
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值