mybatis源码-解析配置文件之配置文件Configuration解析

配置文件

mysql.properties

mysql.properties
这是存储数据库信息的对应文件。

mysql.driver=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/mybatis
mysql.username=root
mysql.password=123456

mybatis-config.xml

mybatis-config.xml
核心配置文件, 管理 mybatis 的运行行为。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <!-- 配置全局属性 -->
    <properties resource="mysql.properties"> </properties>
    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>

    <typeAliases>
    <!--<package name="com.example.maybatissource.model.StudentTable"/>-->
        <typeAlias  alias="StudentTable" type="com.example.maybatissource.model.StudentTable"/>
    </typeAliases>
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <!-- config params as the following -->
            <property name="param1" value="value1"/>
        </plugin>
    </plugins>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${mysql.driver}"/>
                <property name="url" value="${mysql.url}"/>
                <property name="username" value="${mysql.username}"/>
                <property name="password" value="${mysql.password}"/>
            </dataSource>
        </environment>
    </environments>


    <mappers>
        <mapper resource="mapper/student_table/StudentTableMapper.xml"/>
    </mappers>
</configuration>

Configuration 类及其解析

Configuration类对应的就是我们的 mybatis-config.xml 配置文件, 我们配置的信息经过解析后就会存储到Configuration的成员变量中。

解析入口

public class XMLConfigBuilder extends BaseBuilder {
    
    
	//省略其他代码
  public Configuration parse() {
    
    
    if (parsed) {
    
    
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    parsed = true;
    //通过parseConfiguration(parser.evalNode("/configuration"))得知,xml文件中的configuration中的内容已经全部加载到XNode中了,后续的相关配置数据的获取都是通过这个XNode来获取的。
    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);
      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"));
      settingsElement(settings);
      // read it after objectFactory and objectWrapperFactory issue #631
      //解析 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);
    }
  }
}

从上边可以看出来,在解析之前他会进行一个判断,这个东西是mybatis的一个保护机制,为了保证多线程情况下防止一个xml文件被解析两次,只有parsed为false的时候才能解析,保证线程安全,然后就从xml中的configuration标签进行解析,下边具体就可以看到的是咱们经常进行的一些配置类标签,在这些对象不断的解析中,一直不断的给configuration对象中进行注入.最终返回这个对象。然后parser.evalNode("/configuration")函数解析解析出 configuration节点, 在使用parseConfiguration函数解析获得旗下各个节点的信息。对应的是图中的根节点
1.0

常用函数

获取节点

其实就是使用 DOM 结合 Xpath 的方法获得各个节点。

  public XNode evalNode(String expression) {
    
    
    return evalNode(document, expression);
  }

而后返回相应的 XNode 类对象。

  public XNode evalNode(Object root, String expression) {
    
    
    Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
    if (node == null) {
    
    
      return null;
    }
    return new XNode(this, node, variables);
  }

最终调用的是xpath.evaluate(expression, root, returnType), 不理解这个过程的可参考之前的文章https://blog.csdn.net/qq_40913932/article/details/112801513

private Object evaluate(String expression, Object root, QName returnType) {
    
    
    try {
    
    
      return xpath.evaluate(expression, root, returnType);
    } catch (Exception e) {
    
    
      throw new BuilderException("Error evaluating XPath.  Cause: " + e, e);
    }
}

获取子节点

public List<XNode> getChildren() {
    
    
    List<XNode> children = new ArrayList<XNode>();
    NodeList nodeList = node.getChildNodes();
    if (nodeList != null) {
    
    
      for (int i = 0, n = nodeList.getLength(); i < n; i++) {
    
    
        Node node = nodeList.item(i);
        if (node.getNodeType() == Node.ELEMENT_NODE) {
    
    
          children.add(new XNode(xpathParser, node, variables));
        }
      }
    }
    return children;
}

实际上是调用了 DOM 中的 Node 类的 getChildNodes 函数, 并将返回的节点转换为 XNode , 添加到对应的 List 中返回。

获取子节点并存到 Properties 对象中

public Properties getChildrenAsProperties() {
    
    
    Properties properties = new Properties();
    for (XNode child : getChildren()) {
    
    
      String name = child.getStringAttribute("name");
      String value = child.getStringAttribute("value");
      if (name != null && value != null) {
    
    
        properties.setProperty(name, value);
      }
    }
    return properties;
}

这些子节点都含有 name 和 value 属性, 存 name->value 的形式。

节点相关成员变量及其解析

properties 属性相关

成员变量

public class Configuration {
    
    
	//省略其他代码
	protected Properties variables = new Properties();
}

对应 XML 节点:<properties>

<properties resource="mysql.properties"> </properties>

作用

如 mysql.properties 文件, 其内容如下:

mysql.driver=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql://localhost:3306/mybatis
mysql.username=root
mysql.password=jim666

在解析后, 存放在 variables 成员变量中, 是这样:
1.1
这样做的目的是我们可以将这些属性在后面的内容中复用:

<dataSource type="POOLED">
	 <property name="driver" value="${mysql.driver}"/>
     <property name="url" value="${mysql.url}"/>
     <property name="username" value="${mysql.username}"/>
     <property name="password" value="${mysql.password}"/>
 </dataSource>

解析过程

private void propertiesElement(XNode context) throws Exception {
    
    
    if (context != null) {
    
    
      // 先解析<properties>下的各个子节点, 以name->value的形式记录到 Properties 对象中
      Properties defaults = context.getChildrenAsProperties();
      // 解析 resource 和 url 属性
      String resource = context.getStringAttribute("resource");
      String url = context.getStringAttribute("url");
      // 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 或 url, 将对应的 .properties 文件加载进来, 并将内容全部存到 Properties 对象中
      if (resource != null) {
    
    
        defaults.putAll(Resources.getResourceAsProperties(resource));
      } else if (url != null) {
    
    
        defaults.putAll(Resources.getUrlAsProperties(url));
      } 
      // 将Configuration 对象中原先的属性合并进来。
      Properties vars = configuration.getVariables();
      if (vars != null) {
    
    
        defaults.putAll(vars);
      }
      // 两个含有该成员变量的类对象更新
      parser.setVariables(defaults);
      //把defaults放到了configuration的variables属性中,代表的是整个mybatis环境中所有的properties信息。这个信息可以在mybatis的配置文件中使用${key}使用,比如,${username},则会从configuration的variables中寻找key为username的属性值,并完成自动属性值替换。
      configuration.setVariables(defaults);
    }
}

总结:
1.先读取的是 <properties> 下的 <propertie> 属性;
2.properties 中, 不能同时存在 resource 和 url 属性;
3.更新时, 不单单只是更新 configuration, XPathParser 对象也需要更新, 后面用得到。

settings 属性相关

成员变量

public class Configuration {
    
    
	  //省略其他代码
	  protected boolean safeRowBoundsEnabled;
	  protected boolean safeResultHandlerEnabled = true;
	  protected boolean mapUnderscoreToCamelCase;
	  protected boolean aggressiveLazyLoading;
	  protected boolean multipleResultSetsEnabled = true;
	  protected boolean useGeneratedKeys;
	  protected boolean useColumnLabel = true;
	  protected boolean cacheEnabled = true;
	  protected boolean callSettersOnNulls;
	  protected boolean useActualParamName = true;
	  protected boolean returnInstanceForEmptyRow;
	
	  protected String logPrefix;
	  protected Class <? extends Log> logImpl;
	  protected Class <? extends VFS> vfsImpl;
	  protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
	  protected JdbcType jdbcTypeForNull = JdbcType.OTHER;
	  protected Set<String> lazyLoadTriggerMethods = new HashSet<String>(Arrays.asList(new String[] {
    
     "equals", "clone", "hashCode", "toString" }));
	  protected Integer defaultStatementTimeout;
	  protected Integer defaultFetchSize;
	  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
	  protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
	  protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;
	  
