Mybatis加载配置信息

Mybatis加载配置信息

            //读取配置文件
            InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
            //通过SqlSessionFactoryBuilder的build模式获取SqlSessionFactory
            SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder().build(inputStream);

获取SqlSessionFactory是通过SqlSessionFactoryBuilder通过build 方法创建一个XMLConfigBuilder,在创建的时候就会初始化Configuration对象,这是一些别名会被注册到Configuration的typeAliasRegistry中。最终会调用XMLConfigBuilder类中的parseConfiguration方法,但是在使用这个方法之前,会在parse方法里面判断,配置文件只加载一次。根据配置文件的加载顺序,将各参数依次加载到Configuration配置类里面。

XMLConfigBuilder有一个父类,BaseBuilder,几个实现类各司其职。
在这里插入图片描述

XMLConfigBuilder用来解析Mybatis的配置文件

XMLMapperBuilder用来及解析Mybatis中的映射文件(xxxMapeer.xml)

XMLStatementBuilder用来解析映射文件中的sql

MapperBuilderAssistant,主要是XMLMapperBuilder的协作者。也是提供最终put到mappedStatements方法

XMLScriptBuilder 解析xml中各个节点sql部分的Builder。

public Configuration parse() {
    // 只能解析一次
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    parseConfiguration(parser.evalNode("/configuration"));
    return configuration;
  }
  private void parseConfiguration(XNode root) {
    try {
      //issue #117 read properties first
      // 解析 properties 节点
      propertiesElement(root.evalNode("properties"));
      // 解析 settings 节点
      Properties settings = settingsAsProperties(root.evalNode("settings"));
      loadCustomVfs(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"));
      settingsElement(settings);
      // 解析 environments 节点, 需要在 objectFactory 和 objectWrapperFactory才能读取
      environmentsElement(root.evalNode("environments"));
      // 解析 databaseIdProvider 节点
      databaseIdProviderElement(root.evalNode("databaseIdProvider"));
      // 解析 typeHandlers 节点
      typeHandlerElement(root.evalNode("typeHandlers"));
      // 解析 mappers 节点
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }

properties

<!-- 方法一: 从外部指定properties配置文件, 除了使用resource属性指定外,还可通过url属性指定url  -->   
<properties resource="dbConfig.properties"></properties>
    <properties url="file:///E:/Mybatis/src/main/resources/jdbcConfig.properties"></properties>
   <!-- 方法二: 直接配置为xml -->
        <properties>
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/test1"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </properties>

properties 属性可以给系统配置一些运行参数。url和resource只能存在一个。无论使用的是url或者是resource,xml类型配置的都优先加载。

settings

setting 标签的配置是配置 MyBatis 框架运行时的一些行为的,例如缓存、延迟加载、结果集控制、执行器、分页设置、命名规则、驼峰命名映射等一系列控制性参数,其所有的 setting 配置都放在父标签 settings 标签中。

typeAliases

由于类的全限定名称很长,需要大量使用的时候,总写那么长的名称不方便。在 MyBatis 中允许定义一个简写来代表这个类,这就是别名,别名分为系统定义别名和自定义别名。这些别名都会被存入configurationtypeAliasRegistry容器中。

 <typeAliases>
      <!--
      通过package, 可以直接指定package的名字, mybatis会自动扫描你指定包下面的javabean,
      并且默认设置一个别名,默认的名字为: javabean 的首字母小写的非限定类名来作为它的别名。
      也可在javabean 加上注解@Alias 来自定义别名, 例如: @Alias(user) 
      <package name="xx.xx.entity"/>
       -->
      <typeAlias alias="UserEntity" type="xxx.xxx.xxx.User"/>
  </typeAliases>

plugins

是一个可选配置,主要是为了实现自定义插件。可以拦截Executor 、StatementHandler 、ParameterHandler 、ResultSetHandler 的部分方法,处理我们自己的逻辑。一个插件拦截器如果全部拦截,执行顺序为Executor -> StatementHandler -> ParameterHandler -> StatementHandler -> ResultSetHandler

插件功能的实现,其实就是通过代理模式,代理上面四个对象,最终执行代理的invoke方法。

同个拦截对象,多个插件的执行顺序。主要是取决于生成代理对象的顺序。

/**
 * 责任链模式
 * 存放plugins的 所有字标签中Class全路径名的反射实例化对象
 */
public class InterceptorChain {

  /**
   * 所有拦截器的实例化对象
   */
  private final List<Interceptor> interceptors = new ArrayList<>();

  /**
   * 将目标对象 target 通过代理方式包裹责任链,目标对象执行前,会先走一遍拦截器的过滤链
   * target可以是Executor 、StatementHandler 、ParameterHandler 、ResultSetHandler
   * */
  public Object pluginAll(Object target) {
    for (Interceptor interceptor : interceptors) {
      //将目标对象进行 plugin 的 代理包装
      target = interceptor.plugin(target);
    }
    return target;
  }
  /**
   * 添加拦截器
   *
   * @param interceptor
   */
  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }
  /**
   * 获取拦截器列表
   *
   * @return
   */
  public List<Interceptor> getInterceptors() {
    return Collections.unmodifiableList(interceptors);
  }
}

上面的源码中,可以看到通过遍历interceptors列表对target 对象进行包装。但是此时interceptors列表最开始的插件会最先包裹到target 对象外层。所以看到的执行顺序是和配置的顺序完全相反的。
在这里插入图片描述

objectFactory

在创建返回结果集的时候,MyBatis 会使用一个对象工厂来完成创建这个结果集实例。默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。在默认的情况下,MyBatis 会使用其定义的对象工厂DefaultObjectFactory(org.apache.ibatis.reflection.factory.DefaultObjectFactory)来完成对应的工作。MyBatis 允许注册自定义的 ObjectFactory。如果自定义,则需要实现接口 org.apache.ibatis.reflection.factory.ObjectFactory,并给予配置。在大部分的情况下,我们都不需要自定义返回规则,因为这些比较复杂而且容易出错,在更多的情况下,都会考虑继承系统已经实现好的 DefaultObjectFactory ,通过一定的改写来完成我们所需要的工作。

objectWrapperFactory

reflectorFactory

Mybatis的反射模块,位于org.apache.ibatis.reflection包中,它对常见的反射操作做了进一步的封装,提供简单易用的API。

每一个Reflector对象都对应一个类,在Reflector中缓存了反射操作需要使用的类的元信息。

environments

可以配置多个environment,会根据environments节点中默认的环境加载environment节点。主要是创建DataSourceTransactionFactory放置到Environment这个类中,最终赋值给configuration对象。事务, mybatis有两种:JDBC 和 MANAGED, 配置为JDBC则直接使用JDBC的事务,配置为MANAGED则是将事务托管给容器,

databaseIdProvider

Mybatis会根据配置的数据厂商执行不同的语句。这种多厂商的支持是基于映射语句中的 databaseId 属性。 MyBatis 会加载不带 databaseId 属性和带有匹配当前数据库 databaseId 属性的所有语句。 如果同时找到带有 databaseId 和不带 databaseId 的相同语句,则只会使用带databaseId 的。

 <databaseIdProvider type="DB_VENDOR">
        <property name="MySQL" value="mysql" />
        <property name="Oracle" value="oracle" />
    </databaseIdProvider>

typeHandlers

<typeHandlers>
      <!-- 
          当配置package的时候,mybatis会去配置的package扫描TypeHandler
          <package name="xxx.xx.demo"/>
       -->
      <!-- handler属性直接配置我们要指定的TypeHandler -->
      <typeHandler handler=""/>
      <!-- javaType 配置java类型,例如String, 如果配上javaType, 那么指定的typeHandler就只作用于指定的类型 -->
      <typeHandler javaType="" handler=""/>
      <!-- jdbcType 配置数据库基本数据类型,例如varchar, 如果配上jdbcType, 那么指定的typeHandler就只作用于指定的类型  -->
      <typeHandler jdbcType="" handler=""/>
      <!-- 也可两者都配置 -->
      <typeHandler javaType="" jdbcType="" handler=""/>
  </typeHandlers>

无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时,都会用类型处理器将获取的值以合适的方式转换成 Java 类型。Mybatis默认为我们实现了许多TypeHandler, 当我们没有配置指定TypeHandler时,Mybatis会根据参数或者返回结果的不同,默认为我们选择合适的TypeHandler处理。

mappers

 <mappers>
      <!-- 第一种方式:通过resource指定 -->
    <mapper resource="com/dy/dao/userDao.xml"/>
     <!-- 第二种方式, 通过class指定接口,进而将接口与对应的xml文件形成映射关系
             不过,使用这种方式必须保证 接口与mapper文件同名(不区分大小写), 
             我这儿接口是UserDao,那么意味着mapper文件为UserDao.xml 
     <mapper class="com.dy.dao.UserDao"/>
      -->
      <!-- 第三种方式,直接指定包,自动扫描,与方法二同理 
      <package name="com.dy.dao"/>
      -->
      <!-- 第四种方式:通过url指定mapper文件位置
      <mapper url="file://........"/>
       -->
  </mappers>
  • MyBatis会遍历<mappers>下所有的子节点,如果当前遍历到的节点是<package>,则MyBatis会将该包下的所有Mapper Class注册到configurationmapperRegistry容器中。

  • 如果当前节点为<mapper>,则会依次获取resource、url、class属性,解析映射文件,并将映射文件对应的Mapper Class注册到configurationmapperRegistry容器中。

  • 在解析sql之前会创建XMLMapperBuilder

//解析<mapper> 节点
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();

private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) {
  // 将configuration赋给BaseBuilder
  super(configuration);
  // 创建MapperBuilderAssistant对象(该对象为MapperBuilder的协助者)
  this.builderAssistant = new  MapperBuilderAssistant(configuration, resource);
  this.parser = parser;
  this.sqlFragments = sqlFragments;
  this.resource = resource;
}

public void parse() {
  // 若当前的Mapper.xml尚未被解析,则开始解析
  // PS:若<mappers>节点下有相同的<mapper>节点,那么就无需再次解析了
  if (!configuration.isResourceLoaded(resource)) {
    // 解析<mapper>节点
    configurationElement(parser.evalNode("/mapper"));
    // 将该Mapper.xml添加至configuration的LoadedResource容器中,下回无需再解析
    configuration.addLoadedResource(resource);
    // 将该Mapper.xml对应的Mapper Class注册进configuration的mapperRegistry容器中
    bindMapperForNamespace();
  }

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

//最终解析mapper节点的地方
private void configurationElement(XNode context) {
try {
  // 获取<mapper>节点上的namespace属性,该属性必须存在,表示当前映射文件对应的Mapper Class是谁
  String namespace = context.getStringAttribute("namespace");
  if (namespace == null || namespace.equals("")) {
    throw new BuilderException("Mapper's namespace cannot be empty");
  }
  // 将namespace属性值赋给builderAssistant
  builderAssistant.setCurrentNamespace(namespace);
  // 解析<cache-ref>节点
  cacheRefElement(context.evalNode("cache-ref"));
  // 解析<cache>节点
  cacheElement(context.evalNode("cache"));
  // 解析<parameterMap>节点
  parameterMapElement(context.evalNodes("/mapper/parameterMap"));
  // 解析<resultMap>节点
  resultMapElements(context.evalNodes("/mapper/resultMap"));
  // 解析<sql>节点
  sqlElement(context.evalNodes("/mapper/sql"));
  // 解析sql语句      
  buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
  throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}

buildStatementFromContext这个方法最终XMLStatementBuilder将处理的sql、结果集、参数、是否缓存、是否有嵌套的结果集封装到MappedStatement最后会MapperBuilderAssistant.``addMappedStatement方法put到mappedStatements这个Map里面。key是命名空间+mapperId

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值