前言
前面简单的写了个mybatis的demo运行,并且根据demo运行了解了mybatis的执行流程,其实mybatis的源码还是很简单的,看过Spring之后就会感觉看其他框架的源码就是福利啊,在mybatis的执行流程中有很多经常听到或者面试被问到的几个类,尤其是SqlSession,本文将对SqlSession的执行进行简单的分析。
一、SqlSession
SqlSession是mybatis中一个非常重要的核心类,它主要用来维持程序与数据库之间的连接以及通信,可以执行对应的mapper方法获取返回结果,相当于JDBC中的Connection。接下来看其源码:
public interface SqlSession extends Closeable {
<T> T selectOne(String var1);
<T> T selectOne(String var1, Object var2);
<E> List<E> selectList(String var1);
<E> List<E> selectList(String var1, Object var2);
<E> List<E> selectList(String var1, Object var2, RowBounds var3);
<K, V> Map<K, V> selectMap(String var1, String var2);
<K, V> Map<K, V> selectMap(String var1, Object var2, String var3);
<K, V> Map<K, V> selectMap(String var1, Object var2, String var3, RowBounds var4);
void select(String var1, Object var2, ResultHandler var3);
void select(String var1, ResultHandler var2);
void select(String var1, Object var2, RowBounds var3, ResultHandler var4);
int insert(String var1);
int insert(String var1, Object var2);
int update(String var1);
int update(String var1, Object var2);
int delete(String var1);
int delete(String var1, Object var2);
void commit();
void commit(boolean var1);
void rollback();
void rollback(boolean var1);
List<BatchResult> flushStatements();
void close();
void clearCache();
Configuration getConfiguration();
<T> T getMapper(Class<T> var1);
Connection getConnection();
}
可以看到SqlSession是一个接口,提供了一些增删改查方法以及事务的操作等方法。SqlSession有两个实现类SqlSessionManager和DefaultSqlSession。
二、DefaultSqlSession
通过打断点可以知道我们示例代码中的SqlSession使用的是DefaultSqlSession。那么我们就对主要对DefaultSqlSession进行一下研究。
我们看一下其源码,源码中有这么几个属性:configuration主要用来存放mybatis-config.xml初始化加载的配置文件中的信息,这里要知道mapper.xml其实也是config中的配置文件,因为这是在config里面配置了扫包引入的;Executor是mybatis中的执行器,是用来执行SQL操作的;autoCommit是自动事务的提交;
这里需要注意的一件事就是我把源码的注释也复制过来了,看这里有一行很重要的信息:这个类不是线程安全的。
/**
* The default implementation for {@link SqlSession}.
* Note that this class is not Thread-Safe.
*/
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
private Executor executor;
private boolean autoCommit;
private boolean dirty;
}
随便找个查询语句来看一下其执行的流程:
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
List var5;
MappedStatement ms = this.configuration.getMappedStatement(statement);
var5 = this.executor.query(ms, this.wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
...//省略简化了N多代码
return var5;
}
首先通过执行的方法(com.mayikt.mapper.UserMapper.getUser)作为参数从配置文件中取出了一个Statement对象,来看一下MappedStatement是干什么的。
三、MappedStatement
public final class MappedStatement {
private String resource;
private Configuration configuration;
private String id;
private Integer fetchSize;
private Integer timeout;
private StatementType statementType;
private ResultSetType resultSetType;
private SqlSource sqlSource;
private Cache cache;
private ParameterMap parameterMap;
private List<ResultMap> resultMaps;
private boolean flushCacheRequired;
private boolean useCache;
private boolean resultOrdered;
private SqlCommandType sqlCommandType;
private KeyGenerator keyGenerator;
private String[] keyProperties;
private String[] keyColumns;
private boolean hasNestedResultMaps;
private String databaseId;
private Log statementLog;
private LanguageDriver lang;
private String[] resultSets;
}
假装不知道这个类是干什么的,由于源码上面没有注释,我们直接看一下其属性,里面存放有很多熟悉的信息像来源、超时时间、resultType、parameterMap等信息,我们就可以猜测其是mapper.xml的实例化对象,然后看一下断点上面记录的信息,简单截取部分断点信息,通过id就可以判断这是mapper里面的一个方法节点的实例化,因为其很多属性都是单一字符串的例如id等属性,直接指向了其中的一个节点getUser,所以MappedStatement是mapper.xml中一个节点的实例化对象。
继续往下执行代码,执行了executor.query(),咱们继续往下接着看。
四、Executor
Executor是mybatis中的执行器,真正执行SQL操作的,可以看一下这是个接口类,下面有多种实现类:BaseExecutor、SimpleExecutor、BatchExecutor、ReuseExecutor 以及CachingExecutor。看名称就知道BatchExecutor是进行批处理操作的执行器,CachingExecutor是带缓存的执行器,SimpleExecutor是真正干活的执行器,那些都是在其基础上进行了封装。mybatis在没有配置的情况下默认走的CachingExecutor。
看一下CachingExecutor属性,直接断点的截图,可以看到其有两个属性,其中一个执行器用了简单的执行器SimpleExecutor,还有事务的管理器属性。
执行查询操作:
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = this.createCacheKey(ms, parameterObject, rowBounds, boundSql);
return this.query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
其中boundSql是从MappedStatement获取的SQL信息,包含SQL语句以及参数等信息。CacheKey是缓存的key,这个要看一下,通过缓存的key就可以知道mybatis中缓存的内容是什么。这个缓存key又是从BaseExecutor中的createCacheKey()方法构建出来的。
public CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql) {
if (this.closed) {
throw new ExecutorException("Executor was closed.");
} else {
CacheKey cacheKey = new CacheKey();
//设置ID,其实就是指定的方法com.mayikt.mapper.UserMapper.getUser
cacheKey.update(ms.getId());
//查询的分页偏移量
cacheKey.update(rowBounds.getOffset());
cacheKey.update(rowBounds.getLimit());
//执行的SQL语句
cacheKey.update(boundSql.getSql());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
TypeHandlerRegistry typeHandlerRegistry = ms.getConfiguration().getTypeHandlerRegistry();
//下面又对方法的参数进行了记录
for(int i = 0; i < parameterMappings.size(); ++i) {
ParameterMapping parameterMapping = (ParameterMapping)parameterMappings.get(i);
...
cacheKey.update(value);
}
...
return cacheKey;
}
}
比较简单咱就不看类的源码了,直接断点看一下存放的内容,跟上面的分析结果一样,方法、参数、环境。
然后再进行查询,查询先到缓存中根据缓存的key去查找,最终存放在了PerpetualCache类中,里面有个map的缓存记录,key就是缓存的key,value是对应key查询出来的结果为了演示再写一个重复查询看一下断点,源码比较简单就不往上贴了,看一下结果其实第二次执行的时候直接从缓存里面取出了结果返回的。
再往里执行其实又到了SimpleExecutor去执行,到了这里其实很多人都已经不陌生了,再往下就是JDBC的操作了。
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
List var9;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this.wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = this.prepareStatement(handler, ms.getStatementLog());
var9 = handler.query(stmt, resultHandler);
} finally {
this.closeStatement(stmt);
}
return var9;
}