写于:2020-01-06 08:52:37

《Mybatis技术内幕》一书中提到 Mybatis 可以分为三层结构:接口层、核心处理层、基础支持层。它们之间的关系如下图:

Mybatis整体结构分析_整体架构图

其中 “基础支持层” 是整个 Mybatis 得以运行的基础。

# 基础支持-配置文件解析

源码分析,只走主线,对细节感兴趣可以自己深入。

Mybatis 中所有基础配置的加载以配置文件为主,通过加载配置文件来构建基础数据,包括数据源、mapper.xml 信息、mybatis 运行基础参数配置等信息。

来看看个别信息在配置文件中的配置(此处取名为:mybatis-config.xml)

mybatis-configxml配置信息

“基础支持层” 的主要功能,加载并解析 mybaits-config.xml 文件,并将这些配置信息整合到 Configuration 对象中。

# mybatis 基础配置加载 构建 Configuration 对象

代码入口:XMLConfigBuilder#parse

先来看看简版的时序图

mybatis-configxml解析简图

整个 mybatis-config.xml 配置文件的解析在 XMLConfigBuilder 中完成。 XMLConfigBuilder 将解析之后的数据,根据规约构造成相应的 JAVA 对象。 最后组合成一个复杂对象 Configuration。

XMLConfigBuilder 采用了建造者模型进行复杂对象 Configuration 的构建。

# mybaits-config.xml 配置文件根据解析特点可以分为三种

  • 1、直接解析标签,然后构建 JAVA 对象,最后调用 Configuration的 XXX方法进行赋值。 (这也是大部分的标签的解析方式)

比如:<properties> 的解析、<plugins> 的解析 、<environments> 的解析。 以 <environment> 的解析方式为例:

public class XMLConfigBuilder extends BaseBuilder {
  /**
   * <p> 解析 <environments> 标签 </p>
   *
   * 配置信息参考: mybatis-config.xml
   *
   * @param context
   * @throws Exception
   */
  private void environmentsElement(XNode context) throws Exception {
    if (context != null) {
      // 没有指定配置环境时【如:dev,test】,默认为:default
      if (environment == null) {
        environment = context.getStringAttribute("default");
      }
      // 遍历解析 <environments> 下的所有子节点
      // 多个环境配置,就有多个子节点【如:dev,test环境】
      for (XNode child : context.getChildren()) {
        // 获取当前节点的配置环境【如:dev,test】
        String id = child.getStringAttribute("id");
        // 匹配环境中的配置是否有符合本次配置的要求
        // 如果 environment=dev,匹配是否存在 id=dev 的环境配置
        if (isSpecifiedEnvironment(id)) {
          // 解析 <transactionManager> 加载 事务管理工厂
          TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager"));
          // 解析 <dataSource> 加载 数据源工厂
          DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource"));
          // 获取数据源
          DataSource dataSource = dsFactory.getDataSource();
          // 根据数据源 + 事务管理工厂 构建环境对象 Environment
          Environment.Builder environmentBuilder = new Environment.Builder(id)
              .transactionFactory(txFactory)
              .dataSource(dataSource);
          // 环境设值到 Configuratino 中
          configuration.setEnvironment(environmentBuilder.build());
        }
      }
    }
  }
}
  • 2、直接解析标签,构建 JAVA 对象,直接赋值给 Configuration 的引用对象。

例如 <typeAliases><typeHandlers> 的解析使用了该方式。

<typeAliases> 的解析方式为例:

public class XMLConfigBuilder extends BaseBuilder {
  /**
   * <p> typeAliases 标签解析</p>
   * @param parent
   */
  private void typeAliasesElement(XNode parent) {
    // <typeAliases> 节点标签存在
    if (parent != null) {
      // 获取 <typeAliases> 下的所有子节点,并进行遍历
      for (XNode child : parent.getChildren()) {
        // 如果是 <package > 子节点
        if ("package".equals(child.getName())) {
          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) {
              typeAliasRegistry.registerAlias(clazz);
            } else {
              typeAliasRegistry.registerAlias(alias, clazz);
            }
          } catch (ClassNotFoundException e) {
            throw new BuilderException("Error registering typeAlias for '" + alias + "'. Cause: " + e, e);
          }
        }
      }
    }
  }
}

其中 typeAliasRegistry 持有的是 Configuration 的引用。 typeAliasRegistry 在 构造 XMLConfigBuilder 对象时,调用其父类 BaseBuilder 构造函数时进行应用赋值

public abstract class BaseBuilder {
  public BaseBuilder(Configuration configuration) {
    this.configuration = configuration;
    // 指向 Configuration 中的引用
    this.typeAliasRegistry = this.configuration.getTypeAliasRegistry();
    this.typeHandlerRegistry = this.configuration.getTypeHandlerRegistry();
  }
}
  • 3、直接将 <mappers> 的解析归为一类。(单纯因为其解析复杂且重要而进行划分) 虽然 <mappers> 标签的解析较为复杂,mapper 相关的解析是由 XMLMapperBuilder 进行信息的。但是最后同样是将解析的信息构造成 Java 对象,然后赋值给 Configuration 对象。

# Configuration 对象中常见的配置对象

# <environment> 解析后抽象的Java对象

<environment> 解析后构建的对象为 Environment

类图如下: Environment类图

其中 中包含了:

  • 数据源-DataSource
  • 事务工厂-TransactionFactory

由于 Environment 的存在,Configuration 拥有了数据库操作的能力。如:

  • 获取数据库Connection
  • 开启数据库事务

# <typeHandlers> 解析后抽象的Java对象

<typeHandlers> 解析后构建的对象为 TypeHandlerRegistry

TypeHandlerRegistry 提供了 Java 对象与 Mysql 字段类型间的转换关系。其功能:

  • 查询时 Mysql 字段类型需要映射成 Java 的那种对象类型。如:VARCHAR 类型对应的 JAVA 类型为 String
  • 进行插入操作数据库时, Java 类型需要转换成那种 Mysql 字段类型。如:Enum 类型插入时,需要 ordinal(int 类型) 还是 name(String类型)。

在使用 Mybatis 时一般不需要关心 类类型和数据库类型之间的转换,因为 Mybatis 本身已经提供了大部分场景所需要的转换关系,相关代码:

public final class TypeHandlerRegistry {
  public TypeHandlerRegistry() {
    register(Boolean.class, new BooleanTypeHandler());
    register(boolean.class, new BooleanTypeHandler());
    register(JdbcType.BOOLEAN, new BooleanTypeHandler());
    register(JdbcType.BIT, new BooleanTypeHandler());
    ......
  }
}

当需要定制化需求时,可以通过在 mybatis-config.xml 总配置 <typeHandlers> 来加载我们的定制化配置。

由于 TypeHandlerRegistry 的存在,Configuration 能够提供数据库字段类型和程序Java对象类型间的转换能力。

# <plugin> 解析后抽象的Java对象

<plugin> 解析后构建的对象为 InterceptorChain 。来看看其代码:

public class InterceptorChain {
  private final List<Interceptor> interceptors = new ArrayList<Interceptor>();
}

InterceptorChain 保存了 Interceptor 列表。

Interceptor 是 Mybatis 提供的埋点,或者说是扩展点。通过 Interceptor 能够在 SQL 语句执行调用过程中的某一点进行拦截处理。

Interceptor 能够拦截的方法调用有:

  • Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler (getParameterObject, setParameters)
  • ResultSetHandler (handleResultSets, handleOutputParameters)
  • StatementHandler (prepare, parameterize, batch, update, query)

通过 InterceptorChain ,用户可以根据自身需求拦截特定的方法节点而实现定制化的功能。如:SQL 打印、分页插件的实现等。

# <mappers> 解析后抽象的Java对象

XMLConfigBuilder 对于 <mappers> 的解析仅仅只是将需要加载的 Mapper.xml 加载。而真正进行解析的是 XMLMapperBuilder

对于 Mapper.xml 的解析较为复杂,解析之后产生的对象也比较多。

下面来看看 Mapper.xml 文件解析后个别信息构建的对象在 Configuration 中的呈现

# 缓存相关- Mapper.xml 中的 <cache-ref><cache>

  • <cache-ref> 在 Configuration 中通过

    // key:namespace
    // value: <cache-ref> 解析得到的值(另一个 namespace)
    Map<String, String> cacheRefMap = new HashMap<String, String>();
    

    保存了所有 Mapper.xml 文件的 <cache-ref> 的解析数据。

  • <cache> <cache> 解析之后得到 Cache 对象。该对象指定了当前 Mapper.xml 文件所使用的二级缓存策略以及其他相关参数信息。 在 Configuration 中通过

    // key: namespace
    Map<String, Cache> caches = new StrictMap<Cache>
    

    保存了所有 Mapper.xml 文件的 <cache> 的解析数据。

<cache-ref><cache> 为“核心处理层” 实现二级缓存提供了数据基础。

# 结果集映射:<resultMap>

在 Configuration 中通过

// key: <resultMap> 中配置参数 id值
Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");

保存了所有 Mapper.xml 文件的 <resultMap> 的解析数据。

该结果集数据集为 “核心处理层” 在执行完 SQL 语句后的结果集映射提供基础数据。

# 通用 SQL:<sql>

在 Configuration 中通过

// key: namespace +( <sql> 上的id参数值 )
Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");

保存了所有 Mapper.xml 文件中 <sql> 的解析数据

该通用SQL数据集为 “核心处理层” 在拼凑执行 SQL 时提供基础数据

# 真正的SQL语句:<select|insert|update|delete>

<select|insert|update|delete> 的解析由 XMLStatementBuilder 完成

Mybaits 将 <select|insert|update|delete> 解析后的数据抽象成 MappedStatement 对象。

在 Configuration 中通过

// key:namespace + <select|insert|update|delete>上的 id 参数
Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");

保存了所有 Mapper.xml 文件中 所有增删改查的 SQL 语句集

该SQL语句集合为 “核心处理层” 提供了执行 SQL 语句。

小贴士:当存在二级缓存时,MappedStatement 也会存储一份 <cache> 的解析数据

# 总结

“基础支持层-配置文件解析” 加载了关于 Mybatis 运行时的所有需要的配置信息。如:

  • Mybatis 运行参数
  • Mysql 链接信息、数据源信息及事务管理器。
  • Mybatis 执行所需要的 SQL、结果映射集合、类型转换集合。
  • Mybatis 定制拦截插件 plugin 等信息。

以上这些信息拼凑起了整个 Mybatis 操作数据的基础数据。

精彩内容推送,请关注公众号!
最近更新时间: 3/24/2020, 9:44:42 PM