温馨提示×

温馨提示×

您好,登录后才能下订单哦!

密码登录×
登录注册×
其他方式登录
点击 登录注册 即表示同意《亿速云用户服务条款》

mybatis核心流程的示例分析

发布时间:2021-12-30 09:53:26 来源:亿速云 阅读:163 作者:小新 栏目:大数据

这篇文章将为大家详细讲解有关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核心流程的示例分析

这些解析内容中,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核心流程的示例分析

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核心流程的示例分析

可以看到 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核心流程的示例分析

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

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核心流程的示例分析

它实现了 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核心流程的示例分析

它的实现:

/**
 * {@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核心流程的示例分析

可以看到,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核心流程的示例分析

我们看下它的实现,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核心流程的示例分析

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核心流程的示例分析

注意了 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核心流程的示例分析

分别是:

  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核心流程的示例分析

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核心流程的示例分析

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 的缓存小结图来阅读。

关于“mybatis核心流程的示例分析”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,使各位可以学到更多知识,如果觉得文章不错,请把它分享出去让更多的人看到。

向AI问一下细节

免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。

AI