前言
在上一篇文章深入浅出Mybatis源码解析——SqlSource的创建流程中,说了SqlSource的创建流程这样一个完整的创建流程,在这个流程中包含了:动态SQL标签处理器、解析动态SQL、创建MappedStatement对象。这样给我们对SQLSource这样的整个流程有了一个大概的了解。
而这个过程的作用其实就是把Mapper文件中的SQL语句进行相关的解析和封装,一遍后续的执行,那今天就来说说SQL执行的第一步,SqlSession的执行流程,这个过程的代码看似很简单,但是却涉及大量的类。说到这里那就开始吧!
一、获取Mapper代理对象流程
在没说SQLSession的执行流程之前,我们先来看看,关于Mapper代理对象这样的一个获取的过程,这个过长还是很简单的,来先找出它的入口:DefaultSqlSession#getMapper 。代码如下:
@Override
public <T> T getMapper(Class<T> type) {
// 从Configuration对象中,根据Mapper接口,获取Mapper代理对象
return configuration.<T>getMapper(type, this);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 根据Mapper接口的类型,从Map集合中获取Mapper代理对象工厂
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 通过MapperProxyFactory生产MapperProxy,通过MapperProxy产生Mapper代理对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
上面的代码中,核心部分就在最后的那部分:getMapper(Class<T> type, SqlSession sqlSession),在这部分中大体就是先通过knownMappers来获得一个MapperProxyFactory对象,这是mapper的代理工厂,在获得这个工厂后,再由它来创建一个实例,就是Mapper代理对象。
二、SqlSession执行主流程
万剑归宗,还是要先找入口,没有入口,那就是连门儿都没有了,所以先看看入口代码如何。DefaultSqlSession#selectList()
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 根据传入的statementId,获取MappedStatement对象
MappedStatement ms = configuration.getMappedStatement(statement);
// 调用执行器的查询方法
// RowBounds是用来逻辑分页(按照条件将数据从数据库查询到内存中,在内存中进行分页)
// wrapCollection(parameter)是用来装饰集合或者数组参数
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
2.1获取MappedStatement对象
从上面代码中,我们看到MappedStatement对象的获取就只有一行代码,不过如果细看,会发现这个步骤的代码还是相当复杂,那就系好安全带好好准备观赏了。
// 1.
public MappedStatement getMappedStatement(String id) {
return this.getMappedStatement(id, true);
}
// 2.
public MappedStatement getMappedStatement(String id, boolean validateIncompleteStatements) {
if (validateIncompleteStatements) {
buildAllStatements();
}
return mappedStatements.get(id);
}
// 3.
/*
* Parses all the unprocessed statement nodes in the cache. It is recommended to
* call this method once all the mappers are added as it provides fail-fast
* statement validation.
* 解析缓存中所有未处理的语句节点。建议在添加所有映射器后调用此方法,因为它提供了快速失败的声明验证。
*/
protected void buildAllStatements() {
// 处理configurationElement中解析失败的<resultMap>节点
parsePendingResultMaps();
if (!incompleteCacheRefs.isEmpty()) {
// 用同步锁来保证线程安全
synchronized (incompleteCacheRefs) {
// 处理不完整的缓存引用
incompleteCacheRefs.removeIf(x -> x.resolveCacheRef() != null);
}
}
if (!incompleteStatements.isEmpty()) {
synchronized (incompleteStatements) {
// 处理incompleteStatements
incompleteStatements.removeIf(x -> {
x.parseStatementNode();
return true;
});
}
}
if (!incompleteMethods.isEmpty()) {
// 处理incompleteMethods
synchronized (incompleteMethods) {
incompleteMethods.removeIf(x -> {
x.resolve();
return true;
});
}
}
}
上面代码中的第一步、第二步很简单,第三步中相对复杂些,从源代码注释中可以得出,这里主要是为了快速失败的处理,对于parsePendingResultMaps()的代码这里就不贴了,它里面只是简单的对incompleteResultMaps进行了迭代,在迭代的同时将其remove掉。
我们还是来看看三个带同步锁的代码中的处理方法(当然parsePendingResultMaps方法中也是有同步锁的),先看第一个resolveCacheRef()方法:
public Cache resolveCacheRef() {
return assistant.useCacheRef(cacheRefNamespace);
}
public Cache useCacheRef(String namespace) {
if (namespace == null) {
throw new BuilderException("cache-ref element requires a namespace attribute.");
}
try {
unresolvedCacheRef = true;
Cache cache = configuration.getCache(namespace);
if (cache == null) {
throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.");
}
currentCache = cache;
unresolvedCacheRef = false;
return cache;
} catch (IllegalArgumentException e) {
throw new IncompleteElementException("No cache for namespace '" + namespace + "' could be found.", e);
}
}
上面的代码逻辑很简单,只是单纯的通过namespace拿到cache,如果cache等于null,则直接抛出异常,否则继续处理。那继续看看parseStatementNode,代码如下:
/**
* 解析<select>\<insert>\<update>\<delete>子标签
*/
public void parseStatementNode() {
// 获取statement的id属性(特别关键的值)
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
Integer fetchSize = context.getIntAttribute("fetchSize");
Integer timeout = context.getIntAttribute("timeout");
String parameterMap = context.getStringAttribute("parameterMap");
// 获取入参类型
String parameterType = context.getStringAttribute("parameterType");
// 别名处理,获取入参对应的Java类型
Class<?> parameterTypeClass = resolveClass(parameterType);
// 获取ResultMap
String resultMap = context.getStringAttribute("resultMap");
// 获取结果映射类型
String resultType = context.getStringAttribute("resultType");
String lang = context.getStringAttribute("lang");
LanguageDriver langDriver = getLanguageDriver(lang);
// 别名处理,获取返回值对应的Java类型
Class<?> resultTypeClass = resolveClass(resultType);
String resultSetType = context.getStringAttribute("resultSetType");
// 设置默认StatementType为Prepared,该参数指定了后面的JDBC处理时,采用哪种Statement
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
String nodeName = context.getNode().getNodeName();
// 解析SQL命令类型是什么?确定操作是CRUD中的哪一种
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
//是否查询语句
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
// <include>标签解析
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
// 解析<selectKey>标签
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)
// 创建SqlSource,解析SQL,封装SQL语句(未参数绑定)和入参信息
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
String keyProperty = context.getStringAttribute("keyProperty");
String keyColumn = context.getStringAttribute("keyColumn");
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
}
// 通过构建者助手,创建MappedStatement对象
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
看到这段代码是不是很惊讶,是不是很惊喜,这不是前面调用的嘛,这个可能是mybatis作者有所其他考虑的,既然代码是前面看到过的,那这里就不在说了,我们继续看最后一个:
void parseStatement(Method method) {
// 获取Mapper接口的形参类型
Class<?> parameterTypeClass = getParameterType(method);
// 解析Lang注解
LanguageDriver languageDriver = getLanguageDriver(method);
//
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
if (sqlSource != null) {
Options options = method.getAnnotation(Options.class);
// 组装mappedStatementId
final String mappedStatementId = type.getName() + "." + method.getName();
Integer fetchSize = null;
Integer timeout = null;
StatementType statementType = StatementType.PREPARED;
ResultSetType resultSetType = null;
// 获取该mapper接口中的方法是CRUD操作的哪一种
SqlCommandType sqlCommandType = getSqlCommandType(method);
// 是否是SELECT操作
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = !isSelect;
boolean useCache = isSelect;
// 主键生成器,用于主键返回
KeyGenerator keyGenerator;
String keyProperty = null;
String keyColumn = null;
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
// first check for SelectKey annotation - that overrides everything else
SelectKey selectKey = method.getAnnotation(SelectKey.class);
if (selectKey != null) {
keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
keyProperty = selectKey.keyProperty();
} else if (options == null) {
keyGenerator = configuration.isUseGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
} else {
keyGenerator = options.useGeneratedKeys() ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
keyProperty = options.keyProperty();
keyColumn = options.keyColumn();
}
} else {
keyGenerator = NoKeyGenerator.INSTANCE;
}
if (options != null) {
if (FlushCachePolicy.TRUE.equals(options.flushCache())) {
flushCache = true;
} else if (FlushCachePolicy.FALSE.equals(options.flushCache())) {
flushCache = false;
}
useCache = options.useCache();
fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
timeout = options.timeout() > -1 ? options.timeout() : null;
statementType = options.statementType();
resultSetType = options.resultSetType();
}
// 处理ResultMap注解
String resultMapId = null;
ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
if (resultMapAnnotation != null) {
String[] resultMaps = resultMapAnnotation.value();
StringBuilder sb = new StringBuilder();
for (String resultMap : resultMaps) {
if (sb.length() > 0) {
sb.append(",");
}
sb.append(resultMap);
}
resultMapId = sb.toString();
} else if (isSelect) {
resultMapId = parseResultMap(method);
}
// 通过Mapper构建助手,创建一个MappedStatement对象,封装信息
assistant.addMappedStatement(
mappedStatementId,
sqlSource,
statementType,
sqlCommandType,
fetchSize,
timeout,
// ParameterMapID
null,
parameterTypeClass,
resultMapId,
getReturnType(method),
resultSetType,
flushCache,
useCache,
// TODO gcode issue #577
false,
keyGenerator,
keyProperty,
keyColumn,
// DatabaseID
null,
languageDriver,
// ResultSets
options != null ? nullOrEmpty(options.resultSets()) : null);
}
}
看到这段代码是不是还是很惊喜,惊喜的原因是代码看上去很长,从这段长长的代码中可以得知,它做了一些获取Mapper接口的形参类型、解析Lang注解、组装mappedStatementId、获取该mapper接口中的方法是CRUD操作类型、主键生成、处理ResultMap注解、通过Mapper构建助手,创建一个MappedStatement对象,封装信息等操作,我们还惊喜的发现这里面有一个issue,不知道有没有修复。
说到这里,貌似MappedStatement对象的获取已经结束了,是不是有点晕,当初的一个方法,却带出这么多代码。
2.2.wrapCollection(parameter)装饰集合或者数组参数
/**
* 将Collection或者Array类型的参数,放入Map集合,并设置key的值
*
* @param object
* @return
*/
private Object wrapCollection(final Object object) {
// Collection集合类型参数
if (object instanceof Collection) {
StrictMap<Object> map = new StrictMap<>();
// key为collection,value为集合参数
map.put("collection", object);
if (object instanceof List) {
// key为list,value为集合参数
map.put("list", object);
}
return map;
} else if (object != null && object.getClass().isArray()) {
StrictMap<Object> map = new StrictMap<>();
// key为array,value为数组数
map.put("array", object);
return map;
}
return object;
}
这个步骤还是相对简单,就是将Collection或者Array类型的参数,放入Map集合,并设置key的值而已,最后返回。
2.3.调用执行器的查询方法
在点这个方法的时候,它会弹出两个实现类,一个是CachingExecutor,一个是BaseExecutor,因为考虑到性能问题,会先到缓存中查询缓存中是否有数据,没有才会查询数据库。那就先看看CachingExecutor类中的实现:
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 获取绑定的SQL语句,比如“SELECT * FROM user WHERE id = ? ”
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 生成缓存Key
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
上面代码很简单,就是先获得boundSQL,然后创建缓存,最后做执行查询,说到这里今天的文章也快结束了,关于getBoundSql和query这些,留待下篇文章详解。有人可能会说BaseExecutor的query没说,我想说这两个方法基本一致,不知道作者为何如此写,应该是考虑到他们的查询有区别,那最后就来个总结啦!
总结
- DefaultSqlSession
- Executor
- CachingExecutor
- BaseExecutor
- SimpleExecutor (此处为涉及)
- StatementHandler
- RoutingStatementHandler SimpleExecutor (此处为涉及)
- PreparedStatementHandler
- ResultSetHandler
- DefaultResultSetHandler
- |DefaultSqlSession#getMapper:获取Mapper代理对象
- Configuration#getMapper:获取Mapper代理对象
- MapperRegistry#getMapper:通过代理对象工厂,获取代理对象
- MapperProxyFactory#newInstance:调用JDK的动态代理方式,创建Mapper代理
- MapperRegistry#getMapper:通过代理对象工厂,获取代理对象
- Configuration#getMapper:获取Mapper代理对象
- DefaultSqlSession#selectLIst
- CachingExecutor#query
- BaseExecutor#query
- BaseExecutor#queryFromDatabase
- SimpleExecutor#doQuery
- Configuration#newStatementHandler:创建StatementHandler,用来执行MappedStatement对象
- RoutingStatementHandler#构造方法:根据路由规则,设置不同的StatementHandlerSimpleExecutor (此处为涉及)
- SimpleExecutor#prepareStatement:主要是设置PreparedStatement的参数SimpleExecutor (此处为涉及)
- SimpleExecutor#getConnection:获取数据库连接(此处为涉及)
- PreparedStatementHandler#prepare:创建PreparedStatement对象(此处为涉及)
- PreparedStatementHandler#parameterize:设置PreparedStatement的参数(此处为涉及)
- PreparedStatementHandler#query:主要是用来执行SQL语句,及处理结果集
- PreparedStatement#execute:调用JDBC的api执行
- StatementDefaultResultSetHandler#handleResultSets:处理结果集
- Configuration#newStatementHandler:创建StatementHandler,用来执行MappedStatement对象
- SimpleExecutor#doQuery
- BaseExecutor#queryFromDatabase
- BaseExecutor#query
- CachingExecutor#query