本文基于mybatis-spring 1.3.1和mybatis 3.4.4版本
本文分析一下Mybatis如何执行SQL查询。
一、调用Mapper接口代理对象
mybatis启动时将MapperProxy类作为InvocationHandler对所有的mapper接口创建了代理,我们在程序中使用的对象都是代理,那么调用代理对象的方法都会调用MapperProxy的invoke方法。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//上面两个if判断表示如果是Object的方法或者是有方法体的实例方法,直接调用即可
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
MapperMethod会对调用的方法进行判断,只有调用接口的方法才会进入到mybatis中。
下面是MapperMethod的execute方法,execute方法内容涉及的比较多,本文只关注查询,因此下面代码把增删改的内容删除了:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
//如果返回是void并且入参有ResultHandler
//下面方法里面会调用SqlSessionTemplate.select()
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
//如果返回值是集合
//下面方法里面会调用SqlSessionTemplate.selectList()
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
//如果返回对象是Map并且配置了MapKey注解
//下面方法里面会调用SqlSessionTemplate.selectMap()
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
//如果返回对象是Cursor
//下面方法里面会调用SqlSessionTemplate.selectCursor()
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
//sqlSession其实是SqlSessionTemplate对象
result = sqlSession.selectOne(command.getName(), param);
}
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
上面每个方法会再调用SqlSessionTemplate的selectXXX方法。下面来详细看一下SqlSessionTemplate类。
二、SqlSessionTemplate
MybatisAutoConfiguration作为mybatis自动配置类,它创建了SqlSessionTemplate。在SqlSessionTemplate的构造方法里面会创建SqlSession代理:
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
//代码有删减
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;//异常翻译器,当数据库抛出异常,可以对异常统一做转码,屏蔽数据库底层
//创建SqlSession代理
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
在mybatis中,SqlSession并不直接对外暴露,所有对SqlSession的调用都通过SqlSessionTemplate调用代理对象sqlSessionProxy完成。大家可以看一下SqlSessionTemplate的代码,所有对SqlSessionTemplate的调用都委托给了sqlSessionProxy。所以MapperMethod.execute里面调用SqlSessionTemplate,其实都委托给了sqlSessionProxy。
在创建代理对象的时候,使用到了SqlSessionInterceptor,调用sqlSessionProxy时,需要执行SqlSessionInterceptor 的invoke方法。
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//获得一个SqlSession对象
//getSqlSession方法下面有介绍
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
//调用SqlSession对象的方法,比如selectOne,selectList
//下面这一步调用会访问数据库,所以result是数据库的执行结果
Object result = method.invoke(sqlSession, args);
//判断当前事务是否由spring控制
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
//如果当前没有事务控制,那么直接调用commit方法提交事务
//commit方法后文分析
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
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);
}
}
}
}
下面是getSqlSession和closeSqlSession的代码:
public static SqlSession getSqlSession(SqlSessionFactory sessionFactory, ExecutorType executorType, PersistenceExceptionTranslator exceptionTranslator) {
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
notNull(executorType, NO_EXECUTOR_TYPE_SPECIFIED);
//判断当前事务是否已经注册过SqlSession,或者说当前事务是否已经创建过SqlSession对象
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);[1]
//如果当前事务已有SqlSession,那么便从holder中取出SqlSession对象,
//并对SqlSession的引用计数+1
SqlSession session = sessionHolder(executorType, holder);
if (session != null) {
return session;
}
//如果当前不在事务中或者是事务执行的第一个SQL语句,
//那么通过SqlSessionFactory创建一个SqlSession
session = sessionFactory.openSession(executorType);
//下面这个方法创建SqlSessionHolder,它持有上面创建的SqlSession对象,
//之后将SqlSessionHolder对象注册到TransactionSynchronizationManager
//这样当在一个事务中时,在[1]处,mybatis就可以取到SqlSession对象
registerSessionHolder(sessionFactory, executorType, exceptionTranslator, session);
return session;
}
private static SqlSession sessionHolder(ExecutorType executorType, SqlSessionHolder holder) {
SqlSession session = null;
if (holder != null && holder.isSynchronizedWithTransaction()) {
if (holder.getExecutorType() != executorType) {
throw new TransientDataAccessResourceException("Cannot change the ExecutorType when there is an existing transaction");
}
//对SqlSession的引用计数+1
holder.requested();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Fetched SqlSession [" + holder.getSqlSession() + "] from current transaction");
}
//从holder中取出SqlSession对象
session = holder.getSqlSession();
}
return session;
}
public static void closeSqlSession(SqlSession session, SqlSessionFactory sessionFactory) {
notNull(session, NO_SQL_SESSION_SPECIFIED);
notNull(sessionFactory, NO_SQL_SESSION_FACTORY_SPECIFIED);
SqlSessionHolder holder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sessionFactory);
if ((holder != null) && (holder.getSqlSession() == session)) {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Releasing transactional SqlSession [" + session + "]");
}
//对SqlSession的引用计数-1
holder.released();
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Closing non transactional SqlSession [" + session + "]");
}
//如果当前不在事务中,则将SqlSession关闭,关闭了SqlSession也就关闭了数据库连接
session.close();
}
}
下面对SqlSessionInterceptor的作用做一下总结:
- 对所有调用SqlSession的请求进行拦截;
- 通过DefaultSqlSessionFactory创建一个SqlSession对象;
- 如果当前在事务中,那么mybatis会将SqlSession对象注册到spring事务管理器中,事务纳入spring管理,如果不在事务中,mybatis对连接自主提交或者回滚。
三、创建SqlSession对象
下面分析一下DefaultSqlSessionFactory如何创建DefaultSqlSession。
创建DefaultSqlSession是在DefaultSqlSessionFactory的openSession方法中完成的:
public SqlSession openSession(ExecutorType execType) {
return openSessionFromDataSource(execType, null, false);
}
//入参execType是执行器类型,level表示隔离级别,
//如果使用spring管理事务,level值不起作用,使用spring设置的隔离级别
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
//获得事务工厂,默认是SpringManagedTransactionFactory
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
//根据事务工厂创建一个事务对象,默认是SpringManagedTransaction
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
//创建一个执行器,执行器中包含了事务对象Transaction
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();
}
}
在openSessionFromDataSource中使用configuration配置对象、执行器、是否自动提交三个参数创建了DefaultSqlSession。这段代码相对还是比较简单的。
从openSessionFromDataSource可以看到如下三者之间的关系:
再来说一下Transaction接口,该接口表示事务,如果当前是spring事务管理,那么接口实现类是SpringManagedTransaction,如果当前使用原生的数据库连接管理事务,那么实现类是JdbcTransaction,事务对象的作用是获得数据库连接、对事务提交或者回滚,也就是mybatis对连接的获取、事务的提交或者回滚都是通过Transaction接口完成的;
从上面的代码分析中可以看到,SqlSession对象、事务、数据库连接三者之间是一一对应关系。创建一个SqlSession对象意味着打开了一个数据库连接,开启了一个数据库事务。
关于SqlSession的提交和回滚文章后面再介绍。
四、执行SQL查询
SqlSession对象创建完之后,就要执行对象对应的查询方法了。
后面的流程比较繁琐,不详细介绍,只介绍大概流程。
DefaultSqlSession会将查询请求委托给Executor执行。比如DefaultSqlSession的方法select():
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
try {
//得到MappedStatement对象,该对象里面包含了sql语句、参数
//每个@Select注解的内容都会放入MappedStatement对象
MappedStatement ms = configuration.getMappedStatement(statement);
//调用执行器执行查询
executor.query(ms, wrapCollection(parameter), rowBounds, handler);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
Mybatis提供了多种Executor实现:
每个执行器的作用后面文章在做分析。下面以SimpleExecutor为例介绍后面的执行逻辑。
- 检查缓存是否存在要查询的数据,这里的缓存是一级缓存;
- 获取数据库连接,如果事务是spring管理,且在一个事务中,那么每次获得的连接都是同一个;
- 设置Statement对象的参数,比如fetchSize、超时时间等;
- 如果使用PreparedStatement对象,还要设置参数;
- 执行查询;
- 对查询结果使用ResultHandler对象处理。ResultHandler对查询结果做转换,比如转换为指定对象、Map对象。
- 将执行结果存入一级缓存中。将结果返回调用方。
五、总结
最后对整个的查询流程做一个总结: