[toc]

0. 目录
1. 例子
2. 源码分析
2.1 解析 mybatis-config.xml 构建 Configuration 配置类流程
2.2 解析 mapper.xml 构建映射声明、缓存等流程
2.2.1 XMLMapperBuilder 解析流程
2.2.2 cacheElement() 构建二级缓存
2.2.2.1 Cache 接口
2.2.2.2 PerpetualCache 缓存
2.2.2.3 LruCache 缓存装饰器
2.2.2.4 ScheduledCache 缓存装饰器
2.2.2.5 SerializedCache 缓存装饰器
2.2.2.6 LoggingCache 缓存装饰器
2.2.2.7 SynchronizedCache 缓存装饰器
2.2.2.8 BlockingCache 缓存装饰器
2.2.2.9 缓存小结
2.2.3 buildStatementFromContext() 构建 SQLStatement 流程
2.2.4 bindMapperForNamespace() 从命令空间构建映射
2.2.4.1 MapperProxyFactory 映射代理工厂
2.2.4.2 MapperProxy 映射代理
2.2.4.3 MapperMethod 映射方法
2.3 创建 SqlSession 流程
2.3.1 获取事务工厂 TransactionFactory
2.3.2 获取事务 Transaction
2.3.3 创建执行器 Executor
2.3.3.1 BaseExecutor 执行器
2.3.3.2 SimpleExecutor 执行器
2.3.3.2.1 StatementHandler 处理器
2.3.3.2.2 PreparedStatementHandler
2.3.4 CachingExecutor 缓存执行器
2.3.4.1 TransactionalCacheManager 事务缓存管理器
2.3.4.2 TransactionalCache 事务缓存
2.3.5 应用插件 interceptorChain.pluginAll()
2.3.5 创建 DefaultSqlSession
2.4 通过 SqlSession 获取映射接口执行目标方法
2.4.1 查询非缓存数据流程
2.4.2 二级缓存调用流程
3. 总结回顾

今天我们来学习下 mybatis 核心流程源码分析。

1. 例子

我们先写个例子。首先要配置一个资源文件 app.properties,配置一些属性,比如环境变量。

# 环境配置
env=local

再配置 mybatis-config.xml,这是 mybatis 的配置文件,是配置 mybatis 的各种配置信息,主要有:属性 properties、全局设置 settings、别名 typeAliases、环境 environments、映射 mappers:

<?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>

  <!-- autoMappingBehavior should be set in each test case -->

  <!-- 读取资源文件-->
  <properties resource="org/apache/ibatis/autoconstructor/app.properties"/>

  <settings>
    <!-- 开启二级缓存-->
    <setting name="cacheEnabled" value="true"/>
    <!-- 开启驼峰式命名-->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
  </settings>

  <!-- 别名配置 -->
  <typeAliases>
    <package name="org.apache.ibatis.autoconstructor"/>
  </typeAliases>

  <!-- 环境配置 -->
  <environments default="${env}">

    <environment id="local">
      <transactionManager type="JDBC">
        <property name="" value=""/>
      </transactionManager>
      <dataSource type="UNPOOLED">
        <property name="driver" value="org.hsqldb.jdbcDriver"/>
        <!-- 此配置是基于内存连接的-->
        <property name="url" value="jdbc:hsqldb:mem:automapping"/>
        <property name="username" value="sa"/>
      </dataSource>
    </environment>

    <environment id="dev">
      <transactionManager type="JDBC">
        <property name="" value=""/>
      </transactionManager>
      <dataSource type="UNPOOLED">
        <property name="driver" value="org.hsqldb.jdbcDriver"/>
        <!-- 此配置是基于内存连接的-->
        <property name="url" value="jdbc:hsqldb:mem:automapping"/>
        <property name="username" value="sa"/>
      </dataSource>
    </environment>
  </environments>

  <mappers>
    <!-- 扫描指定的映射文件 -->
    <mapper resource="org/apache/ibatis/autoconstructor/AutoConstructorMapper.xml"/>
  </mappers>

</configuration>

接着配置映射文件 AutoConstructorMapper.xml,它就是写 SQL 的地方:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
    PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
    "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.apache.ibatis.autoconstructor.AutoConstructorMapper">
  <!--开启二级缓存-->
  <cache/>
  <!--<select id="selectOneById" resultType="org.apache.ibatis.autoconstructor.PrimitiveSubject">-->
  <select id="selectOneById" resultType="primitiveSubject">
    SELECT * FROM subject WHERE id = #{id}
  </select>
</mapper>

然后给出基本的 POJO 和 mapper 接口:

public class PrimitiveSubject implements Serializable {
  private final int id;
  private final String name;
  private final int age;
  private final int height;
  private final int weight;
  private final boolean active;
  private final Date dt;

  public PrimitiveSubject(final int id, final String name, final int age, final int height, final int weight, final boolean active, final Date dt) {
    this.id = id;
    this.name = name;
    this.age = age;
    this.height = height;
    this.weight = weight;
    this.active = active;
    this.dt = dt;
  }

  @Override
  public String toString() {
    return "PrimitiveSubject{ hashcode="+ this.hashCode() + ", id=" + id + ", name='" + name + '\'' + ", age=" + age
      + ", height=" + height + ", weight=" + weight + ", active=" + active + ", dt=" + dt + '}';
  }

}

/**
 * mapper 接口
 */
public interface AutoConstructorMapper {
  PrimitiveSubject selectOneById(int id);
}

初始化 SQL 数据 CreateDB.sql

DROP TABLE subject
IF EXISTS;

DROP TABLE extensive_subject
IF EXISTS;

CREATE TABLE subject (
  id     INT NOT NULL,
  name   VARCHAR(20),
  age    INT NOT NULL,
  height INT,
  weight INT,
  active BIT,
  dt     TIMESTAMP
);

INSERT INTO subject VALUES
  (1, 'a', 10, 100, 45, 1, CURRENT_TIMESTAMP),
  (2, 'b', 10, NULL, 45, 1, CURRENT_TIMESTAMP),
  (2, 'c', 10, NULL, NULL, 0, CURRENT_TIMESTAMP);

最后编写测试类,这个测试类中初始化了 SqlSessionFactory,同时装配了内存数据库;它通过 sqlSessionFactory 开启了一个 SqlSession,然后获取 AutoConstructorMapper 对象,执行了它的 selectOneById 方法:

class AutoConstructorTest {

  private static SqlSessionFactory sqlSessionFactory;

  @BeforeAll
  static void setUp() throws Exception {
    // create a SqlSessionFactory
    try (
      Reader reader = Resources
        .getResourceAsReader("org/apache/ibatis/autoconstructor/mybatis-config.xml")
    ) {
      sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    }

    // populate in-memory database
    BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(),
                           "org/apache/ibatis/autoconstructor/CreateDB.sql");
  }

@Test
  void selectOneById() {
    try (SqlSession sqlSession = sqlSessionFactory.openSession()) {
      // 测试环境
      Environment environment = sqlSessionFactory.getConfiguration().getEnvironment();
      System.out.println("environment = " + environment.getId());

      final AutoConstructorMapper mapper = sqlSession.getMapper(AutoConstructorMapper.class);
      PrimitiveSubject ps1 = mapper.selectOneById(1);
      System.out.println("ps1 = " + ps1);
    }
  }
}

这样,一个简单的例子就编写完毕了。下面我们开始进入 mybatis 的源码中,探索下它的内部流程机制。

2. 源码分析

我们将它的源码分析分为以下几个流程:

  1. 解析 mybatis-config.xml 文件,构建 Configuration 配置类信息流程;
  2. 解析 mapper.xml 进行构建缓存、映射声明等流程;
  3. 创建 SqlSession 流程;
  4. 通过 SqlSession 获取 mapper 接口执行目标方法流程;

下面我们正式开始解析源码。

2.1 解析 mybatis-config.xml 构建 Configuration 配置类流程

这个流程在上面的例子中的单元测试类代码中有体现,具体的相关代码如下:

SqlSessionFactory sqlSessionFactory;
// ...省略...
      try (
      Reader reader = Resources
        .getResourceAsReader("org/apache/ibatis/autoconstructor/mybatis-config.xml")
    ) {
      sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
    }
// ...省略...

上面的逻辑是,加载 mybatis-config.xml 文件到一个输入流中,然后创建一个 SqlSessionFactoryBuilder 对象,进行构建出一个 SqlSessionFactory 实例,这个实例的生命周期非常长,它是随着应用程序的关闭而关闭的。

我们看下它的源码:

public class SqlSessionFactoryBuilder {

  public SqlSessionFactory build(Reader reader) {
    return build(reader, null, null);
  }

// ...省略无关方法...
  
  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      // 创建一个 XMLConfigBuilder 进行解析流,解析为一个 Configuration 实例
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

// ...省略无关方法...
  
  /**
   * 构建一个 SQLsession 工厂
   * @param config
   * @return
   */
  public SqlSessionFactory build(Configuration config) {
    // 创建一个默认的 SQLsession 工厂
    return new DefaultSqlSessionFactory(config);
  }

}

可以看到,上面的代码逻辑,主要是创建一个 XMLConfigBuilder 类型的对象,我们看下它的构造器

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

  private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
    super(new Configuration());
    ErrorContext.instance().resource("SQL Mapper Configuration");
    this.configuration.setVariables(props);
    this.parsed = false;
    this.environment = environment;
    this.parser = parser;
  }

发现它会创建一个 Configuration 对象,关联到父类中。看下 Configuration 的构造器:

public Configuration() {
        // 配置各种基础类的别名
    // 事务管理器
    typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
    typeAliasRegistry.registerAlias("MANAGED", ManagedTransactionFactory.class);

    // 数据源工厂
    typeAliasRegistry.registerAlias("JNDI", JndiDataSourceFactory.class);
    typeAliasRegistry.registerAlias("POOLED", PooledDataSourceFactory.class);
    typeAliasRegistry.registerAlias("UNPOOLED", UnpooledDataSourceFactory.class);

    // 缓存类别名
    typeAliasRegistry.registerAlias("PERPETUAL", PerpetualCache.class);
    typeAliasRegistry.registerAlias("FIFO", FifoCache.class);
    typeAliasRegistry.registerAlias("LRU", LruCache.class);
    typeAliasRegistry.registerAlias("SOFT", SoftCache.class);
    typeAliasRegistry.registerAlias("WEAK", WeakCache.class);

    typeAliasRegistry.registerAlias("DB_VENDOR", VendorDatabaseIdProvider.class);

    typeAliasRegistry.registerAlias("XML", XMLLanguageDriver.class);
    typeAliasRegistry.registerAlias("RAW", RawLanguageDriver.class);

    // 日志类别名
    typeAliasRegistry.registerAlias("SLF4J", Slf4jImpl.class);
    typeAliasRegistry.registerAlias("COMMONS_LOGGING", JakartaCommonsLoggingImpl.class);
    typeAliasRegistry.registerAlias("LOG4J", Log4jImpl.class);
    typeAliasRegistry.registerAlias("LOG4J2", Log4j2Impl.class);
    typeAliasRegistry.registerAlias("JDK_LOGGING", Jdk14LoggingImpl.class);
    typeAliasRegistry.registerAlias("STDOUT_LOGGING", StdOutImpl.class);
    typeAliasRegistry.registerAlias("NO_LOGGING", NoLoggingImpl.class);

    // 动态代理别名
    typeAliasRegistry.registerAlias("CGLIB", CglibProxyFactory.class);
    typeAliasRegistry.registerAlias("JAVASSIST", JavassistProxyFactory.class);

    // xml 脚本解析器
    languageRegistry.setDefaultDriverClass(XMLLanguageDriver.class);
    languageRegistry.register(RawLanguageDriver.class);

  }

可以看到,它的构造器方法中会注册一些基础配置的类的别名,这些别名一般是用在 xml 配置文件中的属性值,后续会根据别名来解析出对应的实际类型。

回过头来继续看 XMLConfigBuilder 的解析方法 parse() 方法,这个方法是把 mybatis 的 xml 文件解析成为一个 Configuration 类型,最后再创建一个 DefaultSqlSessionFactory 类型返回。org.apache.ibatis.builder.xml.XMLConfigBuilder#parse :

  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 {
      // 解析 properties 属性
      // issue #117 read properties first
      propertiesElement(root.evalNode("properties"));
      // 解析设置 setting
      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"));
      // 解析数据库 ID 提供者
      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);
    }
  }

上面的代码也很好理解,主要是针对 mybatis-config.xml 文件中的各个标签元素进行解析:

  1. 解析 properties 属性配置;
  2. 解析 setting 属性配置;
  3. 解析 typeAliases 类型别名配置;
  4. 解析插件 plugins 配置;
  5. 解析 objectFactory 对象工厂配置;
  6. 解析 objectWrapperFactory 对象包装工厂配置;
  7. 解析 reflectorFactory 反射工厂配置;
  8. 解析 environments 环境配置;
  9. 解析 databaseIdProvider 数据库 ID 提供者配置;
  10. 解析 typeHandlers 类型处理器配置;
  11. 解析 mappers 映射文件配置。

Mybatis 核心流程源码分析_Mybatis

这些解析内容中,mappers 解析最为重要,我们详细看下它的解析过程。

2.2 解析 mapper.xml 构建映射声明、缓存等流程

解析 mappers 的逻辑在 org.apache.ibatis.builder.xml.XMLConfigBuilder#mapperElement 方法中:

private void mapperElement(XNode parent) throws Exception {
    if (parent != null) {
      for (XNode child : parent.getChildren()) {
        if ("package".equals(child.getName())) {
          // 解析 package 属性
          String mapperPackage = child.getStringAttribute("name");
          configuration.addMappers(mapperPackage);
        } else {
          // 解析 resource 属性
          String resource = child.getStringAttribute("resource");
          // URL 属性
          String url = child.getStringAttribute("url");
          // class 属性
          String mapperClass = child.getStringAttribute("class");
          if (resource != null && url == null && mapperClass == null) {
            // resource 不为空,URL 和 class 为空
            ErrorContext.instance().resource(resource);
            InputStream inputStream = Resources.getResourceAsStream(resource);
            XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
            mapperParser.parse();
          } else if (resource == null && url != null && mapperClass == null) {
            // URL 不为空,resource 和 class 为空
            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 不为空,resource 和 URL 为空
            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 和 mapper,获取它们的 name、url、class、resource 属性,进行加载解析对应的 mapper.xml 文件。

流程为:

  1. 如果 package 标签存在,就获取其 name 属性值,即包名,将它放入 configuration 配置中保存起来, 通过 MapperAnnotationBuilder 类进行解析;
  2. 如果 package 不存在,就获取 mapper 标签。
    1. 获取它们的 resource、url、class 属性,这里进行了判断,这三个属性只能存在一个;
    2. 其中 resource 和 url 是通过 XMLMapperBuilder 实例进行解析的;
    3. class 属性的值也是会放入到 configuration 配置中进行解析并且保存起来,随后通过 MapperAnnotationBuilder 类进行解析。

2.2.1 XMLMapperBuilder 解析流程

我们这里主要看下 XMLMapperBuilder 类的解析流程。看下它的 parse() 方法,这个方法就是开始了对 mapper.xml 文件进行解析。org.apache.ibatis.builder.xml.XMLMapperBuilder#parse:

  /**
   * 执行解析 mapper.xml 文件
   */
  public void parse() {
    if (!configuration.isResourceLoaded(resource)) {
      // 配置 mapper 根元素
      configurationElement(parser.evalNode("/mapper"));
      // 保存资源路径
      configuration.addLoadedResource(resource);
      // 构建命令空间映射
      bindMapperForNamespace();
    }

    // 解析待定的结果集映射
    parsePendingResultMaps();
    // 解析待定的缓存引用
    parsePendingCacheRefs();
    // 解析待定的 SQL 声明
    parsePendingStatements();
  }

这里执行了以下几个解析逻辑:

  1. 执行 configurationElement() 方法,解析 mapper 根元素;
  2. 保存资源路径到 configuration 实例中;
  3. 执行 bindMapperForNamespace() 方法,根据命名空间加载对应的映射接口;
  4. 执行 parsePendingResultMaps() 方法,解析待定的 ResultMap 结果集映射;
  5. 执行 parsePendingCacheRefs() 方法,解析待定的 CacheRef 缓存引用;
  6. 执行 parsePendingStatements(),解析待定的 Statement SQL 声明。

这主要的方法是 configurationElement(),我们看下它的逻辑 org.apache.ibatis.builder.xml.XMLMapperBuilder#configurationElement:

private void configurationElement(XNode context) {
    try {
      // 构建命名空间
      String namespace = context.getStringAttribute("namespace");
      if (namespace == null || namespace.isEmpty()) {
        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 语句
      sqlElement(context.evalNodes("/mapper/sql"));
      // 构建 SQL 语句声明
      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);
    }
  }

它主要执行的逻辑是:

  1. 构建缓存引用 cache-ref 元素;
  2. 构建二级缓存 cache 元素;
  3. 构建 parameterMap 元素;
  4. 构建 resultMap 元素;
  5. 构建 SQL 元素;
  6. 构建 SQL 语句声明(解析 select|insert|update|delete 标签,这一步最为重要);

Mybatis 核心流程源码分析_Mybatis _02

2.2.2 cacheElement() 构建二级缓存

接着我们看下它的构建二级缓存的流程。它是在 org.apache.ibatis.builder.xml.XMLMapperBuilder#cacheElement 方法中实现的:

  /**
   * 构建二级缓存 cache 元素
   *
   * @param context
   */
  private void cacheElement(XNode context) {
    if (context != null) {
      // 配置默认的 cache 类型
      String type = context.getStringAttribute("type", "PERPETUAL");
      Class<? extends Cache> typeClass = typeAliasRegistry.resolveAlias(type);
      // 过期策略
      String eviction = context.getStringAttribute("eviction", "LRU");
      Class<? extends Cache> evictionClass = typeAliasRegistry.resolveAlias(eviction);
      // 刷新时间
      Long flushInterval = context.getLongAttribute("flushInterval");
      // 缓存大小
      Integer size = context.getIntAttribute("size");
      // 是否只读,默认是 false,即
      boolean readWrite = !context.getBooleanAttribute("readOnly", false);
      // 是否阻塞,为了解决缓存击穿问题(同一时刻出现大量的访问同一个数据的请求)
      boolean blocking = context.getBooleanAttribute("blocking", false);
      // 其他属性
      Properties props = context.getChildrenAsProperties();
      // 构建缓存
      builderAssistant.useNewCache(typeClass, evictionClass, flushInterval, size, readWrite, blocking, props);
    }
  }

注意这里的 cache 标签,是在 mapper.xml 文件中声明的。它的逻辑:

  1. 获取 cache 标签的类型 type 属性值,默认为 PERPETUAL,它对应 PerpetualCache 类型;
  2. 获取过期策略 eviction 属性值,默认为 LRU 最近最少过期策略,它对应 LruCache 类型;
  3. 获取刷新时间 flushInterval 属性值;
  4. 获取缓存大小 size 属性值;
  5. 获取是否只读 readOnly 属性值,默认是 false,如果设置了 true,那么就需要 POJO 实现 Serializable 接口;
  6. 获取是否阻塞 blocking 属性值,这是用来解决缓存击穿问题的,稍后将构建缓存时会具体讲解;
  7. 获取以及其他属性;
  8. 通过调用 MapperBuilderAssistant 映射构建器辅助器的 useNewCache() 方法来构建缓存。

我们看下 MapperBuilderAssistant 映射构建器辅助器的 useNewCache() 方法,org.apache.ibatis.builder.MapperBuilderAssistant#useNewCache:

  public Cache useNewCache(Class<? extends Cache> typeClass,
      Class<? extends Cache> evictionClass,
      Long flushInterval,
      Integer size,
      boolean readWrite,
      boolean blocking,
      Properties props) {
    // 缓存构建器
    Cache cache = new CacheBuilder(currentNamespace)
        // 这里默认使用 PerpetualCache 缓存类型实现,具体的缓存实现类
        .implementation(valueOrDefault(typeClass, PerpetualCache.class))
        // 添加 LruCache 缓存装饰器
        .addDecorator(valueOrDefault(evictionClass, LruCache.class))
        .clearInterval(flushInterval)
        .size(size)
        .readWrite(readWrite)
        .blocking(blocking)
        .properties(props)
        // 开始构建缓存
        .build();
    // 把缓存放入配置类中
    configuration.addCache(cache);
    currentCache = cache;
    return cache;
  }

这里又用到了 CacheBuilder 缓存构建器来构建缓存,,可以看到缓存使用 PerpetualCache 类型实现,并且添加了一个 添加 LruCache 缓存装饰器来装饰缓存,看下它的 build 方法 org.apache.ibatis.mapping.CacheBuilder#build:

  /**
   * 构建一个缓存
   *
   * @return
   */
  public Cache build() {
    // 设置默认实现类,和初始化的装饰器 LruCache
    setDefaultImplementations();
    // 通过反射创建一个 PerpetualCache 对象
    Cache cache = newBaseCacheInstance(implementation, id);
    // 设置缓存属性
    setCacheProperties(cache);
    // 不要为自定义的缓存应用装饰器
    // issue #352, do not apply decorators to custom caches
    if (PerpetualCache.class.equals(cache.getClass())) {
      // 如果是 PerpetualCache 类型的缓存,那么就给它设置装饰器
      for (Class<? extends Cache> decorator : decorators) {
        // 创建一个缓存装饰器实例
        cache = newCacheDecoratorInstance(decorator, cache);
        setCacheProperties(cache);
      }
      // 设置其他标准的装饰器
      cache = setStandardDecorators(cache);
    } else if (!LoggingCache.class.isAssignableFrom(cache.getClass())) {
      cache = new LoggingCache(cache);
    }
    return cache;
  }

  /**
   * 设置缓存的默认实现
   */
  private void setDefaultImplementations() {
    if (implementation == null) {
      implementation = PerpetualCache.class;
      if (decorators.isEmpty()) {
        decorators.add(LruCache.class);
      }
    }
  }

  /**
   * 设置标准的缓存装饰器
   *
   * @param cache
   * @return
   */
  private Cache setStandardDecorators(Cache cache) {
    try {
      // 获取缓存的元对象
      MetaObject metaCache = SystemMetaObject.forObject(cache);
      // 设置元数据的信息
      if (size != null && metaCache.hasSetter("size")) {
        metaCache.setValue("size", size);
      }
      if (clearInterval != null) {
        // 根据清除间隔属性,设置定时刷新缓存的装饰器缓存 ScheduledCache
        cache = new ScheduledCache(cache);
        ((ScheduledCache) cache).setClearInterval(clearInterval);
      }
      if (readWrite) {
        // 根据是否可读写属性,设置序列化缓存装饰器 SerializedCache
        cache = new SerializedCache(cache);
      }
      // 设置日志缓存装饰器 LoggingCache
      cache = new LoggingCache(cache);
      // 设置同步缓存装饰器 SynchronizedCache
      cache = new SynchronizedCache(cache);
      if (blocking) {
        // 根据是否阻塞,设置阻塞缓存装饰器
        cache = new BlockingCache(cache);
      }
      return cache;
    } catch (Exception e) {
      throw new CacheException("Error building standard cache decorators.  Cause: " + e, e);
    }
  }

梳理下这里的逻辑:

  1. 执行 setDefaultImplementations() 方法,如果没有实现类,那就设置默认的实现类 PerpetualCache,添加装饰器 LruCache;
  2. 通过反射创建一个 Cache 实现类的实例;
  3. 如果缓存实例是 PerpetualCache 类型的,则遍历装饰器集合,通过反射创建装饰器实例,并且执行 setStandardDecorators() 方法为缓存实例设置其他标准的装饰器;这里的逻辑有:
    1. 获取缓存的元对象,这是 size 属性;
    2. 根据 flushInterval 刷新间隔属性,设置 ScheduledCache 定时刷新缓存的装饰器对缓存进行装饰;
    3. 根据 readWrite 是否可读写属性,设置 SerializedCache 序列化缓存装饰器对缓存进行装饰;
    4. 设置 LoggingCache 日志缓存装饰器对缓存进行装饰;
    5. 设置 SynchronizedCache 同步缓存装饰器对缓存进行装饰;
    6. 根据 blocking 是否阻塞属性,设置 BlockingCache 阻塞缓存装饰器对缓存进行装饰;
  4. 如果缓存实例不是 LoggingCache 类型,那就设置 LoggingCache 日志缓存装饰器对缓存进行装饰;
  5. 返回缓存实例。

可以看到这里是创建了二级缓存 Cache 接口实例,这里有很多 Cache 装饰器,下面我们深入其中研究下。

2.2.2.1 Cache 接口

我们先看下 Cache 接口的类图:

Mybatis 核心流程源码分析_Mybatis _03

可以看到 Cache 接口有多个实现。

2.2.2.2 PerpetualCache 缓存

上面构建缓存的流程中,我们看到了它首先会创建具体的真正存数据的缓存实例 PerpetualCache,看下它的实现:

/**
 * 永久缓存,用于一级缓存
 *
 * @author Clinton Begin
 */
public class PerpetualCache implements Cache {

  private final String id;

  /**
   * 使用一个 hashmap 作为缓存
   */
  private final Map<Object, Object> cache = new HashMap<>();

  public PerpetualCache(String id) {
    this.id = id;
  }

  @Override
  public String getId() {
    return id;
  }

  @Override
  public int getSize() {
    return cache.size();
  }

  @Override
  public void putObject(Object key, Object value) {
    cache.put(key, value);
  }

  @Override
  public Object getObject(Object key) {
    return cache.get(key);
  }

  @Override
  public Object removeObject(Object key) {
    return cache.remove(key);
  }

  @Override
  public void clear() {
    cache.clear();
  }

  @Override
  public boolean equals(Object o) {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    if (this == o) {
      return true;
    }
    if (!(o instanceof Cache)) {
      return false;
    }

    Cache otherCache = (Cache) o;
    return getId().equals(otherCache.getId());
  }

  @Override
  public int hashCode() {
    if (getId() == null) {
      throw new CacheException("Cache instances require an ID.");
    }
    return getId().hashCode();
  }

}

它有两个属性,String 类型的 id 属性、和一个 HashMap 类型的 cache 属性,可以看到查询的数据会存储到这个 cache 属性中。

2.2.2.3 LruCache 缓存装饰器

接着它会创建一个 LruCache 缓存对 PerpetualCache 实例进行包装,LruCache 的实现如下:

/**
 * Lru (least recently used) cache decorator.
 *
 * @author Clinton Begin
 */
public class LruCache implements Cache {

  private final Cache delegate;
  private Map<Object, Object> keyMap;
  private Object eldestKey;

  public LruCache(Cache delegate) {
    this.delegate = delegate;
    setSize(1024);
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public int getSize() {
    return delegate.getSize();
  }

  public void setSize(final int size) {
    // 重写 LinkedHashMap 的 removeEldestEntry() 方法,实现 LRU 算法
    keyMap = new LinkedHashMap<Object, Object>(size, .75F, true) {
      private static final long serialVersionUID = 4267176411845948333L;

      @Override
      protected boolean removeEldestEntry(Map.Entry<Object, Object> eldest) {
        boolean tooBig = size() > size;
        if (tooBig) {
          eldestKey = eldest.getKey();
        }
        return tooBig;
      }
    };
  }

  @Override
  public void putObject(Object key, Object value) {
    delegate.putObject(key, value);
    cycleKeyList(key);
  }

  @Override
  public Object getObject(Object key) {
    // 这里获取 key 是为了让 key 保持最新,不至于被 LRU 清除掉
    keyMap.get(key); // touch
    return delegate.getObject(key);
  }

  @Override
  public Object removeObject(Object key) {
    return delegate.removeObject(key);
  }

  @Override
  public void clear() {
    delegate.clear();
    keyMap.clear();
  }

  private void cycleKeyList(Object key) {
    keyMap.put(key, key);
    if (eldestKey != null) {
      delegate.removeObject(eldestKey);
      eldestKey = null;
    }
  }

}

可以看到,它持有一个缓存实例 Cache 类型的 delegate 属性,这是一个委派的缓存实例;还有持有一个重写了 LinkedHashMap 类的 keyMap 属性,它重写了 removeEldestEntry() 方法,实现了 LRU 最近最少使用算法;同时还持有一个年级最长的 Object 类型的 key。

当有新的数据要放入缓存时,并且 keyMap 中的数据已经满了的时候,会把年级最长的缓存 key 删除掉,再存入新的数据。

2.2.2.4 ScheduledCache 缓存装饰器

接着看 ScheduledCache 定时刷新缓存装饰器:

public class ScheduledCache implements Cache {

  private final Cache delegate;
  protected long clearInterval;
  protected long lastClear;

  public ScheduledCache(Cache delegate) {
    this.delegate = delegate;
    this.clearInterval = TimeUnit.HOURS.toMillis(1);
    this.lastClear = System.currentTimeMillis();
  }

  public void setClearInterval(long clearInterval) {
    this.clearInterval = clearInterval;
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public int getSize() {
    clearWhenStale();
    return delegate.getSize();
  }

  @Override
  public void putObject(Object key, Object object) {
    clearWhenStale();
    delegate.putObject(key, object);
  }

  @Override
  public Object getObject(Object key) {
    return clearWhenStale() ? null : delegate.getObject(key);
  }

  @Override
  public Object removeObject(Object key) {
    clearWhenStale();
    return delegate.removeObject(key);
  }

  @Override
  public void clear() {
    lastClear = System.currentTimeMillis();
    delegate.clear();
  }

  @Override
  public int hashCode() {
    return delegate.hashCode();
  }

  @Override
  public boolean equals(Object obj) {
    return delegate.equals(obj);
  }

  private boolean clearWhenStale() {
    if (System.currentTimeMillis() - lastClear > clearInterval) {
      clear();
      return true;
    }
    return false;
  }

}

这个类同样也是持有一个委派的 Cache 实例,并且它提供了一个 clearWhenStale() 方法。这个方法会根据当前时间、上次清理的时间,与配置的刷新的间隔时间进行判断,是否需要清理缓存。与当前时间,在获取缓存数据、保存缓存数据、移除缓存数据、查询缓存数据数量的时候进行调用。

2.2.2.5 SerializedCache 缓存装饰器

接着看 SerializedCache 类:

public class SerializedCache implements Cache {

  private final Cache delegate;

  public SerializedCache(Cache delegate) {
    this.delegate = delegate;
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public int getSize() {
    return delegate.getSize();
  }

  @Override
  public void putObject(Object key, Object object) {
    if (object == null || object instanceof Serializable) {
      delegate.putObject(key, serialize((Serializable) object));
    } else {
      throw new CacheException("SharedCache failed to make a copy of a non-serializable object: " + object);
    }
  }

  @Override
  public Object getObject(Object key) {
    Object object = delegate.getObject(key);
    return object == null ? null : deserialize((byte[]) object);
  }

  @Override
  public Object removeObject(Object key) {
    return delegate.removeObject(key);
  }

  @Override
  public void clear() {
    delegate.clear();
  }

  @Override
  public int hashCode() {
    return delegate.hashCode();
  }

  @Override
  public boolean equals(Object obj) {
    return delegate.equals(obj);
  }

  private byte[] serialize(Serializable value) {
    try (ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos)) {
      oos.writeObject(value);
      oos.flush();
      return bos.toByteArray();
    } catch (Exception e) {
      throw new CacheException("Error serializing object.  Cause: " + e, e);
    }
  }

  private Serializable deserialize(byte[] value) {
    Serializable result;
    try (ByteArrayInputStream bis = new ByteArrayInputStream(value);
        ObjectInputStream ois = new CustomObjectInputStream(bis)) {
      result = (Serializable) ois.readObject();
    } catch (Exception e) {
      throw new CacheException("Error deserializing object.  Cause: " + e, e);
    }
    return result;
  }

  public static class CustomObjectInputStream extends ObjectInputStream {

    public CustomObjectInputStream(InputStream in) throws IOException {
      super(in);
    }

    @Override
    protected Class<?> resolveClass(ObjectStreamClass desc) throws ClassNotFoundException {
      return Resources.classForName(desc.getName());
    }

  }

}

它是一个序列化缓存装饰器,用于在保存数据时,把数据序列化成 byte[] 数组,然后把 byte[] 数组保存到委派的缓存实例中去,在查询数据时,再把查询出来的数据反序列化为对应的对象。这里要求保存的数据类要实现 Serializable 接口。

2.2.2.6 LoggingCache 缓存装饰器

接着看 LoggingCache 类型:

public class LoggingCache implements Cache {

  private final Log log;
  private final Cache delegate;
  protected int requests = 0;
  protected int hits = 0;

  public LoggingCache(Cache delegate) {
    this.delegate = delegate;
    this.log = LogFactory.getLog(getId());
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public int getSize() {
    return delegate.getSize();
  }

  @Override
  public void putObject(Object key, Object object) {
    delegate.putObject(key, object);
  }

  @Override
  public Object getObject(Object key) {
    requests++;
    final Object value = delegate.getObject(key);
    if (value != null) {
      hits++;
    }
    if (log.isDebugEnabled()) {
      log.debug("Cache Hit Ratio [" + getId() + "]: " + getHitRatio());
    }
    return value;
  }

  @Override
  public Object removeObject(Object key) {
    return delegate.removeObject(key);
  }

  @Override
  public void clear() {
    delegate.clear();
  }

  @Override
  public int hashCode() {
    return delegate.hashCode();
  }

  @Override
  public boolean equals(Object obj) {
    return delegate.equals(obj);
  }

  private double getHitRatio() {
    return (double) hits / (double) requests;
  }

}

这个缓存装饰器的功能就是在查询缓存的时候打印日志,会根据缓存的请求次数与实际命中的次数计算出的命中率,并且打印出来。

2.2.2.7 SynchronizedCache 缓存装饰器

接着看 SynchronizedCache 类:

public class SynchronizedCache implements Cache {

  private final Cache delegate;

  public SynchronizedCache(Cache delegate) {
    this.delegate = delegate;
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public synchronized int getSize() {
    return delegate.getSize();
  }

  @Override
  public synchronized void putObject(Object key, Object object) {
    delegate.putObject(key, object);
  }

  @Override
  public synchronized Object getObject(Object key) {
    return delegate.getObject(key);
  }

  @Override
  public synchronized Object removeObject(Object key) {
    return delegate.removeObject(key);
  }

  @Override
  public synchronized void clear() {
    delegate.clear();
  }

  @Override
  public int hashCode() {
    return delegate.hashCode();
  }

  @Override
  public boolean equals(Object obj) {
    return delegate.equals(obj);
  }

}

它是一个实现同步功能的缓存装饰器,在调用查询缓存、保存缓存、删除缓存、清空缓存方法时进行同步,防止多线程同时操作。

2.2.2.8 BlockingCache 缓存装饰器

我们看最后一个缓存装饰器 BlockingCache:

/**
 * 一个简单的阻塞装饰器。
 * 一个简单的低效的 EhCache's BlockingCache 装饰器。当元素不存在缓存中的时候,它设置一个锁。
 * 这样其他线程将会等待,直到元素被填充,而不是直接访问数据库。
 * 本质上,如果使用不当,它将会造成死锁。
 *
 * <p>Simple blocking decorator
 *
 * <p>Simple and inefficient version of EhCache's BlockingCache decorator.
 * It sets a lock over a cache key when the element is not found in cache.
 * This way, other threads will wait until this element is filled instead of hitting the database.
 *
 * <p>By its nature, this implementation can cause deadlock when used incorrecly.
 *
 * @author Eduardo Macarron
 *
 */
public class BlockingCache implements Cache {

  private long timeout;
  private final Cache delegate;
  private final ConcurrentHashMap<Object, CountDownLatch> locks;

  public BlockingCache(Cache delegate) {
    this.delegate = delegate;
    this.locks = new ConcurrentHashMap<>();
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public int getSize() {
    return delegate.getSize();
  }

  @Override
  public void putObject(Object key, Object value) {
    try {
      delegate.putObject(key, value);
    } finally {
      releaseLock(key);
    }
  }

  @Override
  public Object getObject(Object key) {
    // 获取锁
    acquireLock(key);
    // 获取对象
    Object value = delegate.getObject(key);
    if (value != null) {
      // 获取的数据不为空,释放锁
      releaseLock(key);
    }
    // 如果 value 为空,则一直不释放锁,让其他查询此 key 的线程永久阻塞,直到该 key 对应的 value 被添加到缓存中,或者调用删除 key 操作,才会释放锁。
    // 这样的操作是用于解决缓存穿透问题,防止大量请求访问一个目前不存在的数据
    return value;
  }

  @Override
  public Object removeObject(Object key) {
    // despite of its name, this method is called only to release locks
    releaseLock(key);
    return null;
  }

  @Override
  public void clear() {
    delegate.clear();
  }

  private void acquireLock(Object key) {
    // 创建一个倒计时闭锁
    CountDownLatch newLatch = new CountDownLatch(1);
    while (true) {
      // 根据给定的 key,放入对应的闭锁
      // 如果 key 对应的闭锁不存在,则放入闭锁,如果存在则不放入,返回以前的值
      CountDownLatch latch = locks.putIfAbsent(key, newLatch);
      if (latch == null) {
        // latch 为 null 说明放入成功,则退出
        break;
      }
      // latch 不为空,说已经有线程放入了 key 对应的闭锁,那就让闭锁阻塞 await,直到闭锁被放入它的线程解锁
      try {
        if (timeout > 0) {
          boolean acquired = latch.await(timeout, TimeUnit.MILLISECONDS);
          if (!acquired) {
            throw new CacheException(
                "Couldn't get a lock in " + timeout + " for the key " + key + " at the cache " + delegate.getId());
          }
        } else {
          latch.await();
        }
      } catch (InterruptedException e) {
        throw new CacheException("Got interrupted while trying to acquire lock for key " + key, e);
      }
    }
  }

  /**
   * 释放锁,它会在保存对象、查询到对象、移除对象时进行调用
   *
   * @param key
   */
  private void releaseLock(Object key) {
    // 释放一个锁
    CountDownLatch latch = locks.remove(key);
    if (latch == null) {
      throw new IllegalStateException("Detected an attempt at releasing unacquired lock. This should never happen.");
    }
    // 倒计时
    latch.countDown();
  }

  public long getTimeout() {
    return timeout;
  }

  public void setTimeout(long timeout) {
    this.timeout = timeout;
  }
}

这个类是借助了 CountDownLatch 闭锁实现了先阻塞操作。当一个线程尝试获取缓存数据时,会创建一个 CountDownLatch,然后再去获取数据,当获取的数据不为空,就把这个 CountDownLatch 删除,否则不删除闭锁,返回空数据。

这样其他线程获取相同 key 对应的缓存时,会拿到这个 CountDownLatch,然后调用它的 await() 方法,该线程就会被阻塞起来,直到这个 CountDownLatch 执行了 countDown() 方法。

当 key 对应的数据被获取到、被删除、被重新填入时,会调用到 CountDownLatch 的 countDown() 方法,唤醒其他被该闭锁阻塞的线程。

这样做的目的是为了防止缓存击穿。在一个 session 当访问一个数据库中一直不存在的数据时,会触发一次数据库查询,此时当 session 还没有提交事务时,此时出现了大量的 session 也是查询该 key 对应的数据,这样就会导致它们都会查询数据库,可想而知,后来这些 session 的查询数据库行为是无效的,而且如果此时 session 过多,可能会打死数据库。

为了避免这样的情况,为一个 key 增加一个闭锁,阻塞那些获取该数据的线程,直到数据被填充或释放锁才能被唤醒。

这样的做是比较低效的,容易引发死锁,比如一个线程如果一直访问缓存中不存在,并且数据库中也不存在的数据时,会创建一个闭锁,查询数据结束也不会释放锁。其他获取该 key 数据的线程访问时将会永久的阻塞,严重的消耗的系统资源。

这个类一般是不用的,cache 元素中的 block 属性默认是 false。

2.2.2.9 缓存小结

上述就是缓存装饰器的全部的介绍了,上面的这些缓存装饰器是使用了适配器模式,如下图:

Mybatis 核心流程源码分析_Mybatis _04

这样设计的好处是,根据各个功能设计出各个装饰器,让它们各司其职。

2.2.3 buildStatementFromContext() 构建 SQLStatement 流程

接着看构建 SQLStatement 逻辑,它通过调用 buildStatementFromContext(context.evalNodes("select|insert|update|delete")) 方法来执行。

org.apache.ibatis.builder.xml.XMLMapperBuilder#buildStatementFromContext(java.util.List<org.apache.ibatis.parsing.XNode>)

 /**
   * 从上下文构建状态
   *
   * @param list
   */
  private void buildStatementFromContext(List<XNode> list) {
    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) {
      final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
      try {
        // 解析 SQL 语句
        statementParser.parseStatementNode();
      } catch (IncompleteElementException e) {
        // 添加不完整的声明
        configuration.addIncompleteStatement(statementParser);
      }
    }
  }

可以看到,这里获取了 select|insert|update|delete 这些元素,然后遍历,通过创建一个 XMLStatementBuilder 类,调用了它的 parseStatementNode() 方法来进行解析,说明一个 select|insert|update|delete 语句对应着一个 XMLStatement,org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode:

  /**
   * 解析增删改查 SQL 语句声明,一个增删改查 SQL 语句就对应一个 MappedStatement
   */
  public void parseStatementNode() {
    // SQL 的 ID 属性
    String id = context.getStringAttribute("id");
    // 数据库 ID
    String databaseId = context.getStringAttribute("databaseId");

    if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
      return;
    }

    // 节点名称
    String nodeName = context.getNode().getNodeName();
    // 根据节点名称解析 SQL 的类型:增删改查
    SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
    // 是否为查询类型
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
    // 是否刷新缓存,除了 select 类型的 SQL 预计,执行的时候都会刷新缓存
    boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
    // 是否使用缓存,默认不填写时是使用缓存的,如果是 select 类型,则默认是启用缓存
    boolean useCache = context.getBooleanAttribute("useCache", isSelect);
    // 结果排序,false
    boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);

    // 解析 includes
    // Include Fragments before parsing
    XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
    includeParser.applyIncludes(context.getNode());

    // 解析参数类型
    String parameterType = context.getStringAttribute("parameterType");
    Class<?> parameterTypeClass = resolveClass(parameterType);

    // 解析语言驱动
    String lang = context.getStringAttribute("lang");
    LanguageDriver langDriver = getLanguageDriver(lang);

    // 解析查询寻的 key
    // Parse selectKey after includes and remove them.
    processSelectKeyNodes(id, parameterTypeClass, langDriver);

    // 解析 selectKey
    // Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
    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;
    }

    // 创建数据源
    SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
    // 声明类型,默认是 PREPARED 类型,预装配模式
    StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
    // fetchSize
    Integer fetchSize = context.getIntAttribute("fetchSize");
    // 超时属性
    Integer timeout = context.getIntAttribute("timeout");
    // 参数映射
    String parameterMap = context.getStringAttribute("parameterMap");
    // 结果类型
    String resultType = context.getStringAttribute("resultType");
    Class<?> resultTypeClass = resolveClass(resultType);
    // 结果映射
    String resultMap = context.getStringAttribute("resultMap");
    // 结果集类型
    String resultSetType = context.getStringAttribute("resultSetType");
    ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
    if (resultSetTypeEnum == null) {
      resultSetTypeEnum = configuration.getDefaultResultSetType();
    }
    // key 属性
    String keyProperty = context.getStringAttribute("keyProperty");
    // key 列
    String keyColumn = context.getStringAttribute("keyColumn");
    // 结果集
    String resultSets = context.getStringAttribute("resultSets");

    // 构建映射声明对象
    builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
        fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
        resultSetTypeEnum, flushCache, useCache, resultOrdered,
        keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
  }

可以看到它的逻辑:

  1. 获取元素的 id 属性、 databaseId 属性;
  2. 根据节点名称解析 SQL 命令类型(UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH);
  3. 获取元素的是否查询类型 isSelect、是否刷新缓存 isSelect、是否使用缓存 isSelect、是否对结果排序 resultOrdered;
  4. 解析 include 元素节点;
  5. 解析元素的 parameterType 属性、解析语言驱动 lang 属性、解析 selectKey;
  6. 创建 keyGenerator;
  7. 创建数据源 sqlSource;
  8. 解析 StatementType 类型,默认是 PREPARED 类型;
  9. 获取 fetchSize、timeout 超时属性、parameterMap 参数映射、resultType 结果类型、resultMap 结果集、resultSetType 结果集类型、
  10. 获取元素的 keyProperty 属性、keyColumn、resultSets
  11. 通过 MapperBuilderAssistant 映射构建器辅助器调用 addMappedStatement() 方法,创建并添加映射 Statement。

我们看下 org.apache.ibatis.builder.MapperBuilderAssistant#addMappedStatement() 方法:

  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
    id = applyCurrentNamespace(id, false);
    boolean isSelect = sqlCommandType == SqlCommandType.SELECT;

    // 开始构建一个映射声明
    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 statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
    if (statementParameterMap != null) {
      statementBuilder.parameterMap(statementParameterMap);
    }

    MappedStatement statement = statementBuilder.build();
    // 把声明对象放入 configuration 中
    configuration.addMappedStatement(statement);
    return statement;
  }

  public String applyCurrentNamespace(String base, boolean isReference) {
    if (base == null) {
      return null;
    }
    if (isReference) {
      // is it qualified with any namespace yet?
      if (base.contains(".")) {
        return base;
      }
    } else {
      // is it qualified with this namespace yet?
      if (base.startsWith(currentNamespace + ".")) {
        return base;
      }
      if (base.contains(".")) {
        throw new BuilderException("Dots are not allowed in element names, please remove it from " + base);
      }
    }
    // 格式为:命令空间 + "." + base
    return currentNamespace + "." + base;
  }

这里的逻辑:

  1. 根据命令空间以及元素 ID 生成一个 MappedStatement 的 ID 属性;
  2. 创建一个 MappedStatement.Builder 实例构建 MappedStatement 实例;
  3. 添加到 configuration 实例中,返回 MappedStatement 实例。

这个 MappedStatement 的生命周期是和 configuration 一样,也是和应用程序的生命周期一样。

2.2.4 bindMapperForNamespace() 从命令空间构建映射

这个方法是根据 mapper.xml 中的命名空间来注册对应的 Mapper 接口类,org.apache.ibatis.builder.xml.XMLMapperBuilder#bindMapperForNamespace:

private void bindMapperForNamespace() {
    // 当前命令空间
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        // 绑定类型就是命名空间对应的接口类
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        // ignore, bound type is not required
      }
      if (boundType != null && !configuration.hasMapper(boundType)) {
        // Spring may not know the real resource name so we set a flag
        // to prevent loading again this resource from the mapper interface
        // look at MapperAnnotationBuilder#loadXmlResource
        // 保存命令空间
        configuration.addLoadedResource("namespace:" + namespace);
        // 保存映射,这里进行了注册
        configuration.addMapper(boundType);
      }
    }
  }

逻辑:

  1. 首先获取了命令空间值,然后加载这个类型,得到的就是对应的声明的 Mapper 接口;
  2. 保存命令空间到 Configuration 配置中;
  3. 把 Mapper 接口注册到 Configuration 中。

我们再看下 configuration.addMapper(boundType); 这个逻辑,org.apache.ibatis.session.Configuration#addMapper:

// org.apache.ibatis.session.Configuration#addMapper:
public <T> void addMapper(Class<T> type) {
    // mapperRegistry 是 MapperRegistry 类型
    mapperRegistry.addMapper(type);
  }

里边又调用了 org.apache.ibatis.binding.MapperRegistry#addMapper() 方法:

public <T> void addMapper(Class<T> type) {
    if (type.isInterface()) {
      if (hasMapper(type)) {
        throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
      }
      boolean loadCompleted = false;
      try {
        // 添加一个映射器代理工厂
        knownMappers.put(type, new MapperProxyFactory<>(type));
        // It's important that the type is added before the parser is run
        // otherwise the binding may automatically be attempted by the
        // mapper parser. If the type is already known, it won't try.
        // 映射注解构建器
        MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
        parser.parse();
        loadCompleted = true;
      } finally {
        if (!loadCompleted) {
          knownMappers.remove(type);
        }
      }
    }
  }

我们看到了这里的逻辑:

  1. 把要注册的类保存到 Map<Class<?>, MapperProxyFactory<?>> 类型的 knownMappers 属性中,它的 key 为注册的类型,value 为 MapperProxyFactory 映射代理工厂类型实例;
  2. 创建一个 MapperAnnotationBuilder 映射注解解析器,对目标类型进行解析。

2.2.4.1 MapperProxyFactory 映射代理工厂

我们看下这个类:

public class MapperProxyFactory<T> {

  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethodInvoker> methodCache = new ConcurrentHashMap<>();

  public MapperProxyFactory(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public Class<T> getMapperInterface() {
    return mapperInterface;
  }

  public Map<Method, MapperMethodInvoker> getMethodCache() {
    return methodCache;
  }

  @SuppressWarnings("unchecked")
  protected T newInstance(MapperProxy<T> mapperProxy) {
    return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
  }

  public T newInstance(SqlSession sqlSession) {
    final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
    return newInstance(mapperProxy);
  }

}
  1. 这个类中维护目标接口类型信息、方法与映射方法执行器属性。
  2. 它提供了创建实例方法 newInstance(),通过 JDK 的动态代理对象创建一个目标接口的代理对象。

2.2.4.2 MapperProxy 映射代理

上面 JDK 动态代理对象时候,传入了一个 MapperProxy 类型的参数,它的实现为:

/**
 * 方法代理器,实现了 JDK 动态代理的执行处理器 InvocationHandler 接口
 *
 * @author Clinton Begin
 * @author Eduardo Macarron
 */
public class MapperProxy<T> implements InvocationHandler, Serializable {

  private static final long serialVersionUID = -4724728412955527868L;
  private static final int ALLOWED_MODES = MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
      | MethodHandles.Lookup.PACKAGE | MethodHandles.Lookup.PUBLIC;
  private static final Constructor<Lookup> lookupConstructor;
  private static final Method privateLookupInMethod;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethodInvoker> methodCache;

  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethodInvoker> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }

  static {
    Method privateLookupIn;
    try {
      privateLookupIn = MethodHandles.class.getMethod("privateLookupIn", Class.class, MethodHandles.Lookup.class);
    } catch (NoSuchMethodException e) {
      privateLookupIn = null;
    }
    privateLookupInMethod = privateLookupIn;

    Constructor<Lookup> lookup = null;
    if (privateLookupInMethod == null) {
      // JDK 1.8
      try {
        lookup = MethodHandles.Lookup.class.getDeclaredConstructor(Class.class, int.class);
        lookup.setAccessible(true);
      } catch (NoSuchMethodException e) {
        throw new IllegalStateException(
            "There is neither 'privateLookupIn(Class, Lookup)' nor 'Lookup(Class, int)' method in java.lang.invoke.MethodHandles.",
            e);
      } catch (Exception e) {
        lookup = null;
      }
    }
    lookupConstructor = lookup;
  }

  /**
   * 动态代理执行器的 invoke 方法
   *
   * @param proxy
   * @param method
   * @param args
   * @return
   * @throws Throwable
   */
  @Override
  public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    try {
      if (Object.class.equals(method.getDeclaringClass())) {
        return method.invoke(this, args);
      } else {
        // 调用 MapperMethodInvoker 映射方法执行器
        return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
      }
    } catch (Throwable t) {
      throw ExceptionUtil.unwrapThrowable(t);
    }
  }

  private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
    try {
      // A workaround for https://bugs.openjdk.java.net/browse/JDK-8161372
      // It should be removed once the fix is backported to Java 8 or
      // MyBatis drops Java 8 support. See gh-1929
      // 从方法缓存中获取映射方法执行器
      MapperMethodInvoker invoker = methodCache.get(method);
      if (invoker != null) {
        return invoker;
      }

      // 创建一个新的方法执行器,并放入 methodCache 缓存中
      return methodCache.computeIfAbsent(method, m -> {
        if (m.isDefault()) {
          // 如果方法是一个接口的 default 方法,那就创建一个 DefaultMethodInvoker 类型
          try {
            if (privateLookupInMethod == null) {
              return new DefaultMethodInvoker(getMethodHandleJava8(method));
            } else {
              return new DefaultMethodInvoker(getMethodHandleJava9(method));
            }
          } catch (IllegalAccessException | InstantiationException | InvocationTargetException
              | NoSuchMethodException e) {
            throw new RuntimeException(e);
          }
        } else {
          // 否则就创建普通的 PlainMethodInvoker 类型执行器
          return new PlainMethodInvoker(new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
        }
      });
    } catch (RuntimeException re) {
      Throwable cause = re.getCause();
      throw cause == null ? re : cause;
    }
  }

  private MethodHandle getMethodHandleJava9(Method method)
      throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
    final Class<?> declaringClass = method.getDeclaringClass();
    return ((Lookup) privateLookupInMethod.invoke(null, declaringClass, MethodHandles.lookup())).findSpecial(
        declaringClass, method.getName(), MethodType.methodType(method.getReturnType(), method.getParameterTypes()),
        declaringClass);
  }

  private MethodHandle getMethodHandleJava8(Method method)
      throws IllegalAccessException, InstantiationException, InvocationTargetException {
    final Class<?> declaringClass = method.getDeclaringClass();
    return lookupConstructor.newInstance(declaringClass, ALLOWED_MODES).unreflectSpecial(method, declaringClass);
  }

  interface MapperMethodInvoker {
    Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable;
  }

  private static class PlainMethodInvoker implements MapperMethodInvoker {
    private final MapperMethod mapperMethod;

    public PlainMethodInvoker(MapperMethod mapperMethod) {
      super();
      this.mapperMethod = mapperMethod;
    }

    /**
     * JDK 动态代理对象的的处理器方法
     *
     * @param proxy
     * @param method
     * @param args
     * @param sqlSession
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
      // 执行目标方法
      return mapperMethod.execute(sqlSession, args);
    }
  }

  private static class DefaultMethodInvoker implements MapperMethodInvoker {
    private final MethodHandle methodHandle;

    public DefaultMethodInvoker(MethodHandle methodHandle) {
      super();
      this.methodHandle = methodHandle;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args, SqlSession sqlSession) throws Throwable {
      // 通过 MethodHandle 方法处理器,绑定代理对象,执行方法
      return methodHandle.bindTo(proxy).invokeWithArguments(args);
    }
  }

再看下它的类图:

Mybatis 核心流程源码分析_Mybatis _05

它实现了 InvocationHandler 接口的 invoke() 方法,里边主要的逻辑是:

  1. 调用 cachedInvoker() 方法,创建一个 MapperMethodInvoker;
    1. 先从 methodCache 缓存中获取,有的话直接返回;
    2. methodCache 缓存没有的话,则创建一个 PlainMethodInvoker 类型的执行器,这个构造器会被传入一个 org.apache.ibatis.binding.MapperMethod 类型对象。
  2. 调用 MapperMethodInvoker 实例的 invoke() 执行目标方法,实际最终会执行 MapperMethod 实例的 execute() 方法。

2.2.4.3 MapperMethod 映射方法

我们看下 MapperMethod 类:

/**
 * 映射方法
 *
 * @author Clinton Begin
 * @author Eduardo Macarron
 * @author Lasse Voss
 * @author Kazuki Shimizu
 */
public class MapperMethod {

  private final SqlCommand command;
  private final MethodSignature method;

  public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
    // SQL 命令
    this.command = new SqlCommand(config, mapperInterface, method);
    // 方法签名
    this.method = new MethodSignature(config, mapperInterface, method);
  }

  /**
   * 执行方法
   *
   * @param sqlSession
   * @param args
   * @return
   */
  public Object execute(SqlSession sqlSession, Object[] args) {
    Object result;
    switch (command.getType()) {
      case INSERT: {
        // 新增类型
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.insert(command.getName(), param));
        break;
      }
      case UPDATE: {
        // 修改
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.update(command.getName(), param));
        break;
      }
      case DELETE: {
        // 删除
        Object param = method.convertArgsToSqlCommandParam(args);
        result = rowCountResult(sqlSession.delete(command.getName(), param));
        break;
      }
      case SELECT:
        // 查询
        if (method.returnsVoid() && method.hasResultHandler()) {
          executeWithResultHandler(sqlSession, args);
          result = null;
        } else if (method.returnsMany()) {
          // 返回多条
          result = executeForMany(sqlSession, args);
        } else if (method.returnsMap()) {
          // 返回 map
          result = executeForMap(sqlSession, args);
        } else if (method.returnsCursor()) {
          // 返回游标
          result = executeForCursor(sqlSession, args);
        } else {
          Object param = method.convertArgsToSqlCommandParam(args);
          result = sqlSession.selectOne(command.getName(), param);
          if (method.returnsOptional()
              && (result == null || !method.getReturnType().equals(result.getClass()))) {
            result = Optional.ofNullable(result);
          }
        }
        break;
      case FLUSH:
        // 刷新
        result = sqlSession.flushStatements();
        break;
      default:
        throw new BindingException("Unknown execution method for: " + command.getName());
    }
    if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
      throw new BindingException("Mapper method '" + command.getName()
          + " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
    }
    return result;
  }

  private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
    MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
    if (!StatementType.CALLABLE.equals(ms.getStatementType())
        && void.class.equals(ms.getResultMaps().get(0).getType())) {
      throw new BindingException("method " + command.getName()
          + " needs either a @ResultMap annotation, a @ResultType annotation,"
          + " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
    }
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
    } else {
      sqlSession.select(command.getName(), param, method.extractResultHandler(args));
    }
  }

  /**
   * 查询多条记录
   *
   * @param sqlSession
   * @param args
   * @param <E>
   * @return
   */
  private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
    List<E> result;
    // 转换参数
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      // 有行绑定
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.selectList(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.selectList(command.getName(), param);
    }
    // issue #510 Collections & arrays support
    if (!method.getReturnType().isAssignableFrom(result.getClass())) {
      if (method.getReturnType().isArray()) {
        return convertToArray(result);
      } else {
        return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
      }
    }
    return result;
  }

  private <T> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args) {
    Cursor<T> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.selectCursor(command.getName(), param, rowBounds);
    } else {
      result = sqlSession.selectCursor(command.getName(), param);
    }
    return result;
  }

  private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args) {
    Map<K, V> result;
    Object param = method.convertArgsToSqlCommandParam(args);
    if (method.hasRowBounds()) {
      RowBounds rowBounds = method.extractRowBounds(args);
      result = sqlSession.selectMap(command.getName(), param, method.getMapKey(), rowBounds);
    } else {
      result = sqlSession.selectMap(command.getName(), param, method.getMapKey());
    }
    return result;
  }
  
  // ...省略无关方法...

