一、前言
Mybatis 官网 以及 本系列文章地址:
- Mybatis 源码 ① :开篇
- Mybatis 源码 ② :流程分析
- Mybatis 源码 ③ :SqlSession
- Mybatis 源码 ④ :TypeHandler
- 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);
}
这里可以看到 :
-
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 方法,如果没有则调用一次,如下:
@Override public 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中
@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);
}
这里我们按照上面的注释来简单解释一下:
-
targetConfiguration 的初始化 :
- 在 MybatisAutoConfiguration#applyConfiguration 中,如果我们配置了
mybatis.configuration
参数则会直接获取到 Configuration,如果没有配置该参数并且未指定本地配置文件地址,则会初始化一个新的 Configuration。当 configuration不为空时,会通过 ConfigurationCustomizer#customize 做进一步定制化处理。 - 在 SqlSessionFactoryBean#buildSqlSessionFactory 中,会判断 Configuration 是否已经有值,如果有值则直接赋值给 targetConfiguration,
- 如果没值(则说明未配置
mybatis.configuration
属性并且指定了本地配置文件)则通过 XMLConfigBuilder 加载本地配置文件并获取配置内容(通过mybatis.config-location
属性配置)并赋值给 targetConfiguration 。 - 如果以上两种情况都不满足,则初始化一个默认值给 targetConfiguration。
- 在 MybatisAutoConfiguration#applyConfiguration 中,如果我们配置了
-
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 ,如果生成则直接返回。
@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 存在三个实现类 :
- DefaultSqlSession :默认的 SqlSession 实现类,非线程安全,提供了与DB 交互的基础功能。事务自动提交
- SqlSessionTemplate :Spring 管理的 SqlSession,线程安全,相较于 DefaultSqlSession,SqlSessionTemplate 将事务的管理交由 Spring框架来控制。
- 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 的创建过程就已经结束,我们来简单总结下整个流程:
- 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 注解修饰的方法会调用该方法,该方法会在获取结果后将结果聚合成 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);
}
}
这里我们按照注释来一步一步分析:
-
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); // 获取 Statement stmt = 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 来进行处理。
@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。
六、总结
综上,我们来进行简单总结:
-
在 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
如有侵扰,联系删除。 内容仅用于自我记录学习使用。如有错误,欢迎指正