Mybatis 源码 ③ :SqlSession

news2024/12/25 9:34:53

一、前言

Mybatis 官网 以及 本系列文章地址:

  1. Mybatis 源码 ① :开篇
  2. Mybatis 源码 ② :流程分析
  3. Mybatis 源码 ③ :SqlSession
  4. Mybatis 源码 ④ :TypeHandler
  5. Mybatis 源码 ∞ :杂七杂八

在 Mybatis 源码 ② :流程分析 我们介绍了 Mybatis 在 SpringBoot 中的整个流程,由于篇幅因素,我们在此篇继续分析SqlSession的功能。
但在此之前,我们需要先了解 SqlSessionFactory 和 SqlSession 的注入过程。

  • SqlSessionFactory :用于创建与 DB 交互的 数据库连接,同时内部保存了关于 Mybatis 的各种配置。

    • 默认情况下SpringBoot引入的类型是 DefaultSqlSessionFactory ;
    • 除此之外还有一个是实现类 SqlSessionManager : SqlSessionManager既实现了SqlSessionFactory,也实现了SqlSession,具备生产SqlSession的能力,也具备SqlSession的能力。相较于 DefaultSqlSession ,SqlSessionManager 提供了对 事务的管理功能,但并非是交由 Spring 框架。
  • SqlSessionTemplate :SqlSessionTemplate 实现了 SqlSession 接口,是Spring 管理的 SqlSession,线程安全,相较于 DefaultSqlSession,SqlSessionTemplate 将事务的管理交由 Spring框架来控制。默认情况下,SpringBoot 注入的 SqlSession类型是 SqlSessionTemplate 。

下面我们具体来看这两个类在 Spring容器中的初始化过程。

二、SqlSessionFactory 和 SqlSessionTemplate

1. SqlSessionFactory

在 MybatisAutoConfiguration 中会将 SqlSessionFactory 注入到容器中,不过需要注意这里并不是直接创建一个SqlSessionFactory ,而是通过 SqlSessionFactoryBean#getObject (实现类型是DefaultSqlSessionFactory )来创建,其具体过程如下:

1.1 MybatisAutoConfiguration#sqlSessionFactory

  @Bean
  @ConditionalOnMissingBean
  public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
  	// 需要注意这里是 SqlSessionFactoryBean 
    SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
    factory.setDataSource(dataSource);
    factory.setVfs(SpringBootVFS.class);
    // 加载Mybatis 各种配置属性
    if (StringUtils.hasText(this.properties.getConfigLocation())) {
      factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
    }
    // 加载 Configuration 属性, 这里会初始化 Configuration 属性
    applyConfiguration(factory);
    if (this.properties.getConfigurationProperties() != null) {
      factory.setConfigurationProperties(this.properties.getConfigurationProperties());
    }
    // 加载插件
    if (!ObjectUtils.isEmpty(this.interceptors)) {
      factory.setPlugins(this.interceptors);
    }
    if (this.databaseIdProvider != null) {
      factory.setDatabaseIdProvider(this.databaseIdProvider);
    }
    if (StringUtils.hasLength(this.properties.getTypeAliasesPackage())) {
      factory.setTypeAliasesPackage(this.properties.getTypeAliasesPackage());
    }
    if (this.properties.getTypeAliasesSuperType() != null) {
      factory.setTypeAliasesSuperType(this.properties.getTypeAliasesSuperType());
    }
    if (StringUtils.hasLength(this.properties.getTypeHandlersPackage())) {
      factory.setTypeHandlersPackage(this.properties.getTypeHandlersPackage());
    }
    if (!ObjectUtils.isEmpty(this.typeHandlers)) {
      factory.setTypeHandlers(this.typeHandlers);
    }
    if (!ObjectUtils.isEmpty(this.properties.resolveMapperLocations())) {
      factory.setMapperLocations(this.properties.resolveMapperLocations());
    }
    // 版本兼容处理,根据 scriptingLanguageDrivers 和 defaultScriptingLanguageDriver 属性确定当前版本,在做对应处理
    Set<String> factoryPropertyNames = Stream
        .of(new BeanWrapperImpl(SqlSessionFactoryBean.class).getPropertyDescriptors()).map(PropertyDescriptor::getName)
        .collect(Collectors.toSet());
    Class<? extends LanguageDriver> defaultLanguageDriver = this.properties.getDefaultScriptingLanguageDriver();
    if (factoryPropertyNames.contains("scriptingLanguageDrivers") && !ObjectUtils.isEmpty(this.languageDrivers)) {
      // Need to mybatis-spring 2.0.2+
      factory.setScriptingLanguageDrivers(this.languageDrivers);
      if (defaultLanguageDriver == null && this.languageDrivers.length == 1) {
        defaultLanguageDriver = this.languageDrivers[0].getClass();
      }
    }
    if (factoryPropertyNames.contains("defaultScriptingLanguageDriver")) {
      // Need to mybatis-spring 2.0.2+
      factory.setDefaultScriptingLanguageDriver(defaultLanguageDriver);
    }
	// 获取 SqlSessionFactory 对象
    return factory.getObject();
  }
  
  // 处理配置信息
  private void applyConfiguration(SqlSessionFactoryBean factory) {
    Configuration configuration = this.properties.getConfiguration();
    // 如果 {mybatis.configuration} 未配置,并且未指定本地 配置文件,则 new 出一个 Configuration作为默认
    if (configuration == null && !StringUtils.hasText(this.properties.getConfigLocation())) {
      configuration = new Configuration();
    }
    // 这里可以通过 ConfigurationCustomizer 来进一步定制配置,configuration 不为空时才执行
    if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) {
      for (ConfigurationCustomizer customizer : this.configurationCustomizers) {
        customizer.customize(configuration);
      }
    }
    factory.setConfiguration(configuration);
  }

这里可以看到 :

  1. SqlSessionFactory 不是直接生成,而是通过 SqlSessionFactoryBean#getObject 生成,并且在创建过程中,初始化了 Configuration 信息。这一点我们下面详细看。

  2. 通过 ConfigurationCustomizer 可以对 Mybatis 配置进行定制化改造。ConfigurationCustomizer 接口定义如下,如果需要定制化 Configuration 实现该接口即可。这里不再赘述。

    public interface ConfigurationCustomizer {
      void customize(Configuration configuration);
    }
    

下面我们着重来看下 SqlSessionFactoryBean 创建 SqlSessionFactory 的过程。

1.2 SqlSessionFactoryBean

如下是 SqlSessionFactoryBean 类图
在这里插入图片描述
可以看到

  1. SqlSessionFactoryBean 实现了 InitializingBean 接口,但是在 MybatisAutoConfiguration#sqlSessionFactory 中创建 SqlSessionFactoryBean 时却是通过 new 的方式创建的,所以在 MybatisAutoConfiguration 中创建时是不会调用 SqlSessionFactoryBean#afterPropertiesSet 方法的,因此在 SqlSessionFactoryBean#getObject 中会根据 sqlSessionFactory 是否为空来判断是否调用过 afterPropertiesSet 方法,如果没有则调用一次,如下:
      @Override
      public SqlSessionFactory getObject() throws Exception {
      	// 由于在上面  MybatisAutoConfiguration#sqlSessionFactory 中是直接new 出来,所以不会主动调用afterPropertiesSet 方法,
      	//这里需要手动调用来完成sqlSessionFactory的初始化
        if (this.sqlSessionFactory == null) {
          afterPropertiesSet();
        }
    
        return this.sqlSessionFactory;
      }
    
  2. SqlSessionFactoryBean 实现了 FactoryBean 接口,因此实际上 SqlSessionFactory 是通过调用 SqlSessionFactoryBean#getObject 获取。

下面我们着重来看这两点。

1.2.1 SqlSessionFactoryBean#afterPropertiesSet

SqlSessionFactoryBean#afterPropertiesSet 的实现如下,可以看到关键逻辑还是在 SqlSessionFactoryBean#buildSqlSessionFactory中

  @Override
  public void afterPropertiesSet() throws Exception {
  	// 状态判断
    notNull(dataSource, "Property 'dataSource' is required");
    notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
    state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
        "Property 'configuration' and 'configLocation' can not specified with together");
	// 构建 sqlSessionFactory 
    this.sqlSessionFactory = buildSqlSessionFactory();
  }

SqlSessionFactoryBean#buildSqlSessionFactory 实现如下, 该方法中实现了对 Mybatis 的基本配置属性以及 XML 文件的解析,并根据解析的内容生成了 SqlSessionFactory,具体如下:

  protected SqlSessionFactory buildSqlSessionFactory() throws Exception {

    final Configuration targetConfiguration;
	// 1. targetConfiguration 的初始化
    XMLConfigBuilder xmlConfigBuilder = null;
    // 这里 configuration 不为空,在 MybatisAutoConfiguration#applyConfiguration 方法中对 configuration 进行了赋值
    if (this.configuration != null) {
      targetConfiguration = this.configuration;
      if (targetConfiguration.getVariables() == null) {
        targetConfiguration.setVariables(this.configurationProperties);
      } else if (this.configurationProperties != null) {
        targetConfiguration.getVariables().putAll(this.configurationProperties);
      }
    } else if (this.configLocation != null) {
      // 如果指定了XML 配置文件, 并且 {mybatis.configuration} 未配置 (configuration  为空表明未配置该属性),
      // 则加载配置文件生成 targetConfiguration 
      xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
      targetConfiguration = xmlConfigBuilder.getConfiguration();
    } else {
      LOGGER.debug(
          () -> "Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
      // 否则启用默认 targetConfiguration 
      targetConfiguration = new Configuration();
      Optional.ofNullable(this.configurationProperties).ifPresent(targetConfiguration::setVariables);
    }

    Optional.ofNullable(this.objectFactory).ifPresent(targetConfiguration::setObjectFactory);
    Optional.ofNullable(this.objectWrapperFactory).ifPresent(targetConfiguration::setObjectWrapperFactory);
    Optional.ofNullable(this.vfs).ifPresent(targetConfiguration::setVfsImpl);
    // 2. Mybatis 参数解析
	// 如果指定了路径 则进行扫描 (即 mybatis.type-aliases-package 属性)
    if (hasLength(this.typeAliasesPackage)) {
      // 扫描指定路径注册别名
      scanClasses(this.typeAliasesPackage, this.typeAliasesSuperType).stream()
          .filter(clazz -> !clazz.isAnonymousClass()).filter(clazz -> !clazz.isInterface())
          .filter(clazz -> !clazz.isMemberClass()).forEach(targetConfiguration.getTypeAliasRegistry()::registerAlias);
    }

    if (!isEmpty(this.typeAliases)) {
      Stream.of(this.typeAliases).forEach(typeAlias -> {
        targetConfiguration.getTypeAliasRegistry().registerAlias(typeAlias);
        LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
      });
    }
	// 对 Mybatis 插件做处理, 添加配置的插件到拦截器链中 (直接用 mybatis.configuration.interceptors 解析会出错, 可以将 interceptors 实例注入容器即可加载)
    if (!isEmpty(this.plugins)) {
      Stream.of(this.plugins).forEach(plugin -> {
        targetConfiguration.addInterceptor(plugin);
        LOGGER.debug(() -> "Registered plugin: '" + plugin + "'");
      });
    }
	// 类型转换处理器处理 即对 mybatis.type-handlers-package 配置的处理
	// javaType 与 JdbcType互转 .
    if (hasLength(this.typeHandlersPackage)) {
      scanClasses(this.typeHandlersPackage, TypeHandler.class).stream().filter(clazz -> !clazz.isAnonymousClass())
          .filter(clazz -> !clazz.isInterface()).filter(clazz -> !Modifier.isAbstract(clazz.getModifiers()))
          .forEach(targetConfiguration.getTypeHandlerRegistry()::register);
    }

    if (!isEmpty(this.typeHandlers)) {
      Stream.of(this.typeHandlers).forEach(typeHandler -> {
        targetConfiguration.getTypeHandlerRegistry().register(typeHandler);
        LOGGER.debug(() -> "Registered type handler: '" + typeHandler + "'");
      });
    }

    targetConfiguration.setDefaultEnumTypeHandler(defaultEnumTypeHandler);

    if (!isEmpty(this.scriptingLanguageDrivers)) {
      Stream.of(this.scriptingLanguageDrivers).forEach(languageDriver -> {
        targetConfiguration.getLanguageRegistry().register(languageDriver);
        LOGGER.debug(() -> "Registered scripting language driver: '" + languageDriver + "'");
      });
    }
    Optional.ofNullable(this.defaultScriptingLanguageDriver)
        .ifPresent(targetConfiguration::setDefaultScriptingLanguage);

    if (this.databaseIdProvider != null) {// fix #64 set databaseId before parse mapper xmls
      try {
        targetConfiguration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
      } catch (SQLException e) {
        throw new NestedIOException("Failed getting a databaseId", e);
      }
    }

    Optional.ofNullable(this.cache).ifPresent(targetConfiguration::addCache);
	// xmlConfigBuilder 不为空说明指定了本地配置文件,进行解析
    if (xmlConfigBuilder != null) {
      try {
        xmlConfigBuilder.parse();
        LOGGER.debug(() -> "Parsed configuration file: '" + this.configLocation + "'");
      } catch (Exception ex) {
        throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
      } finally {
        ErrorContext.instance().reset();
      }
    }
	// 设置环境信息
    targetConfiguration.setEnvironment(new Environment(this.environment,
        this.transactionFactory == null ? new SpringManagedTransactionFactory() : this.transactionFactory,
        this.dataSource));
	// 3. Mybatis XML 文件的解析
	// 如果 xml 文件存在, 则解析 XML 文件内容
    if (this.mapperLocations != null) {
      if (this.mapperLocations.length == 0) {
        LOGGER.warn(() -> "Property 'mapperLocations' was specified but matching resources are not found.");
      } else {
        // 对 Mybatis XML 文件进解析
        for (Resource mapperLocation : this.mapperLocations) {
          if (mapperLocation == null) {
            continue;
          }
          try {
          	// 使用 XMLMapperBuilder 解析,解析出来的结果会保存到 targetConfiguration.mappedStatements 属性中
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
            xmlMapperBuilder.parse();
          } catch (Exception e) {
            throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
          } finally {
            ErrorContext.instance().reset();
          }
          LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
        }
      }
    } else {
      LOGGER.debug(() -> "Property 'mapperLocations' was not specified.");
    }
	// 4. 通过 targetConfiguration 构建 SqlSessionFactory : 默认返回 DefaultSqlSessionFactory
    return this.sqlSessionFactoryBuilder.build(targetConfiguration);
  }

这里我们按照上面的注释来简单解释一下:

  1. targetConfiguration 的初始化

    • 在 MybatisAutoConfiguration#applyConfiguration 中,如果我们配置了 mybatis.configuration 参数则会直接获取到 Configuration,如果没有配置该参数并且未指定本地配置文件地址,则会初始化一个新的 Configuration。当 configuration不为空时,会通过 ConfigurationCustomizer#customize 做进一步定制化处理。
    • 在 SqlSessionFactoryBean#buildSqlSessionFactory 中,会判断 Configuration 是否已经有值,如果有值则直接赋值给 targetConfiguration,
    • 如果没值(则说明未配置 mybatis.configuration 属性并且指定了本地配置文件)则通过 XMLConfigBuilder 加载本地配置文件并获取配置内容(通过 mybatis.config-location 属性配置)并赋值给 targetConfiguration 。
    • 如果以上两种情况都不满足,则初始化一个默认值给 targetConfiguration。
  2. Mybatis 参数解析 : 随后开始对Mybatis 的一些参数的解析, 如 mybatis.type-aliases-package、mybatis.type-handlers-package、mybatis.scripting-language-driver 等配置

  3. Mybatis XML 解析 :这里将 mybatis.mapper-locations 配置指定路径下的 Mapper Xml 通过 XMLMapperBuilder 进行解析,并保存到 targetConfiguration.mappedStatements中。

  4. 根据 targetConfiguration 创建 SqlSessionFactory : sqlSessionFactoryBuilder 是 SqlSessionFactoryBean 的一个普通属性,类型是 SqlSessionFactoryBuilder, 默认是通过 SqlSessionFactoryBuilder#build 创建一个 DefaultSqlSessionFactory。如下:

      // SqlSessionFactoryBuilder#build 
      public SqlSessionFactory build(Configuration config) {
        return new DefaultSqlSessionFactory(config);
      }
    

2.2.2 SqlSessionFactoryBean#getObject

在 MybatisAutoConfiguration#sqlSessionFactory 的最后是通过 SqlSessionFactoryBean#getObject 方法来获取 SqlSessionFactory , 上面我们看到生成的 SqlSessionFactory 是 DefaultSqlSessionFactory 类型,所以 DefaultSqlSessionFactory#getObject 实现如下
则是判断是否已经生成过 sqlSessionFactory ,如果生成则直接返回。

  @Override
  public SqlSessionFactory getObject() throws Exception {
    if (this.sqlSessionFactory == null) {
      // sqlSessionFactory  为空表明还没初始化,调用初始化
      afterPropertiesSet();
    }
	// 返回初始化后的 sqlSessionFactory, 实际类型是上面提到的 DefaultSqlSessionFactory
    return this.sqlSessionFactory;
  }

至此,我们解析出了 SqlSessionFactory 注入容器的过程,知道这里注入的 SqlSessionFactory 实现类型是DefaultSqlSessionFactory。

2. SqlSessionTemplate 的初始化

SqlSesson :我们与DB 交互需要先建立一个会话,即 SqlSession,官方注释 【通过该接口,您可以执行命令、获取映射器和管理事务】。SqlSesson 存在三个实现类 :

  1. DefaultSqlSession :默认的 SqlSession 实现类,非线程安全,提供了与DB 交互的基础功能。事务自动提交
  2. SqlSessionTemplate :Spring 管理的 SqlSession,线程安全,相较于 DefaultSqlSession,SqlSessionTemplate 将事务的管理交由 Spring框架来控制。
  3. SqlSessionManager :SqlSessionManager既实现了SqlSessionFactory,也实现了SqlSession,具备生产SqlSession的能力,也具备SqlSession的能力。相较于 DefaultSqlSession ,SqlSessionManager 提供了对 事务的管理功能,但并非是交由 Spring 框架。

在 MybatisAutoConfiguration 中 会注入 SqlSessionTemplate,SqlSessionTemplate 的构造需要依赖 SqlSessionFactory ,而我们上面已经分析过了 SqlSessionFactory 的注入过程,可以得知这里的 SqlSessionFactory 实现类型是 DefaultSqlSessionFactory 。如下:

  @Bean
  @ConditionalOnMissingBean
  // SqlSessionFactory  类型是 DefaultSqlSessionFactory 
  public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
  	// 通过 {mybatis.executor-type} 指定的执行模式
    ExecutorType executorType = this.properties.getExecutorType();
    // 创建SqlSessionTemplate
    if (executorType != null) {
      return new SqlSessionTemplate(sqlSessionFactory, executorType);
    } else {
      return new SqlSessionTemplate(sqlSessionFactory);
    }
  }

这里需要注意 : 在mybatis的ExecutorType中,执行sql有三种执行模式,分别为 SIMPLE、REUSE、BATCH,可以通mybatis.executor-type 参数指定,默认为 SIMPLE, 这三种模式分别对应着三种执行器,如下:

  • SimpleExecutor 是一种常规执行器,每次执行都会创建一个statement,用完后关闭。
  • ReuseExecutor 是可重用执行器,将statement存入map中,操作map中的statement而不会重复创建statement。
  • BatchExecutor 是批处理型执行器,doUpdate预处理存储过程或批处理操作,doQuery提交并执行过程。

SimpleExecutor 比 ReuseExecutor 的性能要差 , 因为 SimpleExecutor 没有做 PSCache。为什么做了 PSCache 性能就会高呢 , 因为当SQL越复杂占位符越多的时候预编译的时间也就越长,创建一个PreparedStatement对象的时间也就越长。猜想中BatchExecutor比ReuseExecutor功能强大性能高,实际上并非如此,BatchExecutor是没有做PSCache的。BatchExecutor 与 SimpleExecutor 和 ReuseExecutor 还有一个区别就是 , BatchExecutor 的事务是没法自动提交的。因为 BatchExecutor 只有在调用了 SqlSession 的 commit 方法的时候,它才会去执行 executeBatch 方法。

除了上述三种之外,Mybatis 还提供了缓存执行器 CachingExecutor,如果开启,上述生成的 Executor 会被他包装,他会缓存Sql 执行结果,我们可以通过 mybatis.configuration.cache-enabled 指定是否开启缓存,默认开启。上述处理逻辑在 Configuration#newExecutor 中体现,我们后面会详细分析。下面我们来看 SqlSessionTemplate 的具体逻辑。


2.1 SqlSessionTemplate 的构造

SqlSessionTemplate 的类图如下,可以看到 SqlSessionTemplate 实现了 SqlSession 接口,实际上,SqlSessionTemplate 本身并没有实现 SqlSession 的逻辑,当外部调用 SqlSessionTemplate 的 SqlSession 相关方法时, SqlSessionTemplate 会通过 SqlSessionFactory (DefaultSqlSessionFactory )来创建一个 SqlSession (DefaultSqlSession)并完成调用。下面我们来详细看下整个过程。
在这里插入图片描述

我们这里需要关注下 SqlSessionTemplate 的构造函数,这里会为 SqlSessionFactory 创建一个代理对象,如下:

  public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType) {
    this(sqlSessionFactory, executorType,
        new MyBatisExceptionTranslator(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), true));
  }

  public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {
	// 必填参数校验
    notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
    notNull(executorType, "Property 'executorType' is required");

    this.sqlSessionFactory = sqlSessionFactory;
    this.executorType = executorType;
    this.exceptionTranslator = exceptionTranslator;
    // 创建 SqlSession 的代理对象,代理拦截器为 SqlSessionInterceptor,该代理对象是后续逻辑的关键
    this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
        new Class[] { SqlSession.class }, new SqlSessionInterceptor());
  }

这里需要注意的是在这里 SqlSessionTemplate 创建了一个SqlSession 代理对象 (sqlSessionProxy ),增强类是 SqlSessionInterceptor。而 SqlSessionTemplate 内所有的 SqlSession 方法都委托给了 sqlSessionProxy ,而 sqlSessionProxy 执行方法时会通过 SqlSessionInterceptor#invoke 来完成,因此下面我们需要看下SqlSessionInterceptor#invoke 的实现。

2.2 SqlSessionInterceptor#invoke

SqlSessionInterceptor#invoke 的实现如下:

  private class SqlSessionInterceptor implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
      // 1. 通过 sqlSessionFactory 获取 SqlSession,即通过 DefaultSqlSessionFactory 获取到 DefaultSqlSession 对象
      SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
          SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
      try {
        // 2.调用 sqlSession 指定的方法
        Object result = method.invoke(sqlSession, args);
        // 判断当前事务如果不是交由 Spring 管理则直接提交
        if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
          // force commit even on non-dirty sessions because some databases require
          // a commit/rollback before calling close()
          sqlSession.commit(true);
        }
        return result;
      } catch (Throwable t) {
      	// 异常处理
        Throwable unwrapped = unwrapThrowable(t);
        if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
          // release the connection to avoid a deadlock if the translator is no loaded. See issue #22
          // 释放连接避免死锁
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
          sqlSession = null;
          Throwable translated = SqlSessionTemplate.this.exceptionTranslator
              .translateExceptionIfPossible((PersistenceException) unwrapped);
          if (translated != null) {
            unwrapped = translated;
          }
        }
        throw unwrapped;
      } finally {
        if (sqlSession != null) {
          closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
        }
      }
    }
  }

我们这里需要关注的是 如何通过 SqlSessionFactory 获取 SqlSession 的 , SqlSessionUtils#getSqlSession 实现如下:

  // 从Spring Transaction Manager获取SqlSession,或在需要时创建一个新的。尝试从当前事务中获取SqlSession。如果没有,它会创建一个新的,并绑定到当前线程的事务上
  public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType,
      PersistenceExceptionTranslator exceptionTranslator) {
	// 获取 事务的 SqlSession 持有者
    SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);	
    // 从事务 Session 持有者 中获取 SqlSession
    SqlSession session = sessionHolder(executorType, holder);
    // 如果获取到了,说明当前事务已经与DB建立连接,直接返回
    if (session != null) {
      return session;
    }
	// 到这一步说明 当前事务没有持有 SqlSession, 重新创建,返回类型是 DefaultSqlSession
    session = sessionFactory.openSession(executorType);
	// 注册session 到当前事务
    registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);

    return session;
  }

我们这里不关注SqlSession的绑定和注册,这里调用了SqlSessionFactory#openSession获取新的SqlSession ,这里调用的是 DefaultSqlSessionFactory#openSession ,如下:

  @Override
  public SqlSession openSession(ExecutorType execType) {
    return openSessionFromDataSource(execType, null, false);
  }
  
  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);
      // 根据 execType 返回 BatchExecutor、ReuseExecutor、SimpleExecutor,同时会加载 Mybatis 拦截器
      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();
    }
  }

其中 Configuration#newExecutor 实现如下,这里就是我们上面提到过的三种 Executor 的获取以及 CachingExecutor 的处理 :

  public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
    executorType = executorType == null ? defaultExecutorType : executorType;
    executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
    Executor 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);
    }
    // {mybatis.configuration.cache-enabled} 属性来决定是否开启缓存
    // 如果启用缓存,则使用 CachingExecutor 来包装 executor (默认为true)
    if (cacheEnabled) {
      executor = new CachingExecutor(executor);
    }
    // 装载拦截器链路
    executor = (Executor) interceptorChain.pluginAll(executor);
    return executor;
  }

InterceptorChain#pluginAll 实现如下,该方法完成了 Mybatis Plugin 功能,该部分由于篇幅所限,如有需要具体详参
Mybatis 源码 ∞ :杂七杂八

  public Object pluginAll(Object target) {
  	// interceptors 即 注册的 Interceptor 集合
    for (Interceptor interceptor : interceptors) {
      target = interceptor.plugin(target);
    }
    return target;
  }

3. 总结

至此,SqlSession 的创建过程就已经结束,我们来简单总结下整个流程:

  1. SpringBoot 启动后 MybatisAutoConfiguration 中会注入 SqlSessionFactory 和 SqlSessionTemplate。
  2. SqlSessionFactory 创建是会解析Mybatis 配置以及 Mapper Xml 文件并保存,此时注入的实际类型是 DefaultSqlSessionFactory。
  3. SqlSessionTemplate 实现了SqlSession 接口,并且创建时依赖 SqlSessionFactory ,同时 SqlSessionTemplate 内部会创建 SqlSession 代理类,当调用SqlSessionTemplate 的 SqlSesion 接口方法时会委托给代理类 sqlSessionProxy 执行,而代理类 sqlSessionProxy 执行时SqlSession 的接口方法时会调用增强方法 SqlSessionInterceptor#invoke ( sqlSessionProxy 同时也已经将事务交由 Spring 来管理)
  4. SqlSessionInterceptor#invoke 利用 SqlSessionFactory 获取 SqlSession 实例并执行方法(默认情况是 DefaultSqlSession 类型)。需要注意的是 SqlSession 中的 Executor 属性被 Interceptor 代理,即执行具体的操作时首先会经过 Interceptor 处理。如下图: 在这里插入图片描述

三、DefaultSqlSession

上面我们提到了 DefaultSqlSessionFactory 创建的 SqlSession 实际类型是 DefaultSqlSession,可以得知 SqlSessionTemplate 将SqlSession相关的逻辑都委托给了 DefaultSqlSession 。也就是与 DB 交互的内容都是交由 DefaultSqlSession 来完成的。

如下调用流程

1. 调用 SqlSessionTemplate 方法时会交由 SqlSessionTemplate# sqlSessionProxy 执行
2. sqlSessionProxy 是 SqlSessionTemplate 内部创建的 SqlSession 代理类,其增强类是 SqlSessionInterceptor,所以会调用 SqlSessionInterceptor#invoke
3. SqlSessionInterceptor#invoke 方法会先判断当前事务是否绑定了 SqlSession,如果没有绑定则通过 SqlSessionFactory#openSession 创建一个SqlSession 再执行DB的交互。
4. 此时的 SqlSessionFactory 就是 DefaultSqlSessionFactory 类型,而 SqlSessionFactory#openSession 创建的 SqlSession 就是 DefaultSqlSession  类型。

因此下面我们来看看 DefaultSqlSession 的实现, DefaultSqlSession 类图如下:

在这里插入图片描述

可以看到 DefaultSqlSession 实现了 SqlSession 接口,由于其内部方法众多,我们挑下面几个方法来做分析,其余的大多都是重载方法,这里就不再赘述。DefaultSqlSession 中有很多重载方法,下面我们调其中几个关键方法来解释下 :

  • DefaultSqlSession#selectList :集合查询 或者单一查询,具有多个重载方法,一般查询的调用该方法
  • DefaultSqlSession#selectMap :被 @MapKey 注解修饰的方法会调用该方法,该方法会在获取结果后将结果聚合成 Map
  • DefaultSqlSession#update : 增删改 操作调用的方法,也具有多个重载方法
  • DefaultSqlSession#commit && DefaultSqlSession#rollback : 事务提交 or 回滚 的方法

下面我们具体来看上面几个方法:

1. DefaultSqlSession#selectList

该方法用于列表查询或单独查询(DefaultSqlSession#selectOne 内部也是调用的该方法,判断结果集大小是否是1,不是1 则抛出异常)

  private <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
    try {
      // 根据 statement 获取要执行的 MappedStatement,MappedStatement 包含了 Mapper语句信息,包括 语句类型、语句内容、结果映射规则等
      // 这里的 statement 格式为 : {Mapper全路径名}.{方法名}
      // 如 调用SysUserMapper 的 count 方法时,这里的 statement 值为 com.kingfish.dao.SysUserDao.count
      MappedStatement ms = configuration.getMappedStatement(statement);
      // 交由 executor 查询
      return executor.query(ms, wrapCollection(parameter), rowBounds, handler);
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error querying database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

2. DefaultSqlSession#selectMap

用于处理被 @MapKey 注解修饰的方法,方法返回结果是 Map 类型

  @Override
  public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {
  	// 调用 selectList 查询结果
    final List<? extends V> list = selectList(statement, parameter, rowBounds);
    // 构建一个默认的Map结果处理器
    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);
    }
    // 返回聚合后的Map 结果
    return mapResultHandler.getMappedResults();
  }

这里可以看到,对于查询出来的结果集,这里是通过 DefaultMapResultHandler 对结果进行了聚合操作,最终返回了 Map 结构。

3. DefaultSqlSession#update

增删改操作最终都会调用该方法

  @Override
  public int update(String statement, Object parameter) {
    try {
      // 标记可能存在脏数据,该语句执行后,数据可能会与 DB 不一致,即执行后必须要提交或回滚事务
      dirty = true;
      // 获取执行语句
      MappedStatement ms = configuration.getMappedStatement(statement);
      // 交由 Executor 执行
      return executor.update(ms, wrapCollection(parameter));
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error updating database.  Cause: " + e, e);
    } finally {
      ErrorContext.instance().reset();
    }
  }

5. DefaultSqlSession#commit & rollback

这两个方法完成了事务的提交和回归。

  @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(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();
    }
  }
  // 判断是否需要提交或回滚
  private boolean isCommitOrRollbackRequired(boolean force) {
  	// 非自动提交 && 有脏数据(有增删改操作,数据与数据库产生了不一致就需要提交) 或者需要暴力提交
    return (!autoCommit && dirty) || force;
  }

上面可以看到 DefaultSqlSession 方法内部的操作还是交由了 Executor 执行的,因此下面我们来看看 Executor 的执行过程。

四、 Executor

关于 Executor 的几个子类的区别和关系,上面已经介绍过了,这里再重复一遍 : 在mybatis的ExecutorType中,执行sql有三种执行模式,分别为 SIMPLE、REUSE、BATCH,可以通过{mybatis.executor-type} 参数指定,默认为 SIMPLE, 这三种模式分别对应着三种执行器,如下:

  • SimpleExecutor 是一种常规执行器,每次执行都会创建一个statement,用完后关闭。
  • ReuseExecutor 是可重用执行器,将statement存入map中,操作map中的statement而不会重复创建statement。
  • BatchExecutor 是批处理型执行器,doUpdate预处理存储过程或批处理操作,doQuery提交并执行过程。

SimpleExecutor 比 ReuseExecutor 的性能要差 , 因为 SimpleExecutor 没有做 PSCache。为什么做了 PSCache 性能就会高呢 , 因为当SQL越复杂占位符越多的时候预编译的时间也就越长,创建一个PreparedStatement对象的时间也就越长。猜想中BatchExecutor比ReuseExecutor功能强大性能高,实际上并非如此,BatchExecutor是没有做PSCache的。BatchExecutor 与 SimpleExecutor 和 ReuseExecutor 还有一个区别就是 , BatchExecutor 的事务是没法自动提交的。因为 BatchExecutor 只有在调用了 SqlSession 的 commit 方法的时候,它才会去执行 executeBatch 方法。

除了上述三种之外,Mybatis 还提供了缓存执行器 CachingExecutor,如果开启,上述生成的 Executor 会被他包装,他会缓存Sql 执行结果,我们可以通过 mybatis.configuration.cache-enabled 指定是否开启缓存,默认开启。因此默认情况下这里的 Executor 是 CachingExecutor,CachingExecutor 会缓存执行结果,同时将执行逻辑委托给内部的 delegate 属性执行,这里的 delegate 默认类型是 SimpleExecutor。


为了节省篇幅,下面仅分析 Executor#query 和 Executor#update 方法。

1. CachingExecutor

上面我们提到,在 DefaultSqlSessionFactory#openSessionFromDataSource 创建 SqlSession 时会调用 Configuration#newExecutor 方法来创建 Executor,默认情况下, Configuration#newExecutor 方法中会创建一个 CachingExecutor 来包装 Executor(SimpleExecutor)。

当外部调用时会首先调用 CachingExecutor#query,如果缓存未命中才会去调用内部的 delegate#query 去再次查询。下面我们来看具体逻辑。

1.1 CachingExecutor#query

CachingExecutor#query 实现如下:

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
  	// 1. 获取 Sql 信息
    BoundSql boundSql = ms.getBoundSql(parameterObject);
    // 2. 创建 缓存 Key
    CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
    // 3. 调用查询
    return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

我们按照上面注释来进行分析,如下:

1.1.1 MappedStatement#getBoundSql

MappedStatement#getBoundSql 方法获取了当前Sql 执行的信息,如 参数映射、结果映射、结果映射是否具有嵌套结果等。具体实现如下:

  public BoundSql getBoundSql(Object parameterObject) {
  	// 构建当前执行的Sql
    BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
    // 获取 参数映射集合, 
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings == null || parameterMappings.isEmpty()) {
      boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
    }

    // check for nested result maps in parameter mappings (issue #30)
    // 检查 ResultMap 是否具有嵌套
    for (ParameterMapping pm : boundSql.getParameterMappings()) {
      String rmId = pm.getResultMapId();
      // 根据 ResultMap ID 获取 ResultMap 
      if (rmId != null) {
        ResultMap rm = configuration.getResultMap(rmId);
        if (rm != null) {
          // 判断当前 ResultMap  是否具有嵌套 ResultMap (hasNestedResultMaps 属性是 MappedStatement 中的)
          hasNestedResultMaps |= rm.hasNestedResultMaps();
        }
      }
    }

    return boundSql;
  }

1.1.2 CachingExecutor#createCacheKey

CachingExecutor#createCacheKey 是获取当前 Sql的 缓存 Key,这里直接交由 委托给 SimpleExecutor#createCacheKey 执行。CachingExecutor#createCacheKey 如下:

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

SimpleExecutor#createCacheKey 是在其父类中实现,简单来说就是获取到当前Sql的各个属性等进行特殊处理后得到的一个key,这里不再过多赘述。

1.1.3 CachingExecutor#query

CachingExecutor#query 实现如下,简单来说就是首先尝试从缓存中获取,如果缓存中获取不到,则通过 delegate#query 实时查询。这里的 delegate 默认情况下是 SimpleExecutor。

  @Override
  public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
      throws SQLException {
    // 尝试获取语句缓存
    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;
      }
    }
    // 直接查询
    return delegate.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
  }

1.2 CachingExecutor#update

CachingExecutor#update 的实现就很简单了,清除缓存后,交由委托类来处理。

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

2. SimpleExecutor

上面我们提到,默认情况下 CachingExecutor 内部的委托类是 SimpleExecutor (我们也可以通过 mybatis.executor-type 属性指定不同的类型),而在上面我们也看到了 CachingExecutor#query 和 CachingExecutor#update 方法的核心逻辑还是委托给 SimpleExecutor 来执行的,而 SimpleExecutor#query 和 SimpleExecutor#update 于 DB 交互的具体逻辑都在 SimpleExecutor#doQuery 和 SimpleExecutor#doUpdate 中,为了节省篇幅,我们这里直接来看 SimpleExecutor#doQuery 和 SimpleExecutor#doUpdate 的实现。

2.1 SimpleExecutor#doQuery

SimpleExecutor#query 查询实际上是 BaseExecutor#query,这里也会判断 :如果能从缓存获取到则从缓存获取,如果缓存获取不到则通过 BaseExecutor#doQuery 从数据库查询, BaseExecutor#doQuery 是由子类来实现,因此这里我们来看下 SimpleExecutor#doQuery 和 SimpleExecutor#doUpdate :

  @Override
  public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
    Statement stmt = null;
    try {
      // 1. 获取配置信息 : 保存所有 Mapper Method 语句等信息
      Configuration configuration = ms.getConfiguration();
      // 2、 创建一个语句处理器, 这里可以使用 Mybatis Plugin 扩展 StatementHandler。
      // 默认类型是 RoutingStatementHandler
      StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
      // 3. 执行预处理
      stmt = prepareStatement(handler, ms.getStatementLog());
      // 4. 调用 RoutingStatementHandler#query执行查询
      return handler.query(stmt, resultHandler);
    } finally {
      closeStatement(stmt);
    }
  }

这里我们按照注释来一步一步分析:

  1. ms.getConfiguration() : 这里是获取之前初始化解析的配置信息,里面包含解析后的 Mapper Interface 的 方法语句等信息

  2. configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql) : 创建一个语句处理器, 这里 返回 RoutingStatementHandler 类型,会根据 ms#statementType 类型来路由到对应的 StatementHandler。需要注意的是,这里是可以被 Mybatis Plugin 扩展 StatementHandler的,这点我们下面会详细说明。

    Configuration#newStatementHandler 实现如下:

      public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
      	// 内部会根据 mappedStatement.statementType 生成对应的委托类来处理
        StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
        // 被 Mybatis 
        statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
        return statementHandler;
      }
    
  3. prepareStatement(handler, ms.getStatementLog()) : 执行Sql 预处理

      private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
        Statement stmt;
        // 获取 DB 连接 
        Connection connection = getConnection(statementLog);
        // 获取 Statement
        stmt = handler.prepare(connection, transaction.getTimeout());
        // 参数化 :可以实现该方法用于可扩展
        handler.parameterize(stmt);
        return stmt;
      }
    

    这里需要注意的是 handler.parameterize(stmt);, 该方法可用于在Sql执行前对参数进行与处理,在 PreparedStatementHandler#parameterize 中则是调用了 DefaultParameterHandler#setParameters 对Sql 查询参数做处理。这点我们在下面会详细说明。

  4. handler.query(stmt, resultHandler) : 调用 StatementHandler#query 进行查询。这点我们在下面会详细说明。

2.2 SimpleExecutor#doUpdate

SimpleExecutor#doUpdate 实现如下,可以看到与 SimpleExecutor#doQuery 实现很类似,不同的是 最后调用的是 StatementHandler#update 来进行处理。

  @Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      // 1. 获取配置信息 : 保存所有 Mapper Method 语句
      Configuration configuration = ms.getConfiguration();
      // 2、 创建一个语句处理器, 这里可以使用 Mybatis Plugin 扩展 StatementHandler。
      // 默认类型是 RoutingStatementHandler
      StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
      // 3. 执行预处理
      stmt = prepareStatement(handler, ms.getStatementLog());
      // 4. 调用 RoutingStatementHandler#update执行查询
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
  }

上面可以看到上面通过 不管是 SimpleExecutor#doQuery 还是 SimpleExecutor#doUpdate 都是在内部创建了一个 StatementHandler ,并委托他来执行具体的任务。因此下面我们来看下 StatementHandler 。

五、StatementHandler

StatementHandler 见名知意,Sql 语句处理器,它具有四个实现类 ,如下:

  • RoutingStatementHandler : 根据 statementType 路由到下面三种不同的 StatementHandler
  • SimpleStatementHandler :管理 Statement 对象并向数据库中推送不需要预编译的SQL语句
  • PreparedStatementHandler :管理 Statement 对象并向数据中推送需要预编译的SQL语句
  • CallableStatementHandler : 管理 Statement 对象并调用数据库中的存储过程。能力有限,本文不涉及存储过程相关逻辑分析。

在 org.apache.ibatis.mapping.StatementType 中具有statementType 的枚举类型,默认是 PREPARED,即 PreparedStatementHandler 。我们可以通过如下方式指定 statementType

	<!-- 指定statementType 类型为 STATEMENT -->
    <select id="queryById" resultMap="BaseResultMap" statementType="STATEMENT">
        select
          id, create_time, modify_time, user_name, password, status, is_delete, nick_name, phone, extend
        from sys_user
        where id = #{id}
    </select>

注 :根据 MapperBuilderAssistant#addMappedStatement 方法的调用链路可以看到 MappedStatement 的创建过程,这里由于篇幅所限,不再赘述。


在上面的代码中我们知道通过 configuration.newStatementHandler 创建的 StatementHandler 实例类型为 RoutingStatementHandler,下面我们具体来看:

1. RoutingStatementHandler

RoutingStatementHandler 构造如下,可以看到 RoutingStatementHandler起到一个分发的作用,具体的逻辑还是委托给 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());
    }
  }

SimpleStatementHandler 与 PreparedStatementHandler 逻辑也类似,Sql 执行结束后交由 ResultSetHandler 来处理结果集,不同的是 PreparedStatementHandler 会进行预处理,所以这里不再分析 SimpleStatementHandler 的逻辑过程。下面以 PreparedStatementHandler 为例来进行分析。


2. PreparedStatementHandler

2.1 构造函数

PreparedStatementHandler 的构造函数如下,这里可以看到直接调用了父类的构造

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

父类构造函数 BaseStatementHandler#BaseStatementHandler 如下:

  protected BaseStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
    this.configuration = mappedStatement.getConfiguration();
    this.executor = executor;
    this.mappedStatement = mappedStatement;
    this.rowBounds = rowBounds;
	// 获取类型转换注器,里面包含了注册的类型转换器
    this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
    this.objectFactory = configuration.getObjectFactory();

    if (boundSql == null) { // issue #435, get the key before calculating the statement
   	  // 对 KeyGenerator 处理,这里调用 KeyGenerator#processBefore 方法
      generateKeys(parameterObject);
      boundSql = mappedStatement.getBoundSql(parameterObject);
    }

    this.boundSql = boundSql;
	// 创建 parameterHandler , 默认类型是 DefaultParameterHandler,可以被插件扩展
    this.parameterHandler = configuration.newParameterHandler(mappedStatement, parameterObject, boundSql);
    // 创建 resultSetHandler , 默认类型是 DefaultResultSetHandler,可以被插件扩展
    this.resultSetHandler = configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
  }

这里可以看到,PreparedStatementHandler 在构造函数中创建了 parameterHandler (DefaultParameterHandler 类型,用于处理Sql执行前的参数处理)和 resultSetHandler (DefaultResultSetHandler 类型,用于处理Sql执行的返回值)。

2.3 PreparedStatementHandler#parameterize

上面我们看到在构造函数中,parameterHandler 被声明称了 DefaultParameterHandler 类型,因此下面调用的是 DefaultParameterHandler#setParameters。而 DefaultParameterHandler#setParameters 方法则完成了对参数的类型解析。

  @Override
  public void parameterize(Statement statement) throws SQLException {
    parameterHandler.setParameters((PreparedStatement) statement);
  }

DefaultParameterHandler#setParameters 的实现如下, 该方法会根据参数类型寻找合适的 TypeHandler 来进行参数处理,这里不再赘述。

 @Override
  public void setParameters(PreparedStatement ps) {
    ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
    // 获取当前 Sql的 参数信息
    List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
    if (parameterMappings != null) {
      for (int i = 0; i < parameterMappings.size(); i++) {
      	// 遍历每个参数
        ParameterMapping parameterMapping = parameterMappings.get(i);
        // 判断是否参数类型是否是 OUT ,可通过 <parameter> 标签指定, 涉及存储过程,不再赘述
        if (parameterMapping.getMode() != ParameterMode.OUT) {
          Object value;
          String propertyName = parameterMapping.getProperty();
          if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
            value = boundSql.getAdditionalParameter(propertyName);
          } else if (parameterObject == null) {
            value = null;
          } else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
          	// 判断是否有可以 处理该参数类型的 TypeHandler
            value = parameterObject;
          } else {
            MetaObject metaObject = configuration.newMetaObject(parameterObject);
            value = metaObject.getValue(propertyName);
          }
          TypeHandler typeHandler = parameterMapping.getTypeHandler();
          JdbcType jdbcType = parameterMapping.getJdbcType();
          if (value == null && jdbcType == null) {
            jdbcType = configuration.getJdbcTypeForNull();
          }
          try {
          	// 交由 TypeHandler 来处理参数
            typeHandler.setParameter(ps, i + 1, value, jdbcType);
          } catch (TypeException | SQLException e) {
            throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
          }
        }
      }
    }
  }

2.2 PreparedStatementHandler#update & query

PreparedStatementHandler 部分代码实现如下 :

  @Override
  public int update(Statement statement) throws SQLException {
    PreparedStatement ps = (PreparedStatement) statement;
    // 预处理执行
    ps.execute();
    // 获取本次执行 ,增、删、改 的数据条数
    int rows = ps.getUpdateCount();
    Object parameterObject = boundSql.getParameterObject();
    // 对主键处理,将新增的主键id赋值给参数主键
    KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
    keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
    return rows;
  }

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

基于上面代码,我们来进行简单总结:

  • PreparedStatementHandler#parameterize : 调用 DefaultParameterHandler#setParameters 方法对Sql参数做类型处理。
  • PreparedStatementHandler#update : ps.execute() 执行后,调用 KeyGenerator#KeyGenerator 将新增记录主键赋值给参数。
  • PreparedStatementHandler#query : ps.execute() 执行后, 会由 ResultSetHandler 来处理结果,将执行结果映射成我们想要的类型。而我们在PreparedStatementHandler 构造函数中可以看到 这里的 ResultSetHandler 实际类型是 DefaultResultSetHandler。

六、总结

综上,我们来进行简单总结:

  1. 在 MybatisAutoConfiguration 中会将 SqlSessionFactory 注入到容器中,不过这里是通过 SqlSessionFactoryBean#getObject 来注册,而在 SqlSessionFactory 中保存了 Mybatis 的基本信息数据。

  2. 同时在 MybatisAutoConfiguration 中会将 SqlSessionTemplate 注入到容器中,其中第一步创建的 SqlSessionFactory 会作为 SqlSessionTemplate 的 属性保存。

  3. 当Mapper Interface 方法被调用时会通过如下时序图调用到 SqlSession,而这里的SqlSession 指的就是上面的 SqlSessionTemplate。
    在这里插入图片描述

  4. 而 SqlSessionTemplate 的方法都会交由 SqlSessionTemplate 自己创建的代理类 sqlSessionProxy 来执行,sqlSessionProxy 的增强类是 SqlSessionInterceptor,而 SqlSessionInterceptor#invoke 会尝试获取当前事务绑定的SqlSession,如果没有获取到则通过 SqlSessionFactory#openSession 来创建,这里会创建一个 DefaultSqlSession 的类型的 SqlSession。随后通过反射的方式调用SqlSession的相关方法(如 查询则是 DefaultSqlSession#selectList,增删改则是 DefaultSqlSession#selectList ),如下图

    在这里插入图片描述

  5. DefaultSqlSession 会通过 Executor 来执行具体操作,而最后会调用 SimpleExecutor 方法来处理。SimpleExecutor 则会创建一个 StatementHandler 来执行最后的方法

  6. 在 PreparedStatementHandler 中存在两个属性 ParameterHandler parameterHandler 用于处理Sql 执行前的参数预处理, ResultSetHandler resultSetHandler 用于处理Sql执行后的返回值处理。==由于篇幅所限,关于这两个属性的具体逻辑如有需要详参 Mybatis 源码 ④ :TypeHandler


以上:内容部分参考
https://blog.csdn.net/javageektech/article/details/96539536
https://www.cnblogs.com/hochan100/p/15133686.html
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/878917.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

ubuntu 部署 ChatGLM-6B 完整流程 模型量化 Nvidia

ubuntu 部署 ChatGLM-6B 完整流程 模型量化 Nvidia 初环境与设备环境准备克隆模型代码部署 ChatGLM-6B完整代码 ChatGLM-6B 是一个开源的、支持中英双语的对话语言模型&#xff0c;基于 General Language Model (GLM) 架构&#xff0c;具有 62 亿参数。结合模型量化技术&#x…

Go学习-Day1

Go学习-Day1 个人博客&#xff1a;CSDN博客 打卡。 Go语言的核心开发团队&#xff1a; Ken Thompson (C语言&#xff0c;B语言&#xff0c;Unix的发明者&#xff0c;牛人)Rob Pike(UTF-8发明人)Robert Griesemer(协助HotSpot编译器&#xff0c;Js引擎V8) Go语言有静态语言的…

Sentinel使用实例

