Mybatis源码分析之Select返回数据分析
Posted 叶长风
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mybatis源码分析之Select返回数据分析相关的知识,希望对你有一定的参考价值。
Mybatis源码分析之Select返回数据分析
在之前的一篇文章中分析了@Select注解的使用方法,在查询方法中我们知可以返回Map类型,也可以返回指针,或者是list集合,或是单条记录,今天就对这几种返回做一个源码分析。
Select查询
在这里就不需要再写一个demo演示了,用法无非这么多,直接看查询的源码吧。
首先在看对应select的查询源码时先看下MyBatis是怎么定义method的返回类型的,这里在将method包装成MappedMethod时可以得到对应返回类型。
//返回的MapperProxy代理对象中包装method
final MapperMethod mapperMethod = cachedMapperMethod(method);
private MapperMethod cachedMapperMethod(Method method)
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null)
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
return mapperMethod;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config)
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method)
Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);
if (resolvedReturnType instanceof Class<?>)
this.returnType = (Class<?>) resolvedReturnType;
else if (resolvedReturnType instanceof ParameterizedType)
this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();
else
this.returnType = method.getReturnType();
this.returnsVoid = void.class.equals(this.returnType);
this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();
this.returnsCursor = Cursor.class.equals(this.returnType);
this.mapKey = getMapKey(method);
this.returnsMap = this.mapKey != null;
this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);
this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);
this.paramNameResolver = new ParamNameResolver(configuration, method);
可以看到,在MethodSignature方法中对各种返回值类型进行了判断,因此有了下面的SELECT方法个对返回类型的判断从而使用不同SELECT逻辑。
case SELECT:
if (method.returnsVoid() && method.hasResultHandler())
executeWithResultHandler(sqlSession, args);
result = null;
else if (method.returnsMany())
result = executeForMany(sqlSession, args);
else if (method.returnsMap())
result = executeForMap(sqlSession, args);
else if (method.returnsCursor())
result = executeForCursor(sqlSession, args);
else
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
就是以上五种情况,第一种返回void,这种还是比较少见的,不做分析。
第二种条件,如果是返回多条数据的,就是返回一个List列表,根据之前的源码分析,executeForMany和selectOne最终调用方法应该是一致的,应该从前文中分析得知selectOne最终还是调用selectList方法,只是对返回结果进行了判断,size大于1时抛出错误。
先转到executeForMany方法,如下:
private <E> Object executeForMany(SqlSession sqlSession, Object[] args)
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds())
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
else
result = sqlSession.<E>selectList(command.getName(), param);
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass()))
if (method.getReturnType().isArray())
return convertToArray(result);
else
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
return result;
和预想的差不多,调用selectList得到返回结果,但是在返回时对具体返回类型进行了判断,针对返回类型是数组还是集合对查询结果做了不同的包装。对convertToArray和方法convertToDeclaredCollection没必要再去看实现,无非就是List集合转Array或者直接返回之类的。
现在看第二种返回类型,返回类型为Map的,先做个猜测吧,有可能也是调用的selectList方法,然后将返回值放在map中,转到executeForMap方法看看具体吧。
private <K, V> Map<K, V> executeForMap(SqlSession sqlSession, Object[] args)
Map<K, V> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds())
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey(), rowBounds);
else
result = sqlSession.<K, V>selectMap(command.getName(), param, method.getMapKey());
return result;
session调用的是selectMap方法,和预想中还是有一些不同的,有些意思,继续转到selectMap方法中。
@Override
public <K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds)
final List<? extends V> list = selectList(statement, parameter, rowBounds);
final DefaultMapResultHandler<K, V> mapResultHandler = new DefaultMapResultHandler<K, V>(mapKey,
configuration.getObjectFactory(), configuration.getObjectWrapperFactory(), configuration.getReflectorFactory());
final DefaultResultContext<V> context = new DefaultResultContext<V>();
for (V o : list)
context.nextResultObject(o);
mapResultHandler.handleResult(context);
return mapResultHandler.getMappedResults();
底层还是调用的selectList方法,倒也是没怎么出乎意料之外,基本上其他查询方法都能用selectList包装出来,在查询出结果后,再对结果进行包装,最后返回map,不过这里最开始还是有一个疑惑,就是这里查询出来的list是单条记录的kv的集合还是多条数据库中查询出来的记录,后来我做了个测试,在mapper中加一个返回map的方法,然后再用session调用一下,具体演示源码就不展示了,然后发现如果直接定义返回类型是Map集合的时候仍然调用的是SELECT中的最后一种情况,就是返回单条记录,然后我又把上面的查询map的条件再看了一遍,在MethodSignature方法中,判断当前方法的返回值是否是map类型,还增加了如下判断:
this.mapKey = getMapKey(method);
this.returnsMap = this.mapKey != null;
private String getMapKey(Method method)
String mapKey = null;
if (Map.class.isAssignableFrom(method.getReturnType()))
final MapKey mapKeyAnnotation = method.getAnnotation(MapKey.class);
if (mapKeyAnnotation != null)
mapKey = mapKeyAnnotation.value();
return mapKey;
这里加了一个对方法上是否加了MapKey注解的方法判断,如果有MapKey则认为当前值返回Map类型,如果不是则按返回多条或者单条来处理,这里倒是挺有意思,以后找个时间把返回Map和加上MapKey的效果一起写一篇文章出来。
对返回Map类型分析就到此为止了,我们继续看返回类型为指针的。
else if (method.returnsCursor())
result = executeForCursor(sqlSession, args);
要说返回类型为指针,最早见到使用指针移到返回一条条记录的,还是在C语言里面看到过一回,然后后来写python的时候,使用pymysql的时候,其他不多说,转到executeForCursor方法。
private <T> Cursor<T> executeForCursor(SqlSession sqlSession, Object[] args)
Cursor<T> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds())
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<T>selectCursor(command.getName(), param, rowBounds);
else
result = sqlSession.<T>selectCursor(command.getName(), param);
return result;
底层调用的仍然是sqlSession中的selectCursor方法,继续追溯。
@Override
public <T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds)
try
MappedStatement ms = configuration.getMappedStatement(statement);
Cursor<T> cursor = executor.queryCursor(ms, wrapCollection(parameter), rowBounds);
registerCursor(cursor);
return cursor;
catch (Exception e)
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
finally
ErrorContext.instance().reset();
@Override
protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>queryCursor(stmt);
@Override
public <E> Cursor<E> queryCursor(Statement statement) throws SQLException
String sql = boundSql.getSql();
statement.execute(sql);
return resultSetHandler.<E>handleCursorResultSets(statement);
这里挺有意思,最终到StatementHandler还是调用了execute方法,然后对当前statement对象做了一层包装,可以继续看看handleCursorResultSets。
public <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException
ErrorContext.instance().activity("handling cursor results").object(mappedStatement.getId());
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
if (resultMapCount != 1)
throw new ExecutorException("Cursor results cannot be mapped to multiple resultMaps");
ResultMap resultMap = resultMaps.get(0);
return new DefaultCursor<E>(this, resultMap, rsw, rowBounds);
这个方法做的事情就是继续包装statement对象,获取到statement返回的第一条结果,然后将resultMap、包装对象rsw、rowBounds包装为DefaultCursor对象返回。这里有空以后继续深究一下。
最后一种SELECT其实就没有太多可以分析的,前面文章中几乎都是对selectOne查看的源码。
对Select几种查询的分析就到这了,不过对于MapKey和Cursor这几种以后有空可以继续探索一下,还是挺有意思的。
以上是关于Mybatis源码分析之Select返回数据分析的主要内容,如果未能解决你的问题,请参考以下文章