当前位置: 代码迷 >> 综合 >> 花里胡哨系列之Mybatis源码---结果映射(七)
  详细解决方案

花里胡哨系列之Mybatis源码---结果映射(七)

热度:25   发布时间:2024-02-01 17:39:14.0

一.结果映射介绍

    在映射文件中,Mybatis可以根据配置,自动将查询结果封装成对象。常见的结果配置有:

    1.通过 resultType 映射

    

<select id="selectFromAuthor" parameterType="java.lang.String" resultType="org.apache.ibatis.domain.blog.Author">select * from Author where id = #{id} and username = #{username}
</select>

    2.通过 resultMap 映射

  

<resultMap id="selectAuthor" type="org.apache.ibatis.domain.blog.Author"><id column="id" property="id" /><result property="username" column="username" /><result property="password" column="password" /><result property="email" column="email" /><result property="bio" column="bio" /><result property="favouriteSection" column="favourite_section" />
</resultMap>
<select id="selectAuthor" parameterMap="selectAuthor" resultMap="selectAuthor">select * from Author where id = #{id} and username = #{username}
</select>

二.结果映射标签解析过程

     在映射文件加载过程中,上述两种方式结果映射会被解析成什么样呢?简单回顾一下

     首先回顾一下resultMap标签解析过程

