mybatis源码解读(二):XMLConfigBuilder详解

功能

XMLConfigBuilder要完成的最主要的功能其实就是解析xml配置文件,其中分为了这几步
第一步,是构建一个叫做 XPathParser 的类,这个类主要是负责对xml的声明校验以及将xml解析成为document,mybatis解析xml用的是原生的dom解析。
第二步,得到document之后就是configuration的构造,首先会通过XPathParser 解析document的每个节点,调用的是XPath将每个节点解析成为XNode
第三步,得到XNode之后就是解析配置文件中的配置信息,主要是解析properties(属性),settings(设置),typeAliases(类型别名),plugins(插件),objectFactory(实例化对象工厂),objectWrapperFactory(给对象设置工厂),reflectorFactory(反射元数据构建工厂),environments(环境配置),databaseIdProvider(数据库厂商标识),typeHandlers(类型处理器),mappers(映射器)这些标签并且将其放入到上下文configuration中

UML

在这里插入图片描述

时序图

在这里插入图片描述

代码解析

首先是构建 XPathParser
在SqlSessionFactoryBuilder去build的时候其实调用的是这里,会调用一个默认的XMLConfigBuilder多参构造,将文件流传入进来

public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
      // 构造 XMLConfigBuilder,properties传入的键值配置
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      // (parser.parse() 解析xml
      return build(parser.parse());
  }

XMLConfigBuilder的构造方法,这里会去创建XPathParser,并且传入一个XMLMapperEntityResolver,XMLMapperEntityResolver这个类是mybatis用来验证xml声明的,想要了解xml的声明怎么验证可以查看我之前的博客

public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) {
    this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props);
  }

XPathParser的构造方法主要完成了两个步骤,首先是设置validation 为true也就是打开验证,然后就是entityResolver 赋值,这里最关键的是构建XPath,之后是解析document,用的就是java自带的dom解析方式,代码也比较简单

public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
    // 构建初始参数,主要是构建XPath
    commonConstructor(validation, variables, entityResolver);
    // 解析document,用的dom解析
    this.document = createDocument(new InputSource(inputStream));
  }
  
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
    this.validation = validation;
    this.entityResolver = entityResolver;
    this.variables = variables;
    XPathFactory factory = XPathFactory.newInstance();
    this.xpath = factory.newXPath();
  }

private Document createDocument(InputSource inputSource) {
    // important: this must only be called AFTER common constructor
      DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
      factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
      factory.setValidating(validation);

      factory.setNamespaceAware(false);
      factory.setIgnoringComments(true);
      factory.setIgnoringElementContentWhitespace(false);
      factory.setCoalescing(false);
      factory.setExpandEntityReferences(true);

      DocumentBuilder builder = factory.newDocumentBuilder();
      builder.setEntityResolver(entityResolver);
      return builder.parse(inputSource);
 
  }

构建好XMLConfigBuilder之后就是parse()解析了,在parse()中有个 parsed字段,这个字段是用来标识当前configuration是否已经解析过了,防止重复解析的,可以看到在整个configuration解析完成后将parsed设置为true,这里有个有趣的地方是写死的一个字符串 “/configuration”,它会调用XPathParser的evalNode()方法去获取到document的节点,这也是为什么我们的mybatis配置文件的根节点要写成 configuration 了

public Configuration parse() {
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }

parseConfiguration()是一个重要的方法,在这个方法中会去解析在configuration配置的那些信息,比如properties等,从代码中可以看到各个解析动作是很清晰的

private void parseConfiguration(XNode root) {
    try {
      //解析各个节点的信息
      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);
      // read it after objectFactory and objectWrapperFactory issue #631
      environmentsElement(root.evalNode("environments"));
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      typeHandlerElement(root.evalNode("typeHandlers"));
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

我们随便进入一个方法里边看一下,发现他正常就是xnode解析,然后根据配置的信息找到这个类最终塞到全局的配置文件configuration中,configuration是mybatis里很重要的一个东西,全局上下文就一个,几乎所有的元数据信息都存储在它中

private void objectFactoryElement(XNode context) throws Exception {
    if (context != null) {
      String type = context.getStringAttribute("type");
      Properties properties = context.getChildrenAsProperties();
      ObjectFactory factory = (ObjectFactory) resolveClass(type).getDeclaredConstructor().newInstance();
      factory.setProperties(properties);
      configuration.setObjectFactory(factory);
    }
  }

settings

在XMLConfigBuilder中不得不说的一个地方就是settings文件的配置,settings其实还是有一些比较有趣的东西的,适当去配置一下可以调整mybatis的一直策略从而提升性能。
比如说你想让自定义嵌套结果也自动字段映射的话,可以在配置autoMappingBehavior为FULL,这样mybatis会自动将嵌套结果映射不过性能可能会有些损耗,但是方便。
再比如可以配置延迟加载lazyLoadingEnabled,这样只有用到了才会加载,可以适当提升性能,或者是aggressiveLazyLoading按需加载。
当然也可以这是默认的执行器,执行器是mybatis执行sql查询的核心,默认是simple,你可以设置为REUSE ,重用prepareStatement,这样就不用每次都去解析prepareStatement了。
最有趣的还是logPrefix这个参数,他可以为mybatis的日志添加一个前缀,这样查看起来日志也比较方便。
其实这些都是可以适当调整修改的,如果你有需要,可以仔细看看这一块他们分别所起的作用。

private void settingsElement(Properties props) {
    // 指定 MyBatis 应如何自动映射列到字段或属性。PARTIAL 只会自动映射没有定义嵌套结果映射的字段。
    configuration.setAutoMappingBehavior(AutoMappingBehavior.valueOf(props.getProperty("autoMappingBehavior", "PARTIAL")));
    // 指定发现自动映射目标未知列(或未知属性类型)的行为。NONE: 不做任何反应
    configuration.setAutoMappingUnknownColumnBehavior(AutoMappingUnknownColumnBehavior.valueOf(props.getProperty("autoMappingUnknownColumnBehavior", "NONE")));
    // 全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。默认为开启
    configuration.setCacheEnabled(booleanValueOf(props.getProperty("cacheEnabled"), true));
    // 指定 Mybatis 创建可延迟加载对象所用到的代理工具。JAVASSIST (MyBatis 3.3 以上)
    configuration.setProxyFactory((ProxyFactory) createInstance(props.getProperty("proxyFactory")));
    //延迟加载的全局开关。默认为关闭
    configuration.setLazyLoadingEnabled(booleanValueOf(props.getProperty("lazyLoadingEnabled"), false));
    // 开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载
    configuration.setAggressiveLazyLoading(booleanValueOf(props.getProperty("aggressiveLazyLoading"), false));
    // 是否允许单个语句返回多结果集,默认我true
    configuration.setMultipleResultSetsEnabled(booleanValueOf(props.getProperty("multipleResultSetsEnabled"), true));
    // 使用列标签代替列名。
    configuration.setUseColumnLabel(booleanValueOf(props.getProperty("useColumnLabel"), true));
    // 允许 JDBC 支持自动生成主键,需要数据库驱动支持。
    configuration.setUseGeneratedKeys(booleanValueOf(props.getProperty("useGeneratedKeys"), false));
    // 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(PreparedStatement); BATCH 执行器不仅重用语句还会执行批量更新。
    configuration.setDefaultExecutorType(ExecutorType.valueOf(props.getProperty("defaultExecutorType", "SIMPLE")));
    // 设置超时时间,它决定数据库驱动等待数据库响应的秒数。
    configuration.setDefaultStatementTimeout(integerValueOf(props.getProperty("defaultStatementTimeout"), null));
    // 为驱动的结果集获取数量(fetchSize)设置一个建议值。此参数只可以在查询设置中被覆盖。
    configuration.setDefaultFetchSize(integerValueOf(props.getProperty("defaultFetchSize"), null));
    // 指定语句默认的滚动策略。
    configuration.setDefaultResultSetType(resolveResultSetType(props.getProperty("defaultResultSetType")));
    // 是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。
    configuration.setMapUnderscoreToCamelCase(booleanValueOf(props.getProperty("mapUnderscoreToCamelCase"), false));
    // 是否允许在嵌套语句中使用分页
    configuration.setSafeRowBoundsEnabled(booleanValueOf(props.getProperty("safeRowBoundsEnabled"), false));
    // MyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION,会缓存一个会话中执行的所有查询。
    configuration.setLocalCacheScope(LocalCacheScope.valueOf(props.getProperty("localCacheScope", "SESSION")));
    // 当没有为参数指定特定的 JDBC 类型时,空值的默认 JDBC 类型。这里为OTHER,也可以设置为null
    configuration.setJdbcTypeForNull(JdbcType.valueOf(props.getProperty("jdbcTypeForNull", "OTHER")));
    // 指定对象的哪些方法触发一次延迟加载。
    configuration.setLazyLoadTriggerMethods(stringSetValueOf(props.getProperty("lazyLoadTriggerMethods"), "equals,clone,hashCode,toString"));
    // 是否允许在嵌套语句中使用结果处理器(ResultHandler)。如果允许使用则设置为 false。
    configuration.setSafeResultHandlerEnabled(booleanValueOf(props.getProperty("safeResultHandlerEnabled"), true));
    // 指定动态 SQL 生成使用的默认脚本语言。默认为 XMLLanguageDriver
    configuration.setDefaultScriptingLanguage(resolveClass(props.getProperty("defaultScriptingLanguage")));
    // 指定 Enum 使用的默认 TypeHandler 。
    configuration.setDefaultEnumTypeHandler(resolveClass(props.getProperty("defaultEnumTypeHandler")));
    // 指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法。
    configuration.setCallSettersOnNulls(booleanValueOf(props.getProperty("callSettersOnNulls"), false));
    configuration.setUseActualParamName(booleanValueOf(props.getProperty("useActualParamName"), true));
    // 当返回行的所有列都是空时,MyBatis默认返回 null。 当开启这个设置时,MyBatis会返回一个空实例。
    configuration.setReturnInstanceForEmptyRow(booleanValueOf(props.getProperty("returnInstanceForEmptyRow"), false));
    // 指定 MyBatis 增加到日志名称的前缀
    configuration.setLogPrefix(props.getProperty("logPrefix"));
    configuration.setConfigurationFactory(resolveClass(props.getProperty("configurationFactory")));
  }

XMLConfigBuilder做的事情还是比较中规中矩的,复杂的地方在于mapper映射器的解析,也就是在mapperElement中,里边的内容下一章会介绍。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值