001/*
002 *  Copyright (c) 2022-2025, Mybatis-Flex (fuhai999@gmail.com).
003 *  <p>
004 *  Licensed under the Apache License, Version 2.0 (the "License");
005 *  you may not use this file except in compliance with the License.
006 *  You may obtain a copy of the License at
007 *  <p>
008 *  http://www.apache.org/licenses/LICENSE-2.0
009 *  <p>
010 *  Unless required by applicable law or agreed to in writing, software
011 *  distributed under the License is distributed on an "AS IS" BASIS,
012 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 *  See the License for the specific language governing permissions and
014 *  limitations under the License.
015 */
016package com.mybatisflex.core.mybatis;
017
018import com.mybatisflex.core.FlexGlobalConfig;
019import com.mybatisflex.core.util.MapUtil;
020import org.apache.ibatis.annotations.AutomapConstructor;
021import org.apache.ibatis.annotations.Param;
022import org.apache.ibatis.binding.MapperMethod.ParamMap;
023import org.apache.ibatis.cache.CacheKey;
024import org.apache.ibatis.cursor.Cursor;
025import org.apache.ibatis.cursor.defaults.DefaultCursor;
026import org.apache.ibatis.executor.ErrorContext;
027import org.apache.ibatis.executor.Executor;
028import org.apache.ibatis.executor.ExecutorException;
029import org.apache.ibatis.executor.loader.ResultLoader;
030import org.apache.ibatis.executor.loader.ResultLoaderMap;
031import org.apache.ibatis.executor.parameter.ParameterHandler;
032import org.apache.ibatis.executor.result.DefaultResultContext;
033import org.apache.ibatis.executor.result.DefaultResultHandler;
034import org.apache.ibatis.executor.result.ResultMapException;
035import org.apache.ibatis.executor.resultset.DefaultResultSetHandler;
036import org.apache.ibatis.executor.resultset.ResultSetWrapper;
037import org.apache.ibatis.mapping.*;
038import org.apache.ibatis.reflection.MetaClass;
039import org.apache.ibatis.reflection.MetaObject;
040import org.apache.ibatis.reflection.ReflectorFactory;
041import org.apache.ibatis.reflection.factory.ObjectFactory;
042import org.apache.ibatis.session.*;
043import org.apache.ibatis.type.JdbcType;
044import org.apache.ibatis.type.TypeHandler;
045import org.apache.ibatis.type.TypeHandlerRegistry;
046import org.apache.ibatis.type.UnknownTypeHandler;
047
048import java.lang.reflect.Constructor;
049import java.lang.reflect.Executable;
050import java.lang.reflect.Parameter;
051import java.sql.CallableStatement;
052import java.sql.ResultSet;
053import java.sql.SQLException;
054import java.sql.Statement;
055import java.text.MessageFormat;
056import java.util.*;
057
058/**
059 * 复制于 DefaultResultSetHandler,并开放若干方法,方便子类重写
060 *
061 * @author Clinton Begin
062 * @author Eduardo Macarron
063 * @author Iwao AVE!
064 * @author Kazuki Shimizu
065 * @author Michael
066 */
067public class FlexDefaultResultSetHandler extends DefaultResultSetHandler {
068
069    private static final Object DEFERRED = new Object();
070
071    private final Executor executor;
072    private final Configuration configuration;
073    private final MappedStatement mappedStatement;
074    private final RowBounds rowBounds;
075    private final ParameterHandler parameterHandler;
076    private final ResultHandler<?> resultHandler;
077    private final BoundSql boundSql;
078    private final TypeHandlerRegistry typeHandlerRegistry;
079    private final ObjectFactory objectFactory;
080    private final ReflectorFactory reflectorFactory;
081
082    // nested resultmaps
083    private final Map<CacheKey, Object> nestedResultObjects = new HashMap<>();
084    private final Map<String, Object> ancestorObjects = new HashMap<>();
085    private Object previousRowValue;
086
087    // multiple resultsets
088    private final Map<String, ResultMapping> nextResultMaps = new HashMap<>();
089    private final Map<CacheKey, List<PendingRelation>> pendingRelations = new HashMap<>();
090
091    // Cached Automappings
092    private final Map<String, List<UnMappedColumnAutoMapping>> autoMappingsCache = new HashMap<>();
093    private final Map<String, List<String>> constructorAutoMappingColumns = new HashMap<>();
094
095    // temporary marking flag that indicate using constructor mapping (use field to reduce memory usage)
096    private boolean useConstructorMappings;
097
098    private static class PendingRelation {
099        public MetaObject metaObject;
100        public ResultMapping propertyMapping;
101    }
102
103    private static class UnMappedColumnAutoMapping {
104        private final String column;
105        private final String property;
106        private final TypeHandler<?> typeHandler;
107        private final boolean primitive;
108
109        public UnMappedColumnAutoMapping(String column, String property, TypeHandler<?> typeHandler, boolean primitive) {
110            this.column = column;
111            this.property = property;
112            this.typeHandler = typeHandler;
113            this.primitive = primitive;
114        }
115    }
116
117    public FlexDefaultResultSetHandler(Executor executor, MappedStatement mappedStatement, ParameterHandler parameterHandler,
118                                       ResultHandler<?> resultHandler, BoundSql boundSql, RowBounds rowBounds) {
119        super(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
120        this.executor = executor;
121        this.configuration = mappedStatement.getConfiguration();
122        this.mappedStatement = mappedStatement;
123        this.rowBounds = rowBounds;
124        this.parameterHandler = parameterHandler;
125        this.boundSql = boundSql;
126        this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
127        this.objectFactory = configuration.getObjectFactory();
128        this.reflectorFactory = configuration.getReflectorFactory();
129        this.resultHandler = resultHandler;
130    }
131
132    //
133    // HANDLE OUTPUT PARAMETER
134    //
135
136    @Override
137    public void handleOutputParameters(CallableStatement cs) throws SQLException {
138        final Object parameterObject = parameterHandler.getParameterObject();
139        final MetaObject metaParam = configuration.newMetaObject(parameterObject);
140        final List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
141        for (int i = 0; i < parameterMappings.size(); i++) {
142            final ParameterMapping parameterMapping = parameterMappings.get(i);
143            if (parameterMapping.getMode() == ParameterMode.OUT || parameterMapping.getMode() == ParameterMode.INOUT) {
144                if (ResultSet.class.equals(parameterMapping.getJavaType())) {
145                    handleRefCursorOutputParameter((ResultSet) cs.getObject(i + 1), parameterMapping, metaParam);
146                } else {
147                    final TypeHandler<?> typeHandler = parameterMapping.getTypeHandler();
148                    metaParam.setValue(parameterMapping.getProperty(), typeHandler.getResult(cs, i + 1));
149                }
150            }
151        }
152    }
153
154    private void handleRefCursorOutputParameter(ResultSet rs, ParameterMapping parameterMapping, MetaObject metaParam)
155        throws SQLException {
156        if (rs == null) {
157            return;
158        }
159        try {
160            final String resultMapId = parameterMapping.getResultMapId();
161            final ResultMap resultMap = configuration.getResultMap(resultMapId);
162            final ResultSetWrapper rsw = new ResultSetWrapper(rs, configuration);
163            if (this.resultHandler == null) {
164                final DefaultResultHandler resultHandler = new DefaultResultHandler(objectFactory);
165                handleRowValues(rsw, resultMap, resultHandler, new RowBounds(), null);
166                metaParam.setValue(parameterMapping.getProperty(), resultHandler.getResultList());
167            } else {
168                handleRowValues(rsw, resultMap, resultHandler, new RowBounds(), null);
169            }
170        } finally {
171            // issue #228 (close resultsets)
172            closeResultSet(rs);
173        }
174    }
175
176    //
177    // HANDLE RESULT SETS
178    //
179    @Override
180    public List<Object> handleResultSets(Statement stmt) throws SQLException {
181        ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
182
183        final List<Object> multipleResults = new ArrayList<>();
184
185        int resultSetCount = 0;
186        ResultSetWrapper rsw = getFirstResultSet(stmt);
187
188        List<ResultMap> resultMaps = mappedStatement.getResultMaps();
189        int resultMapCount = resultMaps.size();
190        validateResultMapsCount(rsw, resultMapCount);
191        while (rsw != null && resultMapCount > resultSetCount) {
192            ResultMap resultMap = resultMaps.get(resultSetCount);
193            handleResultSet(rsw, resultMap, multipleResults, null);
194            rsw = getNextResultSet(stmt);
195            cleanUpAfterHandlingResultSet();
196            resultSetCount++;
197        }
198
199        String[] resultSets = mappedStatement.getResultSets();
200        if (resultSets != null) {
201            while (rsw != null && resultSetCount < resultSets.length) {
202                ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
203                if (parentMapping != null) {
204                    String nestedResultMapId = parentMapping.getNestedResultMapId();
205                    ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
206                    handleResultSet(rsw, resultMap, null, parentMapping);
207                }
208                rsw = getNextResultSet(stmt);
209                cleanUpAfterHandlingResultSet();
210                resultSetCount++;
211            }
212        }
213
214        return collapseSingleResultList(multipleResults);
215    }
216
217    @Override
218    public <E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException {
219        ErrorContext.instance().activity("handling cursor results").object(mappedStatement.getId());
220
221        ResultSetWrapper rsw = getFirstResultSet(stmt);
222
223        List<ResultMap> resultMaps = mappedStatement.getResultMaps();
224
225        int resultMapCount = resultMaps.size();
226        validateResultMapsCount(rsw, resultMapCount);
227        if (resultMapCount != 1) {
228            throw new ExecutorException("Cursor results cannot be mapped to multiple resultMaps");
229        }
230
231        ResultMap resultMap = resultMaps.get(0);
232        return new DefaultCursor<>(this, resultMap, rsw, rowBounds);
233    }
234
235    private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException {
236        ResultSet rs = stmt.getResultSet();
237        while (rs == null) {
238            // move forward to get the first resultset in case the driver
239            // doesn't return the resultset as the first result (HSQLDB 2.1)
240            if (stmt.getMoreResults()) {
241                rs = stmt.getResultSet();
242            } else if (stmt.getUpdateCount() == -1) {
243                // no more results. Must be no resultset
244                break;
245            }
246        }
247        return rs != null ? new ResultSetWrapper(rs, configuration) : null;
248    }
249
250    private ResultSetWrapper getNextResultSet(Statement stmt) {
251        // Making this method tolerant of bad JDBC drivers
252        try {
253            // Crazy Standard JDBC way of determining if there are more results
254            if (stmt.getConnection().getMetaData().supportsMultipleResultSets()
255                && (stmt.getMoreResults() || (stmt.getUpdateCount() != -1))) {
256                ResultSet rs = stmt.getResultSet();
257                if (rs == null) {
258                    return getNextResultSet(stmt);
259                }
260                return new ResultSetWrapper(rs, configuration);
261            }
262        } catch (Exception e) {
263            // Intentionally ignored.
264        }
265        return null;
266    }
267
268    private void closeResultSet(ResultSet rs) {
269        try {
270            if (rs != null) {
271                rs.close();
272            }
273        } catch (SQLException e) {
274            // ignore
275        }
276    }
277
278    private void cleanUpAfterHandlingResultSet() {
279        nestedResultObjects.clear();
280    }
281
282    private void validateResultMapsCount(ResultSetWrapper rsw, int resultMapCount) {
283        if (rsw != null && resultMapCount < 1) {
284            throw new ExecutorException(
285                "A query was run and no Result Maps were found for the Mapped Statement '" + mappedStatement.getId()
286                    + "'. 'resultType' or 'resultMap' must be specified when there is no corresponding method.");
287        }
288    }
289
290    private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List<Object> multipleResults,
291                                 ResultMapping parentMapping) throws SQLException {
292        try {
293            if (parentMapping != null) {
294                handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping);
295            } else if (resultHandler == null) {
296                if (resultMap.getId().startsWith("com.mybatisflex.core.row.RowMapper.selectFirstAndSecondColumnsAsMap")) {
297                    ResultSet resultSet = rsw.getResultSet();
298                    skipRows(resultSet, rowBounds);
299                    Map<Object, Object> row = new HashMap<>();
300                    while (!resultSet.isClosed() && resultSet.next()) {
301                        row.put(resultSet.getObject(1), resultSet.getObject(2));
302                    }
303                    List<Map<Object, Object>> mapArrayList = new ArrayList<>(1);
304                    mapArrayList.add(row);
305                    multipleResults.add(mapArrayList);
306                } else {
307                    DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory);
308                    handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null);
309                    multipleResults.add(defaultResultHandler.getResultList());
310                }
311            } else {
312                handleRowValues(rsw, resultMap, resultHandler, rowBounds, null);
313            }
314        } finally {
315            // issue #228 (close resultsets)
316            closeResultSet(rsw.getResultSet());
317        }
318    }
319
320    @SuppressWarnings("unchecked")
321    private List<Object> collapseSingleResultList(List<Object> multipleResults) {
322        return multipleResults.size() == 1 ? (List<Object>) multipleResults.get(0) : multipleResults;
323    }
324
325    //
326    // HANDLE ROWS FOR SIMPLE RESULTMAP
327    //
328
329    @Override
330    public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler<?> resultHandler,
331                                RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
332        if (resultMap.hasNestedResultMaps()) {
333            ensureNoRowBounds();
334            checkResultHandler();
335            handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
336        } else {
337            handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping);
338        }
339    }
340
341    private void ensureNoRowBounds() {
342        if (configuration.isSafeRowBoundsEnabled() && rowBounds != null
343            && (rowBounds.getLimit() < RowBounds.NO_ROW_LIMIT || rowBounds.getOffset() > RowBounds.NO_ROW_OFFSET)) {
344            throw new ExecutorException(
345                "Mapped Statements with nested result mappings cannot be safely constrained by RowBounds. "
346                    + "Use safeRowBoundsEnabled=false setting to bypass this check.");
347        }
348    }
349
350    @Override
351    protected void checkResultHandler() {
352        if (resultHandler != null && configuration.isSafeResultHandlerEnabled() && !mappedStatement.isResultOrdered()) {
353            throw new ExecutorException(
354                "Mapped Statements with nested result mappings cannot be safely used with a custom ResultHandler. "
355                    + "Use safeResultHandlerEnabled=false setting to bypass this check "
356                    + "or ensure your statement returns ordered data and set resultOrdered=true on it.");
357        }
358    }
359
360    private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap,
361                                                   ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
362        DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
363        ResultSet resultSet = rsw.getResultSet();
364        skipRows(resultSet, rowBounds);
365        while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
366            ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
367            Object rowValue = getRowValue(rsw, discriminatedResultMap, null);
368            storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
369        }
370    }
371
372    private void storeObject(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext, Object rowValue,
373                             ResultMapping parentMapping, ResultSet rs) throws SQLException {
374        if (parentMapping != null) {
375            linkToParents(rs, parentMapping, rowValue);
376        } else {
377            callResultHandler(resultHandler, resultContext, rowValue);
378        }
379    }
380
381    @SuppressWarnings("unchecked" /* because ResultHandler<?> is always ResultHandler<Object> */)
382    private void callResultHandler(ResultHandler<?> resultHandler, DefaultResultContext<Object> resultContext,
383                                   Object rowValue) {
384        resultContext.nextResultObject(rowValue);
385        ((ResultHandler<Object>) resultHandler).handleResult(resultContext);
386    }
387
388    private boolean shouldProcessMoreRows(ResultContext<?> context, RowBounds rowBounds) {
389        return !context.isStopped() && context.getResultCount() < rowBounds.getLimit();
390    }
391
392    private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException {
393        if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) {
394            if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) {
395                rs.absolute(rowBounds.getOffset());
396            }
397        } else {
398            for (int i = 0; i < rowBounds.getOffset(); i++) {
399                if (!rs.next()) {
400                    break;
401                }
402            }
403        }
404    }
405
406    //
407    // GET VALUE FROM ROW FOR SIMPLE RESULT MAP
408    //
409
410    private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException {
411        final ResultLoaderMap lazyLoader = new ResultLoaderMap();
412        Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
413        if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
414            final MetaObject metaObject = configuration.newMetaObject(rowValue);
415            boolean foundValues = this.useConstructorMappings;
416            if (shouldApplyAutomaticMappings(resultMap, false)) {
417                foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
418            }
419            foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
420            foundValues = lazyLoader.size() > 0 || foundValues;
421            rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
422        }
423        return rowValue;
424    }
425
426    //
427    // GET VALUE FROM ROW FOR NESTED RESULT MAP
428    //
429
430    private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, String columnPrefix,
431                               Object partialObject) throws SQLException {
432        final String resultMapId = resultMap.getId();
433        Object rowValue = partialObject;
434        if (rowValue != null) {
435            final MetaObject metaObject = configuration.newMetaObject(rowValue);
436            putAncestor(rowValue, resultMapId);
437            applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false);
438            ancestorObjects.remove(resultMapId);
439        } else {
440            final ResultLoaderMap lazyLoader = new ResultLoaderMap();
441            rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix);
442            if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
443                final MetaObject metaObject = configuration.newMetaObject(rowValue);
444                boolean foundValues = this.useConstructorMappings;
445                if (shouldApplyAutomaticMappings(resultMap, true)) {
446                    foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues;
447                }
448                foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues;
449                putAncestor(rowValue, resultMapId);
450                foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true)
451                    || foundValues;
452                ancestorObjects.remove(resultMapId);
453                foundValues = lazyLoader.size() > 0 || foundValues;
454                rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null;
455            }
456            if (combinedKey != CacheKey.NULL_CACHE_KEY) {
457                nestedResultObjects.put(combinedKey, rowValue);
458            }
459        }
460        return rowValue;
461    }
462
463    private void putAncestor(Object resultObject, String resultMapId) {
464        ancestorObjects.put(resultMapId, resultObject);
465    }
466
467    private boolean shouldApplyAutomaticMappings(ResultMap resultMap, boolean isNested) {
468        if (resultMap.getAutoMapping() != null) {
469            return resultMap.getAutoMapping();
470        }
471        if (isNested) {
472            return AutoMappingBehavior.FULL == configuration.getAutoMappingBehavior();
473        } else {
474            return AutoMappingBehavior.NONE != configuration.getAutoMappingBehavior();
475        }
476    }
477
478    //
479    // PROPERTY MAPPINGS
480    //
481
482    private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
483                                          ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
484        final Collection<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
485        boolean foundValues = false;
486        final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
487        for (ResultMapping propertyMapping : propertyMappings) {
488            String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
489            if (propertyMapping.getNestedResultMapId() != null) {
490                // the user added a column attribute to a nested result map, ignore it
491                column = null;
492            }
493            if (propertyMapping.isCompositeResult()
494                || column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))
495                || propertyMapping.getResultSet() != null) {
496                Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader,
497                    columnPrefix);
498                // issue #541 make property optional
499                final String property = propertyMapping.getProperty();
500                if (property == null) {
501                    continue;
502                }
503                if (value == DEFERRED) {
504                    foundValues = true;
505                    continue;
506                }
507                if (value != null) {
508                    foundValues = true;
509                }
510                if (value != null
511                    || configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive()) {
512                    // gcode issue #377, call setter on nulls (value is not 'found')
513                    metaObject.setValue(property, value);
514                }
515            }
516        }
517        return foundValues;
518    }
519
520    private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping,
521                                           ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
522        if (propertyMapping.getNestedQueryId() != null) {
523            return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix);
524        }
525        if (propertyMapping.getResultSet() != null) {
526            addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK?
527            return DEFERRED;
528        } else {
529            final TypeHandler<?> typeHandler = propertyMapping.getTypeHandler();
530            final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix);
531            return typeHandler.getResult(rs, column);
532        }
533    }
534
535    private List<UnMappedColumnAutoMapping> createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap,
536                                                                    MetaObject metaObject, String columnPrefix) throws SQLException {
537        final String mapKey = resultMap.getId() + ":" + columnPrefix;
538        List<UnMappedColumnAutoMapping> autoMapping = autoMappingsCache.get(mapKey);
539        if (autoMapping == null) {
540            autoMapping = new ArrayList<>();
541            final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
542            // Remove the entry to release the memory
543            List<String> mappedInConstructorAutoMapping = constructorAutoMappingColumns.remove(mapKey);
544            if (mappedInConstructorAutoMapping != null) {
545                unmappedColumnNames.removeAll(mappedInConstructorAutoMapping);
546            }
547            for (String columnName : unmappedColumnNames) {
548                String propertyName = columnName;
549                if (columnPrefix != null && !columnPrefix.isEmpty()) {
550                    // When columnPrefix is specified,
551                    // ignore columns without the prefix.
552                    if (!columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
553                        continue;
554                    }
555                    propertyName = columnName.substring(columnPrefix.length());
556                }
557                final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase());
558                if (property != null && metaObject.hasSetter(property)) {
559                    if (resultMap.getMappedProperties().contains(property)) {
560                        continue;
561                    }
562                    final Class<?> propertyType = metaObject.getSetterType(property);
563                    if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) {
564                        final TypeHandler<?> typeHandler = rsw.getTypeHandler(propertyType, columnName);
565                        autoMapping
566                            .add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive()));
567                    } else {
568                        configuration.getAutoMappingUnknownColumnBehavior().doAction(mappedStatement, columnName, property,
569                            propertyType);
570                    }
571                } else {
572                    configuration.getAutoMappingUnknownColumnBehavior().doAction(mappedStatement, columnName,
573                        property != null ? property : propertyName, null);
574                }
575            }
576            autoMappingsCache.put(mapKey, autoMapping);
577        }
578        return autoMapping;
579    }
580
581    private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
582                                           String columnPrefix) throws SQLException {
583        List<UnMappedColumnAutoMapping> autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix);
584        boolean foundValues = false;
585        if (!autoMapping.isEmpty()) {
586            for (UnMappedColumnAutoMapping mapping : autoMapping) {
587                final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column);
588                if (value != null) {
589                    foundValues = true;
590                }
591                if (value != null || configuration.isCallSettersOnNulls() && !mapping.primitive) {
592                    // gcode issue #377, call setter on nulls (value is not 'found')
593                    metaObject.setValue(mapping.property, value);
594                }
595            }
596        } else {
597            UnMappedColumnHandler unMappedColumnHandler = FlexGlobalConfig.getDefaultConfig().getUnMappedColumnHandler();
598            if (unMappedColumnHandler != null) {
599                // 增加未匹配列自定义处理
600                final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
601                for (String unmappedColumnName : unmappedColumnNames) {
602                    // 不明确类型,直接取object
603                    final Object value = typeHandlerRegistry.getMappingTypeHandler(UnknownTypeHandler.class).getResult(rsw.getResultSet(), unmappedColumnName);
604                    // 自定义处理未匹配列
605                    unMappedColumnHandler.handleUnMappedColumn(metaObject, unmappedColumnName, value);
606                }
607            }
608        }
609        return foundValues;
610    }
611
612    // MULTIPLE RESULT SETS
613
614    private void linkToParents(ResultSet rs, ResultMapping parentMapping, Object rowValue) throws SQLException {
615        CacheKey parentKey = createKeyForMultipleResults(rs, parentMapping, parentMapping.getColumn(),
616            parentMapping.getForeignColumn());
617        List<PendingRelation> parents = pendingRelations.get(parentKey);
618        if (parents != null) {
619            for (PendingRelation parent : parents) {
620                if (parent != null && rowValue != null) {
621                    linkObjects(parent.metaObject, parent.propertyMapping, rowValue);
622                }
623            }
624        }
625    }
626
627    private void addPendingChildRelation(ResultSet rs, MetaObject metaResultObject, ResultMapping parentMapping)
628        throws SQLException {
629        CacheKey cacheKey = createKeyForMultipleResults(rs, parentMapping, parentMapping.getColumn(),
630            parentMapping.getColumn());
631        PendingRelation deferLoad = new PendingRelation();
632        deferLoad.metaObject = metaResultObject;
633        deferLoad.propertyMapping = parentMapping;
634        List<PendingRelation> relations = MapUtil.computeIfAbsent(pendingRelations, cacheKey, k -> new ArrayList<>());
635        // issue #255
636        relations.add(deferLoad);
637        ResultMapping previous = nextResultMaps.get(parentMapping.getResultSet());
638        if (previous == null) {
639            nextResultMaps.put(parentMapping.getResultSet(), parentMapping);
640        } else if (!previous.equals(parentMapping)) {
641            throw new ExecutorException("Two different properties are mapped to the same resultSet");
642        }
643    }
644
645    private CacheKey createKeyForMultipleResults(ResultSet rs, ResultMapping resultMapping, String names, String columns)
646        throws SQLException {
647        CacheKey cacheKey = new CacheKey();
648        cacheKey.update(resultMapping);
649        if (columns != null && names != null) {
650            String[] columnsArray = columns.split(",");
651            String[] namesArray = names.split(",");
652            for (int i = 0; i < columnsArray.length; i++) {
653                Object value = rs.getString(columnsArray[i]);
654                if (value != null) {
655                    cacheKey.update(namesArray[i]);
656                    cacheKey.update(value);
657                }
658            }
659        }
660        return cacheKey;
661    }
662
663    //
664    // INSTANTIATION & CONSTRUCTOR MAPPING
665    //
666
667    private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader,
668                                      String columnPrefix) throws SQLException {
669        this.useConstructorMappings = false; // reset previous mapping result
670        final List<Class<?>> constructorArgTypes = new ArrayList<>();
671        final List<Object> constructorArgs = new ArrayList<>();
672        Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix);
673        if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) {
674            final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings();
675            for (ResultMapping propertyMapping : propertyMappings) {
676                // issue gcode #109 && issue #149
677                if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) {
678                    resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration,
679                        objectFactory, constructorArgTypes, constructorArgs);
680                    break;
681                }
682            }
683        }
684        this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result
685        return resultObject;
686    }
687
688    private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes,
689                                      List<Object> constructorArgs, String columnPrefix) throws SQLException {
690        final Class<?> resultType = resultMap.getType();
691        final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory);
692        final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings();
693        if (hasTypeHandlerForResultObject(rsw, resultType)) {
694            return createPrimitiveResultObject(rsw, resultMap, columnPrefix);
695        }
696        if (!constructorMappings.isEmpty()) {
697            return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs,
698                columnPrefix);
699        } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) {
700            return objectFactory.create(resultType);
701        } else if (shouldApplyAutomaticMappings(resultMap, false)) {
702            return createByConstructorSignature(rsw, resultMap, columnPrefix, resultType, constructorArgTypes,
703                constructorArgs);
704        }
705        throw new ExecutorException("Do not know how to create an instance of " + resultType);
706    }
707
708    Object createParameterizedResultObject(ResultSetWrapper rsw, Class<?> resultType,
709                                           List<ResultMapping> constructorMappings, List<Class<?>> constructorArgTypes, List<Object> constructorArgs,
710                                           String columnPrefix) {
711        boolean foundValues = false;
712        for (ResultMapping constructorMapping : constructorMappings) {
713            final Class<?> parameterType = constructorMapping.getJavaType();
714            final String column = constructorMapping.getColumn();
715            final Object value;
716            try {
717                if (constructorMapping.getNestedQueryId() != null) {
718                    value = getNestedQueryConstructorValue(rsw.getResultSet(), constructorMapping, columnPrefix);
719                } else if (constructorMapping.getNestedResultMapId() != null) {
720                    final ResultMap resultMap = configuration.getResultMap(constructorMapping.getNestedResultMapId());
721                    value = getRowValue(rsw, resultMap, getColumnPrefix(columnPrefix, constructorMapping));
722                } else {
723                    final TypeHandler<?> typeHandler = constructorMapping.getTypeHandler();
724                    value = typeHandler.getResult(rsw.getResultSet(), prependPrefix(column, columnPrefix));
725                }
726            } catch (ResultMapException | SQLException e) {
727                throw new ExecutorException("Could not process result for mapping: " + constructorMapping, e);
728            }
729            constructorArgTypes.add(parameterType);
730            constructorArgs.add(value);
731            foundValues = value != null || foundValues;
732        }
733        return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
734    }
735
736    private Object createByConstructorSignature(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix,
737                                                Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) throws SQLException {
738        return applyConstructorAutomapping(rsw, resultMap, columnPrefix, resultType, constructorArgTypes, constructorArgs,
739            findConstructorForAutomapping(resultType, rsw).orElseThrow(() -> new ExecutorException(
740                "No constructor found in " + resultType.getName() + " matching " + rsw.getClassNames())));
741    }
742
743    private Optional<Constructor<?>> findConstructorForAutomapping(final Class<?> resultType, ResultSetWrapper rsw) {
744        Constructor<?>[] constructors = resultType.getDeclaredConstructors();
745        if (constructors.length == 1) {
746            return Optional.of(constructors[0]);
747        }
748        Optional<Constructor<?>> annotated = Arrays.stream(constructors)
749            .filter(x -> x.isAnnotationPresent(AutomapConstructor.class)).reduce((x, y) -> {
750                throw new ExecutorException("@AutomapConstructor should be used in only one constructor.");
751            });
752        if (annotated.isPresent()) {
753            return annotated;
754        }
755        if (configuration.isArgNameBasedConstructorAutoMapping()) {
756            // Finding-best-match type implementation is possible,
757            // but using @AutomapConstructor seems sufficient.
758            throw new ExecutorException(MessageFormat.format(
759                "'argNameBasedConstructorAutoMapping' is enabled and the class ''{0}'' has multiple constructors, so @AutomapConstructor must be added to one of the constructors.",
760                resultType.getName()));
761        } else {
762            return Arrays.stream(constructors).filter(x -> findUsableConstructorByArgTypes(x, rsw.getJdbcTypes())).findAny();
763        }
764    }
765
766    private boolean findUsableConstructorByArgTypes(final Constructor<?> constructor, final List<JdbcType> jdbcTypes) {
767        final Class<?>[] parameterTypes = constructor.getParameterTypes();
768        if (parameterTypes.length != jdbcTypes.size()) {
769            return false;
770        }
771        for (int i = 0; i < parameterTypes.length; i++) {
772            if (!typeHandlerRegistry.hasTypeHandler(parameterTypes[i], jdbcTypes.get(i))) {
773                return false;
774            }
775        }
776        return true;
777    }
778
779    private Object applyConstructorAutomapping(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix,
780                                               Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, Constructor<?> constructor)
781        throws SQLException {
782        boolean foundValues = false;
783        if (configuration.isArgNameBasedConstructorAutoMapping()) {
784            foundValues = applyArgNameBasedConstructorAutoMapping(rsw, resultMap, columnPrefix, constructorArgTypes,
785                constructorArgs, constructor, foundValues);
786        } else {
787            foundValues = applyColumnOrderBasedConstructorAutomapping(rsw, constructorArgTypes, constructorArgs, constructor,
788                foundValues);
789        }
790        return foundValues || configuration.isReturnInstanceForEmptyRow()
791            ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null;
792    }
793
794    private boolean applyColumnOrderBasedConstructorAutomapping(ResultSetWrapper rsw, List<Class<?>> constructorArgTypes,
795                                                                List<Object> constructorArgs, Constructor<?> constructor, boolean foundValues) throws SQLException {
796
797        // fixed IndexOutOfBoundsException https://gitee.com/mybatis-flex/mybatis-flex/issues/I98ZO9
798        List<String> columnNames = rsw.getColumnNames();
799        if (columnNames.size() < constructor.getParameterCount()) {
800            throw new IllegalArgumentException("Can not invoke the constructor[" + buildMethodString(constructor) + "] with value names: "
801                + Arrays.toString(columnNames.toArray()) + ",\n"
802                + "Perhaps you can add a default (no parameters) constructor to fix it."
803            );
804        }
805
806        Class<?>[] parameterTypes = constructor.getParameterTypes();
807        for (int i = 0; i < parameterTypes.length; i++) {
808            Class<?> parameterType = parameterTypes[i];
809            String columnName = columnNames.get(i);
810            TypeHandler<?> typeHandler = rsw.getTypeHandler(parameterType, columnName);
811            Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
812            constructorArgTypes.add(parameterType);
813            constructorArgs.add(value);
814            foundValues = value != null || foundValues;
815        }
816        return foundValues;
817    }
818
819
820    private static String buildMethodString(Executable method) {
821        StringBuilder sb = new StringBuilder()
822            .append(method.getDeclaringClass().getName())
823            .append(".")
824            .append(method.getName())
825            .append("(");
826
827        Class<?>[] params = method.getParameterTypes();
828        int in = 0;
829        for (Class<?> clazz : params) {
830            sb.append(clazz.getName());
831            if (++in < params.length) {
832                sb.append(",");
833            }
834        }
835
836        return sb.append(")").toString();
837    }
838
839
840    private boolean applyArgNameBasedConstructorAutoMapping(ResultSetWrapper rsw, ResultMap resultMap,
841                                                            String columnPrefix, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, Constructor<?> constructor,
842                                                            boolean foundValues) throws SQLException {
843        List<String> missingArgs = null;
844        Parameter[] params = constructor.getParameters();
845        for (Parameter param : params) {
846            boolean columnNotFound = true;
847            Param paramAnno = param.getAnnotation(Param.class);
848            String paramName = paramAnno == null ? param.getName() : paramAnno.value();
849            for (String columnName : rsw.getColumnNames()) {
850                if (columnMatchesParam(columnName, paramName, columnPrefix)) {
851                    Class<?> paramType = param.getType();
852                    TypeHandler<?> typeHandler = rsw.getTypeHandler(paramType, columnName);
853                    Object value = typeHandler.getResult(rsw.getResultSet(), columnName);
854                    constructorArgTypes.add(paramType);
855                    constructorArgs.add(value);
856                    final String mapKey = resultMap.getId() + ":" + columnPrefix;
857                    if (!autoMappingsCache.containsKey(mapKey)) {
858                        MapUtil.computeIfAbsent(constructorAutoMappingColumns, mapKey, k -> new ArrayList<>()).add(columnName);
859                    }
860                    columnNotFound = false;
861                    foundValues = value != null || foundValues;
862                }
863            }
864            if (columnNotFound) {
865                if (missingArgs == null) {
866                    missingArgs = new ArrayList<>();
867                }
868                missingArgs.add(paramName);
869            }
870        }
871        if (foundValues && constructorArgs.size() < params.length) {
872            throw new ExecutorException(MessageFormat.format(
873                "Constructor auto-mapping of ''{1}'' failed " + "because ''{0}'' were not found in the result set; "
874                    + "Available columns are ''{2}'' and mapUnderscoreToCamelCase is ''{3}''.",
875                missingArgs, constructor, rsw.getColumnNames(), configuration.isMapUnderscoreToCamelCase()));
876        }
877        return foundValues;
878    }
879
880    private boolean columnMatchesParam(String columnName, String paramName, String columnPrefix) {
881        if (columnPrefix != null) {
882            if (!columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
883                return false;
884            }
885            columnName = columnName.substring(columnPrefix.length());
886        }
887        return paramName
888            .equalsIgnoreCase(configuration.isMapUnderscoreToCamelCase() ? columnName.replace("_", "") : columnName);
889    }
890
891    protected Object createPrimitiveResultObject(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix)
892        throws SQLException {
893        final Class<?> resultType = resultMap.getType();
894        final String columnName;
895        if (!resultMap.getResultMappings().isEmpty()) {
896            final List<ResultMapping> resultMappingList = resultMap.getResultMappings();
897            final ResultMapping mapping = resultMappingList.get(0);
898            columnName = prependPrefix(mapping.getColumn(), columnPrefix);
899        } else {
900            columnName = rsw.getColumnNames().get(0);
901        }
902        final TypeHandler<?> typeHandler = rsw.getTypeHandler(resultType, columnName);
903        return typeHandler.getResult(rsw.getResultSet(), columnName);
904    }
905
906    //
907    // NESTED QUERY
908    //
909
910    private Object getNestedQueryConstructorValue(ResultSet rs, ResultMapping constructorMapping, String columnPrefix)
911        throws SQLException {
912        final String nestedQueryId = constructorMapping.getNestedQueryId();
913        final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
914        final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
915        final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, constructorMapping,
916            nestedQueryParameterType, columnPrefix);
917        Object value = null;
918        if (nestedQueryParameterObject != null) {
919            final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
920            final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT,
921                nestedBoundSql);
922            final Class<?> targetType = constructorMapping.getJavaType();
923            final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery,
924                nestedQueryParameterObject, targetType, key, nestedBoundSql);
925            value = resultLoader.loadResult();
926        }
927        return value;
928    }
929
930    private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping,
931                                              ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException {
932        final String nestedQueryId = propertyMapping.getNestedQueryId();
933        final String property = propertyMapping.getProperty();
934        final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId);
935        final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType();
936        final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping,
937            nestedQueryParameterType, columnPrefix);
938        Object value = null;
939        if (nestedQueryParameterObject != null) {
940            final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject);
941            final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT,
942                nestedBoundSql);
943            final Class<?> targetType = propertyMapping.getJavaType();
944            if (executor.isCached(nestedQuery, key)) {
945                executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType);
946                value = DEFERRED;
947            } else {
948                final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery,
949                    nestedQueryParameterObject, targetType, key, nestedBoundSql);
950                if (propertyMapping.isLazy()) {
951                    lazyLoader.addLoader(property, metaResultObject, resultLoader);
952                    value = DEFERRED;
953                } else {
954                    value = resultLoader.loadResult();
955                }
956            }
957        }
958        return value;
959    }
960
961    private Object prepareParameterForNestedQuery(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType,
962                                                  String columnPrefix) throws SQLException {
963        if (resultMapping.isCompositeResult()) {
964            return prepareCompositeKeyParameter(rs, resultMapping, parameterType, columnPrefix);
965        }
966        return prepareSimpleKeyParameter(rs, resultMapping, parameterType, columnPrefix);
967    }
968
969    private Object prepareSimpleKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType,
970                                             String columnPrefix) throws SQLException {
971        final TypeHandler<?> typeHandler;
972        if (typeHandlerRegistry.hasTypeHandler(parameterType)) {
973            typeHandler = typeHandlerRegistry.getTypeHandler(parameterType);
974        } else {
975            typeHandler = typeHandlerRegistry.getUnknownTypeHandler();
976        }
977        return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix));
978    }
979
980    private Object prepareCompositeKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType,
981                                                String columnPrefix) throws SQLException {
982        final Object parameterObject = instantiateParameterObject(parameterType);
983        final MetaObject metaObject = configuration.newMetaObject(parameterObject);
984        boolean foundValues = false;
985        for (ResultMapping innerResultMapping : resultMapping.getComposites()) {
986            final Class<?> propType = metaObject.getSetterType(innerResultMapping.getProperty());
987            final TypeHandler<?> typeHandler = typeHandlerRegistry.getTypeHandler(propType);
988            final Object propValue = typeHandler.getResult(rs, prependPrefix(innerResultMapping.getColumn(), columnPrefix));
989            // issue #353 & #560 do not execute nested query if key is null
990            if (propValue != null) {
991                metaObject.setValue(innerResultMapping.getProperty(), propValue);
992                foundValues = true;
993            }
994        }
995        return foundValues ? parameterObject : null;
996    }
997
998    private Object instantiateParameterObject(Class<?> parameterType) {
999        if (parameterType == null) {
1000            return new HashMap<>();
1001        }
1002        if (ParamMap.class.equals(parameterType)) {
1003            return new HashMap<>(); // issue #649
1004        } else {
1005            return objectFactory.create(parameterType);
1006        }
1007    }
1008
1009    //
1010    // DISCRIMINATOR
1011    //
1012
1013    @Override
1014    public ResultMap resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix)
1015        throws SQLException {
1016        Set<String> pastDiscriminators = new HashSet<>();
1017        Discriminator discriminator = resultMap.getDiscriminator();
1018        while (discriminator != null) {
1019            final Object value = getDiscriminatorValue(rs, discriminator, columnPrefix);
1020            final String discriminatedMapId = discriminator.getMapIdFor(String.valueOf(value));
1021            if (!configuration.hasResultMap(discriminatedMapId)) {
1022                break;
1023            }
1024            resultMap = configuration.getResultMap(discriminatedMapId);
1025            Discriminator lastDiscriminator = discriminator;
1026            discriminator = resultMap.getDiscriminator();
1027            if (discriminator == lastDiscriminator || !pastDiscriminators.add(discriminatedMapId)) {
1028                break;
1029            }
1030        }
1031        return resultMap;
1032    }
1033
1034    private Object getDiscriminatorValue(ResultSet rs, Discriminator discriminator, String columnPrefix)
1035        throws SQLException {
1036        final ResultMapping resultMapping = discriminator.getResultMapping();
1037        final TypeHandler<?> typeHandler = resultMapping.getTypeHandler();
1038        return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix));
1039    }
1040
1041
1042    protected String prependPrefix(String columnName, String prefix) {
1043        if (columnName == null || columnName.length() == 0 || prefix == null || prefix.length() == 0) {
1044            return columnName;
1045        }
1046        return prefix + columnName;
1047    }
1048
1049    //
1050    // HANDLE NESTED RESULT MAPS
1051    //
1052
1053    private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap,
1054                                                   ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException {
1055        final DefaultResultContext<Object> resultContext = new DefaultResultContext<>();
1056        ResultSet resultSet = rsw.getResultSet();
1057        skipRows(resultSet, rowBounds);
1058        Object rowValue = previousRowValue;
1059        while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) {
1060            final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null);
1061            final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null);
1062            Object partialObject = nestedResultObjects.get(rowKey);
1063            // issue #577 && #542
1064            if (mappedStatement.isResultOrdered()) {
1065                if (partialObject == null && rowValue != null) {
1066                    nestedResultObjects.clear();
1067                    storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
1068                }
1069                rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
1070            } else {
1071                rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject);
1072                if (partialObject == null) {
1073                    storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
1074                }
1075            }
1076        }
1077        if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) {
1078            storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet);
1079            previousRowValue = null;
1080        } else if (rowValue != null) {
1081            previousRowValue = rowValue;
1082        }
1083    }
1084
1085    //
1086    // NESTED RESULT MAP (JOIN MAPPING)
1087    //
1088
1089    private boolean applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject,
1090                                              String parentPrefix, CacheKey parentRowKey, boolean newObject) {
1091        boolean foundValues = false;
1092        for (ResultMapping resultMapping : resultMap.getPropertyResultMappings()) {
1093            final String nestedResultMapId = resultMapping.getNestedResultMapId();
1094            if (nestedResultMapId != null && resultMapping.getResultSet() == null) {
1095                try {
1096                    final String columnPrefix = getColumnPrefix(parentPrefix, resultMapping);
1097                    final ResultMap nestedResultMap = getNestedResultMap(rsw.getResultSet(), nestedResultMapId, columnPrefix);
1098                    if (resultMapping.getColumnPrefix() == null) {
1099                        // try to fill circular reference only when columnPrefix
1100                        // is not specified for the nested result map (issue #215)
1101                        Object ancestorObject = ancestorObjects.get(nestedResultMapId);
1102                        if (ancestorObject != null) {
1103                            if (newObject) {
1104                                linkObjects(metaObject, resultMapping, ancestorObject); // issue #385
1105                            }
1106                            continue;
1107                        }
1108                    }
1109                    final CacheKey rowKey = createRowKey(nestedResultMap, rsw, columnPrefix);
1110                    final CacheKey combinedKey = combineKeys(rowKey, parentRowKey);
1111                    Object rowValue = nestedResultObjects.get(combinedKey);
1112                    boolean knownValue = rowValue != null;
1113                    instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject); // mandatory
1114                    if (anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw)) {
1115                        rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue);
1116                        if (rowValue != null && !knownValue) {
1117                            linkObjects(metaObject, resultMapping, rowValue);
1118                            foundValues = true;
1119                        }
1120                    }
1121                } catch (SQLException e) {
1122                    throw new ExecutorException(
1123                        "Error getting nested result map values for '" + resultMapping.getProperty() + "'.  Cause: " + e, e);
1124                }
1125            }
1126        }
1127        return foundValues;
1128    }
1129
1130    private String getColumnPrefix(String parentPrefix, ResultMapping resultMapping) {
1131        final StringBuilder columnPrefixBuilder = new StringBuilder();
1132        if (parentPrefix != null) {
1133            columnPrefixBuilder.append(parentPrefix);
1134        }
1135        if (resultMapping.getColumnPrefix() != null) {
1136            columnPrefixBuilder.append(resultMapping.getColumnPrefix());
1137        }
1138        return columnPrefixBuilder.length() == 0 ? null : columnPrefixBuilder.toString().toUpperCase(Locale.ENGLISH);
1139    }
1140
1141    private boolean anyNotNullColumnHasValue(ResultMapping resultMapping, String columnPrefix, ResultSetWrapper rsw)
1142        throws SQLException {
1143        Set<String> notNullColumns = resultMapping.getNotNullColumns();
1144        if (notNullColumns != null && !notNullColumns.isEmpty()) {
1145            ResultSet rs = rsw.getResultSet();
1146            for (String column : notNullColumns) {
1147                rs.getObject(prependPrefix(column, columnPrefix));
1148                if (!rs.wasNull()) {
1149                    return true;
1150                }
1151            }
1152            return false;
1153        }
1154        if (columnPrefix != null) {
1155            for (String columnName : rsw.getColumnNames()) {
1156                if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix.toUpperCase(Locale.ENGLISH))) {
1157                    return true;
1158                }
1159            }
1160            return false;
1161        }
1162        return true;
1163    }
1164
1165    private ResultMap getNestedResultMap(ResultSet rs, String nestedResultMapId, String columnPrefix)
1166        throws SQLException {
1167        ResultMap nestedResultMap = configuration.getResultMap(nestedResultMapId);
1168        return resolveDiscriminatedResultMap(rs, nestedResultMap, columnPrefix);
1169    }
1170
1171    //
1172    // UNIQUE RESULT KEY
1173    //
1174
1175    private CacheKey createRowKey(ResultMap resultMap, ResultSetWrapper rsw, String columnPrefix) throws SQLException {
1176        final CacheKey cacheKey = new CacheKey();
1177        cacheKey.update(resultMap.getId());
1178        List<ResultMapping> resultMappings = getResultMappingsForRowKey(resultMap);
1179        if (resultMappings.isEmpty()) {
1180            if (Map.class.isAssignableFrom(resultMap.getType())) {
1181                createRowKeyForMap(rsw, cacheKey);
1182            } else {
1183                createRowKeyForUnmappedProperties(resultMap, rsw, cacheKey, columnPrefix);
1184            }
1185        } else {
1186            createRowKeyForMappedProperties(resultMap, rsw, cacheKey, resultMappings, columnPrefix);
1187        }
1188        if (cacheKey.getUpdateCount() < 2) {
1189            return CacheKey.NULL_CACHE_KEY;
1190        }
1191        return cacheKey;
1192    }
1193
1194    private CacheKey combineKeys(CacheKey rowKey, CacheKey parentRowKey) {
1195        if (rowKey.getUpdateCount() > 1 && parentRowKey.getUpdateCount() > 1) {
1196            CacheKey combinedKey;
1197            try {
1198                combinedKey = rowKey.clone();
1199            } catch (CloneNotSupportedException e) {
1200                throw new ExecutorException("Error cloning cache key.  Cause: " + e, e);
1201            }
1202            combinedKey.update(parentRowKey);
1203            return combinedKey;
1204        }
1205        return CacheKey.NULL_CACHE_KEY;
1206    }
1207
1208    private List<ResultMapping> getResultMappingsForRowKey(ResultMap resultMap) {
1209        List<ResultMapping> resultMappings = resultMap.getIdResultMappings();
1210        if (resultMappings.isEmpty()) {
1211            resultMappings = resultMap.getPropertyResultMappings();
1212        }
1213        return resultMappings;
1214    }
1215
1216    private void createRowKeyForMappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey,
1217                                                 List<ResultMapping> resultMappings, String columnPrefix) throws SQLException {
1218        for (ResultMapping resultMapping : resultMappings) {
1219            if (resultMapping.isSimple()) {
1220                final String column = prependPrefix(resultMapping.getColumn(), columnPrefix);
1221                final TypeHandler<?> th = resultMapping.getTypeHandler();
1222                Collection<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix);
1223                // Issue #114
1224                if (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) {
1225                    final Object value = th.getResult(rsw.getResultSet(), column);
1226                    if (value != null || configuration.isReturnInstanceForEmptyRow()) {
1227                        cacheKey.update(column);
1228                        cacheKey.update(value);
1229                    }
1230                }
1231            }
1232        }
1233    }
1234
1235    private void createRowKeyForUnmappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey,
1236                                                   String columnPrefix) throws SQLException {
1237        final MetaClass metaType = MetaClass.forClass(resultMap.getType(), reflectorFactory);
1238        List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix);
1239        for (String column : unmappedColumnNames) {
1240            String property = column;
1241            if (columnPrefix != null && !columnPrefix.isEmpty()) {
1242                // When columnPrefix is specified, ignore columns without the prefix.
1243                if (!column.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) {
1244                    continue;
1245                }
1246                property = column.substring(columnPrefix.length());
1247            }
1248            if (metaType.findProperty(property, configuration.isMapUnderscoreToCamelCase()) != null) {
1249                String value = rsw.getResultSet().getString(column);
1250                if (value != null) {
1251                    cacheKey.update(column);
1252                    cacheKey.update(value);
1253                }
1254            }
1255        }
1256    }
1257
1258    private void createRowKeyForMap(ResultSetWrapper rsw, CacheKey cacheKey) throws SQLException {
1259        List<String> columnNames = rsw.getColumnNames();
1260        for (String columnName : columnNames) {
1261            final String value = rsw.getResultSet().getString(columnName);
1262            if (value != null) {
1263                cacheKey.update(columnName);
1264                cacheKey.update(value);
1265            }
1266        }
1267    }
1268
1269    private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue) {
1270        final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject);
1271        if (collectionProperty != null) {
1272            final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty);
1273            targetMetaObject.add(rowValue);
1274        } else {
1275            metaObject.setValue(resultMapping.getProperty(), rowValue);
1276        }
1277    }
1278
1279    private Object instantiateCollectionPropertyIfAppropriate(ResultMapping resultMapping, MetaObject metaObject) {
1280        final String propertyName = resultMapping.getProperty();
1281        Object propertyValue = metaObject.getValue(propertyName);
1282        if (propertyValue == null) {
1283            Class<?> type = resultMapping.getJavaType();
1284            if (type == null) {
1285                type = metaObject.getSetterType(propertyName);
1286            }
1287            try {
1288                if (objectFactory.isCollection(type)) {
1289                    propertyValue = objectFactory.create(type);
1290                    metaObject.setValue(propertyName, propertyValue);
1291                    return propertyValue;
1292                }
1293            } catch (Exception e) {
1294                throw new ExecutorException(
1295                    "Error instantiating collection property for result '" + resultMapping.getProperty() + "'.  Cause: " + e,
1296                    e);
1297            }
1298        } else if (objectFactory.isCollection(propertyValue.getClass())) {
1299            return propertyValue;
1300        }
1301        return null;
1302    }
1303
1304    private boolean hasTypeHandlerForResultObject(ResultSetWrapper rsw, Class<?> resultType) {
1305        if (rsw.getColumnNames().size() == 1) {
1306            return typeHandlerRegistry.hasTypeHandler(resultType, rsw.getJdbcType(rsw.getColumnNames().get(0)));
1307        }
1308        return typeHandlerRegistry.hasTypeHandler(resultType);
1309    }
1310
1311}