MyBatis源码解析(一)——构建流程

1、MyBatis整体架构

Mybatis架构图
本次解析使用的MyBatis版本为3.4.6。

MyBatis最上层是接口层,接口层用来给开发人员在Mapper或者Dao接口中定义进行哪种操作,查询、新增、更新或删除。

中间层是数据处理层,主要是配置Mapper接口 -> XML文件之间的参数映射、SQL解析、SQL执行、结果映射等过程。

最下层是基础支持层,用来提供功能支撑,包括连接管理、事务管理、配置加载、缓存处理等。

1.1、接口层

单独使用 MyBatis 执行数据库的操作时,常见代码如下:

InputStream is = Resources.getResourceAsStream("spring/mybatis-config.xml");
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(is);
SqlSession session = factory.openSession();
User user = session.selectOne("org.xxx.dao.IUserDao.queryUserInfoById", 1L);

SqlSessionFactoryBuilder、SqlSessionFactory 和 SqlSession 是 MyBatis 主要的接口类,下面章节进行详细介绍。

1.2、数据处理层

配置解析

在MyBatis初始化过程中,会加载 mybatis-config.xml 配置文件、映射配置文件以及 Mapper接口中的注解信息,解析后的配置信息生成相应的对象,保存在 Configuration 对象中,之后根据该对象创建 SqlSessionFactory 对象。待 MyBatis 初始化完成后,可以通过 SqlSessionFactory 对象进行数据库操作。

SQL解析

MyBatis 中的 scripting 模块会根据用户传入的参数,解析映射文件中定义的动态 SQL节点,生成数据库可执行的 SQL语句。

SQL执行

MyBatis层级架构图
MyBatis层级结构如上图所示,先简单介绍一下各个组件:

  • SqlSession:它是 MyBatis 的核心API,主要用来执行命令,获取映射,管理事务。接收开发人员提供的StatementId和参数,并返回操作结果。
  • Executor:执行器,是 MyBatis 调度的核心,负责SQL语句的生成以及查询缓存的维护。
  • StatementHandler:用于封装JDBC中Statement的操作。即声明SQL、设置参数、执行SQL等。通常一次SQL执行完之后就要关闭它,最多它的生命周期也不会超过一次事务的范围。
  • ParameterHandler:负责对用户传递过来的参数转换成JDBC Statement所需要的参数。
  • ResultSetHandler:负责将 JDBC 返回的ResultSet结果集对象转换成List类型的集合。
  • TypeHandler:用于 Java类型和 JDBC类型之间的转换。
  • BoundSql:表示动态生成的 SQL语句以及相应的参数信息。
  • MappedStatement:动态SQL 的封装,存储了一个 SQL所有的信息。
  • SqlSource:表示从 XML文件或注解方法读取的映射语句的内容,它创建将从用户接收的输入参数传递给数据库的SQL。
  • Configuration:MyBatis 所有的配置信息都维持在 Configuration 对象中。

1.3、基础支持层

类型转换模块

主要有两个功能,一是 MyBatis 的别名机制,能够简化配置文件。二是实现 JDBC类型与Java类型的转换,在 SQL语句绑定参数时,会将数据由 Java类型转换为 JDBC类型,在映射结果时,会将数据由 JDBC类型转换为 Java类型。

解析器模块

主要有两个功能,一是封装了XPath,为 MyBatis 初始化时解析 mybatis-config.xml 配置文件以及 xxxMapper.xml 映射配置文件提供支持。二是为处理动态 SQL语句中的占位符提供支持。

事务管理模块

一般地,Mybatis 与 Spring 框架集成,由 Spring 框架管理事务。

缓存模块

MyBatis中有一级缓存和二级缓存,这两级缓存与 MyBatis 以及整个应用是运行在同一个JVM中的,共享同一块内存,如果这两级缓存的数据量较大,则会影响系统的其他功能。缓存大量数据时,优先考虑Redis等缓存。

Binding模块

在调用 SqlSession 相应方法执行数据库操作时,需要指定映射文件中定义的 SQL节点,如果 SQL 中出现了指定失败的情况,那只能在运行时发现。为了可以尽早发现这种错误,MyBatis 通过 Binding模块将用户自定义的 Mapper接口与映射文件关联起来,系统可以通过调用 Mapper接口中的方法执行相应的 SQL语句完成数据库操作。在开发中,我们只是创建了 Mapper接口,而没有其实现类,这是因为 MyBatis 自动为 Mapper接口创建了动态代理对象。


2、解析配置文件

2.1、概述

常见的 mybatis-config.xml 配置文件如下所示:

<?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>
    <settings>
        <setting name="cacheEnabled" value="true"/>
        <setting name="lazyLoadingEnabled" value="true"/>
        <setting name="aggressiveLazyLoading" value="false"/>
        <setting name="multipleResultSetsEnabled" value="true"/>
        <setting name="useColumnLabel" value="true"/>
        <setting name="defaultExecutorType" value="SIMPLE"/>
    </settings>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="xxx"/>
                <property name="username" value="xxx"/>
                <property name="password" value="xxx"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <package name="com.xxx.mapper"/>
    </mappers>

</configuration>

配置文件中配置了数据库连接信息、处理器(声明处理器、参数处理器、结果映射器等)、mapper配置文件的包路径等信息,从第一部分 MyBatis 执行数据库操作的代码中可以看出,MyBatis 使用 SqlSessionFactoryBuilder 中的 build 方法去解析配置文件,那么接下来从源码的角度进行分析 MyBatis 是怎么解析的。

2.2、SqlSessionFactoryBuilder源码解析

从文件名可以看出,SqlSessionFactoryBuilder 使用的是建造者模式,方便根据传入不同的属性去生成 SqlSessionFactory,优点为各个建造者方法之间相互独立,利于系统扩展。

public class SqlSessionFactoryBuilder {

  // 通过文件输入流生成SqlSessionFactory
  public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }

  public SqlSessionFactory build(InputStream inputStream, String environment) {
    return build(inputStream, environment, null);
  }

  public SqlSessionFactory build(InputStream inputStream, Properties properties) {
    return build(inputStream, null, properties);
  }

  // 具体生成SqlSessionFactory的方法,其他方法最终都调用该方法
  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      // 新建Configuration对象的建造者类
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      // 先使用XMLConfigBuilder的parse方法构建Configuration对象,然后调用下边的build方法
      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.
      }
    }
  }
  
  // 使用Configuration对象生成SqlSessionFactory
  public SqlSessionFactory build(Configuration config) {
    // 返回新建的DefaultSqlSessionFactory
    return new DefaultSqlSessionFactory(config);
  }
  ...
}

从源码中可以看出,使用 XMLConfigBuilder 的 parse 去解析配置文件,那我们先从 XMLConfigBuilder 源码里去分析。

2.2.1、XMLConfigBuilder源码解析

public class XMLConfigBuilder extends BaseBuilder {

  public Configuration parse() {
    // 如果已经解析过了,抛出异常
    if (parsed) {
      throw new BuilderException("Each XMLConfigBuilder can only be used once.");
    }
    // 设置解析标识为ture
    parsed = true;
    // 解析配置文件中configuration节点的内容,即从根节点解析
    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"));
      // 加载自定义VFS
      loadCustomVfs(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"));
      // (核心)解析mapper文件
      mapperElement(root.evalNode("mappers"));
    } catch (Exception e) {
      throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
    }
  }
}

从源码可以看出,依次解析配置文件中的各个标签节点,如 properties、settings、plugins 等,其中最重要的就是解析标签,加载的所有配置都会保存在 Configuration 对象中,所以,我们先看下 Configuration 对象。

2.2.1.1、Configuration对象
public class Configuration {

  protected Environment environment;

  // 允许在嵌套语句中使用分页(RowBounds),允许则设置为false,默认为false
  protected boolean safeRowBoundsEnabled;
  // 允许在嵌套语句中使用分页(ResultHandler),允许则设置为false
  protected boolean safeResultHandlerEnabled = true;
  // 是否开启自动驼峰命名规则映射,即从数据库列名到Java属性名的映射
  protected boolean mapUnderscoreToCamelCase;
  // 当开启时,任何方法的调用都会加载该对象的所有属性,否则每个属性按需加载,默认为false
  protected boolean aggressiveLazyLoading;
  // 是否允许单一语句返回多结果集(需要兼容驱动)
  protected boolean multipleResultSetsEnabled = true;
  // 允许JDBC支持自动生成主键,需要兼容驱动,默认值为true
  protected boolean useGeneratedKeys;
  // 使用列标签替代列名
  protected boolean useColumnLabel = true;
  // 是否启用缓存,默认是开启的
  protected boolean cacheEnabled = true;
  // 指定当结果集中值为null时,是否调用映射对象的setter方法
  protected boolean callSettersOnNulls;
  // 允许使用方法签名中的名称作为语句参数名称,使用该特性,必须采用Java8编译,且加上-parameters选项
  protected boolean useActualParamName = true;
  // 当返回行的所有列都是空的,MyBatis默认返回null。当开启这个设置时,MyBatis会返回一个空实例
  protected boolean returnInstanceForEmptyRow;

  // 指定MyBatis增加到日志名称的前缀
  protected String logPrefix;
  // 指定MyBatis所有日志的具体实现,未指定时将自动查找。一般使用log4j
  protected Class <? extends Log> logImpl;
  // 指定VFS的实现,VFS是MyBatis提供的用于访问AS内资源的一个简便接口
  protected Class <? extends VFS> vfsImpl;
  // MyBatis利用本地缓存机制(Local Cache)防止循环引用或加速重复嵌套查询。
  // 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。
  protected LocalCacheScope localCacheScope = LocalCacheScope.SESSION;
  // 当没有为参数提供特定的JDBC类型时,为空值指定JDBC类型
  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;
  // 设置默认的执行器。
  // SIMPLE:普通的执行器
  // REUSE:执行器会重用预处理语句(prepared statements)
  // BATCH:执行器将重用语句并执行批量更新
  protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
  // 指定MyBatis应该如何自动映射列到字段或属性。
  // NONE:取消自动映射
  // PARTIAL:只会自动映射没有定义嵌套结果集映射的结果集
  // PARTIAL:会自动映射任意复杂的结果集,无论是否嵌套
  protected AutoMappingBehavior autoMappingBehavior = AutoMappingBehavior.PARTIAL;
  // 指定发现自动映射目标未知列(或者未知属性类型)的行为
  protected AutoMappingUnknownColumnBehavior autoMappingUnknownColumnBehavior = AutoMappingUnknownColumnBehavior.NONE;

  // setttings下的properties属性
  protected Properties variables = new Properties();
  // 反射器工厂,用于操作属性、构造器
  protected ReflectorFactory reflectorFactory = new DefaultReflectorFactory();
  // 对象工厂,所有的resultMap类都需要依赖对象工厂来实例化
  protected ObjectFactory objectFactory = new DefaultObjectFactory();
  // 对象包装工厂,主要用来创建非原生对象,比如增加了某些监控或者特殊属性的代理类
  protected ObjectWrapperFactory objectWrapperFactory = new DefaultObjectWrapperFactory();

  // 延迟加载的全局开关,当开启时,所有关联对象都会延迟加载
  protected boolean lazyLoadingEnabled = false;
  // 指定MyBatis创建具有延迟加载能力的对象所用到的代理工具,MyBatis3.3+使用JAVASSIST
  protected ProxyFactory proxyFactory = new JavassistProxyFactory(); // #224 Using internal Javassist instead of OGNL

  // MyBatis可以根据不同的数据库厂商执行不同的语句,多厂商支持基于映射语句中的databaseId属性
  protected String databaseId;
  
  protected Class<?> configurationFactory;

  // 存储动态SQL
  protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
  // 存储缓存
  protected final Map<String, Cache> caches = new StrictMap<Cache>("Caches collection");
  // 存储ResultMap
  protected final Map<String, ResultMap> resultMaps = new StrictMap<ResultMap>("Result Maps collection");
  // 存储ParameterMap
  protected final Map<String, ParameterMap> parameterMaps = new StrictMap<ParameterMap>("Parameter Maps collection");
  // 存储KeyGenerator,密钥生成器
  protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<KeyGenerator>("Key Generators collection");

 ...
  
  // 内部类,集成HashMap,用来存储SQL语句、缓存、resultmap等
  protected static class StrictMap<V> extends HashMap<String, V> {

    private static final long serialVersionUID = -4950446264854982944L;
    private final String name;

    public StrictMap(String name, int initialCapacity, float loadFactor) {
      super(initialCapacity, loadFactor);
      this.name = name;
    }
		...

    @SuppressWarnings("unchecked")
    public V put(String key, V value) {
      if (containsKey(key)) {
        throw new IllegalArgumentException(name + " already contains value for " + key);
      }
      if (key.contains(".")) {
        final String shortKey = getShortName(key);
        if (super.get(shortKey) == null) {
          super.put(shortKey, value);
        } else {
          super.put(shortKey, (V) new Ambiguity(shortKey));
        }
      }
      return super.put(key, value);
    }

    public V get(Object key) {
      V value = super.get(key);
      if (value == null) {
        throw new IllegalArgumentException(name + " does not contain value for " + key);
      }
      if (value instanceof Ambiguity) {
        throw new IllegalArgumentException(((Ambiguity) value).getSubject() + " is ambiguous in " + name
            + " (try using the full name including the namespace, or rename one of the entries)");
      }
      return value;
    }

    ...
  }
}
2.2.1.2、解析mappers标签——mapperElement

常见的mappers标签包含以下四种方式,分别为映射文件相对路径、绝对路径、Mapper接口全路径、映射文件包名路径。

<mappers>
  <mapper resource="xxx/xxx/xxxMapper.xml"/>
  <mapper url="file:./xxx/xxx/xxxMapper.xml"/>
  <mapper class="xxx.xxx.xxxMapper"/>
  <package name="xxx.xxx.mapper"/>
</mappers>
private void mapperElement(XNode parent) throws Exception {
  if (parent != null) {
    // 遍历mappers子节点标签
    for (XNode child : parent.getChildren()) {
      // 如果为package标签
      if ("package".equals(child.getName())) {
        // 获取name属性的值,即映射文件包名路径
        String mapperPackage = child.getStringAttribute("name");
        // 添加到configuration对象的mappers
        configuration.addMappers(mapperPackage);
      } else {
        // 类路径
        String resource = child.getStringAttribute("resource");
        // url绝对路径
        String url = child.getStringAttribute("url");
        // java类名
        String mapperClass = child.getStringAttribute("class");
        if (resource != null && url == null && mapperClass == null) {
          // 解析类路径
          ErrorContext.instance().resource(resource);
          InputStream inputStream = Resources.getResourceAsStream(resource);
          // 每次循环都新建XMLMapperBuilder进行解析,并且对应的配置对象也封装在configuration中
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
          mapperParser.parse();
        } else if (resource == null && url != null && mapperClass == null) {
          // 解析url绝对路径
          ErrorContext.instance().resource(url);
          InputStream inputStream = Resources.getUrlAsStream(url);
          // 和上述一样,使用XMLMapperBuilder进行解析
          XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
          mapperParser.parse();
        } else if (resource == null && url == null && mapperClass != null) {
          // 解析java类名
          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.");
        }
      }
    }
  }
}

通过源码可以了解到,会对 mappers 标签里的子标签进行遍历,对于资源标识为 package 和 class 的,使用 configuration 的 addMappers 和 addMapper 方法解析;资源标识为 resource 和 url 的,每个 mapper 子标签都会新建一个 XMLMapperBuilder 去解析对应的 mapper 映射文件,下面分别看一下 Configuration的addMappers 和 XMLMapperBuilder 的 parse 方法。

2.2.1.3、mapper注册器——MapperRegistry

mapper 注册器用于将所有的 mapper接口添加到内存中,Mapper 注册器自身维护着两个属性,config 和 knownMappers。knownMappers 表示某个类路径对应的 Mapper 代理工厂,MapperProxyFactory 是通过代理模式创建处一个 MapperProxy,MapperProxy 实现了 InvocationHandler 接口,这表示 MapperProxy 会通过 invoke() 方法实现 Mapper 接口指定方法的调用。

public class Configuration {
  ...
  protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
  ...
  // 根据mapper映射文件包名路径,进行解析,调用的是MapperRegistry的addMappers方法
	public void addMappers(String packageName) {
    mapperRegistry.addMappers(packageName);
  }
}  

public class MapperRegistry {

  private final Configuration config;
  // 维护了接口类与代理工厂的映射关系
  private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

  public <T> void addMapper(Class<T> type) {
    // 对应MyBatis mapper接口文件,必须是interface,不能是class
    if (type.isInterface()) {
      // 判断是否加载过
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        // 为mapper接口创建一个MapperProxyFactory代理
        knownMappers.put(type, new MapperProxyFactory<T>(type));
        // 使用MapperAnnotationBuilder进行解析
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }
  
  public void addMappers(String packageName, Class<?> superType) {
    // MyBatis框架提供的搜索classpath下指定package以及子package中符合条件(注解或继承某个类/接口)的类
    // 默认使用Thread.currentThread().getContextClassLoader()返回的加载器
    ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<Class<?>>();
    // 无条件的加载所有类,因为调用方传递了Object.class作为父类,这也给以后的指定mapper接口预留了余地
    resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
    // 所有匹配的class都被存储在ResolverUtil.matchaes字段中
    Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
    for (Class<?> mapperClass : mapperSet) {
      // 调用addMapper方法进行具体的mapper类/接口解析
      addMapper(mapperClass);
    }
  }
  
  // Configuration对象调用该方法,传入mapper映射文件包名路径
  public void addMappers(String packageName) {
    addMappers(packageName, Object.class);
  }
}

通过源码可以了解到,对于资源标识为 package 和 class 的,使用 configuration 的 addMapper 方法解析,最后会调用 mapper 注册器(MapperRegistry)的 addMapper 方法,会去解析指定的 mapper 接口。接下来进一步分析解析指定 mapper 接口的源码,mapper 注解构造器(MapperAnnotationBuilder)。

2.2.1.4、mapper注解构造器——MapperAnnotationBuilder

MapperAnnotationBuilder 就是解析指定的 mapper 接口对应的 Class 对象,包含 MyBatis 框架定义的注解,并生成 Cache、ResultMap、MappedStatement 三种类型对象,不过会优先解析 mapper 映射文件,mapper 映射文件必须与 Class 对象所在的包路径一致,且文件名要与类名一致。

public class MapperAnnotationBuilder {

  // 只有4个元素:@Select、@Insert、@Update、@Delete,sql语句保存在注解中
  private final Set<Class<? extends Annotation>> sqlAnnotationTypes = new HashSet<Class<? extends Annotation>>();
  // 只有4个元素:@SelectProvider、@InsertProvider、@UpdateProvider、@DeleteProvider,sql语句保存在注解指定的类的指定方法中
  private final Set<Class<? extends Annotation>> sqlProviderAnnotationTypes = new HashSet<Class<? extends Annotation>>();

  // 核心配置对象
  private final Configuration configuration;
  // Mapper构建助手,用于组装解析出来的配置,生成Cache、ResultMap、MappedStatement等对象
  private final MapperBuilderAssistant assistant;
  // 要解析的mapper接口Class对象
  private final Class<?> type;

  // 解析的mapper接口Class对象
  public void parse() {
    // Class对象的唯一标识
    String resource = type.toString();
    // 如果当前对象已经解析过,则不再解析
    if (!configuration.isResourceLoaded(resource)) {
      // 加载并解析mapper接口对应的mapper映射文件,即xxMapper.xml
      loadXmlResource();
      // 把Class对应的标识添加到已加载的资源列表中
      configuration.addLoadedResource(resource);
      // 设置当前namespace为接口Class的全限定名
      assistant.setCurrentNamespace(type.getName());
      // 解析缓存对象
      parseCache();
      // 解析缓存引用,会覆盖以前解析的缓存对象
      parseCacheRef();
      // 获取所有方法,解析方法上的注解,生成MappedStatement和ResultMap
      Method[] methods = type.getMethods();
      // 遍历获取到的所有方法
      for (Method method : methods) {
        try {
          // issue #237
          if (!method.isBridge()) {
            // 解析一个方法生成对应的MappedStatement对象
            parseStatement(method);
          }
        } catch (IncompleteElementException e) {
          configuration.addIncompleteMethod(new MethodResolver(this, method));
        }
      }
    }
    // 解析挂起的方法
    parsePendingMethods();
  }

	/**
   * 解析指定的mapper映射文件
   * MapperAnnotationBuilder每解析一个mapper映射文件,都会以文件所在路径为mapper配置文件的唯一标识,并把标识添加到已加载的资源文件列表中
   */
  private void loadXmlResource() {
    // 若是已加载资源列表中指定key已存在,则不再解析xml文件,资源名称为namespace:全限定名
    if (!configuration.isResourceLoaded("namespace:" + type.getName())) {
      // 根据Class对象生成mapper配置文件路径
      String xmlResource = type.getName().replace('.', '/') + ".xml";
      InputStream inputStream = null;
      try {
        // 获取文件字节流
        inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource);
      } catch (IOException e) {
        // ignore, resource is not required
      }
      if (inputStream != null) {
        // 使用XMLMapperBuilder进行解析
        XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName());
        xmlParser.parse();
      }
    }
  }

  ...

  // 解析注解生成新的ResultMap对象
  private String parseResultMap(Method method) {
    // 获取目标bean的类型
    Class<?> returnType = getReturnType(method);
    // 获取方法上的@ConstructorArgs注解
    ConstructorArgs args = method.getAnnotation(ConstructorArgs.class);
    // 获取方法上的@Results
    Results results = method.getAnnotation(Results.class);
    // 获取方法上的@TypeDiscriminator注解
    TypeDiscriminator typeDiscriminator = method.getAnnotation(TypeDiscriminator.class);
    // 根据方法生成resultMap的惟一标识,格式为:类全限定名.方法名-参数类型简单名称
    String resultMapId = generateResultMapName(method);
    // resultMapId和返回值类型已经解析完毕,再解析剩下的构造方法映射、属性映射和鉴别器,以后添加结果映射到配置对象中
    applyResultMap(resultMapId, returnType, argsIf(args), resultsIf(results), typeDiscriminator);
    return resultMapId;
  }

	// 解析方法上的注解,MyBatis可以以注解方式编写SQL
  void parseStatement(Method method) {
    // 获取输入参数的类型,这个参数没用,由于如今都是以输入参数对象为根,经过ognl表达式寻值的
    Class<?> parameterTypeClass = getParameterType(method);
    // 经过方法上的@Lang注解获取语言驱动
    LanguageDriver languageDriver = getLanguageDriver(method);
    // 经过方法上的@Select等注解获取SqlSource
    SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
    // 若是成功建立了SqlSource,则继续
    if (sqlSource != null) {
      // 获取方法上的@Options注解
      Options options = method.getAnnotation(Options.class);
      // 映射语句id为类的全限定名.方法名
      final String mappedStatementId = type.getName() + "." + method.getName();
      Integer fetchSize = null;
      Integer timeout = null;
      StatementType statementType = StatementType.PREPARED;
      ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
      // 经过注解获取Sql命令类型
      SqlCommandType sqlCommandType = getSqlCommandType(method);
      boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
      boolean flushCache = !isSelect;
      boolean useCache = isSelect;

      KeyGenerator keyGenerator;
      String keyProperty = "id";
      String keyColumn = null;
      // 若是是insert或update命令
      if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
        // 首先检查@SelectKey注解 ,它会覆盖任何其余的配置
				// 获取方法上的SelectKey注解
        SelectKey selectKey = method.getAnnotation(SelectKey.class);
        // 若是存在@SelectKey注解
        if (selectKey != null) {
          keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
          keyProperty = selectKey.keyProperty();
        } else if (options == null) {
          keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
        } else {
          keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
          keyProperty = options.keyProperty();
          keyColumn = options.keyColumn();
        }
      } else {
        // 其余sql命令均没有键生成器
        keyGenerator = NoKeyGenerator.INSTANCE;
      }

      if (options != null) {
        if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
          flushCache = true;
        } else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
          flushCache = false;
        }
        useCache = options.useCache();
        fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
        timeout = options.timeout() > -1 ? options.timeout() : null;
        statementType = options.statementType();
        resultSetType = options.resultSetType();
      }

      // 处理方法上的@ResultMap注解
      String resultMapId = null;
      // 获取注解,@ResultMap注解表明引用已经存在的resultMap对象的id
      ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
      // 若是方法上存在@ResultMap注解,则生成引用id便可
      if (resultMapAnnotation != null) {
        // 获取指定的多个resultMapId
        String[] resultMaps = resultMapAnnotation.value();
        StringBuilder sb = new StringBuilder();
        // 遍历String数组,拼接为一个String,逗号分隔
        for (String resultMap : resultMaps) {
          if (sb.length() > 0) {
            sb.append(",");
          }
          sb.append(resultMap);
        }
        resultMapId = sb.toString();
        // 不存在@ResultMap注解,且语句为select类型,则经过解析@Args、@Results等注解生成新的ResultMap对象
      } else if (isSelect) {
        // 生成新的ResultMap对象,加入Configuration配置对象,同时返回resultMapId
        resultMapId = parseResultMap(method);
      }

      // 构建MappedStatement并添加到配置对象中
      assistant.addMappedStatement(
          mappedStatementId,
          sqlSource,
          statementType,
          sqlCommandType,
          fetchSize,
          timeout,
          // ParameterMapID
          null,
          parameterTypeClass,
          resultMapId,
          getReturnType(method),
          resultSetType,
          flushCache,
          useCache,
          // TODO gcode issue #577
          false,
          keyGenerator,
          keyProperty,
          keyColumn,
          // DatabaseID
          null,
          languageDriver,
          // ResultSets
          options != null ? nullOrEmpty(options.resultSets()) : null);
    }
  }
  ...
}

总结一下解析流程:

1、判断当前 class 对象是否解析过,如果没有解析过,才进行解析。
2、先解析 class 对象对应的 mapper 映射文件,先 key 放置到已加载资源列表中,key = namespace:全限定名,然后使用 XMLMapperBuilder 进行解析(看到这里大家是否还有印象,在 mappers 标签里的子标签进行遍历时,对于资源标识为 resource 和 url 的,也是通过 XMLMapperBuilder 去解析,殊途共归了)
3、解析方法上的注解,因为 MyBatis 可以以注解方式编写 SQL,所以这里获取这些 SQL 生成 MappedStatement 对象

下面开始分析解析 mapper 映射文件的源码 XMLMapperBuilder。

2.2.1.5、mapper映射文件构造器——XMLMapperBuilder
public void parse() {
  // 如果没有加载过,才会去加载
  if (!configuration.isResourceLoaded(resource)) {
    // 配置mapper
    configurationElement(parser.evalNode("/mapper"));
    // 标记已经加载过了
    configuration.addLoadedResource(resource);
    // 绑定映射器到namespace
    bindMapperForNamespace();
  }

  // 可以忽略
  parsePendingResultMaps();
  parsePendingCacheRefs();
  parsePendingStatements();
}

private void configurationElement(XNode context) {
  try {
    // 获取mapper文件中的namespace属性,配置namespace
    String namespace = context.getStringAttribute("namespace");
    if (namespace == null || namespace.equals("")) {
      throw new BuilderException("Mapper's namespace cannot be empty");
    }
    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(定义可重用的SQL代码段)
    sqlElement(context.evalNodes("/mapper/sql"));
    // 配置select|insert|update|delete,根据前面的sql片段创建在Mapper中真正的配置对象MappedStatement
    buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
  } catch (Exception e) {
    throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
  }
}

// 传入 select|insert|update|delete 标签的节点列表进行构建 MappedStatement
private void buildStatementFromContext(List<XNode> list) {
  // 判断databaseId
  if (configuration.getDatabaseId() != null) {
    buildStatementFromContext(list, configuration.getDatabaseId());
  }
  buildStatementFromContext(list, null);
}

private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
  // 遍历所有 select|insert|update|delete 标签节点列表
  for (XNode context : list) {
    // 使用XMLStatementBuilder进行解析
    final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
    try {
      statementParser.parseStatementNode();
    } catch (IncompleteElementException e) {
      // sql语句有问题的,放置在configuration中
      configuration.addIncompleteStatement(statementParser);
    }
  }
}

从源码中看出,将 mapper 映射文件中的 select、insert、update、delete 标签解析,使用的是 XMLStatementBuilder 中的 parseStatementNode 方法,下面来看这个方法:

2.2.1.6、mapper映射文件声明构造器——XMLStatementBuilder
public class XMLStatementBuilder extends BaseBuilder {

  private final MapperBuilderAssistant builderAssistant;
  private final XNode context;
  private final String requiredDatabaseId;

	...

  // 这里解析select、insert、update、delete 标签
  public void parseStatementNode() {
    // 获取id属性
    String id = context.getStringAttribute("id");
    // 获取databaseId属性
    String databaseId = context.getStringAttribute("databaseId");

    // 如果databaseId不匹配,返回
    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    // 驱动程序每次批量返回的结果行数
    Integer fetchSize = context.getIntAttribute("fetchSize");
    // 超时时间
    Integer timeout = context.getIntAttribute("timeout");
    // parameterMap,已废弃
    String parameterMap = context.getStringAttribute("parameterMap");
    // 参数类型
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);
    // 结果集
    String resultMap = context.getStringAttribute("resultMap");
    // 结果类型
    String resultType = context.getStringAttribute("resultType");
    // 脚本语言,mybatis3.2的新功能 不重要
    String lang = context.getStringAttribute("lang");
    // 得到语言驱动 不重要
    LanguageDriver langDriver = getLanguageDriver(lang);

    Class<?> resultTypeClass = resolveClass(resultType);
    // 结果集类型,FORWARD_ONLY|SCROLL_SENSITIVE|SCROLL_INSENSITIVE 中的一种
    String resultSetType = context.getStringAttribute("resultSetType");
    // 语句类型, STATEMENT|PREPARED|CALLABLE 的一种,获取 Statement类型 这个是需要和 JDBC做映射的
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);

    // 获取命令类型(select|insert|update|delete)
    String nodeName = context.getNode().getNodeName();
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    // 是否要缓存select结果
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // 解析之前先解析<include>SQL片段 这个在前面 XMLMapperBuilder 中已经构建完成,这里需要调用并解析
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // 解析之前先解析<selectKey> selectKey主要涉及需要某些特殊关系来设置主键的值
    processSelectKeyNodes(id, parameterTypeClass, langDriver);
    
    // 解析成SqlSource,一般是DynamicSqlSource
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    String resultSets = context.getStringAttribute("resultSets");
    String keyProperty = context.getStringAttribute("keyProperty");
    String keyColumn = context.getStringAttribute("keyColumn");
    KeyGenerator keyGenerator;
    String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
    keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
    if (configuration.hasKeyGenerator(keyStatementId)) {
      keyGenerator = configuration.getKeyGenerator(keyStatementId);
    } else {
      keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
          configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
          ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
    }

    // 调用助手类去真正创建MappedStatement然后加入配置Configuration中 
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered, 
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }
  
}