不说了&#xff0c;直接上官方文档 https://github.com/alibaba/spring-cloud-alibaba/blob/master/spring-cloud-alibaba-examples/sentinel-example/sentinel-core-example/readme-zh.md Sentinel Example 项目说明 本项目演示如何使用 Sentinel starter 完成 Spring Clo…

推荐工具!使终端便于 DevOps 和 Kubernetes 使用

如果你熟悉 DevOps 和 Kubernetes 的使用&#xff0c;就会知道命令行界面&#xff08;CLI&#xff09;对于管理任务有多么重要。好在现在市面上有一些工具可以让终端在这些环境中更容易使用。在本文中&#xff0c;我们将探讨可以让工作流程简化的优秀工具&#xff0c;帮助你在 …

Intel汇编和ATT汇编的区别?

一、前缀不同 在 Intel 语法中&#xff0c;没有寄存器前缀或立即前缀。 然而&#xff0c;在 AT&T 中&#xff0c;寄存器的前缀是“%”&#xff0c;而 immed 的前缀是“$”。 Intel 语法十六进制或二进制即时数据分别带有“h”和“b”后缀。 此外&#xff0c;如果第一个十六…

最新AI系统ChatGPT网站程序源码+搭建教程/公众号/H5端/安装配置教程/完整知识库

1、前言 SparkAi系统是基于国外很火的ChatGPT进行开发的Ai智能问答系统。本期针对源码系统整体测试下来非常完美&#xff0c;可以说SparkAi是目前国内一款的ChatGPT对接OpenAI软件系统。 那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧&#xff01;…

nginx一般轮询、加权轮询、ip_hash等负载均衡模式配置介绍

一.负载均衡含义简介 二.nginx负载均衡配置方式 准备三台设备&#xff1a; 2.190均衡服务器&#xff0c;2.191web服务器1&#xff0c;2.160web服务器2&#xff0c;三台设备均安装nginx&#xff0c;两台web服务器均有网页内容 1.一般轮询负载均衡 &#xff08;1&#xff09…

基于深度信念神经网络的矿石产量预测,基于DBN的矿石产量预测,DBN的详细原理

目录 背影 DBN神经网络的原理 DBN神经网络的定义 受限玻尔兹曼机(RBM) DBN的矿石产量预测 基本结构 主要参数 数据 MATALB代码 结果图 展望 背影 DBN是一种深度学习神经网络,拥有提取特征,非监督学习的能力,是一种非常好的分类算法,本文将DBN算法进行矿石产量预测 DB…

golang Cobra 快速使用

package mainimport("fmt""os""github.com/spf13/cobra" )func f1(){fmt.Printf("这是主命令的运行函数 ") } func f2(){fmt.Println("这是一个子命令的运行函数") }func main(){var rootCmd &cobra.Command{Use: &quo…

【果树农药喷洒机器人】Part8:果树对靶变量喷药实验

&#x1f4e2;&#xff1a;博客主页 【https://blog.csdn.net/weixin_51244852】 &#x1f4e2;&#xff1a;文章若有幸对你有帮助&#xff0c;可点赞 &#x1f44d; 收藏 ⭐不迷路&#x1f649; &#x1f4e2;&#xff1a;内容若有错误&#xff0c;敬请留言 &#x1f4dd;指正…

Maven - 全面解析 Maven BOM (Bill of Materials):打造高效依赖管理与模块化开发

文章目录 Whats BOMWhy Bom常見的官方BOMSpring Maven BOM dependencySpringBoot SpringCloud Maven BOM dependencyJBOSS Maven BOM dependencyRESTEasy Maven BOM dependencyJersey Maven BOM dependency How Bom定义BOM其他工程使用的方法 BOM VS POM What’s BOM BOM&…

jmeter中用户参数和用户定义的变量的区别

如果使用jmeter做过参数化的人都知道&#xff0c;参数化的方式有多种&#xff0c;其中一种就是使用用户定义的变量&#xff0c;还有一种是使用用户参数。那么&#xff0c;这两个有什么异同呢&#xff1f; 一、先说相同的点&#xff1a; 1、都可以参数化&#xff0c;以供sample…

ArrayList浅拷贝clone,Kotlin

ArrayList浅拷贝clone&#xff0c;Kotlin import kotlin.collections.ArrayListfun main() {var list ArrayList<MyData>()for (i in 0..<3) {list.add(MyData(i, 0))}//浅拷贝list到copyListvar copyList list.clone()println(list)println(copyList)println("…

SSM整合(XML方式)

文章目录 SSM整合之后xml方式1 系统环境1.1 软件环境1.2 项目环境1.3 配置web.xml1.4 配置jdbc.properties文件1.5 配置SpringMVC核心文件1.6 配置Spring的核心文件1.7 配置MyBatis的核心文件1.8 配置数据库1.9 配置文件位置 2 编写后端代码2.1 编写实体类2.2 编写Dao接口2.3 编…

AOP与SpringAOP

AOP与SpringAOP 一、什么是AOP&#xff0c;什么是SpringAOP二、AOP与拦截器的区别三、实现SpringAOP1.添加SpringBootAOP依赖2.创建切面3.创建切点4.创建通知5.创建连接点 效果 一、什么是AOP&#xff0c;什么是SpringAOP AOP即Aspect-Oriented Programming面向切面编程。 它是…

CRC 校验码

CRC 校验码 题目解答发送端如何计算商 接收端 题目 假设生成多项式为 G(X)X4X31&#xff0c;要求出二进制序列10110011的CRC校验码 解答 发送端 首先 生成多项式为&#xff1a;G(X)X4X31&#xff0c;改写为二进制比特串为11001(有X的几次方&#xff0c;对应的2的几次方的位…

stable diffusion 单张图片换头roop安装配置

1.首先安装秋叶大佬的webui 2.然后在拓展里面搜索roop,下载roop插件,然后重启webui 3.重启后,在文生图和图生图的界面,就可以看到roop的入口 4.这里面,需要提前安装Visual Studio. 勾选一些必要的选项,这里可以参照b站的视频 # 秋叶版本Stablediffusion的Roop插件的安装 …

RocketMQ消费者可以手动消费但无法主动消费问题,或生成者发送超时

1.大多数是配置问题 修改rocketmq文件夹broker.conf 2.配置与集群IP或本地IPV4一样 重启 在RocketMQ独享实例中支持IPv4和IPv6双栈&#xff0c;主要是通过在网络层面上同时支持IPv4和IPv6协议栈来实现的。RocketMQ的Broker端、Namesrv端和客户端都需要支持IPv4和IPv6协议&…

Qt扫盲-Qt Paint System 概述

Qt Paint System 概述 一、概述二、绘图设备和后端1. Widget2. Image3. Pixmap4. OpenGL绘制设备5. Picture6. 自定义绘制后端 三、绘图与填充1. Drawing2. 填充 Filling 四、坐标系统1. 渲染Window-Viewport转换 五、读写图像文件1. QMovie 六、绘图相关设备 一、概述 Qt的pa…

【Antd】DatePicker日期选择框设置disabledDate不可选择的日期

需要先引入moment import moment from moment; 调用 antd 组件时&#xff1a; <RangePicker disabledDate{disabledDate} /> 其中 disabledDate 赋值如下&#xff1a; 1、当天之前的不可选&#xff0c;不包括当天&#xff1a; const disabledDate (current) > {/…