# MyBatis源码-mapper解析
这一节我们来看看 MyBatis 如何读取配置文件,通过 mapper 文件解析查询结果
# 示例代码
SqlSessionFactory sqlSessionFactory =
new SqlSessionFactoryBuilder().build(inputStream);
// 第二阶段:数据读写阶段
try (SqlSession session = sqlSessionFactory.openSession()) {
// 找到接口对应的实现
UserMapper userMapper = session.getMapper(UserMapper.class);
// 组建查询参数
User userParam = new User();
userParam.setSchoolName("Sunny School");
// 调用接口展开数据库操作
List<User> userList = userMapper.queryUserBySchoolName(userParam);
// 打印查询结果
for (User user : userList) {
System.out.println("name : " + user.getName() + " ; email : " + user.getEmail());
}
}
# 大概流程:
# 获取 mapper 代理
我们从示例代码一步一步来分析,首先看获取 mapper 对象:
UserMapper userMapper = session.getMapper(UserMapper.class);
上面代码通过 SqlSession
获取 mapper 返回的是一个代理对象,通过这个代理对象调用相应方法时才能把查询结果映射到实体类。
源码如下:
//MapperRegistry类
//从这个方法里边获取 mapper 代理对象
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//knownMappers 是一个map Map<Class<?>, MapperProxyFactory<?>> knownMappers
//根据对象类型获取代理工厂 这里基本上都能拿到结果,因为系统初始化的时候已经初始化了相应对象,具体看下边源码。
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
mapper 代理工厂 knownMappers
的值是在什么时候创建的呢?看下边的源码:
//MapperRegistry类
//通过这个方法添加 mapper 代理对象
//该方法 系统启动的时候就会调用
public <T> void addMapper(Class<T> type) {
//是否是接口
if (type.isInterface()) {
//是否已经创建了改 mapper 对象
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
//通过注解的方式解析
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
下面对以上内容总结:
最重要的是 getMapper
拿到的是一个代理对象,这样在后续执行方法的时候会被代理对象拦截。
# 执行具体的方法
当拿到具体的 mapper 代理对象后,就要开始执行具体的方法了,比如:
List<User> userList = userMapper.queryUserBySchoolName(userParam);
调用 queryUserBySchoolName
方法,最终执行的就是 MapperProxy
代理类,代理会拦截方法去解析 xml 文件对应的 sql , 然后把结果处理完返回。
//MapperProxy 类
//调用mapper具体方法都会走到这里
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
//getDeclaringClass() 返回声明由此 Method 对象表示的方法的类的 Class 对象
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (method.isDefault()) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//先从缓存中获取方法信息
final MapperMethod mapperMethod = cachedMapperMethod(method);
//开始执行方法 args 为查询该方法的相关参数, execute 看下边源码。
return mapperMethod.execute(sqlSession, args);
}
// MapperMethod 类
// 这个方法具体执行对应的增、删、改、查
// 方法具体做了,根据参数和方法信息查询并组装sql,执行sql把结果设置到resultMap 对象中。
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
//返回受影响行数
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
//返回受影响行数
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
//返回受影响行数
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
//没有返回结果
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
//返回结果是多个
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
//返回结果是 map
} 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);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
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;
}
以下面的示例代码, 返回的是一个 list 结果集,自然走到 executeForMany
这个方法:
List<User> userList = userMapper.queryUserBySchoolName(userParam);
// MapperMethod 类
// 查询结果是列表形式
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
//查询参数是否包含分页参数 RowBounds
if (method.hasRowBounds()) {
//如果包含分页参数,解析对象
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
//这个方法是处理没有包含分页参数的,但调用的方法和分页参数都是一个,只不过后面会传递一个默认的分页参数 RowBounds.DEFAULT
result = sqlSession.selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
//mapper 定义的返回结果类型和查询类型不符时处理
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
selectList
方法一路跟踪下来,走到 query
查询这里,这里具体操作数据库。
//PreparedStatementHandler 类
//真正执行查询方法
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
//一个代理对象,目的是增加了查询参数日志
PreparedStatement ps = (PreparedStatement) statement;
//开始执行
ps.execute();
//处理返回结果,该方法源码看下面。
return resultSetHandler.handleResultSets(ps);
}
下面对以上内容总结:
拿到 mapper
的代理对象,执行具体方法判断该方法是增、删、改、查哪一种,然后走不同分支处理。
系统在启动的时候,已经把 xml 文件里边的方法及对应的 sql 语句进行了解析,当执行具体方法是会从配置里边查找对应的 xml 方法,拿到执行语句及 resultMap 也就是返回对象。然后执行语句把结果映射到对象中。
# 解析查询结果
//DefaultResultSetHandler类
//解析查询结果
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
//获取第一个结果集内容
ResultSetWrapper rsw = getFirstResultSet(stmt);
//查询一共返回几个结果集
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
//结果集数量
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
//获取xml 方法所对应的返回结果类
ResultMap resultMap = resultMaps.get(resultSetCount);
//这一步就是把结果解析为对应的实体类!!! multipleResults 就是数据库查询数据处理为结果集
handleResultSet(rsw, resultMap, multipleResults, null);
//如果返回了多个结果集合 获取下一个
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
//通常解析是存储函数
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
以上代码说的是当我们拿到查询结果,获取结果集:
首先,判断返回结果集是否是多个,存储过程形式可能返回多个结果集;
第二步,校验该查询方法对应是否存在 resultType 对应内容;
第三步,把 ResultMap 及 ResultSet 传递给 handleResultSet 方法进行参数结果解析映射;
//DefaultResultSetHandler类
//处理结果
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {
try {
if (parentMapping != null) {
//处理 存储过程的 resultSets
handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
} else {
if (resultHandler == null) {
//使用默认结果处理器
DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
//见下面详细源码
handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
//处理完这个获取结果集并添加到multipleResults
multipleResults.add(defaultResultHandler.getResultList());
} else {
//用户自定义 resultHandler
handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
}
}
} finally {
// issue #228 (close resultsets)
closeResultSet(rsw.getResultSet());
}
}
上述代码是结果处理,主要是处理存储过程、自定义处理器、默认处理器这3种形式。
//DefaultResultSetHandler类
//处理结果
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
//是否是嵌套映射
if (resultMap.hasNestedResultMaps()) {
ensureNoRowBounds();
checkResultHandler();
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
} else {
//处理数据库查询的结果然后映射到resultMap上面,具体见下面源码
handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
}
}
//DefaultResultSetHandler类
//处理数据库查询的结果然后映射
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)
throws SQLException {
//存放结果上下文,每次存放最近一条记录
DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
ResultSet resultSet = rsw.getResultSet();
//分页 跳过相应行
skipRows(resultSet, rowBounds);
while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
//获取解析并映射的结果
Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
//保存结果
storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
}
}
//DefaultResultSetHandler类
//获取行具体结果
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
final ResultLoaderMap lazyLoader = new ResultLoaderMap();
//创建result返回对象
Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
//把返回对象转换为MetaObject 这里边包含了类的相关信息及反射
final MetaObject metaObject = configuration.newMetaObject(rowValue);
boolean foundValues = this.useConstructorMappings;
if (shouldApplyAutomaticMappings(resultMap, false)) {
foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
}
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
foundValues = lazyLoader.size() > 0 || foundValues;
rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
}
return rowValue;
}
上述代码还是在处理结果,比较重要的就是 getRowValue 方法。
第一步,根据 resultType 创建一个空对象;
第二步,把创建的对象通过 MetaObject 包装一下,MetaObject 会把对象字段及方法进行处理,然后暴露相关方法,可以理解为反射工具类;
第三步,通过 applyAutomaticMappings 方法进行字段赋值;
//DefaultResultSetHandler类
//这个方法是拿到数据库 ResultSet 然后再根据 resultMap 字段进行映射
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {
//创建一个对象里边字段属性的list ,包含字段名称 、字段类型
List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
boolean foundValues = false;
if (!autoMapping.isEmpty()) {
for (UnMappedColumnAutoMapping mapping : autoMapping) {
//这里就是通过字段名称获取到数据库查询出来的具体值了!!!
final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
if (value != null) {
foundValues = true;
}
if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) {
// gcode issue #377, call setter on nulls (value is not 'found')
//通过反射把值设置进去
metaObject.setValue(mapping.property, value);
}
}
}
return foundValues;
}
参考文章:
- MyBatis ResultSetHandler 结果集解析过程 (https://juejin.cn/post/7198821253037391909 )
- MyBatis handleResultSet 结果集解析过程 (https://juejin.cn/post/7199577622120202300 )