XMLMapperBuilder:
private void configurationElement(XNode context) {try {//获取命名空间String namespace = context.getStringAttribute("namespace");if (namespace == null || namespace.equals("")) {throw new BuilderException("Mapper's namespace cannot be empty");}builderAssistant.setCurrentNamespace(namespace);// 解析 <cache-ref> 节点//在 MyBatis 中,二级缓存是可以共用的。这需要通过<cache-ref>节点为命名空间配置参//照缓存,引用其他命名空间的缓存,在一个mapper.xml文件中,cache 和 cache-ref一般只会存在一个//如果都存在,看具体解析的顺序cacheRefElement(context.evalNode("cache-ref"));//解析<cache> 节点,二级缓存,在解析 select|insert|update|delete 会将二级缓存设置进去cacheElement(context.evalNode("cache"));parameterMapElement(context.evalNodes("/mapper/parameterMap"));resultMapElements(context.evalNodes("/mapper/resultMap"));sqlElement(context.evalNodes("/mapper/sql"));buildStatementFromContext(context.evalNodes("select|insert|update|delete"));} catch (Exception e) {throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);}
}
private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> additionalResultMappings, Class<?> enclosingType) throws Exception {ErrorContext.instance().activity("processing " + resultMapNode.getValueBasedIdentifier());//解析type属性String type = resultMapNode.getStringAttribute("type",resultMapNode.getStringAttribute("ofType",resultMapNode.getStringAttribute("resultType",resultMapNode.getStringAttribute("javaType"))));Class<?> typeClass = resolveClass(type);if (typeClass == null) {typeClass = inheritEnclosingType(resultMapNode, enclosingType);}Discriminator discriminator = null;List<ResultMapping> resultMappings = new ArrayList<>();resultMappings.addAll(additionalResultMappings);List<XNode> resultChildren = resultMapNode.getChildren();for (XNode resultChild : resultChildren) {//解析子标签<result> </result>,<id>if ("constructor".equals(resultChild.getName())) {processConstructorElement(resultChild, typeClass, resultMappings);} else if ("discriminator".equals(resultChild.getName())) {//discriminator 根据数据库返回结果值,决定引用 哪个resultMapdiscriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);} else {List<ResultFlag> flags = new ArrayList<>();if ("id".equals(resultChild.getName())) {flags.add(ResultFlag.ID);}//解析id和type,  创建ResultMapping,获取<result> </result>标签中的所有属性resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));}}//解析id属性String id = resultMapNode.getStringAttribute("id",resultMapNode.getValueBasedIdentifier());String extend = resultMapNode.getStringAttribute("extends");Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator, resultMappings, autoMapping);try {// 根据前面获取到的信息构建 ResultMap 对象, id --> ResultMapreturn resultMapResolver.resolve();} catch (IncompleteElementException  e) {configuration.addIncompleteResultMap(resultMapResolver);throw e;}
}
ResultMapResolver:
public ResultMap resolve() {return assistant.addResultMap(this.id, this.type, this.extend, this.discriminator, this.resultMappings, this.autoMapping);
}
MapperBuilderAssistant:
public ResultMap addResultMap(String id,Class<?> type,String extend,Discriminator discriminator,List<ResultMapping> resultMappings,Boolean autoMapping) {id = applyCurrentNamespace(id, false);extend = applyCurrentNamespace(extend, true);if (extend != null) {if (!configuration.hasResultMap(extend)) {//抛异常,外部捕获,后续补偿throw new IncompleteElementException("Could not find a parent resultmap with id '" + extend + "'");}ResultMap resultMap = configuration.getResultMap(extend);List<ResultMapping> extendedResultMappings = new ArrayList<>(resultMap.getResultMappings());extendedResultMappings.removeAll(resultMappings);// Remove parent constructor if this resultMap declares a constructor.boolean declaresConstructor = false;for (ResultMapping resultMapping : resultMappings) {if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {declaresConstructor = true;break;}}if (declaresConstructor) {extendedResultMappings.removeIf(resultMapping -> resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR));}resultMappings.addAll(extendedResultMappings);}ResultMap resultMap = new ResultMap.Builder(configuration, id, type, resultMappings, autoMapping).discriminator(discriminator).build();configuration.addResultMap(resultMap);return resultMap;
}

        最终,ResultMap会被解析成ResultMap对象,简单的看一下这个对象的一下属性:

private Configuration configuration;
/*** <resultMap id="selectAuthor" type="org.apache.ibatis.domain.blog.Author">*/
private String id;
private Class<?> type;
/*** <result></result>* <result></result>* <result></result>*/
private List<ResultMapping> resultMappings;
private List<ResultMapping> idResultMappings;
private List<ResultMapping> constructorResultMappings;
private List<ResultMapping> propertyResultMappings;/*** column 集合*/
private Set<String> mappedColumns;
/***  property 集合*/
private Set<String> mappedProperties;
private Discriminator discriminator;
private boolean hasNestedResultMaps;
private boolean hasNestedQueries;
private Boolean autoMapping;

      接下来看看  resultType 这种方式,因为这个属性属于 <select>标签中的,所有解析过程在 解析<select>标签过程中

XMLMapperBuilder:
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {for (XNode context : list) {final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);try {//开始解析statementParser.parseStatementNode();} catch (IncompleteElementException e) {configuration.addIncompleteStatement(statementParser);}}
}
XMLStatementBuilder:
public void parseStatementNode() {String id = context.getStringAttribute("id");String databaseId = context.getStringAttribute("databaseId");if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {return;}String nodeName = context.getNode().getNodeName();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());String parameterType = context.getStringAttribute("parameterType");Class<?> parameterTypeClass = resolveClass(parameterType);String lang = context.getStringAttribute("lang");LanguageDriver langDriver = getLanguageDriver(lang);// Parse selectKey after includes and remove them.//解析<selectKey>标签// <selectKey keyProperty="id" resultType="int" order="BEFORE">//      select author_seq.nextval from dual//  </selectKey>processSelectKeyNodes(id, parameterTypeClass, langDriver);// Parse the SQL (pre: <selectKey> and <include> were parsed and removed)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;}//解析sql语句SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));Integer fetchSize = context.getIntAttribute("fetchSize");Integer timeout = context.getIntAttribute("timeout");String parameterMap = context.getStringAttribute("parameterMap");// resultType属性,最终解析成 ResultMap 对象String resultType = context.getStringAttribute("resultType");//通过别名解析 resultType 对应的类型Class<?> resultTypeClass = resolveClass(resultType);String resultMap = context.getStringAttribute("resultMap");String resultSetType = context.getStringAttribute("resultSetType");ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);if (resultSetTypeEnum == null) {resultSetTypeEnum = configuration.getDefaultResultSetType();}String keyProperty = context.getStringAttribute("keyProperty");String keyColumn = context.getStringAttribute("keyColumn");String resultSets = context.getStringAttribute("resultSets");//创建 MappedStatement,包含了sql的所有信息,如sql语句,入参,结果映射,缓存, 保存到configuration中,builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,resultSetTypeEnum, flushCache, useCache, resultOrdered,keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
MapperBuilderAssistant:
public MappedStatement addMappedStatement(String id,SqlSource sqlSource,StatementType statementType,SqlCommandType sqlCommandType,Integer fetchSize,Integer timeout,String parameterMap,Class<?> parameterType,String resultMap,Class<?> resultType,ResultSetType resultSetType,boolean flushCache,boolean useCache,boolean resultOrdered,KeyGenerator keyGenerator,String keyProperty,String keyColumn,String databaseId,LanguageDriver lang,String resultSets) {if (unresolvedCacheRef) {//抛异常,后续会继续创建MappedStatementthrow new IncompleteElementException("Cache-ref not yet resolved");}id = applyCurrentNamespace(id, false);boolean isSelect = sqlCommandType == SqlCommandType.SELECT;MappedStatement.Builder statementBuilder = new MappedStatement.Builder(configuration, id, sqlSource, sqlCommandType).resource(resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType).keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang).resultOrdered(resultOrdered).resultSets(resultSets).resultMaps(getStatementResultMaps(resultMap, resultType, id))//处理结果映射.resultSetType(resultSetType).flushCacheRequired(valueOrDefault(flushCache, !isSelect)).useCache(valueOrDefault(useCache, isSelect)).cache(currentCache);//设置二级缓存ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);if (statementParameterMap != null) {statementBuilder.parameterMap(statementParameterMap);}MappedStatement statement = statementBuilder.build();configuration.addMappedStatement(statement);return statement;
}
private List<ResultMap> getStatementResultMaps(String resultMap,Class<?> resultType,String statementId) {resultMap = applyCurrentNamespace(resultMap, true);List<ResultMap> resultMaps = new ArrayList<>();if (resultMap != null) {//处理<resultMap>String[] resultMapNames = resultMap.split(",");for (String resultMapName : resultMapNames) {try {resultMaps.add(configuration.getResultMap(resultMapName.trim()));} catch (IllegalArgumentException e) {throw new IncompleteElementException("Could not find result map '" + resultMapName + "' referenced from '" + statementId + "'", e);}}} else if (resultType != null) {//处理 resultType 属性ResultMap inlineResultMap = new ResultMap.Builder(configuration,statementId + "-Inline",resultType,new ArrayList<>(),null).build();resultMaps.add(inlineResultMap);}return resultMaps;
}

     可以看到,resultType最终也会被封装成 ResultMap对象,这样方便了之后对结果集的处理。

三.处理结果集

     在执行完sql后,Mybatis根据配置将结果映射到相应的对象中。

    基本思路:

    1.封装结果集中的信息,如收集列名集合,列个数等

    2.获取对应的ResultMap对象,根据配置获取结果集中的结果,并设置到最终对象中

PreparedStatementHandler:
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {PreparedStatement ps = (PreparedStatement) statement;//执行sqlps.execute();//处理结果return resultSetHandler.handleResultSets(ps);
}
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);//获取配置的 <ResultMap>结果映射标签List<ResultMap> 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) {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);
}
private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {//获取结果集ResultSet rs = stmt.getResultSet();while (rs == null) {/** 移动 ResultSet 指针到下一个上,有些数据库驱动可能需要使用者* 先调用 getMoreResults 方法,然后才能调用 getResultSet 方法* 获取到第一个 ResultSet*/// move forward to get the first resultset in case the driver// doesn't return the resultset as the first result (HSQLDB 2.1)if (stmt.getMoreResults()) {rs = stmt.getResultSet();} else {if (stmt.getUpdateCount() == -1) {// no more results. Must be no resultsetbreak;}}}//对结果集信息进行包装,如列名集合return rs != null ? new ResultSetWrapper(rs, configuration) : null;
}
ResultSetWrapper:
public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {super();this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();this.resultSet = rs;//获取结果集的 元数据,如类型,个数final ResultSetMetaData metaData = rs.getMetaData();final int columnCount = metaData.getColumnCount();for (int i = 1; i <= columnCount; i++) {//数据库返回的字段名集合columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));//数据库返回的字段类型集合classNames.add(metaData.getColumnClassName(i));}
}

       结果集有了,ResultMap也有了,接下来就是根据ResultMap中的信息 在结果集中查找结果,关注handleResultSet(rsw, resultMap, multipleResults, null)

DefaultResultSetHandler:
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);multipleResults.add(defaultResultHandler.getResultList());} else {handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);}}} finally {// issue #228 (close resultsets)closeResultSet(rsw.getResultSet());}
}
public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {if (resultMap.hasNestedResultMaps()) {//处理嵌套映射,嵌套查询是指<ResultMap>中嵌套了一个<ResultMap>ensureNoRowBounds();checkResultHandler();handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);} else {//处理简单映射handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);}
}
private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)throws SQLException {DefaultResultContext<Object> resultContext = new DefaultResultContext<>();ResultSet resultSet = rsw.getResultSet();// 根据 RowBounds 定位到指定行记录,可以根据RowBounds进行逻辑分页(一般不会这样做)skipRows(resultSet, rowBounds);// 检测是否还有更多行的数据需要处理,可以根据RowBounds进行逻辑分页(一般不会这样做)while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {// 获取经过鉴别器处理后的 ResultMapResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);//rowValue 最终封装的对象结果Object rowValue = getRowValue(rsw, discriminatedResultMap, null);//保存结果storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);}
}

