经过sql参数解析、sql动态组装和执行sql,相对而言,结果集的封装,是mybatis数据处理的最后一环。这里只对查询结果而言,因为更新语句一般都是返回影响的行数。抛开mybatis,如果让我们组装结果,我们该如何进行呢?mybatis的查询结果统一表示为:
List<E>
即使是查询单个对象,它的查询结果还是封装成 List 对象,然后返回list集合的第一个元素。
个人根据mybatis的源码,将mybatis对结果集的封装,分成两步:
(1)通过反射,创建结果对象,其所有属性为默认值,例如,如果结果是实体对象,那么将通过无参构造函数创建对象,其所有属性一般为空,如果结果是List,则会创建一个空的List
(2)为结果对象的属性赋值,这里也是通过反射,找到set方法赋值
下面开始进入主题:
一、数据准备
- 查询sql以及自定义映射的resultMap:
<resultMap id="userMap" type="User"><id property="id" column="id"></id><result property="username" column="username"></result><result property="password" column="password"></result><result property="isValid" column="is_valid"></result><collection property="blogs" javaType="java.util.List" ofType="Blog"><id property="id" column="blog_id"></id><result property="title" column="title"></result><result property="userId" column="user_id"></result></collection></resultMap><select id="getUserAndBlogByUserId" parameterType="string" resultMap="userMap">select u.id,u.username,u.password,u.is_valid,b.id as blog_id,b.title,b.user_idfrom t_user u LEFT JOIN t_blog b ON u.id = b.user_idwhere u.id=#{id}</select>
从查询sql,你也可以发现一二,比如博客表t_blog中,含有一个逻辑外键user_id,表示该博客属于哪个用户的,而每个用户可以拥有多个博客,显然是一对多的关系,而查询条件则为用户id。
2.实体类
public class User implements Serializable{private String id;private String username;private String password;private Integer isValid;//一个用户,对应多篇博客private List<Blog> blogs;
}
public class Blog implements Serializable{private String id;private String title;private String userId;
}
3.mapper接口
public interface UserMapper {//根据id查询用户及其所有博客User getUserAndBlogByUserId(String id);
}
4.测试方法
public class One2ManyQuery {public static void main(String[] args) throws IOException {//读取配置信息InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");//根据配置信息,创建SqlSession工厂SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);//SqlSession工厂创建SqlSessionSqlSession sqlSession = factory.openSession();//获取接口的代理对象UserMapper mapper = sqlSession.getMapper(UserMapper.class);User user = mapper.getUserAndBlogByUserId("123");System.out.println(user);}
}
二、结果对象的创建
本次程序的起点是PreparedStatementHandler的方法,它是查询的结束,同时也是结果封装的开始入口:
@Overridepublic <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;ps.execute();return resultSetHandler.handleResultSets(ps);}
由此,进入到 resultSetHandler.handleResultSets(ps) 方法,而默认会进入到的DefaultResultSetHandler的handleResultSets方法:
@Overridepublic 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对象,// ResultSetWrapper对象含有已映射和未映射的列名和属性的对应关系ResultSetWrapper rsw = getFirstResultSet(stmt);// 获取所有的ResultMapList<ResultMap> resultMaps = mappedStatement.getResultMaps();// ResultMap的个数int resultMapCount = resultMaps.size();// 校验:如果结果集有数据,但是没有定义返回的结果类型,就会报错validateResultMapsCount(rsw, resultMapCount);while (rsw != null && resultMapCount > resultSetCount) {// 依次获取ResultMapResultMap resultMap = resultMaps.get(resultSetCount);// 处理结果集,这里是重点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);}
那么,我们重点关注一下这句,因为它才是真正的处理结果集:
// 处理结果集,这里是重点handleResultSet(rsw, resultMap, multipleResults, null);
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults, ResultMapping parentMapping) throws SQLException {try {if (parentMapping != null) {handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);} else {if (resultHandler == null) {// 如果结果处理器为空,则使用默认的结果处理器,没有自定义的情况下,都是走这个流程DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);// 处理每一行的值handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);// 将处理结果放到list集中multipleResults.add(defaultResultHandler.getResultList());} else {handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);}}} finally {// issue #228 (close resultsets)closeResultSet(rsw.getResultSet());}}
根据上面的代码,我们关注这一句代码是如何处理的:
// 处理每一行的值handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
查看详细的 handleRowValues 方法:
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {// 如果有嵌套的ResultMapif (resultMap.hasNestedResultMaps()) {ensureNoRowBounds();checkResultHandler();// 处理含有嵌套ResultMap的结果handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);} else {// 处理不含有嵌套ResultMap的结果handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);}}
由于我们使用了 collection 标签做一对多的映射,所以是属于嵌套的resultMap查询,个人理解,即使是一个实体对象,它也是一个resultMap,只不过它的resultType是实体对象罢了,所以走的嵌套分支:
// 处理含有嵌套ResultMap的结果
handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
这个方法比较长:
private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {final DefaultResultContext<Object> resultContext = new DefaultResultContext<>();ResultSet resultSet = rsw.getResultSet();// 跳过已处理的行skipRows(resultSet, rowBounds);Object rowValue = previousRowValue;while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);Object partialObject = nestedResultObjects.get(rowKey);// issue #577 && #542if (mappedStatement.isResultOrdered()) {if (partialObject == null && rowValue != null) {nestedResultObjects.clear();storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);}rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);} else {// 看这行代码,获取行值rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);if (partialObject == null) {storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);}}}if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) {storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);previousRowValue = null;} else if (rowValue != null) {previousRowValue = rowValue;}}
我们找重点看吧:
// 看这行代码,获取行值rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
这其中 getRowValue 方法,我认为是比较重要的方法,处理逻辑大部分都在这里:
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, String columnPrefix, Object partialObject) throws SQLException {// resultMap的唯一标志idfinal String resultMapId = resultMap.getId();// 返回值Object rowValue = partialObject;if (rowValue != null) {final MetaObject metaObject = configuration.newMetaObject(rowValue);putAncestor(rowValue, resultMapId);applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false);ancestorObjects.remove(resultMapId);} else {final ResultLoaderMap lazyLoader = new ResultLoaderMap();// 创建结果对象rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);// 行值不为空,并且结果对象有类型处理器if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {// 将行值包装成元数据对象MetaObjectfinal MetaObject metaObject = configuration.newMetaObject(rowValue);boolean foundValues = this.useConstructorMappings;// 是否使用自动映射,因为我们自定义了resultMap,所以不会走这个if (shouldApplyAutomaticMappings(resultMap, true)) {foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;}// 给对象的属性赋值foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;putAncestor(rowValue, resultMapId);// 给嵌套的结果属性赋值foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues;ancestorObjects.remove(resultMapId);foundValues = lazyLoader.size() > 0 || foundValues;rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;}if (combinedKey != CacheKey.NULL_CACHE_KEY) {nestedResultObjects.put(combinedKey, rowValue);}}return rowValue;}
结果对象的创建,就在这里:
// 创建结果对象rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
详细如下:
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {this.useConstructorMappings = false; // reset previous mapping resultfinal List<Class<?>> constructorArgTypes = new ArrayList<>();final List<Object> constructorArgs = new ArrayList<>();Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();for (ResultMapping propertyMapping : propertyMappings) {// issue gcode #109 && issue #149if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);break;}}}this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping resultreturn resultObject;}
你会发现这句,最终也是通过反射创建对象的:
resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs);
三、属性赋值
回到上一步的 DefaultResultSetHandler的getRowValue 方法
通过代码发现,这里的逻辑关系,大概是这样的,sql中的列名 ->通过映射关系,拿到对应的属性名 ->通过属性名,获取set的方法名,最后,通过反射设置值
1.普通属性赋值
// 给对象的属性赋值
foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix)throws SQLException {// 已经映射的列名集合final List<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);boolean foundValues = false;// 所有属性和列的映射规则final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();for (ResultMapping propertyMapping : propertyMappings) {// 列名String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);if (propertyMapping.getNestedResultMapId() != null) {// the user added a column attribute to a nested result map, ignore itcolumn = null;}if (propertyMapping.isCompositeResult()|| (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)))|| propertyMapping.getResultSet() != null) {// 根据列名,获取属性值Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix);// issue #541 make property optional// 属性名final String property = propertyMapping.getProperty();if (property == null) {continue;} else if (value == DEFERRED) {foundValues = true;continue;}if (value != null) {foundValues = true;}if (value != null || (configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive())) {// gcode issue #377, call setter on nulls (value is not 'found')// 给对象的属性设置值metaObject.setValue(property, value);}}}return foundValues;}
这里也就是分3步:
(1)根据列名获取值,在resultSet中,根据列名获取值,这个是很容易拿到的
(2)获取属性名,在我们定义的映射规则中就包含了属性名,这个也可以拿到
(3)给对象的属性设置值,这里还是通过反射技术,调用相应的set方法来设值
2.集合对象的属性的设置
回到 DefaultResultSetHandler的getRowValue 方法
// 给嵌套的结果属性赋值
foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues;
private boolean applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String parentPrefix, CacheKey parentRowKey, boolean newObject) {boolean foundValues = false;for (ResultMapping resultMapping : resultMap.getPropertyResultMappings()) {final String nestedResultMapId = resultMapping.getNestedResultMapId();if (nestedResultMapId != null && resultMapping.getResultSet() == null) {try {final String columnPrefix = getColumnPrefix(parentPrefix, resultMapping);final ResultMap nestedResultMap = getNestedResultMap(rsw.getResultSet(), nestedResultMapId, columnPrefix);if (resultMapping.getColumnPrefix() == null) {// try to fill circular reference only when columnPrefix// is not specified for the nested result map (issue #215)Object ancestorObject = ancestorObjects.get(nestedResultMapId);if (ancestorObject != null) {if (newObject) {linkObjects(metaObject, resultMapping, ancestorObject); // issue #385}continue;}}final CacheKey rowKey = createRowKey(nestedResultMap, rsw, columnPrefix);final CacheKey combinedKey = combineKeys(rowKey, parentRowKey);Object rowValue = nestedResultObjects.get(combinedKey);boolean knownValue = rowValue != null;instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject); // mandatoryif (anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw)) {rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue);if (rowValue != null && !knownValue) {linkObjects(metaObject, resultMapping, rowValue);foundValues = true;}}} catch (SQLException e) {throw new ExecutorException("Error getting nested result map values for '" + resultMapping.getProperty() + "'. Cause: " + e, e);}}}return foundValues;}
我们着重看这句:
// 实例化集合属性
instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
这一步,便会处理集合属性:
private Object instantiateCollectionPropertyIfAppropriate(ResultMapping resultMapping, MetaObject metaObject) {final String propertyName = resultMapping.getProperty();Object propertyValue = metaObject.getValue(propertyName);if (propertyValue == null) {Class<?> type = resultMapping.getJavaType();if (type == null) {type = metaObject.getSetterType(propertyName);}try {//如果是集合if (objectFactory.isCollection(type)) {// 创建一个空集合propertyValue = objectFactory.create(type);// 设置集合属性metaObject.setValue(propertyName, propertyValue);// 返回集合属性的引用return propertyValue;}} catch (Exception e) {throw new ExecutorException("Error instantiating collection property for result '" + resultMapping.getProperty() + "'. Cause: " + e, e);}} else if (objectFactory.isCollection(propertyValue.getClass())) {return propertyValue;}return null;}
那么list集合里面的元素又是在什么时候放进去的呢?这是在DefaultResultSetHandler的applyNestedResultMappings中拿到
行值rowValue后,才进行添加的。
DefaultResultSetHandler的applyNestedResultMappings的部分代码:
boolean knownValue = rowValue != null;instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject); // mandatoryif (anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw)) {rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue);if (rowValue != null && !knownValue) {linkObjects(metaObject, resultMapping, rowValue);foundValues = true;}}
linkObjects方法:
private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue) {final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);if (collectionProperty != null) {final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty);// 集合添加元素targetMetaObject.add(rowValue);} else {metaObject.setValue(resultMapping.getProperty(), rowValue);}}
这样就完成了属性的赋值
控制台的l输出,可以参考一下:
MyBatis 是怎么把查询出来的结果集映射到实体类(JavaBean)上的,原理解释
MyBatis 是怎么把查询出来的结果集映射到实体类(JavaBean)上的,原理解释:
相信有很多开发者对MyBatis并不陌生,但谈起MyBatis底层到底是怎么工作的,没个两三年的工程师并非能完全掌握到手,读完以下这篇文章,相信你很快能理解MyBatis是怎么通过JDBC的封装,得到结果集并将结果集映射到JavaBean实体上的一个过程,在这里就不唠叨套多,先来了解下原理。
Mybatis3源码分析(16)-Sql解析执行-结果集映射(ResultSetHandler)
MyBatis查询结果集映射到JavaBean原理浅谈