阳江企业网站建设,辽宁网站建设哪里好,做任务领取礼品的网站,数据分析师需要考哪些证书一、前言
Mybatis 官网 以及 本系列文章地址#xff1a;
Mybatis 源码 ① #xff1a;开篇Mybatis 源码 ② #xff1a;流程分析Mybatis 源码 ③ #xff1a;SqlSessionMybatis 源码 ④ #xff1a;TypeHandlerMybatis 源码 ∞ #xff1a;杂七杂八 在 Mybatis 源码 ②…一、前言
Mybatis 官网 以及 本系列文章地址
Mybatis 源码 ① 开篇Mybatis 源码 ② 流程分析Mybatis 源码 ③ SqlSessionMybatis 源码 ④ TypeHandlerMybatis 源码 ∞ 杂七杂八 在 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线程安全相较于 DefaultSqlSessionSqlSessionTemplate 将事务的管理交由 Spring框架来控制。默认情况下SpringBoot 注入的 SqlSession类型是 SqlSessionTemplate 。
下面我们具体来看这两个类在 Spring容器中的初始化过程。
二、SqlSessionFactory 和 SqlSessionTemplate
1. SqlSessionFactory
在 MybatisAutoConfiguration 中会将 SqlSessionFactory 注入到容器中不过需要注意这里并不是直接创建一个SqlSessionFactory 而是通过 SqlSessionFactoryBean#getObject 实现类型是DefaultSqlSessionFactory 来创建其具体过程如下
1.1 MybatisAutoConfiguration#sqlSessionFactory BeanConditionalOnMissingBeanpublic 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 属性确定当前版本在做对应处理SetString 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.2factory.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.2factory.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);}这里可以看到 SqlSessionFactory 不是直接生成而是通过 SqlSessionFactoryBean#getObject 生成并且在创建过程中初始化了 Configuration 信息。这一点我们下面详细看。 通过 ConfigurationCustomizer 可以对 Mybatis 配置进行定制化改造。ConfigurationCustomizer 接口定义如下如果需要定制化 Configuration 实现该接口即可。这里不再赘述。 public interface ConfigurationCustomizer {void customize(Configuration configuration);
}下面我们着重来看下 SqlSessionFactoryBean 创建 SqlSessionFactory 的过程。
1.2 SqlSessionFactoryBean
如下是 SqlSessionFactoryBean 类图 可以看到
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;}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);}这里我们按照上面的注释来简单解释一下 targetConfiguration 的初始化 在 MybatisAutoConfiguration#applyConfiguration 中如果我们配置了 mybatis.configuration 参数则会直接获取到 Configuration如果没有配置该参数并且未指定本地配置文件地址则会初始化一个新的 Configuration。当 configuration不为空时会通过 ConfigurationCustomizer#customize 做进一步定制化处理。在 SqlSessionFactoryBean#buildSqlSessionFactory 中会判断 Configuration 是否已经有值如果有值则直接赋值给 targetConfiguration如果没值则说明未配置 mybatis.configuration 属性并且指定了本地配置文件则通过 XMLConfigBuilder 加载本地配置文件并获取配置内容通过 mybatis.config-location 属性配置并赋值给 targetConfiguration 。如果以上两种情况都不满足则初始化一个默认值给 targetConfiguration。 Mybatis 参数解析 随后开始对Mybatis 的一些参数的解析 如 mybatis.type-aliases-package、mybatis.type-handlers-package、mybatis.scripting-language-driver 等配置 Mybatis XML 解析 这里将 mybatis.mapper-locations 配置指定路径下的 Mapper Xml 通过 XMLMapperBuilder 进行解析并保存到 targetConfiguration.mappedStatements中。 根据 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 存在三个实现类
DefaultSqlSession 默认的 SqlSession 实现类非线程安全提供了与DB 交互的基础功能。事务自动提交SqlSessionTemplate Spring 管理的 SqlSession线程安全相较于 DefaultSqlSessionSqlSessionTemplate 将事务的管理交由 Spring框架来控制。SqlSessionManager SqlSessionManager既实现了SqlSessionFactory也实现了SqlSession具备生产SqlSession的能力也具备SqlSession的能力。相较于 DefaultSqlSession SqlSessionManager 提供了对 事务的管理功能但并非是交由 Spring 框架。
在 MybatisAutoConfiguration 中 会注入 SqlSessionTemplateSqlSessionTemplate 的构造需要依赖 SqlSessionFactory 而我们上面已经分析过了 SqlSessionFactory 的注入过程可以得知这里的 SqlSessionFactory 实现类型是 DefaultSqlSessionFactory 。如下: BeanConditionalOnMissingBean// 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 默认为trueif (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 的创建过程就已经结束我们来简单总结下整个流程
SpringBoot 启动后 MybatisAutoConfiguration 中会注入 SqlSessionFactory 和 SqlSessionTemplate。SqlSessionFactory 创建是会解析Mybatis 配置以及 Mapper Xml 文件并保存此时注入的实际类型是 DefaultSqlSessionFactory。SqlSessionTemplate 实现了SqlSession 接口并且创建时依赖 SqlSessionFactory 同时 SqlSessionTemplate 内部会创建 SqlSession 代理类当调用SqlSessionTemplate 的 SqlSesion 接口方法时会委托给代理类 sqlSessionProxy 执行而代理类 sqlSessionProxy 执行时SqlSession 的接口方法时会调用增强方法 SqlSessionInterceptor#invoke ( sqlSessionProxy 同时也已经将事务交由 Spring 来管理)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 注解修饰的方法会调用该方法该方法会在获取结果后将结果聚合成 MapDefaultSqlSession#update : 增删改 操作调用的方法也具有多个重载方法DefaultSqlSession#commit DefaultSqlSession#rollback : 事务提交 or 回滚 的方法
下面我们具体来看上面几个方法
1. DefaultSqlSession#selectList
该方法用于列表查询或单独查询DefaultSqlSession#selectOne 内部也是调用的该方法判断结果集大小是否是1不是1 则抛出异常 private E ListE 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 MapK, V selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds) {// 调用 selectList 查询结果final List? extends V list selectList(statement, parameter, rowBounds);// 构建一个默认的Map结果处理器final DefaultMapResultHandlerK, V mapResultHandler new DefaultMapResultHandler(mapKey,configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());final DefaultResultContextV 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 是 CachingExecutorCachingExecutor 会缓存执行结果同时将执行逻辑委托给内部的 delegate 属性执行这里的 delegate 默认类型是 SimpleExecutor。 为了节省篇幅下面仅分析 Executor#query 和 Executor#update 方法。
1. CachingExecutor
上面我们提到在 DefaultSqlSessionFactory#openSessionFromDataSource 创建 SqlSession 时会调用 Configuration#newExecutor 方法来创建 Executor默认情况下 Configuration#newExecutor 方法中会创建一个 CachingExecutor 来包装 ExecutorSimpleExecutor。
当外部调用时会首先调用 CachingExecutor#query如果缓存未命中才会去调用内部的 delegate#query 去再次查询。下面我们来看具体逻辑。
1.1 CachingExecutor#query
CachingExecutor#query 实现如下 Overridepublic E ListE 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);// 获取 参数映射集合, ListParameterMapping 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 ListE 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)// 从事务缓存管理器中获取缓存ListE list (ListE) 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 ListE 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);}}这里我们按照注释来一步一步分析 ms.getConfiguration() 这里是获取之前初始化解析的配置信息里面包含解析后的 Mapper Interface 的 方法语句等信息 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;}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 查询参数做处理。这点我们在下面会详细说明。 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 路由到下面三种不同的 StatementHandlerSimpleStatementHandler 管理 Statement 对象并向数据库中推送不需要预编译的SQL语句PreparedStatementHandler 管理 Statement 对象并向数据中推送需要预编译的SQL语句CallableStatementHandler 管理 Statement 对象并调用数据库中的存储过程。能力有限本文不涉及存储过程相关逻辑分析。
在 org.apache.ibatis.mapping.StatementType 中具有statementType 的枚举类型默认是 PREPARED即 PreparedStatementHandler 。我们可以通过如下方式指定 statementType !-- 指定statementType 类型为 STATEMENT --select idqueryById resultMapBaseResultMap statementTypeSTATEMENTselectid, 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的 参数信息ListParameterMapping 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 ListE 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。
六、总结
综上我们来进行简单总结 在 MybatisAutoConfiguration 中会将 SqlSessionFactory 注入到容器中不过这里是通过 SqlSessionFactoryBean#getObject 来注册而在 SqlSessionFactory 中保存了 Mybatis 的基本信息数据。 同时在 MybatisAutoConfiguration 中会将 SqlSessionTemplate 注入到容器中其中第一步创建的 SqlSessionFactory 会作为 SqlSessionTemplate 的 属性保存。 当Mapper Interface 方法被调用时会通过如下时序图调用到 SqlSession而这里的SqlSession 指的就是上面的 SqlSessionTemplate。 而 SqlSessionTemplate 的方法都会交由 SqlSessionTemplate 自己创建的代理类 sqlSessionProxy 来执行sqlSessionProxy 的增强类是 SqlSessionInterceptor而 SqlSessionInterceptor#invoke 会尝试获取当前事务绑定的SqlSession如果没有获取到则通过 SqlSessionFactory#openSession 来创建这里会创建一个 DefaultSqlSession 的类型的 SqlSession。随后通过反射的方式调用SqlSession的相关方法如 查询则是 DefaultSqlSession#selectList增删改则是 DefaultSqlSession#selectList 如下图 DefaultSqlSession 会通过 Executor 来执行具体操作而最后会调用 SimpleExecutor 方法来处理。SimpleExecutor 则会创建一个 StatementHandler 来执行最后的方法 在 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 如有侵扰联系删除。 内容仅用于自我记录学习使用。如有错误欢迎指正