简单看一下  skipRows(resultSet, rowBounds)

private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {// 检测 rs 的类型,不同的类型行数据定位方式是不同的if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {rs.absolute(rowBounds.getOffset());}} else {for (int i = 0; i < rowBounds.getOffset(); i++) {if (!rs.next()) {break;}}}
}
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {final ResultLoaderMap lazyLoader = new ResultLoaderMap();//创建实体类对象Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {//对于简单类型,如int等有 typeHander处理器,则不会进入此处,rowValue已是最终结果final MetaObject metaObject = configuration.newMetaObject(rowValue);boolean foundValues = this.useConstructorMappings;// 检测是否应该自动映射结果集if (shouldApplyAutomaticMappings(resultMap, false)) {//自动映射,未在<resultMap>中配置的属性,如hashMap,自动映射返回对象(只配置resultType)foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;}// 根据 <resultMap> 节点中配置的映射关系进行映射foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;foundValues = lazyLoader.size() > 0 || foundValues;rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;}return rowValue;
}

        getRowValue中,先创建返回对象,然后再进行结果映射。对应一下简单的返回类型,如 String,Integer等已经注册过TypeHandler,在创建返回对象时值已经设置到对象中,无需进行结果映射。

DefaultResultSetHandler:
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;
}
private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, String columnPrefix)throws SQLException {final Class<?> resultType = resultMap.getType();final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);// 获取 <constructor> 节点对应的 ResultMappingfinal List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();// 检测是否有与返回值类型相对应的 TypeHandler,若有则直接从// 通过 TypeHandler 从结果集中提取数据,并生成返回值对象(后续无需再填充值)if (hasTypeHandlerForResultObject(rsw, resultType)) {// 通过 TypeHandler 获取取,并生成返回值对象return createPrimitiveResultObject(rsw, resultMap, columnPrefix);} else if (!constructorMappings.isEmpty()) {// 通过 <constructor> 节点配置的映射信息从 ResultSet 中?取数据,// 然后将这些数据传给指定构造方法,即可创建实体类对象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);}throw new ExecutorException("Do not know how to create an instance of " + resultType);
}

       Mybatis默认注册了很多TypeHandler,TypeHandler 接口用来设置入参,获取结果集

