目录 映射结果入口 创建实体类对象 结果集映射 自动映射 映射result节点 关联查询与延迟加载 CglibProxyFactory JavassistProxyFactory 正文 上一篇文章我们已经将SQL发送到了数据库,并返回了ResultSet,接下来就是将结果集 ResultSet 自动映射成实体类对象。这样使用者就无需再手动操作结果集,并将数据填充到实体类对象中。这可大大降低开发的工作量,提高工作效率。 回到顶部 映射结果入口 我们来看看上次看源码的位置 复制代码 public List query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement)statement; //执行数据库SQL ps.execute(); //进行resultSet自动映射 return this.resultSetHandler.handleResultSets(ps); } 复制代码 结果集的处理入口方法是 handleResultSets 复制代码 public List handleResultSets(Statement stmt) throws SQLException { final List multipleResults = new ArrayList(); int resultSetCount = 0; //获取第一个ResultSet,通常只会有一个 ResultSetWrapper rsw = getFirstResultSet(stmt); //从配置中读取对应的ResultMap,通常也只会有一个,设置多个是通过逗号来分隔,我们平时有这样设置吗? List resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); while (rsw != null && resultMapCount > resultSetCount) { ResultMap resultMap = resultMaps.get(resultSetCount); // 处理结果集 handleResultSet(rsw, resultMap, multipleResults, null); rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } // 以下逻辑均与多结果集有关,就不分析了,代码省略 String[] resultSets = mappedStatement.getResultSets(); if (resultSets != null) {...} return collapseSingleResultList(multipleResults); } 复制代码 在实际运行过程中,通常情况下一个Sql语句只返回一个结果集,对多个结果集的情况不做分析 。实际很少用到。继续看handleResultSet方法 复制代码 private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List 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); // 将结果加入multipleResults中 multipleResults.add(defaultResultHandler.getResultList()); } else { handleRowValues(rsw, resultMap, resultHandler, rowBounds, null); } } } finally { closeResultSet(rsw.getResultSet()); } } 复制代码 通过handleRowValues 映射ResultSet结果,最后映射的结果会在defaultResultHandler的ResultList集合中,最后将结果加入到multipleResults中就可以返回了,我们继续跟进handleRowValues这个核心方法 复制代码 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 { // 处理简单映射,本文先只分析简单映射 handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping); } } 复制代码 我们可以通过resultMap.hasNestedResultMaps()知道查询语句是否是嵌套查询,如果resultMap中包含且其select属性不为空,则为嵌套查询,大家可以看看我第三篇文章关于解析 resultMap 节点。本文先分析简单的映射 复制代码 private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { DefaultResultContext resultContext = new DefaultResultContext(); // 根据 RowBounds 定位到指定行记录 skipRows(rsw.getResultSet(), rowBounds); // ResultSet是一个集合,很有可能我们查询的就是一个List,这就就每条数据遍历处理 while (shouldProcessMoreRows(resultContext, rowBounds) && rsw.getResultSet().next()) { ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null); // 从 resultSet 中获取结果 Object rowValue = getRowValue(rsw, discriminatedResultMap); // 存储结果到resultHandler的ResultList,最后ResultList加入multipleResults中返回 storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet()); } } 复制代码 我们查询的结果很有可能是一个集合,所以这里要遍历集合,每条结果单独进行映射,最后映射的结果加入到resultHandler的ResultList MyBatis 默认提供了 RowBounds 用于分页,从上面的代码中可以看出,这并非是一个高效的分页方式,是查出所有的数据,进行内存分页。除了使用 RowBounds,还可以使用一些第三方分页插件进行分页。我们后面文章来讲,我们来看关键代码getRowValue,处理一行数据 复制代码 private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap) throws SQLException { // 这个Map是用来存储延迟加载的BountSql的,我们下面来看 final ResultLoaderMap lazyLoader = new ResultLoaderMap(); // 创建实体类对象,比如 Employ 对象 Object rowValue = createResultObject(rsw, resultMap, lazyLoader, null); if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { final MetaObject metaObject = configuration.newMetaObject(rowValue); boolean foundValues = this.useConstructorMappings; if (shouldApplyAutomaticMappings(resultMap, false)) { //自动映射,结果集中有的column,但resultMap中并没有配置 foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, null) || foundValues; } // 根据 节点中配置的映射关系进行映射 foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, null) || foundValues; foundValues = lazyLoader.size() > 0 || foundValues; rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null; } return rowValue; } 复制代码 重要的逻辑已经注释出来了。分别如下: 创建实体类对象 自动映射结果集中有的column,但resultMap中并没有配置 根据 节点中配置的映射关系进行映射 回到顶部 创建实体类对象 我们想将查询结果映射成实体类对象,第一步当然是要创建实体类对象了,下面我们来看一下 MyBatis 创建实体类对象的过程。 复制代码 private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { this.useConstructorMappings = false; final List> constructorArgTypes = new ArrayList>(); final List constructorArgs = new ArrayList(); // 调用重载方法创建实体类对象 Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix); if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { final List propertyMappings = resultMap.getPropertyResultMappings(); for (ResultMapping propertyMapping : propertyMappings) { // 如果开启了延迟加载,则为 resultObject 生成代理类,如果仅仅是配置的关联查询,没有开启延迟加载,是不会创建代理类 if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) { /* * 创建代理类,默认使用 Javassist 框架生成代理类。 * 由于实体类通常不会实现接口,所以不能使用 JDK 动态代理 API 为实体类生成代理。 * 并且将lazyLoader传进去了 */ resultObject = configuration.getProxyFactory() .createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); break; } } } this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); return resultObject; } 复制代码 我们先来看 createResultObject 重载方法的逻辑 复制代码 private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List> constructorArgTypes, List constructorArgs, String columnPrefix) throws SQLException { final Class resultType = resultMap.getType(); final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory); final List constructorMappings = resultMap.getConstructorResultMappings(); if (hasTypeHandlerForResultObject(rsw, resultType)) { return createPrimitiveResultObject(rsw, resultMap, columnPrefix); } else if (!constructorMappings.isEmpty()) { return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix); } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) { // 通过 ObjectFactory 调用目标类的默认构造方法创建实例 return objectFactory.create(resultType); } else if (shouldApplyAutomaticMappings(resultMap, false)) { return createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs, columnPrefix); } throw new ExecutorException("Do not know how to create an instance of " + resultType); } 复制代码 一般情况下,MyBatis 会通过 ObjectFactory 调用默认构造方法创建实体类对象。看看是如何创建的 复制代码 public T create(Class type, List> constructorArgTypes, List constructorArgs) { Class classToCreate = this.resolveInterface(type); return this.instantiateClass(classToCreate, constructorArgTypes, constructorArgs); } T instantiateClass(Class type, List> constructorArgTypes, List constructorArgs) { try { Constructor constructor; if (constructorArgTypes != null && constructorArgs != null) { constructor = type.getDeclaredConstructor((Class[])constructorArgTypes.toArray(new Class[constructorArgTypes.size()])); if (!constructor.isAccessible()) { constructor.setAccessible(true); } return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()])); } else { //通过反射获取构造器 constructor = type.getDeclaredConstructor(); if (!constructor.isAccessible()) { constructor.setAccessible(true); } //通过构造器来实例化对象 return constructor.newInstance(); } } catch (Exception var9) { throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + var9, var9); } } 复制代码 很简单,就是通过反射创建对象 回到顶部 结果集映射 映射结果集分为两种情况:一种是自动映射(结果集有但在resultMap里没有配置的字段),在实际应用中,都会使用自动映射,减少配置的工作。自动映射在Mybatis中也是默认开启的。第二种是映射ResultMap中配置的,我们分这两者映射来看 自动映射 复制代码 private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException { // 获取 UnMappedColumnAutoMapping 列表 List autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix); boolean foundValues = false; if (!autoMapping.isEmpty()) { for (UnMappedColumnAutoMapping mapping : autoMapping) { // 通过 TypeHandler 从结果集中获取指定列的数据 final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column); if (value != null) { foundValues = true; } if (value != null || (configuration.isCallSettersOnNulls() && !mapping.primitive)) { // 通过元信息对象设置 value 到实体类对象的指定字段上 metaObject.setValue(mapping.property, value); } } } return foundValues; } 复制代码 首先是获取 UnMappedColumnAutoMapping 集合,然后遍历该集合,并通过 TypeHandler 从结果集中获取数据,最后再将获取到的数据设置到实体类对象中。 UnMappedColumnAutoMapping 用于记录未配置在 节点中的映射关系。它的代码如下: 复制代码 private static class UnMappedColumnAutoMapping { private final String column; private final String property; private final TypeHandler typeHandler; private final boolean primitive; public UnMappedColumnAutoMapping(String column, String property, TypeHandler typeHandler, boolean primitive) { this.column = column; this.property = property; this.typeHandler = typeHandler; this.primitive = primitive; } } 复制代码 仅用于记录映射关系。下面看一下获取 UnMappedColumnAutoMapping 集合的过程,如下: 复制代码 private List createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException { final String mapKey = resultMap.getId() + ":" + columnPrefix; // 从缓存中获取 UnMappedColumnAutoMapping 列表 List autoMapping = autoMappingsCache.get(mapKey); // 缓存未命中 if (autoMapping == null) { autoMapping = new ArrayList(); // 从 ResultSetWrapper 中获取未配置在 中的列名 final List unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix); for (String columnName : unmappedColumnNames) { String propertyName = columnName; if (columnPrefix != null && !columnPrefix.isEmpty()) { if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) { propertyName = columnName.substring(columnPrefix.length()); } else { continue; } } // 将下划线形式的列名转成驼峰式,比如 AUTHOR_NAME -> authorName final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase()); if (property != null && metaObject.hasSetter(property)) { // 检测当前属性是否存在于 resultMap 中 if (resultMap.getMappedProperties().contains(property)) { continue; } // 获取属性对于的类型 final Class propertyType = metaObject.getSetterType(property); if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) { final TypeHandler typeHandler = rsw.getTypeHandler(propertyType, columnName); // 封装上面获取到的信息到 UnMappedColumnAutoMapping 对象中 autoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrim