当前位置: 首页 > news >正文

网站建设服务海南百度推广中心

网站建设服务,海南百度推广中心,英文网站建站公司,网站前端跟后端怎么做一、前言 Mybatis 官网 以及 本系列文章地址: Mybatis 源码 ① :开篇Mybatis 源码 ② :流程分析Mybatis 源码 ③ :SqlSessionMybatis 源码 ④ :TypeHandlerMybatis 源码 ∞ :杂七杂八 在 Mybatis 源码 ②…

一、前言

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@ConditionalOnMissingBeanpublic 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 方法,如果没有则调用一次,如下:
      @Overridepublic 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中

  @Overridepublic 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 xmlstry {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 : 默认返回 DefaultSqlSessionFactoryreturn 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 ,如果生成则直接返回。

  @Overridepublic SqlSessionFactory getObject() throws Exception {if (this.sqlSessionFactory == null) {// sqlSessionFactory  为空表明还没初始化,调用初始化afterPropertiesSet();}// 返回初始化后的 sqlSessionFactory, 实际类型是上面提到的 DefaultSqlSessionFactoryreturn 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();// 创建SqlSessionTemplateif (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 {@Overridepublic 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 持有者 中获取 SqlSessionSqlSession session = sessionHolder(executorType, holder);// 如果获取到了,说明当前事务已经与DB建立连接,直接返回if (session != null) {return session;}// 到这一步说明 当前事务没有持有 SqlSession, 重新创建,返回类型是 DefaultSqlSessionsession = sessionFactory.openSession(executorType);// 注册session 到当前事务registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);return session;}

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

  @Overridepublic 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);// 创建并返回 DefaultSqlSessionreturn 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.countMappedStatement 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 类型

  @Overridepublic <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

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

  @Overridepublic 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

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

  @Overridepublic 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();}}@Overridepublic 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 实现如下:

  @Overridepublic <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {// 1. 获取 Sql 信息BoundSql boundSql = ms.getBoundSql(parameterObject);// 2. 创建 缓存 KeyCacheKey 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) {// 构建当前执行的SqlBoundSql 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 如下:

  @Overridepublic 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。

  @Overridepublic <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 的实现就很简单了,清除缓存后,交由委托类来处理。

  @Overridepublic 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 :

  @Overridepublic <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。// 默认类型是 RoutingStatementHandlerStatementHandler 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);// 获取 Statementstmt = 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 来进行处理。

  @Overridepublic int doUpdate(MappedStatement ms, Object parameter) throws SQLException {Statement stmt = null;try {// 1. 获取配置信息 : 保存所有 Mapper Method 语句Configuration configuration = ms.getConfiguration();// 2、 创建一个语句处理器, 这里可以使用 Mybatis Plugin 扩展 StatementHandler。// 默认类型是 RoutingStatementHandlerStatementHandler 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">selectid, create_time, modify_time, user_name, password, status, is_delete, nick_name, phone, extendfrom sys_userwhere 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 方法则完成了对参数的类型解析。

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

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

 @Overridepublic 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 paramsvalue = boundSql.getAdditionalParameter(propertyName);} else if (parameterObject == null) {value = null;} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {// 判断是否有可以 处理该参数类型的 TypeHandlervalue = 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 部分代码实现如下 :

  @Overridepublic 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;}@Overridepublic <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;// 执行Sqlps.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.tj-hxxt.cn/news/4044.html

相关文章:

  • 工作站做网站化妆品营销推广方案
  • 南京做网站建设有哪些内容网络营销一般月薪多少
  • 做特效的网站百度官方营销推广平台
  • 一站式建站价格搜索引擎市场份额2023
  • 网络推广和竞价推广有什么区别唐山seo优化
  • 旅游网站功能简介app拉新推广
  • 上海网络维护有哪些公司seo视频教程百度云
  • dede网站地图路径修改网络广告营销方案策划
  • 网站建设入什么科目合肥360seo排名
  • 毕业设计做网站论文识别关键词软件
  • 传奇网站模块下载2021年新闻摘抄
  • 做啊网站国产系统2345
  • 福清市建设局网站微信公众号营销
  • 网站建设工作进度举一个网络营销的例子
  • 网站设计师岗位职责南宁关键词排名公司
  • 宣传性网站建设策划方案网络营销专业课程
  • 江苏系统建站怎么用google浏览器官网入口
  • 网站建设 浙icp 0578网站自动提交收录
  • 网站建设中服务器的搭建方式有几种百度推广助手官方下载
  • 湖北中牛建设有限公司网站nba最新消息交易情况
  • 美国电子政务门户网站建设如何利用seo赚钱
  • 阿里巴巴网站规划长沙seo平台
  • 个性个人网站模板宁阳网站seo推广
  • 建设一个旅游电子商务网站外链link
  • 江西做网站多少钱市场监督管理局职责
  • 怎样建设一个卡盟网站谷歌广告投放
  • 3d做网站seo网站推广全程实例
  • 做积分网站百度商务合作联系
  • 惠州网站建设web91哪里有软件培训班
  • 学校网站设计图片北京seo排名外包