public interface TypeHandler<T> {void setParameter(PreparedStatement ps, int i, T parameter, JdbcType jdbcType) throws SQLException;/*** @param columnName Colunm name, when configuration <code>useColumnLabel</code> is <code>false</code>*/T getResult(ResultSet rs, String columnName) throws SQLException;T getResult(ResultSet rs, int columnIndex) throws SQLException;T getResult(CallableStatement cs, int columnIndex) throws SQLException;}
public TypeHandlerRegistry() {register(Boolean.class, new BooleanTypeHandler());register(boolean.class, new BooleanTypeHandler());register(JdbcType.BOOLEAN, new BooleanTypeHandler());register(JdbcType.BIT, new BooleanTypeHandler());register(Byte.class, new ByteTypeHandler());register(byte.class, new ByteTypeHandler());register(JdbcType.TINYINT, new ByteTypeHandler());register(Short.class, new ShortTypeHandler());register(short.class, new ShortTypeHandler());register(JdbcType.SMALLINT, new ShortTypeHandler());register(Integer.class, new IntegerTypeHandler());register(int.class, new IntegerTypeHandler());register(JdbcType.INTEGER, new IntegerTypeHandler());register(Long.class, new LongTypeHandler());register(long.class, new LongTypeHandler());register(Float.class, new FloatTypeHandler());register(float.class, new FloatTypeHandler());register(JdbcType.FLOAT, new FloatTypeHandler());register(Double.class, new DoubleTypeHandler());register(double.class, new DoubleTypeHandler());register(JdbcType.DOUBLE, new DoubleTypeHandler());register(Reader.class, new ClobReaderTypeHandler());register(String.class, new StringTypeHandler());register(String.class, JdbcType.CHAR, new StringTypeHandler());register(String.class, JdbcType.CLOB, new ClobTypeHandler());register(String.class, JdbcType.VARCHAR, new StringTypeHandler());register(String.class, JdbcType.LONGVARCHAR, new StringTypeHandler());register(String.class, JdbcType.NVARCHAR, new NStringTypeHandler());register(String.class, JdbcType.NCHAR, new NStringTypeHandler());register(String.class, JdbcType.NCLOB, new NClobTypeHandler());register(JdbcType.CHAR, new StringTypeHandler());register(JdbcType.VARCHAR, new StringTypeHandler());register(JdbcType.CLOB, new ClobTypeHandler());register(JdbcType.LONGVARCHAR, new StringTypeHandler());register(JdbcType.NVARCHAR, new NStringTypeHandler());register(JdbcType.NCHAR, new NStringTypeHandler());register(JdbcType.NCLOB, new NClobTypeHandler());register(Object.class, JdbcType.ARRAY, new ArrayTypeHandler());register(JdbcType.ARRAY, new ArrayTypeHandler());register(BigInteger.class, new BigIntegerTypeHandler());register(JdbcType.BIGINT, new LongTypeHandler());register(BigDecimal.class, new BigDecimalTypeHandler());register(JdbcType.REAL, new BigDecimalTypeHandler());register(JdbcType.DECIMAL, new BigDecimalTypeHandler());register(JdbcType.NUMERIC, new BigDecimalTypeHandler());register(InputStream.class, new BlobInputStreamTypeHandler());register(Byte[].class, new ByteObjectArrayTypeHandler());register(Byte[].class, JdbcType.BLOB, new BlobByteObjectArrayTypeHandler());register(Byte[].class, JdbcType.LONGVARBINARY, new BlobByteObjectArrayTypeHandler());register(byte[].class, new ByteArrayTypeHandler());register(byte[].class, JdbcType.BLOB, new BlobTypeHandler());register(byte[].class, JdbcType.LONGVARBINARY, new BlobTypeHandler());register(JdbcType.LONGVARBINARY, new BlobTypeHandler());register(JdbcType.BLOB, new BlobTypeHandler());register(Object.class, unknownTypeHandler);register(Object.class, JdbcType.OTHER, unknownTypeHandler);register(JdbcType.OTHER, unknownTypeHandler);register(Date.class, new DateTypeHandler());register(Date.class, JdbcType.DATE, new DateOnlyTypeHandler());register(Date.class, JdbcType.TIME, new TimeOnlyTypeHandler());register(JdbcType.TIMESTAMP, new DateTypeHandler());register(JdbcType.DATE, new DateOnlyTypeHandler());register(JdbcType.TIME, new TimeOnlyTypeHandler());register(java.sql.Date.class, new SqlDateTypeHandler());register(java.sql.Time.class, new SqlTimeTypeHandler());register(java.sql.Timestamp.class, new SqlTimestampTypeHandler());register(String.class, JdbcType.SQLXML, new SqlxmlTypeHandler());register(Instant.class, new InstantTypeHandler());register(LocalDateTime.class, new LocalDateTimeTypeHandler());register(LocalDate.class, new LocalDateTypeHandler());register(LocalTime.class, new LocalTimeTypeHandler());register(OffsetDateTime.class, new OffsetDateTimeTypeHandler());register(OffsetTime.class, new OffsetTimeTypeHandler());register(ZonedDateTime.class, new ZonedDateTimeTypeHandler());register(Month.class, new MonthTypeHandler());register(Year.class, new YearTypeHandler());register(YearMonth.class, new YearMonthTypeHandler());register(JapaneseDate.class, new JapaneseDateTypeHandler());// issue #273register(Character.class, new CharacterTypeHandler());register(char.class, new CharacterTypeHandler());
}

       可以看到大部分常见的类型,都默认注册进去了,接下来看看 TypeHandler如何填充结果集