	  protected boolean lazyLoadingEnabled = false;
	  protected ProxyFactory proxyFactory = new JavassistProxyFactory();
	  
	  protected Class<?> configurationFactory;
  }

成员变量名称和以上配置文件(mybatis-config.xml)的 name 属性一一对应。

对应 XML 节点:<settings>

<settings>
    <setting name="logImpl" value="LOG4J"/>
</settings>

在 XML 文件中, 节点内其实有很多 节点, 但是他们都有默认值, 因此一般情况下, 我们只需要配置一些我们需要改变的配置即可。

一个配置完整的 settings 元素的示例如下:

<settings>
  <!--允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为false。-->
  <setting name="safeRowBoundsEnabled" value="false"/>
  <!--允许在嵌套语句中使用分页(ResultHandler)。如果允许使用则设置为false。-->
  <setting name="safeResultHandlerEnabled" value="true"/>
  <!--是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。 -->
  <setting name="mapUnderscoreToCamelCase" value="false"/>
  <!--当开启时,任何方法的调用都会加载该对象的所有属性。否则,每个属性会按需加载(参考lazyLoadTriggerMethods).  (true in ≤3.4.1) -->
  <setting name="aggressiveLazyLoading" value="false"/>
  <!--是否允许单一语句返回多结果集(需要兼容驱动)。-->
  <setting name="multipleResultSetsEnabled" value="true"/>
  <!--允许 JDBC 支持自动生成主键,需要驱动兼容。 如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能兼容但仍可正常工作(比如 Derby)。-->
  <setting name="useGeneratedKeys" value="false"/>
  <!--使用列标签代替列名。不同的驱动在这方面会有不同的表现, 具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。-->
  <setting name="useColumnLabel" value="true"/>
  <!--全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存。 -->
  <setting name="cacheEnabled" value="true"/>
  <!--指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这对于有 Map.keySet() 依赖或 null 值初始化的时候是有用的。注意基本类型(int、boolean等)是不能设置成 null 的。-->
  <setting name="callSettersOnNulls" value="false"/>
  <!--允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的工程必须采用Java 8编译,并且加上-parameters选项。(从3.4.1开始)-->
  <setting name="useActualParamName" value="true"/>
  <!--当返回行的所有列都是空时,MyBatis默认返回null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集 (i.e. collectioin and association)。(从3.4.2开始) -->
  <setting name="returnInstanceForEmptyRow" value="false"/>
  <!--增加到日志名称的前缀, 默认没有-->
  <setting name="logPrefix" value=""/>
  <!--日志的实现类型, 默认未指定, 未指定时将自动查找-->
  <setting name="logImpl" value=""/>
  <!--指定VFS的实现, 默认未指定-->
  <setting name="vfsImpl" value=""/>
  <!--默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。 -->
  <setting name="localCacheScope" value="SESSION"/>
  <!--当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型-->
  <setting name="jdbcTypeForNull" value="OTHER"/>
  <!-- 	指定哪个对象的方法触发一次延迟加载。-->
  <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
  <!--设置超时时间,它决定驱动等待数据库响应的秒数。-->
  <setting name="defaultStatementTimeout" value="25"/>
  <!--为驱动的结果集获取数量(fetchSize)设置一个提示值。此参数只可以在查询设置中被覆盖。 -->
  <setting name="defaultFetchSize" value="100"/>
  <!--配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。-->
  <setting name="defaultExecutorType" value="SIMPLE"/>
  <!--指定 MyBatis 应如何自动映射列到字段或属性-->
  <setting name="autoMappingBehavior" value="PARTIAL"/>
  <!--指定发现自动映射目标未知列(或者未知属性类型)的行为-->
  <setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
  <!--延迟加载的全局开关-->
  <setting name="lazyLoadingEnabled" value="false"/>
  <!--指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具-->
  <setting name="proxyFactory" value="JAVASSIST"/>
  <!--指定一个提供Configuration实例的类-->
  <setting name="configurationFactory" value=""/>
</settings>

更具体的内容, 参见 mybatis简介http://www.mybatis.org/mybatis-3/zh/configuration.html#settings

作用

settings 是 MyBatis 中极为重要的设置,它们会改变 MyBatis 的运行时行为

解析过程

private Properties settingsAsProperties(XNode context) {
    
    
    if (context == null) {
    
    
      return new Properties();
    }
    // 解析所有的子节点,把<setting name="" value="">标签解析为Properties对象。
    Properties props = context.getChildrenAsProperties();
    // Check that all settings are known to the configuration class
    // 创建对应的 MetaClass 对象
    MetaClass metaConfig = MetaClass.forClass(Configuration.class, localReflectorFactory);
    // 检测 Configuraion 对象中是否定义了相应的 setter 方法, 不定义代表不存在该属性, 直接抛异常
    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;
}

具体解析https://blog.csdn.net/qq_40913932/article/details/113116266

typeAliases 相关属性

成员变量

public class Configuration {
    
    
	protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
}

对应 XML 节点:<typeAliases>

typeAliases, 即类型别名。类型别名是为 Java 类型设置一个短的名字。它只和 XML 配置有关,存在的意义仅在于用来减少类完全限定名的冗余。

作用

<typeAliases>
          <typeAlias  alias="StudentTable" type="com.example.maybatissource.model.StudentTable"/>
</typeAliases>

类似以上配置, 当需要使用com.example.maybatissource.model.StudentTable时, 我们可以用StudentTable来进行代替。

当开启包扫描之后, 在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。

解析过程

private void typeAliasesElement(XNode parent) {
    
    
    if (parent != null) {
    
    
      // 处理全部的子类
      for (XNode child : parent.getChildren()) {
    
    
        // 处理 <package> 子节点
        if ("package".equals(child.getName())) {
    
    
          // 获取配置处的属性 name , 其对应着包名
          String typeAliasPackage = child.getStringAttribute("name");
          configuration.getTypeAliasRegistry().registerAliases(typeAliasPackage);
        } else {
    
    
          // 处理 <typeAlias>节点
          String alias = child.getStringAttribute("alias");
          String type = child.getStringAttribute("type");
          try {
    
    
            Class<?> clazz = Resources.classForName(type);
            if (alias == null) {
    
    
              // 如果alias没有配置,则按照约定的方式注册类
              typeAliasRegistry.registerAlias(clazz);
            } else {
    
    
              // alias 配置了, 则将类注册到alias
              typeAliasRegistry.registerAlias(alias, clazz);
            }
          } catch (ClassNotFoundException e) {
    
    
            throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
          }
        }
      }
    }
}

plugins 相关属性

成员变量

public class Configuration {
    
    
	protected final InterceptorChain interceptorChain = new InterceptorChain();
}

对应 XML 节点:<plugins>

<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor">
        <!-- config params as the following -->
        <property name="param1" value="value1"/>
	</plugin>
</plugins>

作用

插件是 MyBatis 提供的扩展机制之一,用户可以通过添加自定义插件在 SQL 语句执行过程中的某一点进行拦截。
详解: link

解析过程

private void pluginElement(XNode parent) throws Exception {
    
    
    if (parent != null) {
    
    
      // 遍历 <pluagins> 下的所有 <plugin> 节点
      for (XNode child : parent.getChildren()) {
    
    
        // 获取对应的 <plugin> 中的 interceptor 属性
        String interceptor = child.getStringAttribute("interceptor");
        // 获取 <plugin> 下的所有 <property> 节点, 并以 name->value 的形式存入 Properties 对象中
        Properties properties = child.getChildrenAsProperties();
        // 把配置的interceptor全限类名解析为一个Class对象,然后通过反射调用其默认的构造方法获得一个实例
        Interceptor interceptorInstance = (Interceptor) resolveClass(interceptor).newInstance();
        // 设置拦截器的属性
        interceptorInstance.setProperties(properties);
        // 将拦截器添加到 Configuration 对象中,这里使用了责任链模式
        configuration.addInterceptor(interceptorInstance);
      }
    }
}

我们来看看configuration.addInterceptor(interceptorInstance);的源码是怎样的,如何使用了责任链模式:

public class Configuration {
    
    
	//省略其他代码
	protected final InterceptorChain interceptorChain = new InterceptorChain();
	 
	public void addInterceptor(Interceptor interceptor) {
    
    
    interceptorChain.addInterceptor(interceptor);
  }
}

/**
 * @author Clinton Begin
 */
public class InterceptorChain {
    
    

  private final List<Interceptor> interceptors = new ArrayList<>();

  public Object pluginAll(Object target) {
    
    
    for (Interceptor interceptor : interceptors) {
    
    
      target = interceptor.plugin(target);
    }
    return target;
  }

  //调用了这个方法,将拦截器对象添加到List<Interceptor> interceptors集合中
  public void addInterceptor(Interceptor interceptor) {
    
    
    interceptors.add(interceptor);
  }

  public List<Interceptor> getInterceptors() {
    
    
    return Collections.unmodifiableList(interceptors);
  }

}

责任链模式: https://blog.csdn.net/qq_40913932/article/details/112951006

objectFactory 相关属性

成员变量

public class Configuration {
    
    
	protected ObjectFactory objectFactory = new DefaultObjectFactory();
}

对应 XML 节点:<objectFactory>

<objectFactory type="org.mybatis.example.ExampleObjectFactory">
  <property name="someProperty" value="100"/>
</objectFactory>

作用

MyBatis 每次创建结果对象的新实例时,它都会使用一个对象工厂(ObjectFactory)实例来完成。 默认的对象工厂需要做的仅仅是实例化目标类,要么通过默认构造方法,要么在参数映射存在的时候通过参数构造方法来实例化。 如果想覆盖对象工厂的默认行为,则可以通过创建自己的对象工厂来实现。

解析过程

private void objectFactoryElement(XNode context) throws Exception {
    
    
    if (context != null) {
    
    
      // 获取 <objectFactory> 的type属性
      String type = context.getStringAttribute("type");
      // 获取 <plugin> 下的所有 <property> 节点, 并以 name->value 的形式存入 Properties 对象中
      Properties properties = context.getChildrenAsProperties();
      // 通过反射生成对象
      ObjectFactory factory = (ObjectFactory) resolveClass(type).newInstance();
      // 设置属性
      factory.setProperties(properties);
      // 将 ObjectFactory 对象设置到 Configuration 对象中
      configuration.setObjectFactory(factory);
    }
}

objectWrapperFactory 相关属性

成员变量

public class Configuration {
    
    
	protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();
}

对应 XML 节点:<objectWrapperFactory>

作用

启用之后, 可以自己实现驼峰效果。

解析过程

private void objectWrapperFactoryElement(XNode context) throws Exception {
    
    
    if (context != null) {
    
    
      // 获取 <objectWrapperFactory> 的type属性
      String type = context.getStringAttribute("type");
      // 通过反射生成 ObjectWrapperFactory 对象
      ObjectWrapperFactory factory = (ObjectWrapperFactory) resolveClass(type).newInstance();
      // 将 ObjectWrapperFactory 对象设置到 Configuration 对象中
      configuration.setObjectWrapperFactory(factory);
    }
}

reflectorFactory 相关属性

成员变量

public class Configuration {
    
    
	  protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
}

对应 XML 节点:<reflectorFactory>

<reflectorFactory type=""/>

作用

可以自己定义反射类

解析过程

