MyBatis源码分析之@ResultMap注解详解

2022-07-28 09:25:55

MyBatis源码分析之@ResultMap注解详解

在前一篇文章讲**@MapKey注解时,我原想将@ResultMap注解也一起拿出来说一下,但是发现@ResultMap解析加载源码非常多,想想就不在一篇文章中讲了,分开单独来说,这一篇就来彻底探索一下@ResultMap**注解。

1. 加载过程


说到解析Mapper方法上的注解**@ResultMap**,这个就要回到解析configuration中的parseMapper位置了,在mapperRegistry加入当前解析出的mapper时我们知此处不仅做了加载mapper的事情,还进行了非xml方法配置时的加载。

public<T>voidaddMapper(Class<T> type){
    mapperRegistry.addMapper(type);}

在此步addMapper之后,还进行了MapperAnnotationBuilder的解析。

knownMappers.put(type,newMapperProxyFactory<T>(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=newMapperAnnotationBuilder(config, type);
        parser.parse();

@ResultMap的解析就在parse方法中,转到parse方法。

publicvoidparse(){
    String resource= type.toString();if(!configuration.isResourceLoaded(resource)){loadXmlResource();
      configuration.addLoadedResource(resource);
      assistant.setCurrentNamespace(type.getName());parseCache();parseCacheRef();
      Method[] methods= type.getMethods();for(Method method: methods){try{// issue #237if(!method.isBridge()){parseStatement(method);}}catch(IncompleteElementException e){
          configuration.addIncompleteMethod(newMethodResolver(this, method));}}}

进入parseStatement(method)方法中。

voidparseStatement(Method method){.....
      String resultMapId= null;
      ResultMap resultMapAnnotation= method.getAnnotation(ResultMap.class);if(resultMapAnnotation!= null){
        String[] resultMaps= resultMapAnnotation.value();
        StringBuilder sb=newStringBuilder();for(String resultMap: resultMaps){if(sb.length()>0){
            sb.append(",");}
          sb.append(resultMap);}
        resultMapId= sb.toString();}elseif(isSelect){
        resultMapId=parseResultMap(method);}

      assistant.addMappedStatement(
          mappedStatementId,
          resultMapId,getReturnType(method),
          resultSetType,
          flushCache);}

上述方法中对@ResultMap注解进行了解析,并生成resultMapId,这的操作很有意思,如果ResultMap中需要多种返回的话,@ResultMap中的value是一个数组,可以传多个值进去,然后生成resultMapId时拼接到一起。@ResultMap传入value为多个时写法如下:

@MapKey("id")@ResultMap({"BaseResultMap","BaseResultMap2"})@Select("select * from user where hotel_address = #{address};")
    Map<Long, User>getUserByAddress(@Param("address") String address);

那么生成的resultMapId为"BaseResultMap,BaseResultMap2"。这倒是挺有意思,但是我一般也没见过这样写的,找个时间我尝试了,有什么特别地方的话我就回来补充到这下面。

在生成resultMapId后将其他参数一起生成MappedStatement对象并保存进mappedStatements中。

publicvoidaddMappedStatement(MappedStatement ms){
    mappedStatements.put(ms.getId(), ms);}

这里使用的key也就是MappedStatement的id,也就是我们在之前文章中说到的id是用当前类名加方法名组装而成的,具体过程在之前的parseStatement中。

voidparseStatement(Method method){
      Options options= method.getAnnotation(Options.class);final String mappedStatementId= type.getName()+"."+ method.getName();}

我原想分析到这就完了,但是这里面仅仅只是得到了resultMapId字段,但是在后面使用的时候实际上是直接取出了整个ResultMap映射关系,所以还要继续看上述parseStatement方法中的assistant.addMappedStatement方法。

public MappedStatementaddMappedStatement(
      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){thrownewIncompleteElementException("Cache-ref not yet resolved");}

    id=applyCurrentNamespace(id,false);boolean isSelect= sqlCommandType== SqlCommandType.SELECT;

    MappedStatement.Builder statementBuilder=newMappedStatement.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;}

addMappedStatement还有一个操作getStatementResultMaps(resultMap, resultType, id),这一步用来获取resultMaps集合。

private List<ResultMap>getStatementResultMaps(
      String resultMap,
      Class<?> resultType,
      String statementId){
    resultMap=applyCurrentNamespace(resultMap,true);

    List<ResultMap> resultMaps=newArrayList<ResultMap>();if(resultMap!= null){
      String[] resultMapNames= resultMap.split(",");for(String resultMapName: resultMapNames){try{
          resultMaps.add(configuration.getResultMap(resultMapName.trim()));}catch(IllegalArgumentException e){thrownewIncompleteElementException("Could not find result map "+ resultMapName, e);}}}elseif(resultType!= null){
      ResultMap inlineResultMap=newResultMap.Builder(
          configuration,
          statementId+"-Inline",
          resultType,newArrayList<ResultMapping>(),
          null).build();
      resultMaps.add(inlineResultMap);}return resultMaps;}

这一步中有两个操作需要关注一下,一个是resultMap对象是从configuration.getResultMap(resultMapName.trim())中取出来的,而configuration中的resultMap是在解析xml时解析ResultMap节点从而初始化的。这一步完结,还有一步比较关键的是在构造ResultMap时,最后一个字段赋值为null,而这个字段名为autoMapping,这个比较重要,在后文映射字段值时需要用到,这个到时再说。

2. 字段映射


在解析完@ResultMap之后,现在就是在查询完之后字段映射时候发挥作用了,在session查询中找到selectList查询方法,继续追踪到Executor的query方法,最终到SimpleStatementHandler中的query方法。

不过在直接看query方法之前还要再回头来看下selectList方法。

@Overridepublic<E> List<E>selectList(String statement, Object parameter, RowBounds rowBounds){try{
      MappedStatement ms= configuration.getMappedStatement(statement);return executor.query(ms,wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);}catch(Exception e){throw ExceptionFactory.wrapException("Error querying database.  Cause: "+ e, e);}finally{
      ErrorContext.instance().reset();}}

这一步有个操作就是获取到MappedStatement对象, 从许多前文中我们知MappedStatement对象中存放着Sql、ResultMap、timeout等等参数,而在后文中就需要从MappedStatement对象中取出ResultMap中,这个等会再说,先看query方法。

@Overridepublic<E> List<E>query(Statement statement, ResultHandler resultHandler)throws SQLException{
    String sql= boundSql.getSql();
    statement.execute(sql);return resultSetHandler.<E>handleResultSets(statement);}

在statement执行完query方法后,剩下的就是处理结果集以我们想要的形式返回,这一步的处理在handleResultSets中。

@Overridepublic List<Object>handleResultSets(Statement stmt)throws SQLException{
    ErrorContext.instance().activity("handling results").object(mappedStatement.getId());final List<Object> multipleResults=newArrayList<Object>();int resultSetCount=0;
    ResultSetWrapper rsw=getFirstResultSet(stmt);

    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++;}}returncollapseSingleResultList(multipleResults);}

这里就要说到前文中的MappedStatement对象了,这里取出了ResultMap集合,然后在遍历rsw中,对rsw记录与resultMap中字段进行映射,进入到handleResultSet方法中。

privatevoidhandleResultSet(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=newDefaultResultHandler(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());}}

处理每一行的数据在handleRowValues方法中,进入handleRowValues方法中。

publicvoidhandleRowValues(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);}}privatevoidhandleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping)throws SQLException{
    DefaultResultContext<Object> resultContext=newDefaultResultContext<Object>();skipRows(rsw.getResultSet(), rowBounds);while(shouldProcessMoreRows(resultContext, rowBounds)&& rsw.getResultSet().next()){
      ResultMap discriminatedResultMap=resolveDiscriminatedResultMap(rsw.getResultSet(), resultMap, null);
      Object rowValue=getRowValue(rsw, discriminatedResultMap);storeObject(resultHandler, resultContext, rowValue, parentMapping, rsw.getResultSet());}}

最后的结果处理在getRowValue中,这里返回的Object对象其实就是对应查询出来的对象类了,在这就是user对象,getRowValue应该就是对具体的字段进行映射与赋值了,还是进去看一下吧。

private ObjectgetRowValue(ResultSetWrapper rsw, ResultMap resultMap)throws SQLException{final ResultLoaderMap lazyLoader=newResultLoaderMap();
    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)){
        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;}

createResultObject这一步实质就是根据类型创建返回对象,如果可以自动映射关系的话,就在applyAutomaticMappings(rsw, resultMap, metaObject, null)这一步进行字段映射,可以进这个方法中看下是如何进行一步步映射字段value的。

privatebooleanapplyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix)throws SQLException{
    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||
  • 作者:叶长风
  • 原文链接:https://bingwen.blog.csdn.net/article/details/86210583
    更新时间:2022-07-28 09:25:55