重点看下它 execute() 方法逻辑:

  1. 判断 SQL 执行类型:insert、update、delete、select;
  2. 根据执行类型最终都会调用 SqlSession 的对应方法,而 SqlSession 的对应方法内部最终会调用 Executor 的对应方法。

2.3 创建 SqlSession 流程

上面我们讲了解析 mybatis-config.xml 以及 mapper.xml 的流程,现在我们来看下获取一个 SqlSession 的流程。

从 1. 例子的单元测类中可以看到,它是通过 SqlSession sqlSession = sqlSessionFactory.openSession() 来获取一个 SqlSession,sqlSessionFactory.openSession 是 DefaultSqlSessionFactory 类型的,我们看下它的 openSession() 方法,org.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSession():

  @Override
  public SqlSession openSession() {
    return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
  }

  /**
   * 打开一个 session
   *
   * @param execType
   * @param level
   * @param autoCommit
   * @return
   */
  private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
      // 获取环境信息
      final Environment environment = configuration.getEnvironment();
      // 获取事务工厂
      final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
      // 获取一个事务
      tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
      // 创建一个执行器
      final Executor executor = configuration.newExecutor(tx, execType);
      // 创建一个默认的 DefaultSqlSession
      return new DefaultSqlSession(configuration, executor, autoCommit);
    } catch (Exception e) {
      // 遇到异常关闭事务
      closeTransaction(tx); // may have fetched a connection so lets call close()
      throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

 /**
   * 从环境信息中创建一个事务工厂
   *
   * @param environment
   * @return
   */
  private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment) {
    if (environment == null || environment.getTransactionFactory() == null) {
      // 创建默认的管理的事务工厂
      return new ManagedTransactionFactory();
    }
    // 从环境中获取事务工厂
    return environment.getTransactionFactory();
  }

  /**
   * 关闭事务
   *
   * @param tx
   */
  private void closeTransaction(Transaction tx) {
    if (tx != null) {
      try {
        tx.close();
      } catch (SQLException ignore) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

可以看到,它的主要流程为:

  1. 获取环境 Environment 信息;
  2. 获取一个 TransactionFactory 事务工厂实例;
  3. 通过事务工厂创建一个事务 Transaction 实例;
  4. 通过配置类创建一个 Executor 执行器;
  5. 创建一个 DefaultSqlSession 对象返回;
  6. 遇到异常关闭事务。

2.3.1 获取事务工厂 TransactionFactory

因为我们在 mybatis-config.xml 中配置了环境信息 environment,其中 transactionManager 元素的 type 为 JDBC ,所以 它会获取到的事务工厂为 JdbcTransactionFactory 类型。

然后通过它来创建了一个事务,org.apache.ibatis.transaction.TransactionFactory#newTransaction(javax.sql.DataSource, org.apache.ibatis.session.TransactionIsolationLevel, boolean):

/**
 * Creates {@link JdbcTransaction} instances.
 *
 * @author Clinton Begin
 *
 * @see JdbcTransaction
 */
public class JdbcTransactionFactory implements TransactionFactory {

  @Override
  public Transaction newTransaction(Connection conn) {
    return new JdbcTransaction(conn);
  }

  @Override
  public Transaction newTransaction(DataSource ds, TransactionIsolationLevel level, boolean autoCommit) {
    return new JdbcTransaction(ds, level, autoCommit);
  }
}

2.3.2 获取事务 Transaction

我们看下 newTransaction() 方法返回的 JdbcTransaction 类型:

Mybatis 核心流程源码分析_Mybatis _06

它的实现:

/**
 * {@link Transaction} that makes use of the JDBC commit and rollback facilities directly.
 * It relies on the connection retrieved from the dataSource to manage the scope of the transaction.
 * Delays connection retrieval until getConnection() is called.
 * Ignores commit or rollback requests when autocommit is on.
 *
 * @author Clinton Begin
 *
 * @see JdbcTransactionFactory
 */
public class JdbcTransaction implements Transaction {

  private static final Log log = LogFactory.getLog(JdbcTransaction.class);

  protected Connection connection;
  protected DataSource dataSource;
  protected TransactionIsolationLevel level;
  protected boolean autoCommit;

  public JdbcTransaction(DataSource ds, TransactionIsolationLevel desiredLevel, boolean desiredAutoCommit) {
    dataSource = ds;
    level = desiredLevel;
    autoCommit = desiredAutoCommit;
  }

  public JdbcTransaction(Connection connection) {
    this.connection = connection;
  }

  @Override
  public Connection getConnection() throws SQLException {
    if (connection == null) {
      openConnection();
    }
    return connection;
  }

  @Override
  public void commit() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Committing JDBC Connection [" + connection + "]");
      }
      connection.commit();
    }
  }

  @Override
  public void rollback() throws SQLException {
    if (connection != null && !connection.getAutoCommit()) {
      if (log.isDebugEnabled()) {
        log.debug("Rolling back JDBC Connection [" + connection + "]");
      }
      connection.rollback();
    }
  }

  @Override
  public void close() throws SQLException {
    if (connection != null) {
      resetAutoCommit();
      if (log.isDebugEnabled()) {
        log.debug("Closing JDBC Connection [" + connection + "]");
      }
      connection.close();
    }
  }

  protected void setDesiredAutoCommit(boolean desiredAutoCommit) {
    try {
      if (connection.getAutoCommit() != desiredAutoCommit) {
        if (log.isDebugEnabled()) {
          log.debug("Setting autocommit to " + desiredAutoCommit + " on JDBC Connection [" + connection + "]");
        }
        connection.setAutoCommit(desiredAutoCommit);
      }
    } catch (SQLException e) {
      // Only a very poorly implemented driver would fail here,
      // and there's not much we can do about that.
      throw new TransactionException("Error configuring AutoCommit.  "
          + "Your driver may not support getAutoCommit() or setAutoCommit(). "
          + "Requested setting: " + desiredAutoCommit + ".  Cause: " + e, e);
    }
  }

  protected void resetAutoCommit() {
    try {
      if (!connection.getAutoCommit()) {
        // MyBatis does not call commit/rollback on a connection if just selects were performed.
        // Some databases start transactions with select statements
        // and they mandate a commit/rollback before closing the connection.
        // A workaround is setting the autocommit to true before closing the connection.
        // Sybase throws an exception here.
        if (log.isDebugEnabled()) {
          log.debug("Resetting autocommit to true on JDBC Connection [" + connection + "]");
        }
        connection.setAutoCommit(true);
      }
    } catch (SQLException e) {
      if (log.isDebugEnabled()) {
        log.debug("Error resetting autocommit to true "
            + "before closing the connection.  Cause: " + e);
      }
    }
  }

  protected void openConnection() throws SQLException {
    if (log.isDebugEnabled()) {
      log.debug("Opening JDBC Connection");
    }
    connection = dataSource.getConnection();
    if (level != null) {
      connection.setTransactionIsolation(level.getLevel());
    }
    setDesiredAutoCommit(autoCommit);
  }

  @Override
  public Integer getTimeout() throws SQLException {
    return null;
  }

}

这是一个jdbc 事务,里边提供了一些获取数据库连接、提交事务、回滚、关闭事务操作。

2.3.3 创建执行器 Executor

接着通过 configuration 创建一个执行器 Executor,org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType):

public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor executor;
    if (ExecutorType.BATCH == executorType) {
      // 批量执行器
      executor = new BatchExecutor(this, transaction);
    } else if (ExecutorType.REUSE == executorType) {
      // 重用执行器
      executor = new ReuseExecutor(this, transaction);
    } else {
      // 简单执行器
      executor = new SimpleExecutor(this, transaction);
    }
    // 如果启用二级缓存
    if (cacheEnabled) {
      // 创建一个 CachingExecutor 类型,使用装饰器模式
      executor = new CachingExecutor(executor);
    }
    // 添加拦截器,这里用户可以实现自定义的拦截器
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

这里的逻辑:

  1. 判断参数 ExecutorType 的类型,根据它的类型来创建不同的执行器,默认是 SIMPLE 类型;
  2. ExecutorType.BATCH 类型,则创建 BatchExecutor 执行器;
  3. ExecutorType.REUSE 类型,则创建 ReuseExecutor 执行器;
  4. 否则创建 SimpleExecutor 执行器;
  5. 如果启用了二级缓存,则创建 CachingExecutor 缓存执行器来包装上述执行器。默认是启用二级缓存;
  6. 为添加拦截器,这里用户可以实现自定义的拦截器;
  7. 返回执行器。

我们看下执行器 Executor 的类图:

Mybatis 核心流程源码分析_Mybatis _07

可以看到,Executor 的继承类图,CachingExecutor 是一个装饰器,里边维护了一个真正的执行器,它默认实现的 SimpleExecutor 类型。

2.3.3.1 BaseExecutor 执行器

我们先看下 BaseExecutor 类的实现如下:

public abstract class BaseExecutor implements Executor {

  private static final Log log = LogFactory.getLog(BaseExecutor.class);

  protected Transaction transaction;
  protected Executor wrapper;

  protected ConcurrentLinkedQueue<DeferredLoad> deferredLoads;

  /**
   * 本地缓存,一级缓存
   */
  protected PerpetualCache localCache;

  /**
   * 本地输出参数缓存
   */
  protected PerpetualCache localOutputParameterCache;
  protected Configuration configuration;

  protected int queryStack;
  private boolean closed;

  protected BaseExecutor(Configuration configuration, Transaction transaction) {
    this.transaction = transaction;
    // 这是干啥的?
    this.deferredLoads = new ConcurrentLinkedQueue<>();
    // 本地
    this.localCache = new PerpetualCache("LocalCache");
    this.localOutputParameterCache = new PerpetualCache("LocalOutputParameterCache");
    this.closed = false;
    this.configuration = configuration;
    this.wrapper = this;
  }

  @Override
  public Transaction getTransaction() {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    return transaction;
  }

  @Override
  public void close(boolean forceRollback) {
    try {
      try {
        rollback(forceRollback);
      } finally {
        if (transaction != null) {
          transaction.close();
        }
      }
    } catch (SQLException e) {
      // Ignore. There's nothing that can be done at this point.
      log.warn("Unexpected exception on closing transaction.  Cause: " + e);
    } finally {
      transaction = null;
      deferredLoads = null;
      localCache = null;
      localOutputParameterCache = null;
      closed = true;
    }
  }

  @Override
  public boolean isClosed() {
    return closed;
  }