private void reflectorFactoryElement(XNode context) throws Exception {
    
    
    if (context != null) {
    
    
       // 获取 <objectWrapperFactory> 的type属性
       String type = context.getStringAttribute("type");
       // 通过反射生成 ReflectorFactory 对象
       ReflectorFactory factory = (ReflectorFactory) resolveClass(type).newInstance();
        // 将 ReflectorFactory 对象设置到 Configuration 对象中
       configuration.setReflectorFactory(factory);
    }
}

environments 相关属性

成员变量

public class Configuration {
    
    
	protected Environment environment;
}

Enviroment 中, 含有id, 事务, 数据源:

public final class Environment {
    
    
  private final String id;
  private final TransactionFactory transactionFactory;
  private final DataSource dataSource;
  ...
}

对应 XML 节点:<environments>

<environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${mysql.driver}"/>
                <property name="url" value="${mysql.url}"/>
                <property name="username" value="${mysql.username}"/>
                <property name="password" value="${mysql.password}"/>
            </dataSource>
        </environment>
    </environments>

Environment主要用于配置数据源和事务信息。Mybatis支持多环境设置,可以为开发,测试,生产使用不同的配置。

作用

就是设置数据库连接的环境, 需要配置事务管理器和数据源来构造相应的对象。
既然environments可以配置多个environment, 那么为什么成员变量的类型不是List?

答: 多个环境,每个 SqlSessionFactory 实例只能选择其一

解析过程

private void environmentsElement(XNode context) throws Exception {
    
    
    if (context != null) {
    
    
      // 未指定, 则使用 <environments>中name是default(key)的值(value)
      if (environment == null) {
    
    
        environment = context.getStringAttribute("default");
      }
      // 遍历所有的 <environment> 子节点
      for (XNode child : context.getChildren()) {
    
    
        String id = child.getStringAttribute("id");
        // 只有 id 与 XMLConfigBuilder 的 environment 匹配上才会进行解析里面的内容
        if (isSpecifiedEnvironment(id)) {
    
    
          // 创建 TransactionFactory 对象
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
          // 创建 DataSourceFactory 对象, 并以之创建 DataSource 对象
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          DataSource dataSource = dsFactory.getDataSource();
          // 创建 Environment.Builder 对象, 将以上产生的对象对应设置到该对象中
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
          // environmentBuilder.build()创建 Environment 对象, 并设置到 Configuration 对象对应的成员变量中
          configuration.setEnvironment(environmentBuilder.build());
        }
      }
    }
  }

databaseIdProvider 相关属性

成员变量

public class Configuration {
    
    
	 protected String databaseId;
}

对应 XML 节点:<databaseIdProvider>

<databaseIdProvider type="DB_VENDOR" />

作用

MyBatis 可以根据不同的数据库厂商执行不同的语句,这种多厂商的支持是基于映射语句中的 databaseId 属性。在后续的 XXXXmapper.xml 文件中, 可以指定 databaseId。

解析过程

private void databaseIdProviderElement(XNode context) throws Exception {
    
    
    DatabaseIdProvider databaseIdProvider = null;
    if (context != null) {
    
    
      String type = context.getStringAttribute("type");
      // awful patch to keep backward compatibility
      // 为了兼容, 更改 type 为 VENDOR 至 DB_VENDOR
      if ("VENDOR".equals(type)) {
    
    
          type = "DB_VENDOR";
      }
      // 解析对应的配置信息
      Properties properties = context.getChildrenAsProperties();
      // 通过反射生成对象
      databaseIdProvider = (DatabaseIdProvider) resolveClass(type).newInstance();
      // 设置属性
      databaseIdProvider.setProperties(properties);
    }
    Environment environment = configuration.getEnvironment();
    
    if (environment != null && databaseIdProvider != null) {
    
    
      String databaseId = databaseIdProvider.getDatabaseId(environment.getDataSource());
      configuration.setDatabaseId(databaseId);
    }
  }

typeHandlers 相关属性

成员变量

public class Configuration {
    
    
	protected final TypeHandlerRegistry typeHandlerRegistry;
}

对应 XML 节点:<typeHandlers>

<typeHandlers>
  <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>

作用

无论是 MyBatis 在预处理语句(PreparedStatement)中设置一个参数时,还是从结果集中取出一个值时, 都会用类型处理器将获取的值以合适的方式转换成 Java 类型。

解析过程

private void typeHandlerElement(XNode parent) throws Exception {
    
    
    if (parent != null) {
    
    
      // 遍历所有的子节点
      for (XNode child : parent.getChildren()) {
    
    
        // 包(package)子节点
        if ("package".equals(child.getName())) {
    
    
          String typeHandlerPackage = child.getStringAttribute("name");
          typeHandlerRegistry.register(typeHandlerPackage);
        } else {
    
     // 非 package 子节点
          // 获取相应的属性
          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);
          // 将对象注册到 typeHandlerRegistry 对象中
          if (javaTypeClass != null) {
    
    
            if (jdbcType == null) {
    
    
              typeHandlerRegistry.register(javaTypeClass, typeHandlerClass);
            } else {
    
    
              typeHandlerRegistry.register(javaTypeClass, jdbcType, typeHandlerClass);
            }
          } else {
    
    
            typeHandlerRegistry.register(typeHandlerClass);
          }
        }
      }
    }
  }

mappers 相关属性

成员变量

public class Configuration {
    
    
	//映射的语句
	protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
      .conflictMessageProducer((savedValue, targetValue) ->
          ". please check " + savedValue.getResource() + " and " + targetValue.getResource());
    // 缓存集合
	protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
	// 结果集合
	protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
	// 存储过程参数集合
	protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
	// 主键生成器合集
	protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");
	
	protected final Set<String> loadedResources = new HashSet<String>();
	protected final Map<String, XNode> sqlFragments = new StrictMap<XNode>("XML fragments parsed from previous mappers");
	
	// 存储不完整的语句
	protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<XMLStatementBuilder>();
	protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<CacheRefResolver>();
	protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<ResultMapResolver>();
	protected final Collection<MethodResolver> incompleteMethods = new LinkedList<MethodResolver>();
}

对应 XML 节点:

<mappers>
    <package name="com.homejim.mybatis.mapper"/>
</mappers>

作用

mybatis-config.xml 配置文件中的<mappers>节点会告诉 MyBatis 去哪些位置查找映射配置文件以及使用了配置注解标识的接口。这也是配置文件解析的重点

解析过程

 private void mapperElement(XNode parent) throws Exception {
    
    
    if (parent != null) {
    
    
      for (XNode child : parent.getChildren()) {
    
    
        // <package> 子节点, 就是包下面的类
        if ("package".equals(child.getName())) {
    
    
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
    
     // 对应 <mapper> 子节点
          // resource / url / class 中 3选1
          String resource = child.getStringAttribute("resource");
          String url = child.getStringAttribute("url");
          String mapperClass = child.getStringAttribute("class");
          // 如果节点指定了 resource 或 url, 则创建 XMLMapperBuilder 对象, 
          // 通过该对象的 parse() 函数将 mapper 添加到 configuration 中
          if (resource != null && url == null && mapperClass == null) {
    
    
          	// 配置一:使用 resource 类路径
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
             // 创建 XMLMapperBuilder 对象
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            // 解析 xxxMapper.xml 
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
    
    
          	// 配置二: 使用 url 绝对路径
            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 类名
          	// 通过反射创建对象
            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.");
          }
        }
      }
    }
  }

借鉴于:https://www.cnblogs.com/homejim/p/9672224.html#3-configuration-%E7%B1%BB%E5%8F%8A%E5%85%B6%E8%A7%A3%E6%9E%90

猜你喜欢

转载自blog.csdn.net/qq_40913932/article/details/112840913
今日推荐