// MappedStatement创建助手
public class MapperBuilderAssistant extends BaseBuilder {
  
	public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, 
                                            String parameterMap, Class<?> parameterType, String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, 
                                            boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, 
                                            LanguageDriver lang, String resultSets) {

    if (unresolvedCacheRef) {
      throw new IncompleteElementException("Cache-ref not yet resolved");
    }

    // 为id加上namespace前缀,由于要放置到configuration中,为了防重复,在mapper映射文件的CRUD标签加上namespace就构建了独一无二的id
    id = applyCurrentNamespace(id, false);
    // 是否是select语句
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

    // 使用传入的参数生成MappedStatement构造器,这里CRUD相关SQL的所有信息都包含在MappedStatement中了
    MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType)
      .resource(resource)
      .fetchSize(fetchSize)
      .timeout(timeout)
      .statementType(statementType)
      .keyGenerator(keyGenerator)
      .keyProperty(keyProperty)
      .keyColumn(keyColumn)
      .databaseId(databaseId)
      .lang(lang)
      .resultOrdered(resultOrdered)
      .resultSets(resultSets)
      .resultMaps(getStatementResultMaps(resultMap, resultType, id))
      .resultSetType(resultSetType)
      .flushCacheRequired(valueOrDefault(flushCache, !isSelect))
      .useCache(valueOrDefault(useCache, isSelect))
      .cache(currentCache);

    // 参数映射,由于parameterMap被弃用,所以这里一般为空。真正传入的其实应该是parameterType这个Class
    ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {
      statementBuilder.parameterMap(statementParameterMap);
    }
		// 构建MappedStatement
    MappedStatement statement = statementBuilder.build();
    // 将MappedStatement添加到configuration的mappedStatements属性中
    configuration.addMappedStatement(statement);
    return statement;
  }
}

mapper 映射文件声明构造器(XMLStatementBuilder)去解析 mapper 映射文件的每个 CRUD(即select、insert、update、delete标签),会去解析标签的所有元素,常用的就是 id、parameterType、resultMap、resultType,将每个标签解析成 SqlSource(SQL的封装)。最后将解析出来的所有属性,使用 MappedStatement 创建助手(MapperBuilderAssistant)去构建 MappedStatement 对象,放置在 configuration 的 mappedStatements 属性中,key为 namespace + id。

2.3、构建流程总结

先简单画一下构建流程图:
MyBatis构建流程图

  1. 先通过 Resource 读取 MyBatis 配置文件(mybatis-config.xml),将其转换成输入流。
  2. 将输入流传入 SqlSession工厂构建器(SqlSessionFactoryBuilder),调用 build方法传入输入流生成 XML配置文件构造器(XMLConfigBuilder)。
  3. XMLConfigBuilder 会解析 mybatis-config.xml 文件中的所有标签,其中最重要的就是解析标签,加载的所有配置都会保存在 Configuration 对象中。
  4. 遍历 mapper 标签,根据资源标识类型进行不同的操作,对于资源标识为 package 和 class 的,使用configuration 的 addMappers 和 addMapper 方法解析;资源标识为 resource 和 url 的,新建一个 mapper映射文件构造器(XMLMapperBuilder)去解析对应的 mapper 映射文件。
  5. 先看 Configuration 对象的 addMappers,调用 mapper 注册器(MapperRegistry)的 addMappers 方法。mapper 注册器解析流程为:资源标识为 package,获取配置包下的所有 mapper 映射文件,再获取文件中指向的 mapper 接口 Class 对象集合,遍历解析 Class 对象。资源标识为 class,则直接解析 Class 对象。调用 mapper 注解构造器(MapperAnnotationBuilder)的 parse 方法解析 Class 对象。
  6. mapper注解构造器主要分为两个步骤,一是调用 XMLMapperBuilder 解析 mapper接口对应的 mapper映射文件,这里和解析资源标识为 resource 和 url 的 mapper 标签,殊途同归了;二是解析 mapper 接口方法上的SQL语句,生成对应的 MappedStatement 对象。
  7. mapper映射文件构造器先解析 mapper 映射文件中的 resultMap、sql 等标签,然后调用 mapper 映射文件声明构造器(XMLStatementBuilder)解析CRUD标签(select、insert、update、delete),遍历这些标签,然后封装为SqlSource,最后调用 MappedStatement 创建助手(MapperBuilderAssistant)封装成MappedStatement 对象,一个 CRUD 标签生成一个 MappedStatement 对象。
  8. 最后将 MappedStatement 添加到 Configuration 对象的 mappedStatements 属性中。
  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值