我们知道Mybatis最终是通过SqlSession对象去执行sql语句的,通过前面几篇文章我们也知道Mybatis是怎么解析mapper.xml文件、怎么把sql语句读入到Mybatis的Congifuration对象中、最后Mapper接口的代理对象是怎么匹配到sql语句、并最终通过SqlSession对象去执行sql语句的。
上述整个过程我们都已经了解过了,唯一一个尚未分析过的问题是:SqlSession是怎么初始化的?以及他具体是怎么执行sql语句的?
今天的主要目标就是要搞清楚以上两个问题:
- SqlSession对象的创建或者初始化过程。
- SqlSession执行sql语句的具体过程。
SqlSession对象的创建
不同的项目,SqlSession会有不同的创建过程,但基本都大同小异。我们今天主要分析Springboot+Mybatis项目中SqlSession的创建过程。
Springboot项目中主要通过配置类MybatisAutoConfiguration来完成Mybatis的初始化。
MybatisAutoConfiguration主要完成:
- 创建并初始化Configuration对象
- 通过SqlSessionFactoryBean初始化SqlSessionFactory(初始化为DefaultSqlSessionFactory)
- 初始化SqlSession,Spring项目SqlSession初始化为SqlSessionTemplate对象
MybatisAutoConfiguration#sqlSessionFactory
sqlSessionFactory方法主要完成Configuration对象的创建,以及SqlSessionFactory对象的创建。方法比较长,最后通过SqlSessionFactoryBean的build方法创建DefaultSqlSessionFactory对象返回,并交给Spring Ioc容器管理。
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
factory.setDataSource(dataSource);
factory.setVfs(SpringBootVFS.class);
if (StringUtils.hasText(this.properties.getConfigLocation())) {
factory.setConfigLocation(this.resourceLoader.getResource(this.properties.getConfigLocation()));
}
applyConfiguration(factory);
//省略
return factory.getObject();
MybatisAutoConfiguration#sqlSessionTemplate
sqlSessionTemplate对象是Spring+Mybatis项目的SqlSession落地对象,通过sqlSessionTemplate可以实现Spring和Mybatis的无缝对接。
通过上面SqlSession的类结构,我们知道SqlSessionTemplate实际是SqlSession的一个实现类,所以,通过@Bean注解的最终返回SqlSessionTemplate对象的方法sqlSessionTemplate(SqlSessionFactory sqlSessionFactory)的目的就是创建SqlSession对象,并最终交给Spring的Ioc容器来管理。其中参数SqlSessionFactory是通过Spring Ioc依赖注入的DefaultSqlSessionFactory对象:
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
最终会调用到SqlSessionTemplate的SqlSessionTemplate方法:
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;
this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class }, new SqlSessionInterceptor());
}
我们需要把关注点放在代码最后一句话,创建了一个动态代理对象(又是动态代理)赋值给SqlSessionTelmplate对象的sqlSessionProxy,这个代理对象的回调对象为SqlSessionInterceptor,我们对JDK动态代理已经非常熟悉了,我们知道所有的对于代理对象sqlSessionProxy的方法调用,最终都会调用到回调对象SqlSessionInterceptor的invoke方法。
好了,到这里我们就知道Mybatis的最重要的一个对象SqlSession被创建好了,他其实就是SqlSessionTelmplate对象,他的两个重要属性我们也要搞清楚:
- sqlSessionFactory:其实就是通过参数传入进来的DefaultSqlSessionFatcory对象。
- sqlSessionProxy:其实就是以SqlSessionInterceptor为回调对象的JDK动态代理对象。
SqlSession执行sql语句的具体过程
这个问题其实上一篇文章已经分析过了,通过上一篇文章其实我们已经知道了mapper.xml文件中的sql语句是怎么被解析到Configuration对象的mappedStatements中,并且也明白了mapper接口中的方法最终怎么匹配到Configuration对象中的mappedStatements并最终执行其中的sql语句。
整个过程我们已经一清二楚了,只不过,最终sql语句执行的时候是通过sqlSession对象来执行的,上一篇文章并没有详细分析sqlSession执行sql语句的具体过程。我们现在来具体分析一下这一部分。
通过前面的分析,现在我们知道sqlSession对象其实是SqlSessionTelmplate对象,我们就以selectOne方法为例,直接看一下SqlSessionTelmplate的selectOne方法。
我们知道mapper.xml文件中的select语句的返回类型如果不是一个list、而是一个实体对象的时候,比如selectXXXById通过主键键值返回一条记录的时候,最终会匹配到这个selectOne方法。
看一下SqlSessionTelmplate源码,他交给sqlSessionProxy去处理,其实我们可以看到SqlSessionTelmplate的所有的sql语句执行的方法最终都是委托给sqlSessionProxy去处理的。
我们已经知道这个sqlSessionProxy其实就是动态代理对象,他最终会调用到回调对象SqlSessionInterceptor的invoke方法。
@Override
public <T> T selectOne(String statement, Object parameter) {
return this.sqlSessionProxy.selectOne(statement, parameter);
}
SqlSessionInterceptor#invoke
方法源码不太长,我们先把源码都贴出来:
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType, SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
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);
}
}
}
}
首先通过SqlSessionUtils的getSqlSessio去获取Sqlsession,Mybatis其实就是通过这个SqlSessionUtils实现与Spring的sqlSession做集成的,他首先去获取TransactionSynchronizationManager中的sqlSession,如果TransactionSynchronizationManager中存在当前线程的sqlSession的话则直接返回,不存在的话再通过DefaultSqlSessionFactory去创建sqlSession返回并存入TransactionSynchronizationManager中。
DefaultSqlSessionFactory的创建sqlSession的代码我们前面其实已经看过了,比如他的openSessionFromDataSource方法,最终返回的是DefaultSqlSession对象,所以真正的SqlSession其实是DefaultSqlSession对象。
获取到DefaultSqlSession对象后,执行:
Object result = method.invoke(sqlSession, args);
这句代码的意思是,通过反射机制执行sqlSession(也就是DefaultSqlSession对象)的selectOne方法。
DefaultSqlSession的selectOne方法通过获取到的数据库connection、以及事务管理等相关参数执行sql语句,这部分我们在前面的文章中也分析过了。
从源码中我们可以看到SqlSession最终通过JDK动态代理的方式创建代理对象执行sql语句、而不是直接创建一个sqlSession对象去执行的目的,可能就是为了通过AOP的方式在sql语句执行完成后做一些必要的善后处理,也就是上面invoke方法中method.invoke方法执行后的commit以及closeSqlSession等相关逻辑,这样的话调用方、也就是应用层就不需要处理这些善后工作了。
以上!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。