DefaultResultSetHandler:
private Object createPrimitiveResultObject(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {final Class<?> resultType = resultMap.getType();final String columnName;if (!resultMap.getResultMappings().isEmpty()) {final List<ResultMapping> resultMappingList = resultMap.getResultMappings();final ResultMapping mapping = resultMappingList.get(0);columnName = prependPrefix(mapping.getColumn(), columnPrefix);} else {columnName = rsw.getColumnNames().get(0);}//获取 resultType 对应的 类型处理器final TypeHandler<?> typeHandler = rsw.getTypeHandler(resultType, columnName);//获取 columnName 对应的 值return typeHandler.getResult(rsw.getResultSet(), columnName);
}
BaseTypeHandler:
public T getResult(ResultSet rs, String columnName) throws SQLException {try {//模板方法,子类具体实现,以String为例return getNullableResult(rs, columnName);} catch (Exception e) {throw new ResultMapException("Error attempting to get column '" + columnName + "' from result set.  Cause: " + e, e);}
}
StringTypeHandler:
public String getNullableResult(ResultSet rs, String columnName)throws SQLException {//获取结果集中的结果return rs.getString(columnName);
}

       对于没有注册TypeHandler 的返回类型,如自定义bean对象,则需要通过反射创建对象 objectFactory.create(resultType);

ExampleObjectFactory:
@Override
public <T> T create(Class<T> type) {return super.<T> create(type);
}
DefaultObjectFactory:
public <T> T create(Class<T> type) {return create(type, null, null);
}
@Override
public <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {Class<?> classToCreate = resolveInterface(type);// we know types are assignablereturn (T) instantiateClass(classToCreate, constructorArgTypes, constructorArgs);
}
private  <T> T instantiateClass(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) {try {Constructor<T> constructor;if (constructorArgTypes == null || constructorArgs == null) {//默认的无参构造函数constructor = type.getDeclaredConstructor();try {//反射创建实例return constructor.newInstance();} catch (IllegalAccessException e) {if (Reflector.canControlMemberAccessible()) {constructor.setAccessible(true);return constructor.newInstance();} else {throw e;}}}//有参构造函数constructor = type.getDeclaredConstructor(constructorArgTypes.toArray(new Class[constructorArgTypes.size()]));try {return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]));} catch (IllegalAccessException e) {if (Reflector.canControlMemberAccessible()) {constructor.setAccessible(true);return constructor.newInstance(constructorArgs.toArray(new Object[constructorArgs.size()]));} else {throw e;}}} catch (Exception e) {String argTypes = Optional.ofNullable(constructorArgTypes).orElseGet(Collections::emptyList).stream().map(Class::getSimpleName).collect(Collectors.joining(","));String argValues = Optional.ofNullable(constructorArgs).orElseGet(Collections::emptyList).stream().map(String::valueOf).collect(Collectors.joining(","));throw new ReflectionException("Error instantiating " + type + " with invalid types (" + argTypes + ") or values (" + argValues + "). Cause: " + e, e);}
}

       创建好了返回实体对象,可以根据结果集和ResultMap进行映射了

DefaultResultSetHandler:
private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {final ResultLoaderMap lazyLoader = new ResultLoaderMap();//创建实体类对象Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {//对于简单类型,如int等有 typeHander处理器,则不会进入此处,rowValue已是最终结果final MetaObject metaObject = configuration.newMetaObject(rowValue);boolean foundValues = this.useConstructorMappings;// 检测是否应该自动映射结果集if (shouldApplyAutomaticMappings(resultMap, false)) {//自动映射,未在<resultMap>中配置的属性,如hashMap,自动映射返回对象(只配置resultType)foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;}// 根据 <resultMap> 节点中配置的映射关系进行映射foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;foundValues = lazyLoader.size() > 0 || foundValues;rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;}return rowValue;
}

        什么是自动映射,例如:

<select id="selectFromAuthor" parameterType="java.lang.String" resultType="org.apache.ibatis.domain.blog.Author">select * from Author where id = #{id} and username = #{username}
</select>

   返回类型并没有使用 resultMap,且 resultType的类型 并没有特殊的TypeHandler处理,这个时候就需要自动映射了。Mybatis默认开启自动映射

DefaultResultSetHandler:

/*** 在 MyBatis 中,结果集自动映射有三种等级。这三种等级官方文档上有所说明,这里直* 接引用一下。如下:* NONE - 禁用自动映射。仅设置手动映射属性* PARTIAL - 将自动映射结果除了那些有内部定义内嵌结果映射的(joins)* FULL - 自动映射所有* 除了以上三种等级,我们还可以显示配置<resultMap>节点的 autoMapping 属性,以启用* 或者禁用指定 ResultMap 的自动映射设定。* @param resultMap* @param isNested* @return*/
private boolean shouldApplyAutomaticMappings(ResultMap resultMap, boolean isNested) {if (resultMap.getAutoMapping() != null) {//检查是否设置自动映射属性return resultMap.getAutoMapping();} else {// 对于嵌套 resultMap,仅当全局的映射行为为 FULL 时,才进行自动映射if (isNested) {return AutoMappingBehavior.FULL == configuration.getAutoMappingBehavior();} else {// 对于普通的 resultMap,只要全局的映射行为不为 NONE,即可进行自动映射return AutoMappingBehavior.NONE != configuration.getAutoMappingBehavior();}}
}
private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {// 获取 UnMappedColumnAutoMapping 列表,即需要自动映射的自动信息List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);boolean foundValues = false;if (!autoMapping.isEmpty()) {for (UnMappedColumnAutoMapping mapping : autoMapping) {// 根据column获取结果集中的值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;
}
private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException {final String mapKey = resultMap.getId() + ":" + columnPrefix;List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);//未命中缓存if (autoMapping == null) {autoMapping = new ArrayList<>();// 从 ResultSetWrapper 中获取未配置在 <resultMap> 中的列名final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);for (String columnName : unmappedColumnNames) {String propertyName = columnName;if (columnPrefix != null && !columnPrefix.isEmpty()) {// When columnPrefix is specified,// ignore columns without the prefix.if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {propertyName = columnName.substring(columnPrefix.length());} else {continue;}}// 将下划线形式的列名转成驼峰式,比如 AUTHOR_NAME -> authorNamefinal String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());if (property != null && metaObject.hasSetter(property)) {if (resultMap.getMappedProperties().contains(property)) {// 检测当前属性是否存在于 resultMap 中continue;}final Class<?> propertyType = metaObject.getSetterType(property);if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {//获取类型处理器final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);//封装到UnMappedColumnAutoMappingautoMapping.add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));} else {configuration.getAutoMappingUnknownColumnBehavior().doAction(mappedStatement, columnName, property, propertyType);}} else {// 若 property 为空,或实体类中无 property 属性,此时无法完成// 列名与实体类属性建立映射关系。针对这种情况,有三种处理方式,// 1. 什么都不做// 2. 仅打印日志// 3. 抛出异常// 默认情况下,是什么都不做configuration.getAutoMappingUnknownColumnBehavior().doAction(mappedStatement, columnName, (property != null) ? property : propertyName, null);}}//写入缓存autoMappingsCache.put(mapKey, autoMapping);}return autoMapping;
}
ResultSetWrapper:
public List<String> getUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {List<String> unMappedColumnNames = unMappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));if (unMappedColumnNames == null) {// 加载已映射与未映射列名loadMappedAndUnmappedColumnNames(resultMap, columnPrefix);unMappedColumnNames = unMappedColumnNamesMap.get(getMapKey(resultMap, columnPrefix));}return unMappedColumnNames;
}
private void loadMappedAndUnmappedColumnNames(ResultMap resultMap, String columnPrefix) throws SQLException {List<String> mappedColumnNames = new ArrayList<>();List<String> unmappedColumnNames = new ArrayList<>();final String upperColumnPrefix = columnPrefix == null ? null : columnPrefix.toUpperCase(Locale.ENGLISH);final Set<String> mappedColumns = prependPrefixes(resultMap.getMappedColumns(), upperColumnPrefix);for (String columnName : columnNames) {//columnNames是结果集final String upperColumnName = columnName.toUpperCase(Locale.ENGLISH);if (mappedColumns.contains(upperColumnName)) {//已有映射关系,即在<resultMap>中有配置mappedColumnNames.add(upperColumnName);} else {//没有映射关系unmappedColumnNames.add(columnName);}}mappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), mappedColumnNames);unMappedColumnNamesMap.put(getMapKey(resultMap, columnPrefix), unmappedColumnNames);
}

       自动映射结束,接下来就是<resultMap>相关映射了,过程和自动映射基本相似

DefaultResultSetHandler:
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) {//获取resultMap 中 <result> column对应在 结果集中的值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')//将 value设置到 property 上metaObject.setValue(property, value);}}}return foundValues;
}

 四.总结

      本节结果映射只是分析了最简单的结果映射,对于更高级的嵌套结果映射并没有进行分析,大家自行分析吧。结果映射流程整体还是清晰的:包装结果集,创建返回对象,填充返回对象属性。

      下一章准备分析Mybatis的数据源,一起期待吧!

       结语:人生得意须尽欢,莫使金樽空对月

 

  相关解决方案