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