  @Override
  public int update(MappedStatement ms, Object parameter) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing an update").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    clearLocalCache();
    return doUpdate(ms, parameter);
  }

  @Override
  public List<BatchResult> flushStatements() throws SQLException {
    return flushStatements(false);
  }

  public List<BatchResult> flushStatements(boolean isRollBack) throws SQLException {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    // 执行刷新声明
    return doFlushStatements(isRollBack);
  }

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 绑定一个 SQL
    BoundSql boundSql = ms.getBoundSql(parameter);
    // 构建一个一级缓存 key
    CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
    return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
  }

  @SuppressWarnings("unchecked")
  @Override
  public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    if (queryStack == 0 && ms.isFlushCacheRequired()) {
      // 清除本地缓存
      clearLocalCache();
    }
    List<E> list;
    try {
      queryStack++;
      // 从一级缓存中获取
      list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;
      if (list != null) {
        handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);
      } else {
        // 查询数据库
        list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);
      }
    } finally {
      queryStack--;
    }
    if (queryStack == 0) {
      // TODO: 2020/9/18 引用队列?
      for (DeferredLoad deferredLoad : deferredLoads) {
        deferredLoad.load();
      }
      // issue #601
      deferredLoads.clear();
      if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
        // issue #482
        clearLocalCache();
      }
    }
    return list;
  }

  @Override
  public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
    BoundSql boundSql = ms.getBoundSql(parameter);
    return doQueryCursor(ms, parameter, rowBounds, boundSql);
  }

  @Override
  public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    DeferredLoad deferredLoad = new DeferredLoad(resultObject, property, key, localCache, configuration, targetType);
    if (deferredLoad.canLoad()) {
      deferredLoad.load();
    } else {
      // 这是干甚的?
      deferredLoads.add(new DeferredLoad(resultObject, property, key, localCache, configuration, targetType));
    }
  }

  /**
   * 创建二级缓存 key
   *
   * @param ms
   * @param parameterObject
   * @param rowBounds
   * @param boundSql
   * @return
   */
  @Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    if (closed) {
      throw new ExecutorException("Executor was closed.");
    }
    CacheKey cacheKey = new CacheKey();
    cacheKey.update(ms.getId());
    cacheKey.update(rowBounds.getOffset());
    cacheKey.update(rowBounds.getLimit());
    cacheKey.update(boundSql.getSql());
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
    // mimic DefaultParameterHandler logic
    for (ParameterMapping parameterMapping : parameterMappings) {
      if (parameterMapping.getMode() != ParameterMode.OUT) {
        Object value;
        String propertyName = parameterMapping.getProperty();
        if (boundSql.hasAdditionalParameter(propertyName)) {
          value = boundSql.getAdditionalParameter(propertyName);
        } else if (parameterObject == null) {
          value = null;
        } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          value = parameterObject;
        } else {
          MetaObject metaObject = configuration.newMetaObject(parameterObject);
          value = metaObject.getValue(propertyName);
        }
        cacheKey.update(value);
      }
    }
    if (configuration.getEnvironment() != null) {
      // issue #176
      cacheKey.update(configuration.getEnvironment().getId());
    }
    return cacheKey;
  }

  @Override
  public boolean isCached(MappedStatement ms, CacheKey key) {
    return localCache.getObject(key) != null;
  }

  @Override
  public void commit(boolean required) throws SQLException {
    if (closed) {
      throw new ExecutorException("Cannot commit, transaction is already closed");
    }
    // 清除本地缓存
    clearLocalCache();
    // 刷新声明
    flushStatements();
    if (required) {
      // 事务提交
      transaction.commit();
    }
  }

  @Override
  public void rollback(boolean required) throws SQLException {
    if (!closed) {
      try {
        clearLocalCache();
        flushStatements(true);
      } finally {
        if (required) {
          transaction.rollback();
        }
      }
    }
  }

  @Override
  public void clearLocalCache() {
    if (!closed) {
      localCache.clear();
      localOutputParameterCache.clear();
    }
  }

  protected abstract int doUpdate(MappedStatement ms, Object parameter) throws SQLException;

  protected abstract List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException;

  protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql)
      throws SQLException;

  protected abstract <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql)
      throws SQLException;

  protected void closeStatement(Statement statement) {
    if (statement != null) {
      try {
        statement.close();
      } catch (SQLException e) {
        // ignore
      }
    }
  }

  /**
   * Apply a transaction timeout.
   *
   * @param statement
   *          a current statement
   * @throws SQLException
   *           if a database access error occurs, this method is called on a closed <code>Statement</code>
   * @since 3.4.0
   * @see StatementUtil#applyTransactionTimeout(Statement, Integer, Integer)
   */
  protected void applyTransactionTimeout(Statement statement) throws SQLException {
    StatementUtil.applyTransactionTimeout(statement, statement.getQueryTimeout(), transaction.getTimeout());
  }

  /**
   * 处理本地缓存输出参数
   *
   * @param ms
   * @param key
   * @param parameter
   * @param boundSql
   */
  private void handleLocallyCachedOutputParameters(MappedStatement ms, CacheKey key, Object parameter, BoundSql boundSql) {
    // 处理 callable 类型,存储过程、存储函数
    if (ms.getStatementType() == StatementType.CALLABLE) {
      final Object cachedParameter = localOutputParameterCache.getObject(key);
      if (cachedParameter != null && parameter != null) {
        final MetaObject metaCachedParameter = configuration.newMetaObject(cachedParameter);
        final MetaObject metaParameter = configuration.newMetaObject(parameter);
        for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
          if (parameterMapping.getMode() != ParameterMode.IN) {
            final String parameterName = parameterMapping.getProperty();
            final Object cachedValue = metaCachedParameter.getValue(parameterName);
            metaParameter.setValue(parameterName, cachedValue);
          }
        }
      }
    }
  }

  /**
   * 从数据库获取
   *
   * @param ms
   * @param parameter
   * @param rowBounds
   * @param resultHandler
   * @param key
   * @param boundSql
   * @param <E>
   * @return
   * @throws SQLException
   */
  private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {
    List<E> list;
    // 放入占位符
    localCache.putObject(key, EXECUTION_PLACEHOLDER);
    try {
      // 开始真正的查询数据
      list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);
    } finally {
      // 清除本地缓存
      localCache.removeObject(key);
    }
    // 放入本地缓存
    localCache.putObject(key, list);
    if (ms.getStatementType() == StatementType.CALLABLE) {
      // 如果是调用存储过程、存储函数,则把参数放入缓存中
      localOutputParameterCache.putObject(key, parameter);
    }
    return list;
  }

  protected Connection getConnection(Log statementLog) throws SQLException {
    Connection connection = transaction.getConnection();
    if (statementLog.isDebugEnabled()) {
      return ConnectionLogger.newInstance(connection, statementLog, queryStack);
    } else {
      return connection;
    }
  }

  @Override
  public void setExecutorWrapper(Executor wrapper) {
    this.wrapper = wrapper;
  }

  private static class DeferredLoad {

    private final MetaObject resultObject;
    private final String property;
    private final Class<?> targetType;
    private final CacheKey key;
    private final PerpetualCache localCache;
    private final ObjectFactory objectFactory;
    private final ResultExtractor resultExtractor;

    // issue #781
    public DeferredLoad(MetaObject resultObject,
                        String property,
                        CacheKey key,
                        PerpetualCache localCache,
                        Configuration configuration,
                        Class<?> targetType) {
      this.resultObject = resultObject;
      this.property = property;
      this.key = key;
      this.localCache = localCache;
      this.objectFactory = configuration.getObjectFactory();
      this.resultExtractor = new ResultExtractor(configuration, objectFactory);
      this.targetType = targetType;
    }

    public boolean canLoad() {
      return localCache.getObject(key) != null && localCache.getObject(key) != EXECUTION_PLACEHOLDER;
    }

    public void load() {
      @SuppressWarnings("unchecked")
      // we suppose we get back a List
      List<Object> list = (List<Object>) localCache.getObject(key);
      Object value = resultExtractor.extractObjectFromList(list, targetType);
      resultObject.setValue(property, value);
    }

  }

}

这个类是抽象类,它实现了 Executor 接口的核心方法,留下一些抽象方法和模板方法交给了子类实现。这个类主要提供几个主要的属性:

  1. PerpetualCache 类型的 localCache 属性,这是一个一级缓存,在同一个 sqlSession 查询相同接口数据时,提供缓存数据,避免查询相同查询语句和参数再次查询数据库。在查询时会从缓存中查找,以及保存缓存,在更新、删除都会清空缓存;
  2. 持有事务 Transaction 属性,用于在执行完一些事务提交、回滚、操作操作时,委派事务执行对应的逻辑;

2.3.3.2 SimpleExecutor 执行器

默认的实际执行器是 SimpleExecutor 类型,看下它的实现:

public class SimpleExecutor extends BaseExecutor {

  public SimpleExecutor(Configuration configuration, Transaction transaction) {
    super(configuration, transaction);
  }

  @Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      // 获取配置类型
      Configuration configuration = ms.getConfiguration();
      // 获取 StatementHandler 处理器
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      // 创建 Statement
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      // 获取配置类型
      Configuration configuration = ms.getConfiguration();
      // 获取 StatementHandler 处理器
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      // 创建 Statement
      stmt = prepareStatement(handler, ms.getStatementLog());
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

  @Override
  protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
    // 获取配置类型
    Configuration configuration = ms.getConfiguration();
    // 获取 StatementHandler 处理器
    StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
    // 创建 Statement
    Statement stmt = prepareStatement(handler, ms.getStatementLog());
    Cursor<E> cursor = handler.queryCursor(stmt);
    stmt.closeOnCompletion();
    return cursor;
  }

  @Override
  public List<BatchResult> doFlushStatements(boolean isRollback) {
    return Collections.emptyList();
  }

  /**
   * 准备一个 Statement
   *
   * @param handler
   * @param statementLog
   * @return
   * @throws SQLException
   */
  private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
    Statement stmt;
    // 获取连接
    Connection connection = getConnection(statementLog);
    // 通过 StatementHandler 创建一个 Statement
    stmt = handler.prepare(connection, transaction.getTimeout());
    // 初始化参数
    handler.parameterize(stmt);
    return stmt;
  }

}

这个类主要实现了 BaseExecutor 抽象的类的抽象的模板方法:doUpdate()、doQuery()、doQueryCursor()、doFlushStatements() 方法,这些方法主要的逻辑为:

  1. 获取 Configuration 配置类;
  2. 通过配置类 Configuration 的 newStatementHandler() 方法来创建 StatementHandler 类;
  3. 调用 prepareStatement() 方法,通过 StatementHandler 创建 Statement;
  4. 再通过 StatementHandler 执行对应的查询、更新相关方法。
2.3.3.2.1 StatementHandler 处理器

在上述的 SimpleExecutor 类中,通过配置类 Configuration 的 newStatementHandler() 方法获取 StatementHandler 实例,我们先看下 StatementHandler 的类图:

Mybatis 核心流程源码分析_Mybatis _08

我们看下它的实现,org.apache.ibatis.session.Configuration#newStatementHandler:

  public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    // 创建一个 RoutingStatementHandler 路由的声明处理器
    StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
    // 对 StatementHandler 应用插件
    statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
    return statementHandler;
  }

它的逻辑:

  1. 创建一个 RoutingStatementHandler 路由的声明处理器;
  2. 对 StatementHandler 应用插件;
  3. 返回 statementHandler。

继续看下 RoutingStatementHandler 这个类:

public class RoutingStatementHandler implements StatementHandler {

  /**
   * 关联一个真正的 RoutingStatementHandler
   */
  private final StatementHandler delegate;

  public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {

    switch (ms.getStatementType()) {
      case STATEMENT:
        delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case PREPARED:
        delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      case CALLABLE:
        delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
        break;
      default:
        throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
    }

  }

  @Override
  public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
    return delegate.prepare(connection, transactionTimeout);
  }

  @Override
  public void parameterize(Statement statement) throws SQLException {
    delegate.parameterize(statement);
  }

  @Override
  public void batch(Statement statement) throws SQLException {
    delegate.batch(statement);
  }

  @Override
  public int update(Statement statement) throws SQLException {
    return delegate.update(statement);
  }

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    return delegate.query(statement, resultHandler);
  }

  @Override
  public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
    return delegate.queryCursor(statement);
  }

  @Override
  public BoundSql getBoundSql() {
    return delegate.getBoundSql();
  }

  @Override
  public ParameterHandler getParameterHandler() {
    return delegate.getParameterHandler();
  }
}

可以看到,这个类实现了 StatementHandler 接口,并且根据 MappedStatement 获取 StatementType,创建对应的 StatementHandler,有:SimpleStatementHandler、PreparedStatementHandler、CallableStatementHandler。默认是会创建 PreparedStatementHandler 实例。

它的其他方法都是使用委派的 StatementHandler 实例去执行,比如 prepare()、parameterize()、batch()、update()、query()、queryCursor()、getBoundSql()、getParameterHandler() 方法。

2.3.3.2.2 PreparedStatementHandler

我们看下实际的 PreparedStatementHandler 类:

public class PreparedStatementHandler extends BaseStatementHandler {

  public PreparedStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    super(executor, mappedStatement, parameter, rowBounds, resultHandler, boundSql);
  }

  @Override
  public int update(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 执行更新
    ps.execute();
    // 获取更新的行数
    int rows = ps.getUpdateCount();
    // 获取参数对象
    Object parameterObject = boundSql.getParameterObject();
    // 获取键生成器
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    // 后置处理器键,比如这里会针对 insert 语句,会设置插入之后的主键到参数对象上。
    keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
    return rows;
  }

  @Override
  public void batch(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 批量查询
    ps.addBatch();
  }

  @Override
  public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 执行查询
    ps.execute();
    // 通过结果集处理器处理结果
    return resultSetHandler.handleResultSets(ps);
  }

  @Override
  public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 执行查询
    ps.execute();
    // 结果集处理器处理数据
    return resultSetHandler.handleCursorResultSets(ps);
  }

  /**
   * 初始化一个 Statement
   *
   * @param connection
   * @return
   * @throws SQLException
   */
  @Override
  protected Statement instantiateStatement(Connection connection) throws SQLException {
    String sql = boundSql.getSql();
    if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
      String[] keyColumnNames = mappedStatement.getKeyColumns();
      if (keyColumnNames == null) {
        return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
      } else {
        return connection.prepareStatement(sql, keyColumnNames);
      }
    } else if (mappedStatement.getResultSetType() == ResultSetType.DEFAULT) {
      //
      return connection.prepareStatement(sql);
    } else {
      return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
    }
  }

  @Override
  public void parameterize(Statement statement) throws SQLException {
    // 使用参数化对象进行设置参数
    parameterHandler.setParameters((PreparedStatement) statement);
  }

}

这个类就是实际真正执行目标 SQL 逻辑的类,它的一些方法逻辑:

  1. update() 方法中,会通过 PreparedStatement 执行 SQL,然后获取参数对象、键生成器,对参数进行后置处理;
  2. query()、queryCursor() 方法中,会通过 PreparedStatement 执行 SQL,然后通过结果集处理器对结果进行处理;

2.3.4 CachingExecutor 缓存执行器

接着该看 CachingExecutor 类了:

/**
 * 缓存执行器,装饰器模式,声明周期是一个 session
 *
 * @author Clinton Begin
 * @author Eduardo Macarron
 */
public class CachingExecutor implements Executor {

  /**
   * 委派的执行器
   */
  private final Executor delegate;

  /**
   * 事务缓存管理器
   */
  private final TransactionalCacheManager tcm = new TransactionalCacheManager();

  public CachingExecutor(Executor delegate) {
    this.delegate = delegate;
    delegate.setExecutorWrapper(this);
  }

  @Override
  public Transaction getTransaction() {
    return delegate.getTransaction();
  }

  @Override
  public void close(boolean forceRollback) {
    try {
      // issues #499, #524 and #573
      if (forceRollback) {
        tcm.rollback();
      } else {
        tcm.commit();
      }
    } finally {
      delegate.close(forceRollback);
    }
  }

  @Override
  public boolean isClosed() {
    return delegate.isClosed();
  }

  @Override
  public int update(MappedStatement ms, Object parameterObject) throws SQLException {
    flushCacheIfRequired(ms);
    return delegate.update(ms, parameterObject);
  }

  @Override
  public <E> Cursor<E> queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException {
    flushCacheIfRequired(ms);
    return delegate.queryCursor(ms, parameter, rowBounds);
  }

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
    // 绑定 SQL
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 构建缓存key
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    // 获取二级缓存配置,它是从解析 mapper.xml 和 mapper 接口的 @CacheNamespace 注解得出来的
    Cache cache = ms.getCache();
    if (cache != null) {
      // 是否需要刷新缓存
      flushCacheIfRequired(ms);
      if (ms.isUseCache() && resultHandler == null) {
        ensureNoOutParams(ms, boundSql);
        @SuppressWarnings("unchecked")
        List<E> list = (List<E>) tcm.getObject(cache, key);
        if (list == null) {
          list = delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
          // 缓存管理器,把缓存
          tcm.putObject(cache, key, list); // issue #578 and #116
        }
        return list;
      }
    }
    // 委派实际的 BaseExecutor 类型的查询
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

  @Override
  public List<BatchResult> flushStatements() throws SQLException {
    return delegate.flushStatements();
  }

  @Override
  public void commit(boolean required) throws SQLException {
    // 提交事务
    delegate.commit(required);
    // 事务缓存管理器提交
    tcm.commit();
  }

  @Override
  public void rollback(boolean required) throws SQLException {
    try {
      delegate.rollback(required);
    } finally {
      if (required) {
        tcm.rollback();
      }
    }
  }

  private void ensureNoOutParams(MappedStatement ms, BoundSql boundSql) {
    if (ms.getStatementType() == StatementType.CALLABLE) {
      for (ParameterMapping parameterMapping : boundSql.getParameterMappings()) {
        if (parameterMapping.getMode() != ParameterMode.IN) {
          throw new ExecutorException("Caching stored procedures with OUT params is not supported.  Please configure useCache=false in " + ms.getId() + " statement.");
        }
      }
    }
  }

  @Override
  public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
    return delegate.createCacheKey(ms, parameterObject, rowBounds, boundSql);
  }

  @Override
  public boolean isCached(MappedStatement ms, CacheKey key) {
    return delegate.isCached(ms, key);
  }

  @Override
  public void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType) {
    delegate.deferLoad(ms, resultObject, property, key, targetType);
  }

  @Override
  public void clearLocalCache() {
    delegate.clearLocalCache();
  }

  private void flushCacheIfRequired(MappedStatement ms) {
    Cache cache = ms.getCache();
    if (cache != null && ms.isFlushCacheRequired()) {
      // 查询之前,先清空二级缓存
      tcm.clear(cache);
    }
  }

  @Override
  public void setExecutorWrapper(Executor executor) {
    throw new UnsupportedOperationException("This method should not be called");
  }

}

这个类是一个 Executor 的装饰器类,主要提供了二级缓存功能。它在查询数据、更新数据、提交、回滚操作时,会对二级缓存进行处理。

它的查询数据逻辑:

  1. 构建一个 CacheKey 类型的缓存 key;
  2. 从 MappedStatement 中获取二级缓存 Cache;
  3. 如果 cache 为空,则执行实际的委派执行器执行查询数据;
  4. 如果 cache 不为空,则先判断是否需要刷新缓存,如果需要刷新则通过 TransactionalCacheManager 清除缓存;然后从 TransactionalCacheManager 对象中获取 key 对应的二级缓存数据,缓存数据不为空直接返回,否则就继续执行实际委派执行器查询数据,然后把数据缓存到二级缓存中。
  5. 最后返回数据。

2.3.4.1 TransactionalCacheManager 事务缓存管理器

看下 TransactionalCacheManager 的实现:

public class TransactionalCacheManager {

  private final Map<Cache, TransactionalCache> transactionalCaches = new HashMap<>();

  public void clear(Cache cache) {
    getTransactionalCache(cache).clear();
  }

  public Object getObject(Cache cache, CacheKey key) {
    return getTransactionalCache(cache).getObject(key);
  }

  public void putObject(Cache cache, CacheKey key, Object value) {
    // 获取 cache 对应的 TransactionalCache,然后把 key 和 value 存入
    getTransactionalCache(cache).putObject(key, value);
  }

  public void commit() {
    // 遍历事务缓存
    for (TransactionalCache txCache : transactionalCaches.values()) {
      // 提交事务
      txCache.commit();
    }
  }

  public void rollback() {
    for (TransactionalCache txCache : transactionalCaches.values()) {
      txCache.rollback();
    }
  }

  private TransactionalCache getTransactionalCache(Cache cache) {
    // 如果 transactionalCaches 中的 cache 键没有对应的数据,则创建 TransactionalCache 对象
    // 把 cache 对象当做 TransactionalCache 构造器的参数传入
    return transactionalCaches.computeIfAbsent(cache, TransactionalCache::new);
  }

}

这个类持有一个 key 是 Cache 类型,value 为 TransactionalCache 类型的 HashMap 类型属性 transactionalCaches,来保存事务缓存数据。

它的 getTransactionalCache() 方法中,参数 cache 是外部传入的二级缓存,当 transactionalCaches 没有这个 cache 对应的 value 时,就创建一个 TransactionalCache 类,并且把 cache 作为参数传入它的构造器中,保存起来。

它的结构为:

Mybatis 核心流程源码分析_Mybatis _09

TransactionalCacheManager 这个个在保存缓存数据时,会调用 TransactionalCache 的 putObject() 方法,在提交事务、回滚事务的时候,会调用 TransactionalCache 的 commit() 和 rollback() 方法。

2.3.4.2 TransactionalCache 事务缓存

我们详细看下这个类。还记得上面 2.2.2 中我们讲过的缓存装饰器吗?没错这里又看见了一个缓存装饰器 TransactionalCache,它是实现如下:

/**
 * The 2nd level cache transactional buffer.
 * <p>
 * This class holds all cache entries that are to be added to the 2nd level cache during a Session.
 * Entries are sent to the cache when commit is called or discarded if the Session is rolled back.
 * Blocking cache support has been added. Therefore any get() that returns a cache miss
 * will be followed by a put() so any lock associated with the key can be released.
 *
 * @author Clinton Begin
 * @author Eduardo Macarron
 */
public class TransactionalCache implements Cache {

  private static final Log log = LogFactory.getLog(TransactionalCache.class);

  private final Cache delegate;
  private boolean clearOnCommit;

  /**
   * 事务未提交前的保存的缓存数据
   */
  private final Map<Object, Object> entriesToAddOnCommit;

  /**
   * 事务未提交前未命中的缓存数据
   */
  private final Set<Object> entriesMissedInCache;

  public TransactionalCache(Cache delegate) {
    this.delegate = delegate;
    this.clearOnCommit = false;
    this.entriesToAddOnCommit = new HashMap<>();
    this.entriesMissedInCache = new HashSet<>();
  }

  @Override
  public String getId() {
    return delegate.getId();
  }

  @Override
  public int getSize() {
    return delegate.getSize();
  }

  @Override
  public Object getObject(Object key) {
    // issue #116
    Object object = delegate.getObject(key);
    if (object == null) {
      entriesMissedInCache.add(key);
    }
    // issue #146
    if (clearOnCommit) {
      return null;
    } else {
      return object;
    }
  }

  @Override
  public void putObject(Object key, Object object) {
    // 把数据先临时保存起来
    entriesToAddOnCommit.put(key, object);
  }

  @Override
  public Object removeObject(Object key) {
    return null;
  }

  @Override
  public void clear() {
    clearOnCommit = true;
    entriesToAddOnCommit.clear();
  }

  public void commit() {
    if (clearOnCommit) {
      // 提交的时候清理二级缓存
      delegate.clear();
    }
    // 提交的时候,刷新查询的数据,用于保存到二级缓存中
    flushPendingEntries();
    reset();
  }

  public void rollback() {
    // 回滚时解析未命中的数据
    unlockMissedEntries();
    reset();
  }

  private void reset() {
    clearOnCommit = false;
    entriesToAddOnCommit.clear();
    entriesMissedInCache.clear();
  }

  private void flushPendingEntries() {
    // 提交的时候,把临时保存的数据,真正放入二级缓存中
    for (Map.Entry<Object, Object> entry : entriesToAddOnCommit.entrySet()) {
      delegate.putObject(entry.getKey(), entry.getValue());
    }
    for (Object entry : entriesMissedInCache) {
      if (!entriesToAddOnCommit.containsKey(entry)) {
        delegate.putObject(entry, null);
      }
    }
  }

  private void unlockMissedEntries() {
    // 移除未命中的数据
    for (Object entry : entriesMissedInCache) {
      try {
        delegate.removeObject(entry);
      } catch (Exception e) {
        log.warn("Unexpected exception while notifiying a rollback to the cache adapter. "
            + "Consider upgrading your cache adapter to the latest version. Cause: " + e);
      }
    }
  }

}

这个类它也是有持有一个实际的委派的缓存,它默认是我们在 2.2.2 节中讲到的 SynchronizedCache 装饰过的二级缓存。

这个类还有个两个属性:Map<Object, Object> entriesToAddOnCommit 和 Set<Object> entriesMissedInCache,它们的作用是在 session 事务没有提交之前,临时保存缓存数据,等待真正的事务提交 commit() 时才会把缓存同步到二级缓存中,在回滚 rollback() 等时会清除未命中的缓存。

我们通过在它的 getObject() 方法中打断点,可以得到如下所示的结论。它是一个缓存装饰器,一层层的包装。

Mybatis 核心流程源码分析_Mybatis _10

注意了 TransactionalCache 的声明周期不与委派的二级缓存一样,它是和一个 SqlSession 的声明一样的。而委派的二级缓存是和应用程序的生命周期一样的。

2.3.5 应用插件 interceptorChain.pluginAll()

我们再看下为执行器应用插件的逻辑 interceptorChain.pluginAll(executor):

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;
  }

  public void addInterceptor(Interceptor interceptor) {
    interceptors.add(interceptor);
  }

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

}

这里会遍历所有的实现了 Interceptor 接口的拦截器类,调用它们的 plugin() 方法,对目标类进行拦截。实际上拦截器的调用一共有四个地方:

Mybatis 核心流程源码分析_Mybatis _11

分别是:

  1. 创建 ParameterHandler 参数处理器时的拦截;
  2. 创建 ResultSetHandler 结果集处理器的拦截;
  3. 创建 StatementHandler 的拦截;
  4. 创建 Executor 的拦截。

我们可以实现自己的拦截器,根据自己的需求针对这四种类型进行拦截调用。比如可以针对 ParameterHandler 类型进行拦截,实现自动查询增加分页 SQL 的功能等等。

2.3.5 创建 DefaultSqlSession

最后一步是根据已经创建好的 Executor 和 Configuration 来创建一个 DefaultSqlSession 实例。

public class DefaultSqlSession implements SqlSession {

  private final Configuration configuration;
  private final Executor executor;

  private final boolean autoCommit;
  private boolean dirty;
  private List<Cursor<?>> cursorList;

  public DefaultSqlSession(Configuration configuration, Executor executor, boolean autoCommit) {
    this.configuration = configuration;
    this.executor = executor;
    this.dirty = false;
    this.autoCommit = autoCommit;
  }

  public DefaultSqlSession(Configuration configuration, Executor executor) {
    this(configuration, executor, false);
  }

  @Override
  public <T> T selectOne(String statement) {
    return this.selectOne(statement, null);
  }

  @Override
  public <T> T selectOne(String statement, Object parameter) {
    // Popular vote was to return null on 0 results and throw exception on too many.
    List<T> list = this.selectList(statement, parameter);
    if (list.size() == 1) {
      return list.get(0);
    } else if (list.size() > 1) {
      throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
    } else {
      return null;
    }
  }

  @Override
  public <K, V> Map<K, V> selectMap(String statement, String mapKey) {
    return this.selectMap(statement, null, mapKey, RowBounds.DEFAULT);
  }

  @Override
  public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey) {
    return this.selectMap(statement, parameter, mapKey, RowBounds.DEFAULT);
  }

  @Override
  public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
    final List<? extends V> list = selectList(statement, parameter, rowBounds);
    final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<>(mapKey,
            configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
    final DefaultResultContext<V> context = new DefaultResultContext<>();
    for (V o : list) {
      context.nextResultObject(o);
      mapResultHandler.handleResult(context);
    }
    return mapResultHandler.getMappedResults();
  }

  @Override
  public <T> Cursor<T> selectCursor(String statement) {
    return selectCursor(statement, null);
  }

  @Override
  public <T> Cursor<T> selectCursor(String statement, Object parameter) {
    return selectCursor(statement, parameter, RowBounds.DEFAULT);
  }

  @Override
  public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      Cursor<T> cursor = executor.queryCursor(ms, wrapCollection(parameter), rowBounds);
      registerCursor(cursor);
      return cursor;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  @Override
  public <E> List<E> selectList(String statement) {
    return this.selectList(statement, null);
  }

  @Override
  public <E> List<E> selectList(String statement, Object parameter) {
    return this.selectList(statement, parameter, RowBounds.DEFAULT);
  }

  @Override
  public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
    try {
      // 从配置类中获取映射声明对象
      // MappedStatement 声明周期很长,随着容器的关闭而关闭
      MappedStatement ms = configuration.getMappedStatement(statement);
      // 查询数据
      return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  @Override
  public void select(String statement, Object parameter, ResultHandler handler) {
    select(statement, parameter, RowBounds.DEFAULT, handler);
  }

  @Override
  public void select(String statement, ResultHandler handler) {
    select(statement, null, RowBounds.DEFAULT, handler);
  }

  @Override
  public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
      MappedStatement ms = configuration.getMappedStatement(statement);
      executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  @Override
  public int insert(String statement) {
    return insert(statement, null);
  }

  @Override
  public int insert(String statement, Object parameter) {
    return update(statement, parameter);
  }

  @Override
  public int update(String statement) {
    return update(statement, null);
  }

  @Override
  public int update(String statement, Object parameter) {
    try {
      dirty = true;
      MappedStatement ms = configuration.getMappedStatement(statement);
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  @Override
  public int delete(String statement) {
    return update(statement, null);
  }

  @Override
  public int delete(String statement, Object parameter) {
    return update(statement, parameter);
  }

  @Override
  public void commit() {
    commit(false);
  }

  @Override
  public void commit(boolean force) {
    try {
      executor.commit(isCommitOrRollbackRequired(force));
      dirty = false;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error committing transaction.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  @Override
  public void rollback() {
    rollback(false);
  }

  @Override
  public void rollback(boolean force) {
    try {
      executor.rollback(isCommitOrRollbackRequired(force));
      dirty = false;
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error rolling back transaction.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  @Override
  public List<BatchResult> flushStatements() {
    try {
      return executor.flushStatements();
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error flushing statements.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

  @Override
  public void close() {
    try {
      executor.close(isCommitOrRollbackRequired(false));
      closeCursors();
      dirty = false;
    } finally {
      ErrorContext.instance().reset();
    }
  }

  private void closeCursors() {
    if (cursorList != null && !cursorList.isEmpty()) {
      for (Cursor<?> cursor : cursorList) {
        try {
          cursor.close();
        } catch (IOException e) {
          throw ExceptionFactory.wrapException("Error closing cursor.  Cause: " + e, e);
        }
      }
      cursorList.clear();
    }
  }

  @Override
  public Configuration getConfiguration() {
    return configuration;
  }

  @Override
  public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }

  @Override
  public Connection getConnection() {
    try {
      return executor.getTransaction().getConnection();
    } catch (SQLException e) {
      throw ExceptionFactory.wrapException("Error getting a new connection.  Cause: " + e, e);
    }
  }

  @Override
  public void clearCache() {
    executor.clearLocalCache();
  }

  private <T> void registerCursor(Cursor<T> cursor) {
    if (cursorList == null) {
      cursorList = new ArrayList<>();
    }
    cursorList.add(cursor);
  }

  private boolean isCommitOrRollbackRequired(boolean force) {
    return (!autoCommit && dirty) || force;
  }

  private Object wrapCollection(final Object object) {
    return ParamNameResolver.wrapToMapIfCollection(object, null);
  }

  /**
   * @deprecated Since 3.5.5
   */
  @Deprecated
  public static class StrictMap<V> extends HashMap<String, V> {

    private static final long serialVersionUID = -5741767162221585340L;

    @Override
    public V get(Object key) {
      if (!super.containsKey(key)) {
        throw new BindingException("Parameter '" + key + "' not found. Available parameters are " + this.keySet());
      }
      return super.get(key);
    }

  }

}

这个类实现了 SqlSession 接口的增删改查方法,最终还是委派 Executor 去执行。

2.4 通过 SqlSession 获取映射接口执行目标方法

接下来,该看通过创建好的 SqlSession 来获取映射接口执行目标方法的流程了。

// 通过 SqlSession 获取映射接口
AutoConstructorMapper mapper = sqlSession.getMapper(AutoConstructorMapper.class);
// 执行目标方法  
PrimitiveSubject ps1 = mapper.selectOneById(999);

从上面的分析,我们知道了 sqlSession 是 DefaultSqlSession 类型,它的 getMapper() 方法,我们在 2.3.5 中看到了它的实现,org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper:

  @Override
  public <T> T getMapper(Class<T> type) {
    return configuration.getMapper(type, this);
  }

它会通过配置类 Configuration 根据类型获取对应的 Mapper 类型,org.apache.ibatis.session.Configuration#getMapper:

  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    return mapperRegistry.getMapper(type, sqlSession);
  }

最后再调用 MapperRegistry 实例的 getMapper() 方法,org.apache.ibatis.binding.MapperRegistry#getMapper:

  /**
   * 获取映射器
   *
   * @param type
   * @param sqlSession
   * @param <T>
   * @return
   */
  @SuppressWarnings("unchecked")
  public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
    final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
    if (mapperProxyFactory == null) {
      throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
    }
    try {
      // 映射器代理工厂获取代理对象
      return mapperProxyFactory.newInstance(sqlSession);
    } catch (Exception e) {
      throw new BindingException("Error getting mapper instance. Cause: " + e, e);
    }
  }

看到这里,我们就就比较熟悉了,在 2.2.4 节中讲了解析 mapper.xml 文件时,会根据 xml 中的命名空间来注册对应的 mapper 接口,会以一个 key 为目标接口类型,value 为 MapperProxyFactory 实例的形式保存到一个 HashMap 实例中。

这里就是获取除了目标类型对应的 MapperProxyFactory 类型,然后调用它的 newInstance() 方法,通过 JDK 动态代理创建代理实例类。

最后,用这个代理对象来执行目标方法。

2.4.1 查询非缓存数据流程

我们在 org.apache.ibatis.executor.statement.PreparedStatementHandler#query 方法处,打个端点看下它的方法调用栈信息:

// 调用 PreparedStatementHandler 的 query 方法
query(Statement, ResultHandler):71, PreparedStatementHandler (org.apache.ibatis.executor.statement), PreparedStatementHandler.java

// 调用 RoutingStatementHandler 的 query 方法
query(Statement, ResultHandler):82, RoutingStatementHandler (org.apache.ibatis.executor.statement), RoutingStatementHandler.java

// 调用 SimpleExecutor 的 doQuery() 方法
doQuery(MappedStatement, Object, RowBounds, ResultHandler, BoundSql):69, SimpleExecutor (org.apache.ibatis.executor), SimpleExecutor.java

// 调用 BaseExecutor 的 queryFromDatabase() 方法
queryFromDatabase(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql):381, BaseExecutor (org.apache.ibatis.executor), BaseExecutor.java

// 调用 CachingExecutor 的 query() 方法
query(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql):173, BaseExecutor (org.apache.ibatis.executor), BaseExecutor.java
query(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql):116, CachingExecutor (org.apache.ibatis.executor), CachingExecutor.java
query(MappedStatement, Object, RowBounds, ResultHandler):100, CachingExecutor (org.apache.ibatis.executor), CachingExecutor.java

// 调用 DefaultSqlSession 的 select() 方法
selectList(String, Object, RowBounds):151, DefaultSqlSession (org.apache.ibatis.session.defaults), DefaultSqlSession.java
selectList(String, Object):141, DefaultSqlSession (org.apache.ibatis.session.defaults), DefaultSqlSession.java
selectOne(String, Object):77, DefaultSqlSession (org.apache.ibatis.session.defaults), DefaultSqlSession.java

// 调用 MapperMethod 类的 execute() 方法
execute(SqlSession, Object[]):105, MapperMethod (org.apache.ibatis.binding), MapperMethod.java

// 调用 MapperProxy 类的 invoke() 方法
invoke(Object, Method, Object[], SqlSession):183, MapperProxy$PlainMethodInvoker (org.apache.ibatis.binding), MapperProxy.java
invoke(Object, Method, Object[]):101, MapperProxy (org.apache.ibatis.binding), MapperProxy.java

// 调用 JDK 动态代理类的 selectOneById() 方法
selectOneById(int):-1, $Proxy15 (com.sun.proxy), Unknown Source

// 单元测试类的查询方法
testSelectOneById():129, AutoConstructorTest (org.apache.ibatis.autoconstructor), AutoConstructorTest.java
...省略无关栈信息...

它的时序图:

Mybatis 核心流程源码分析_Mybatis _12

sequenceDiagram
# 单元测试入口
AutoConstructorTest->>AutoConstructorTest:testSelectOneById() 单元测试方法
AutoConstructorTest->>$Proxy15:selectOneById()
# JDK代理对象
$Proxy15->>MapperProxy:invoke() 执行
# 代理查询
MapperProxy->>PlainMethodInvoker:invoke()
PlainMethodInvoker->>MapperMethod:execute()
# 委派 DefaultSqlSession
MapperMethod->>DefaultSqlSession:selectOne()
DefaultSqlSession->>DefaultSqlSession:selectList()
# 委派 CachingExecutor
DefaultSqlSession->>CachingExecutor:query()
CachingExecutor->>CachingExecutor:query()
# BaseExecutor
CachingExecutor->>BaseExecutor:query()
BaseExecutor->>BaseExecutor:queryFromDatabase()
BaseExecutor->>SimpleExecutor:doQuery()
# RoutingStatementHandler
SimpleExecutor->>RoutingStatementHandler:query()
RoutingStatementHandler->>PreparedStatementHandler:query()

2.4.2 二级缓存调用流程

我们再在查询二级缓逻辑处打断点,看下它的调用栈信息:

// 调用 PerpetualCache 的 getObject() 方法
getObject(Object):59, PerpetualCache (org.apache.ibatis.cache.impl), PerpetualCache.java

// 调用 LruCache 的 getObject() 方法
getObject(Object):75, LruCache (org.apache.ibatis.cache.decorators), LruCache.java

// 调用 SerializedCache 的 getObject() 方法
getObject(Object):63, SerializedCache (org.apache.ibatis.cache.decorators), SerializedCache.java

// 调用 LoggingCache 的 getObject() 方法
getObject(Object):55, LoggingCache (org.apache.ibatis.cache.decorators), LoggingCache.java

// 调用 SynchronizedCache 的 getObject() 方法
getObject(Object):48, SynchronizedCache (org.apache.ibatis.cache.decorators), SynchronizedCache.java

// 调用 TransactionalCache 的 getObject() 方法
getObject(Object):75, TransactionalCache (org.apache.ibatis.cache.decorators), TransactionalCache.java

// 调用 TransactionalCacheManager 的 getObject() 方法
getObject(Cache, CacheKey):35, TransactionalCacheManager (org.apache.ibatis.cache), TransactionalCacheManager.java

// 调用 CachingExecutor 的 query() 方法
query(MappedStatement, Object, RowBounds, ResultHandler, CacheKey, BoundSql):114, CachingExecutor (org.apache.ibatis.executor), CachingExecutor.java
query(MappedStatement, Object, RowBounds, ResultHandler):100, CachingExecutor (org.apache.ibatis.executor), CachingExecutor.java

// 调用 DefaultSqlSession 的 selectList() 方法
selectList(String, Object, RowBounds):151, DefaultSqlSession (org.apache.ibatis.session.defaults), DefaultSqlSession.java
selectList(String, Object):141, DefaultSqlSession (org.apache.ibatis.session.defaults), DefaultSqlSession.java

// 调用 DefaultSqlSession 的 selectOne() 方法
selectOne(String, Object):77, DefaultSqlSession (org.apache.ibatis.session.defaults), DefaultSqlSession.java

// 调用 MapperMethod 的 execute() 方法
execute(SqlSession, Object[]):105, MapperMethod (org.apache.ibatis.binding), MapperMethod.java

// 调用 PlainMethodInvoker 的 invoke() 方法
invoke(Object, Method, Object[], SqlSession):183, MapperProxy$PlainMethodInvoker (org.apache.ibatis.binding), MapperProxy.java

// 调用 MapperProxy 的 invoke() 方法
invoke(Object, Method, Object[]):101, MapperProxy (org.apache.ibatis.binding), MapperProxy.java

// 调用代理对象的 selectOneById() 方法
selectOneById(int):-1, $Proxy15 (com.sun.proxy), Unknown Source

// 单元测试类的方法
testSelectOneById():129, AutoConstructorTest (org.apache.ibatis.autoconstructor), AutoConstructorTest.java
...省略无关栈信息...

画出二级缓存调用的时序图:

Mybatis 核心流程源码分析_Mybatis _13

sequenceDiagram
# 单元测试入口
AutoConstructorTest->>AutoConstructorTest:testSelectOneById() 单元测试方法
AutoConstructorTest->>$Proxy15:selectOneById()

# JDK代理对象
$Proxy15->>MapperProxy:invoke() 执行

# 代理查询
MapperProxy->>PlainMethodInvoker:invoke()
PlainMethodInvoker->>MapperMethod:execute()

# 委派 DefaultSqlSession
MapperMethod->>DefaultSqlSession:selectOne()
DefaultSqlSession->>DefaultSqlSession:selectList()

# 委派 CachingExecutor
DefaultSqlSession->>CachingExecutor:query()
CachingExecutor->>CachingExecutor:query()

# 事务缓存管理器
CachingExecutor->>TransactionalCacheManager:getObject()
# 事务缓存装饰器
TransactionalCacheManager->>TransactionalCache:getObject()
# 同步缓存装饰器
TransactionalCache->>SynchronizedCache:getObject()
# 日志缓存装饰器
SynchronizedCache->>LoggingCache:getObject()
# 序列化装饰器
LoggingCache->>SerializedCache:getObject()
# Lru 缓存装饰器
SerializedCache->>LruCache:getObject()
# 实际的缓存
LruCache->>PerpetualCache:getObject()

这里的调用逻辑中,二级缓存的调用链可以配合着 2.2.2.9 的缓存小结图来阅读。

3. 总结回顾

现在我们已经把 mybatis 的主要的核心流程源码分析完了,这里会发现它的源码要比 spring 的源码结构更加结构化,层次分明,更容易阅读。

总结下上面的主要内容,我们主要从以下几个方面对源码进行了分析:

  1. 解析 mybatis-config.xml 文件,构建 Configuration 配置类信息流程;
  2. 解析 mapper.xml 进行构建缓存、映射声明等流程;
  3. 创建 SqlSession 流程;
  4. 通过 SqlSession 获取 mapper 接口执行目标方法流程。