All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.mybatisflex.core.mybatis.FlexDefaultResultSetHandler Maven / Gradle / Ivy

There is a newer version: 1.10.5
Show newest version
/*
 *  Copyright (c) 2022-2025, Mybatis-Flex ([email protected]).
 *  

* Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at *

* http://www.apache.org/licenses/LICENSE-2.0 *

* Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.mybatisflex.core.mybatis; import com.mybatisflex.core.FlexGlobalConfig; import com.mybatisflex.core.util.MapUtil; import org.apache.ibatis.annotations.AutomapConstructor; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.binding.MapperMethod.ParamMap; import org.apache.ibatis.cache.CacheKey; import org.apache.ibatis.cursor.Cursor; import org.apache.ibatis.cursor.defaults.DefaultCursor; import org.apache.ibatis.executor.ErrorContext; import org.apache.ibatis.executor.Executor; import org.apache.ibatis.executor.ExecutorException; import org.apache.ibatis.executor.loader.ResultLoader; import org.apache.ibatis.executor.loader.ResultLoaderMap; import org.apache.ibatis.executor.parameter.ParameterHandler; import org.apache.ibatis.executor.result.DefaultResultContext; import org.apache.ibatis.executor.result.DefaultResultHandler; import org.apache.ibatis.executor.result.ResultMapException; import org.apache.ibatis.executor.resultset.DefaultResultSetHandler; import org.apache.ibatis.executor.resultset.ResultSetWrapper; import org.apache.ibatis.mapping.*; import org.apache.ibatis.reflection.MetaClass; import org.apache.ibatis.reflection.MetaObject; import org.apache.ibatis.reflection.ReflectorFactory; import org.apache.ibatis.reflection.factory.ObjectFactory; import org.apache.ibatis.session.*; import org.apache.ibatis.type.JdbcType; import org.apache.ibatis.type.TypeHandler; import org.apache.ibatis.type.TypeHandlerRegistry; import org.apache.ibatis.type.UnknownTypeHandler; import java.lang.reflect.Constructor; import java.lang.reflect.Executable; import java.lang.reflect.Parameter; import java.sql.CallableStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.text.MessageFormat; import java.util.*; /** * 复制于 DefaultResultSetHandler,并开放若干方法,方便子类重写 * * @author Clinton Begin * @author Eduardo Macarron * @author Iwao AVE! * @author Kazuki Shimizu * @author Michael */ public class FlexDefaultResultSetHandler extends DefaultResultSetHandler { private static final Object DEFERRED = new Object(); private final Executor executor; private final Configuration configuration; private final MappedStatement mappedStatement; private final RowBounds rowBounds; private final ParameterHandler parameterHandler; private final ResultHandler resultHandler; private final BoundSql boundSql; private final TypeHandlerRegistry typeHandlerRegistry; private final ObjectFactory objectFactory; private final ReflectorFactory reflectorFactory; // nested resultmaps private final Map nestedResultObjects = new HashMap<>(); private final Map ancestorObjects = new HashMap<>(); private Object previousRowValue; // multiple resultsets private final Map nextResultMaps = new HashMap<>(); private final Map> pendingRelations = new HashMap<>(); // Cached Automappings private final Map> autoMappingsCache = new HashMap<>(); private final Map> constructorAutoMappingColumns = new HashMap<>(); // temporary marking flag that indicate using constructor mapping (use field to reduce memory usage) private boolean useConstructorMappings; private static class PendingRelation { public MetaObject metaObject; public ResultMapping propertyMapping; } private static class UnMappedColumnAutoMapping { private final String column; private final String property; private final TypeHandler typeHandler; private final boolean primitive; public UnMappedColumnAutoMapping(String column, String property, TypeHandler typeHandler, boolean primitive) { this.column = column; this.property = property; this.typeHandler = typeHandler; this.primitive = primitive; } } public FlexDefaultResultSetHandler(Executor executor, MappedStatement mappedStatement, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql, RowBounds rowBounds) { super(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds); this.executor = executor; this.configuration = mappedStatement.getConfiguration(); this.mappedStatement = mappedStatement; this.rowBounds = rowBounds; this.parameterHandler = parameterHandler; this.boundSql = boundSql; this.typeHandlerRegistry = configuration.getTypeHandlerRegistry(); this.objectFactory = configuration.getObjectFactory(); this.reflectorFactory = configuration.getReflectorFactory(); this.resultHandler = resultHandler; } // // HANDLE OUTPUT PARAMETER // @Override public void handleOutputParameters(CallableStatement cs) throws SQLException { final Object parameterObject = parameterHandler.getParameterObject(); final MetaObject metaParam = configuration.newMetaObject(parameterObject); final List parameterMappings = boundSql.getParameterMappings(); for (int i = 0; i < parameterMappings.size(); i++) { final ParameterMapping parameterMapping = parameterMappings.get(i); if (parameterMapping.getMode() == ParameterMode.OUT || parameterMapping.getMode() == ParameterMode.INOUT) { if (ResultSet.class.equals(parameterMapping.getJavaType())) { handleRefCursorOutputParameter((ResultSet) cs.getObject(i + 1), parameterMapping, metaParam); } else { final TypeHandler typeHandler = parameterMapping.getTypeHandler(); metaParam.setValue(parameterMapping.getProperty(), typeHandler.getResult(cs, i + 1)); } } } } private void handleRefCursorOutputParameter(ResultSet rs, ParameterMapping parameterMapping, MetaObject metaParam) throws SQLException { if (rs == null) { return; } try { final String resultMapId = parameterMapping.getResultMapId(); final ResultMap resultMap = configuration.getResultMap(resultMapId); final ResultSetWrapper rsw = new ResultSetWrapper(rs, configuration); if (this.resultHandler == null) { final DefaultResultHandler resultHandler = new DefaultResultHandler(objectFactory); handleRowValues(rsw, resultMap, resultHandler, new RowBounds(), null); metaParam.setValue(parameterMapping.getProperty(), resultHandler.getResultList()); } else { handleRowValues(rsw, resultMap, resultHandler, new RowBounds(), null); } } finally { // issue #228 (close resultsets) closeResultSet(rs); } } // // HANDLE RESULT SETS // @Override public List handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); final List multipleResults = new ArrayList<>(); int resultSetCount = 0; ResultSetWrapper rsw = getFirstResultSet(stmt); List resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); while (rsw != null && resultMapCount > resultSetCount) { ResultMap resultMap = resultMaps.get(resultSetCount); handleResultSet(rsw, resultMap, multipleResults, null); rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } String[] resultSets = mappedStatement.getResultSets(); if (resultSets != null) { while (rsw != null && resultSetCount < resultSets.length) { ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]); if (parentMapping != null) { String nestedResultMapId = parentMapping.getNestedResultMapId(); ResultMap resultMap = configuration.getResultMap(nestedResultMapId); handleResultSet(rsw, resultMap, null, parentMapping); } rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } } return collapseSingleResultList(multipleResults); } @Override public Cursor handleCursorResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling cursor results").object(mappedStatement.getId()); ResultSetWrapper rsw = getFirstResultSet(stmt); List resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); if (resultMapCount != 1) { throw new ExecutorException("Cursor results cannot be mapped to multiple resultMaps"); } ResultMap resultMap = resultMaps.get(0); return new DefaultCursor<>(this, resultMap, rsw, rowBounds); } private ResultSetWrapper getFirstResultSet(Statement stmt) throws SQLException { ResultSet rs = stmt.getResultSet(); while (rs == null) { // move forward to get the first resultset in case the driver // doesn't return the resultset as the first result (HSQLDB 2.1) if (stmt.getMoreResults()) { rs = stmt.getResultSet(); } else if (stmt.getUpdateCount() == -1) { // no more results. Must be no resultset break; } } return rs != null ? new ResultSetWrapper(rs, configuration) : null; } private ResultSetWrapper getNextResultSet(Statement stmt) { // Making this method tolerant of bad JDBC drivers try { // Crazy Standard JDBC way of determining if there are more results if (stmt.getConnection().getMetaData().supportsMultipleResultSets() && (stmt.getMoreResults() || (stmt.getUpdateCount() != -1))) { ResultSet rs = stmt.getResultSet(); if (rs == null) { return getNextResultSet(stmt); } return new ResultSetWrapper(rs, configuration); } } catch (Exception e) { // Intentionally ignored. } return null; } private void closeResultSet(ResultSet rs) { try { if (rs != null) { rs.close(); } } catch (SQLException e) { // ignore } } private void cleanUpAfterHandlingResultSet() { nestedResultObjects.clear(); } private void validateResultMapsCount(ResultSetWrapper rsw, int resultMapCount) { if (rsw != null && resultMapCount < 1) { throw new ExecutorException( "A query was run and no Result Maps were found for the Mapped Statement '" + mappedStatement.getId() + "'. 'resultType' or 'resultMap' must be specified when there is no corresponding method."); } } private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, List multipleResults, ResultMapping parentMapping) throws SQLException { try { if (parentMapping != null) { handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping); } else if (resultHandler == null) { if (resultMap.getId().startsWith("com.mybatisflex.core.row.RowMapper.selectFirstAndSecondColumnsAsMap")) { ResultSet resultSet = rsw.getResultSet(); skipRows(resultSet, rowBounds); Map row = new HashMap<>(); while (!resultSet.isClosed() && resultSet.next()) { row.put(resultSet.getObject(1), resultSet.getObject(2)); } List> mapArrayList = new ArrayList<>(1); mapArrayList.add(row); multipleResults.add(mapArrayList); } else { DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory); handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null); multipleResults.add(defaultResultHandler.getResultList()); } } else { handleRowValues(rsw, resultMap, resultHandler, rowBounds, null); } } finally { // issue #228 (close resultsets) closeResultSet(rsw.getResultSet()); } } @SuppressWarnings("unchecked") private List collapseSingleResultList(List multipleResults) { return multipleResults.size() == 1 ? (List) multipleResults.get(0) : multipleResults; } // // HANDLE ROWS FOR SIMPLE RESULTMAP // @Override public void handleRowValues(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { if (resultMap.hasNestedResultMaps()) { ensureNoRowBounds(); checkResultHandler(); handleRowValuesForNestedResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping); } else { handleRowValuesForSimpleResultMap(rsw, resultMap, resultHandler, rowBounds, parentMapping); } } private void ensureNoRowBounds() { if (configuration.isSafeRowBoundsEnabled() && rowBounds != null && (rowBounds.getLimit() < RowBounds.NO_ROW_LIMIT || rowBounds.getOffset() > RowBounds.NO_ROW_OFFSET)) { throw new ExecutorException( "Mapped Statements with nested result mappings cannot be safely constrained by RowBounds. " + "Use safeRowBoundsEnabled=false setting to bypass this check."); } } @Override protected void checkResultHandler() { if (resultHandler != null && configuration.isSafeResultHandlerEnabled() && !mappedStatement.isResultOrdered()) { throw new ExecutorException( "Mapped Statements with nested result mappings cannot be safely used with a custom ResultHandler. " + "Use safeResultHandlerEnabled=false setting to bypass this check " + "or ensure your statement returns ordered data and set resultOrdered=true on it."); } } private void handleRowValuesForSimpleResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { DefaultResultContext resultContext = new DefaultResultContext<>(); ResultSet resultSet = rsw.getResultSet(); skipRows(resultSet, rowBounds); while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) { ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null); Object rowValue = getRowValue(rsw, discriminatedResultMap, null); storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet); } } private void storeObject(ResultHandler resultHandler, DefaultResultContext resultContext, Object rowValue, ResultMapping parentMapping, ResultSet rs) throws SQLException { if (parentMapping != null) { linkToParents(rs, parentMapping, rowValue); } else { callResultHandler(resultHandler, resultContext, rowValue); } } @SuppressWarnings("unchecked" /* because ResultHandler is always ResultHandler */) private void callResultHandler(ResultHandler resultHandler, DefaultResultContext resultContext, Object rowValue) { resultContext.nextResultObject(rowValue); ((ResultHandler) resultHandler).handleResult(resultContext); } private boolean shouldProcessMoreRows(ResultContext context, RowBounds rowBounds) { return !context.isStopped() && context.getResultCount() < rowBounds.getLimit(); } private void skipRows(ResultSet rs, RowBounds rowBounds) throws SQLException { if (rs.getType() != ResultSet.TYPE_FORWARD_ONLY) { if (rowBounds.getOffset() != RowBounds.NO_ROW_OFFSET) { rs.absolute(rowBounds.getOffset()); } } else { for (int i = 0; i < rowBounds.getOffset(); i++) { if (!rs.next()) { break; } } } } // // GET VALUE FROM ROW FOR SIMPLE RESULT MAP // private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException { final ResultLoaderMap lazyLoader = new ResultLoaderMap(); Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix); if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { final MetaObject metaObject = configuration.newMetaObject(rowValue); boolean foundValues = this.useConstructorMappings; if (shouldApplyAutomaticMappings(resultMap, false)) { foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues; } foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues; foundValues = lazyLoader.size() > 0 || foundValues; rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null; } return rowValue; } // // GET VALUE FROM ROW FOR NESTED RESULT MAP // private Object getRowValue(ResultSetWrapper rsw, ResultMap resultMap, CacheKey combinedKey, String columnPrefix, Object partialObject) throws SQLException { final String resultMapId = resultMap.getId(); Object rowValue = partialObject; if (rowValue != null) { final MetaObject metaObject = configuration.newMetaObject(rowValue); putAncestor(rowValue, resultMapId); applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false); ancestorObjects.remove(resultMapId); } else { final ResultLoaderMap lazyLoader = new ResultLoaderMap(); rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix); if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { final MetaObject metaObject = configuration.newMetaObject(rowValue); boolean foundValues = this.useConstructorMappings; if (shouldApplyAutomaticMappings(resultMap, true)) { foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues; } foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues; putAncestor(rowValue, resultMapId); foundValues = applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, true) || foundValues; ancestorObjects.remove(resultMapId); foundValues = lazyLoader.size() > 0 || foundValues; rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null; } if (combinedKey != CacheKey.NULL_CACHE_KEY) { nestedResultObjects.put(combinedKey, rowValue); } } return rowValue; } private void putAncestor(Object resultObject, String resultMapId) { ancestorObjects.put(resultMapId, resultObject); } private boolean shouldApplyAutomaticMappings(ResultMap resultMap, boolean isNested) { if (resultMap.getAutoMapping() != null) { return resultMap.getAutoMapping(); } if (isNested) { return AutoMappingBehavior.FULL == configuration.getAutoMappingBehavior(); } else { return AutoMappingBehavior.NONE != configuration.getAutoMappingBehavior(); } } // // PROPERTY MAPPINGS // private boolean applyPropertyMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { final Collection mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix); boolean foundValues = false; final List propertyMappings = resultMap.getPropertyResultMappings(); for (ResultMapping propertyMapping : propertyMappings) { String column = prependPrefix(propertyMapping.getColumn(), columnPrefix); if (propertyMapping.getNestedResultMapId() != null) { // the user added a column attribute to a nested result map, ignore it column = null; } if (propertyMapping.isCompositeResult() || column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH)) || propertyMapping.getResultSet() != null) { Object value = getPropertyMappingValue(rsw.getResultSet(), metaObject, propertyMapping, lazyLoader, columnPrefix); // issue #541 make property optional final String property = propertyMapping.getProperty(); if (property == null) { continue; } if (value == DEFERRED) { foundValues = true; continue; } if (value != null) { foundValues = true; } if (value != null || configuration.isCallSettersOnNulls() && !metaObject.getSetterType(property).isPrimitive()) { // gcode issue #377, call setter on nulls (value is not 'found') metaObject.setValue(property, value); } } } return foundValues; } private Object getPropertyMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { if (propertyMapping.getNestedQueryId() != null) { return getNestedQueryMappingValue(rs, metaResultObject, propertyMapping, lazyLoader, columnPrefix); } if (propertyMapping.getResultSet() != null) { addPendingChildRelation(rs, metaResultObject, propertyMapping); // TODO is that OK? return DEFERRED; } else { final TypeHandler typeHandler = propertyMapping.getTypeHandler(); final String column = prependPrefix(propertyMapping.getColumn(), columnPrefix); return typeHandler.getResult(rs, column); } } private List createAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException { final String mapKey = resultMap.getId() + ":" + columnPrefix; List autoMapping = autoMappingsCache.get(mapKey); if (autoMapping == null) { autoMapping = new ArrayList<>(); final List unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix); // Remove the entry to release the memory List mappedInConstructorAutoMapping = constructorAutoMappingColumns.remove(mapKey); if (mappedInConstructorAutoMapping != null) { unmappedColumnNames.removeAll(mappedInConstructorAutoMapping); } for (String columnName : unmappedColumnNames) { String propertyName = columnName; if (columnPrefix != null && !columnPrefix.isEmpty()) { // When columnPrefix is specified, // ignore columns without the prefix. if (!columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) { continue; } propertyName = columnName.substring(columnPrefix.length()); } final String property = metaObject.findProperty(propertyName, configuration.isMapUnderscoreToCamelCase()); if (property != null && metaObject.hasSetter(property)) { if (resultMap.getMappedProperties().contains(property)) { continue; } final Class propertyType = metaObject.getSetterType(property); if (typeHandlerRegistry.hasTypeHandler(propertyType, rsw.getJdbcType(columnName))) { final TypeHandler typeHandler = rsw.getTypeHandler(propertyType, columnName); autoMapping .add(new UnMappedColumnAutoMapping(columnName, property, typeHandler, propertyType.isPrimitive())); } else { configuration.getAutoMappingUnknownColumnBehavior().doAction(mappedStatement, columnName, property, propertyType); } } else { configuration.getAutoMappingUnknownColumnBehavior().doAction(mappedStatement, columnName, property != null ? property : propertyName, null); } } autoMappingsCache.put(mapKey, autoMapping); } return autoMapping; } private boolean applyAutomaticMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String columnPrefix) throws SQLException { List autoMapping = createAutomaticMappings(rsw, resultMap, metaObject, columnPrefix); boolean foundValues = false; if (!autoMapping.isEmpty()) { for (UnMappedColumnAutoMapping mapping : autoMapping) { final Object value = mapping.typeHandler.getResult(rsw.getResultSet(), mapping.column); if (value != null) { foundValues = true; } if (value != null || configuration.isCallSettersOnNulls() && !mapping.primitive) { // gcode issue #377, call setter on nulls (value is not 'found') metaObject.setValue(mapping.property, value); } } } else { UnMappedColumnHandler unMappedColumnHandler = FlexGlobalConfig.getDefaultConfig().getUnMappedColumnHandler(); if (unMappedColumnHandler != null) { // 增加未匹配列自定义处理 final List unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix); for (String unmappedColumnName : unmappedColumnNames) { // 不明确类型,直接取object final Object value = typeHandlerRegistry.getMappingTypeHandler(UnknownTypeHandler.class).getResult(rsw.getResultSet(), unmappedColumnName); // 自定义处理未匹配列 unMappedColumnHandler.handleUnMappedColumn(metaObject, unmappedColumnName, value); } } } return foundValues; } // MULTIPLE RESULT SETS private void linkToParents(ResultSet rs, ResultMapping parentMapping, Object rowValue) throws SQLException { CacheKey parentKey = createKeyForMultipleResults(rs, parentMapping, parentMapping.getColumn(), parentMapping.getForeignColumn()); List parents = pendingRelations.get(parentKey); if (parents != null) { for (PendingRelation parent : parents) { if (parent != null && rowValue != null) { linkObjects(parent.metaObject, parent.propertyMapping, rowValue); } } } } private void addPendingChildRelation(ResultSet rs, MetaObject metaResultObject, ResultMapping parentMapping) throws SQLException { CacheKey cacheKey = createKeyForMultipleResults(rs, parentMapping, parentMapping.getColumn(), parentMapping.getColumn()); PendingRelation deferLoad = new PendingRelation(); deferLoad.metaObject = metaResultObject; deferLoad.propertyMapping = parentMapping; List relations = MapUtil.computeIfAbsent(pendingRelations, cacheKey, k -> new ArrayList<>()); // issue #255 relations.add(deferLoad); ResultMapping previous = nextResultMaps.get(parentMapping.getResultSet()); if (previous == null) { nextResultMaps.put(parentMapping.getResultSet(), parentMapping); } else if (!previous.equals(parentMapping)) { throw new ExecutorException("Two different properties are mapped to the same resultSet"); } } private CacheKey createKeyForMultipleResults(ResultSet rs, ResultMapping resultMapping, String names, String columns) throws SQLException { CacheKey cacheKey = new CacheKey(); cacheKey.update(resultMapping); if (columns != null && names != null) { String[] columnsArray = columns.split(","); String[] namesArray = names.split(","); for (int i = 0; i < columnsArray.length; i++) { Object value = rs.getString(columnsArray[i]); if (value != null) { cacheKey.update(namesArray[i]); cacheKey.update(value); } } } return cacheKey; } // // INSTANTIATION & CONSTRUCTOR MAPPING // private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { this.useConstructorMappings = false; // reset previous mapping result final List> constructorArgTypes = new ArrayList<>(); final List constructorArgs = new ArrayList<>(); Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix); if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { final List propertyMappings = resultMap.getPropertyResultMappings(); for (ResultMapping propertyMapping : propertyMappings) { // issue gcode #109 && issue #149 if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) { resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, objectFactory, constructorArgTypes, constructorArgs); break; } } } this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result return resultObject; } private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List> constructorArgTypes, List constructorArgs, String columnPrefix) throws SQLException { final Class resultType = resultMap.getType(); final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory); final List constructorMappings = resultMap.getConstructorResultMappings(); if (hasTypeHandlerForResultObject(rsw, resultType)) { return createPrimitiveResultObject(rsw, resultMap, columnPrefix); } if (!constructorMappings.isEmpty()) { return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix); } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) { return objectFactory.create(resultType); } else if (shouldApplyAutomaticMappings(resultMap, false)) { return createByConstructorSignature(rsw, resultMap, columnPrefix, resultType, constructorArgTypes, constructorArgs); } throw new ExecutorException("Do not know how to create an instance of " + resultType); } Object createParameterizedResultObject(ResultSetWrapper rsw, Class resultType, List constructorMappings, List> constructorArgTypes, List constructorArgs, String columnPrefix) { boolean foundValues = false; for (ResultMapping constructorMapping : constructorMappings) { final Class parameterType = constructorMapping.getJavaType(); final String column = constructorMapping.getColumn(); final Object value; try { if (constructorMapping.getNestedQueryId() != null) { value = getNestedQueryConstructorValue(rsw.getResultSet(), constructorMapping, columnPrefix); } else if (constructorMapping.getNestedResultMapId() != null) { final ResultMap resultMap = configuration.getResultMap(constructorMapping.getNestedResultMapId()); value = getRowValue(rsw, resultMap, getColumnPrefix(columnPrefix, constructorMapping)); } else { final TypeHandler typeHandler = constructorMapping.getTypeHandler(); value = typeHandler.getResult(rsw.getResultSet(), prependPrefix(column, columnPrefix)); } } catch (ResultMapException | SQLException e) { throw new ExecutorException("Could not process result for mapping: " + constructorMapping, e); } constructorArgTypes.add(parameterType); constructorArgs.add(value); foundValues = value != null || foundValues; } return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null; } private Object createByConstructorSignature(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix, Class resultType, List> constructorArgTypes, List constructorArgs) throws SQLException { return applyConstructorAutomapping(rsw, resultMap, columnPrefix, resultType, constructorArgTypes, constructorArgs, findConstructorForAutomapping(resultType, rsw).orElseThrow(() -> new ExecutorException( "No constructor found in " + resultType.getName() + " matching " + rsw.getClassNames()))); } private Optional> findConstructorForAutomapping(final Class resultType, ResultSetWrapper rsw) { Constructor[] constructors = resultType.getDeclaredConstructors(); if (constructors.length == 1) { return Optional.of(constructors[0]); } Optional> annotated = Arrays.stream(constructors) .filter(x -> x.isAnnotationPresent(AutomapConstructor.class)).reduce((x, y) -> { throw new ExecutorException("@AutomapConstructor should be used in only one constructor."); }); if (annotated.isPresent()) { return annotated; } if (configuration.isArgNameBasedConstructorAutoMapping()) { // Finding-best-match type implementation is possible, // but using @AutomapConstructor seems sufficient. throw new ExecutorException(MessageFormat.format( "'argNameBasedConstructorAutoMapping' is enabled and the class ''{0}'' has multiple constructors, so @AutomapConstructor must be added to one of the constructors.", resultType.getName())); } else { return Arrays.stream(constructors).filter(x -> findUsableConstructorByArgTypes(x, rsw.getJdbcTypes())).findAny(); } } private boolean findUsableConstructorByArgTypes(final Constructor constructor, final List jdbcTypes) { final Class[] parameterTypes = constructor.getParameterTypes(); if (parameterTypes.length != jdbcTypes.size()) { return false; } for (int i = 0; i < parameterTypes.length; i++) { if (!typeHandlerRegistry.hasTypeHandler(parameterTypes[i], jdbcTypes.get(i))) { return false; } } return true; } private Object applyConstructorAutomapping(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix, Class resultType, List> constructorArgTypes, List constructorArgs, Constructor constructor) throws SQLException { boolean foundValues = false; if (configuration.isArgNameBasedConstructorAutoMapping()) { foundValues = applyArgNameBasedConstructorAutoMapping(rsw, resultMap, columnPrefix, constructorArgTypes, constructorArgs, constructor, foundValues); } else { foundValues = applyColumnOrderBasedConstructorAutomapping(rsw, constructorArgTypes, constructorArgs, constructor, foundValues); } return foundValues || configuration.isReturnInstanceForEmptyRow() ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null; } private boolean applyColumnOrderBasedConstructorAutomapping(ResultSetWrapper rsw, List> constructorArgTypes, List constructorArgs, Constructor constructor, boolean foundValues) throws SQLException { // fixed IndexOutOfBoundsException https://gitee.com/mybatis-flex/mybatis-flex/issues/I98ZO9 List columnNames = rsw.getColumnNames(); if (columnNames.size() < constructor.getParameterCount()) { throw new IllegalArgumentException("Can not invoke the constructor[" + buildMethodString(constructor) + "] with value names: " + Arrays.toString(columnNames.toArray()) + ",\n" + "Perhaps you can add a default (no parameters) constructor to fix it." ); } Class[] parameterTypes = constructor.getParameterTypes(); for (int i = 0; i < parameterTypes.length; i++) { Class parameterType = parameterTypes[i]; String columnName = columnNames.get(i); TypeHandler typeHandler = rsw.getTypeHandler(parameterType, columnName); Object value = typeHandler.getResult(rsw.getResultSet(), columnName); constructorArgTypes.add(parameterType); constructorArgs.add(value); foundValues = value != null || foundValues; } return foundValues; } private static String buildMethodString(Executable method) { StringBuilder sb = new StringBuilder() .append(method.getDeclaringClass().getName()) .append(".") .append(method.getName()) .append("("); Class[] params = method.getParameterTypes(); int in = 0; for (Class clazz : params) { sb.append(clazz.getName()); if (++in < params.length) { sb.append(","); } } return sb.append(")").toString(); } private boolean applyArgNameBasedConstructorAutoMapping(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix, List> constructorArgTypes, List constructorArgs, Constructor constructor, boolean foundValues) throws SQLException { List missingArgs = null; Parameter[] params = constructor.getParameters(); for (Parameter param : params) { boolean columnNotFound = true; Param paramAnno = param.getAnnotation(Param.class); String paramName = paramAnno == null ? param.getName() : paramAnno.value(); for (String columnName : rsw.getColumnNames()) { if (columnMatchesParam(columnName, paramName, columnPrefix)) { Class paramType = param.getType(); TypeHandler typeHandler = rsw.getTypeHandler(paramType, columnName); Object value = typeHandler.getResult(rsw.getResultSet(), columnName); constructorArgTypes.add(paramType); constructorArgs.add(value); final String mapKey = resultMap.getId() + ":" + columnPrefix; if (!autoMappingsCache.containsKey(mapKey)) { MapUtil.computeIfAbsent(constructorAutoMappingColumns, mapKey, k -> new ArrayList<>()).add(columnName); } columnNotFound = false; foundValues = value != null || foundValues; } } if (columnNotFound) { if (missingArgs == null) { missingArgs = new ArrayList<>(); } missingArgs.add(paramName); } } if (foundValues && constructorArgs.size() < params.length) { throw new ExecutorException(MessageFormat.format( "Constructor auto-mapping of ''{1}'' failed " + "because ''{0}'' were not found in the result set; " + "Available columns are ''{2}'' and mapUnderscoreToCamelCase is ''{3}''.", missingArgs, constructor, rsw.getColumnNames(), configuration.isMapUnderscoreToCamelCase())); } return foundValues; } private boolean columnMatchesParam(String columnName, String paramName, String columnPrefix) { if (columnPrefix != null) { if (!columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) { return false; } columnName = columnName.substring(columnPrefix.length()); } return paramName .equalsIgnoreCase(configuration.isMapUnderscoreToCamelCase() ? columnName.replace("_", "") : columnName); } protected Object createPrimitiveResultObject(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) throws SQLException { final Class resultType = resultMap.getType(); final String columnName; if (!resultMap.getResultMappings().isEmpty()) { final List resultMappingList = resultMap.getResultMappings(); final ResultMapping mapping = resultMappingList.get(0); columnName = prependPrefix(mapping.getColumn(), columnPrefix); } else { columnName = rsw.getColumnNames().get(0); } final TypeHandler typeHandler = rsw.getTypeHandler(resultType, columnName); return typeHandler.getResult(rsw.getResultSet(), columnName); } // // NESTED QUERY // private Object getNestedQueryConstructorValue(ResultSet rs, ResultMapping constructorMapping, String columnPrefix) throws SQLException { final String nestedQueryId = constructorMapping.getNestedQueryId(); final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId); final Class nestedQueryParameterType = nestedQuery.getParameterMap().getType(); final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, constructorMapping, nestedQueryParameterType, columnPrefix); Object value = null; if (nestedQueryParameterObject != null) { final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject); final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql); final Class targetType = constructorMapping.getJavaType(); final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql); value = resultLoader.loadResult(); } return value; } private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { final String nestedQueryId = propertyMapping.getNestedQueryId(); final String property = propertyMapping.getProperty(); final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId); final Class nestedQueryParameterType = nestedQuery.getParameterMap().getType(); final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping, nestedQueryParameterType, columnPrefix); Object value = null; if (nestedQueryParameterObject != null) { final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject); final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, nestedBoundSql); final Class targetType = propertyMapping.getJavaType(); if (executor.isCached(nestedQuery, key)) { executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType); value = DEFERRED; } else { final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, nestedQueryParameterObject, targetType, key, nestedBoundSql); if (propertyMapping.isLazy()) { lazyLoader.addLoader(property, metaResultObject, resultLoader); value = DEFERRED; } else { value = resultLoader.loadResult(); } } } return value; } private Object prepareParameterForNestedQuery(ResultSet rs, ResultMapping resultMapping, Class parameterType, String columnPrefix) throws SQLException { if (resultMapping.isCompositeResult()) { return prepareCompositeKeyParameter(rs, resultMapping, parameterType, columnPrefix); } return prepareSimpleKeyParameter(rs, resultMapping, parameterType, columnPrefix); } private Object prepareSimpleKeyParameter(ResultSet rs, ResultMapping resultMapping, Class parameterType, String columnPrefix) throws SQLException { final TypeHandler typeHandler; if (typeHandlerRegistry.hasTypeHandler(parameterType)) { typeHandler = typeHandlerRegistry.getTypeHandler(parameterType); } else { typeHandler = typeHandlerRegistry.getUnknownTypeHandler(); } return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix)); } private Object prepareCompositeKeyParameter(ResultSet rs, ResultMapping resultMapping, Class parameterType, String columnPrefix) throws SQLException { final Object parameterObject = instantiateParameterObject(parameterType); final MetaObject metaObject = configuration.newMetaObject(parameterObject); boolean foundValues = false; for (ResultMapping innerResultMapping : resultMapping.getComposites()) { final Class propType = metaObject.getSetterType(innerResultMapping.getProperty()); final TypeHandler typeHandler = typeHandlerRegistry.getTypeHandler(propType); final Object propValue = typeHandler.getResult(rs, prependPrefix(innerResultMapping.getColumn(), columnPrefix)); // issue #353 & #560 do not execute nested query if key is null if (propValue != null) { metaObject.setValue(innerResultMapping.getProperty(), propValue); foundValues = true; } } return foundValues ? parameterObject : null; } private Object instantiateParameterObject(Class parameterType) { if (parameterType == null) { return new HashMap<>(); } if (ParamMap.class.equals(parameterType)) { return new HashMap<>(); // issue #649 } else { return objectFactory.create(parameterType); } } // // DISCRIMINATOR // @Override public ResultMap resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix) throws SQLException { Set pastDiscriminators = new HashSet<>(); Discriminator discriminator = resultMap.getDiscriminator(); while (discriminator != null) { final Object value = getDiscriminatorValue(rs, discriminator, columnPrefix); final String discriminatedMapId = discriminator.getMapIdFor(String.valueOf(value)); if (!configuration.hasResultMap(discriminatedMapId)) { break; } resultMap = configuration.getResultMap(discriminatedMapId); Discriminator lastDiscriminator = discriminator; discriminator = resultMap.getDiscriminator(); if (discriminator == lastDiscriminator || !pastDiscriminators.add(discriminatedMapId)) { break; } } return resultMap; } private Object getDiscriminatorValue(ResultSet rs, Discriminator discriminator, String columnPrefix) throws SQLException { final ResultMapping resultMapping = discriminator.getResultMapping(); final TypeHandler typeHandler = resultMapping.getTypeHandler(); return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix)); } protected String prependPrefix(String columnName, String prefix) { if (columnName == null || columnName.length() == 0 || prefix == null || prefix.length() == 0) { return columnName; } return prefix + columnName; } // // HANDLE NESTED RESULT MAPS // private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, ResultHandler resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { final DefaultResultContext resultContext = new DefaultResultContext<>(); ResultSet resultSet = rsw.getResultSet(); skipRows(resultSet, rowBounds); Object rowValue = previousRowValue; while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) { final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null); final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null); Object partialObject = nestedResultObjects.get(rowKey); // issue #577 && #542 if (mappedStatement.isResultOrdered()) { if (partialObject == null && rowValue != null) { nestedResultObjects.clear(); storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet); } rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject); } else { rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject); if (partialObject == null) { storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet); } } } if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) { storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet); previousRowValue = null; } else if (rowValue != null) { previousRowValue = rowValue; } } // // NESTED RESULT MAP (JOIN MAPPING) // private boolean applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String parentPrefix, CacheKey parentRowKey, boolean newObject) { boolean foundValues = false; for (ResultMapping resultMapping : resultMap.getPropertyResultMappings()) { final String nestedResultMapId = resultMapping.getNestedResultMapId(); if (nestedResultMapId != null && resultMapping.getResultSet() == null) { try { final String columnPrefix = getColumnPrefix(parentPrefix, resultMapping); final ResultMap nestedResultMap = getNestedResultMap(rsw.getResultSet(), nestedResultMapId, columnPrefix); if (resultMapping.getColumnPrefix() == null) { // try to fill circular reference only when columnPrefix // is not specified for the nested result map (issue #215) Object ancestorObject = ancestorObjects.get(nestedResultMapId); if (ancestorObject != null) { if (newObject) { linkObjects(metaObject, resultMapping, ancestorObject); // issue #385 } continue; } } final CacheKey rowKey = createRowKey(nestedResultMap, rsw, columnPrefix); final CacheKey combinedKey = combineKeys(rowKey, parentRowKey); Object rowValue = nestedResultObjects.get(combinedKey); boolean knownValue = rowValue != null; instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject); // mandatory if (anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw)) { rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue); if (rowValue != null && !knownValue) { linkObjects(metaObject, resultMapping, rowValue); foundValues = true; } } } catch (SQLException e) { throw new ExecutorException( "Error getting nested result map values for '" + resultMapping.getProperty() + "'. Cause: " + e, e); } } } return foundValues; } private String getColumnPrefix(String parentPrefix, ResultMapping resultMapping) { final StringBuilder columnPrefixBuilder = new StringBuilder(); if (parentPrefix != null) { columnPrefixBuilder.append(parentPrefix); } if (resultMapping.getColumnPrefix() != null) { columnPrefixBuilder.append(resultMapping.getColumnPrefix()); } return columnPrefixBuilder.length() == 0 ? null : columnPrefixBuilder.toString().toUpperCase(Locale.ENGLISH); } private boolean anyNotNullColumnHasValue(ResultMapping resultMapping, String columnPrefix, ResultSetWrapper rsw) throws SQLException { Set notNullColumns = resultMapping.getNotNullColumns(); if (notNullColumns != null && !notNullColumns.isEmpty()) { ResultSet rs = rsw.getResultSet(); for (String column : notNullColumns) { rs.getObject(prependPrefix(column, columnPrefix)); if (!rs.wasNull()) { return true; } } return false; } if (columnPrefix != null) { for (String columnName : rsw.getColumnNames()) { if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix.toUpperCase(Locale.ENGLISH))) { return true; } } return false; } return true; } private ResultMap getNestedResultMap(ResultSet rs, String nestedResultMapId, String columnPrefix) throws SQLException { ResultMap nestedResultMap = configuration.getResultMap(nestedResultMapId); return resolveDiscriminatedResultMap(rs, nestedResultMap, columnPrefix); } // // UNIQUE RESULT KEY // private CacheKey createRowKey(ResultMap resultMap, ResultSetWrapper rsw, String columnPrefix) throws SQLException { final CacheKey cacheKey = new CacheKey(); cacheKey.update(resultMap.getId()); List resultMappings = getResultMappingsForRowKey(resultMap); if (resultMappings.isEmpty()) { if (Map.class.isAssignableFrom(resultMap.getType())) { createRowKeyForMap(rsw, cacheKey); } else { createRowKeyForUnmappedProperties(resultMap, rsw, cacheKey, columnPrefix); } } else { createRowKeyForMappedProperties(resultMap, rsw, cacheKey, resultMappings, columnPrefix); } if (cacheKey.getUpdateCount() < 2) { return CacheKey.NULL_CACHE_KEY; } return cacheKey; } private CacheKey combineKeys(CacheKey rowKey, CacheKey parentRowKey) { if (rowKey.getUpdateCount() > 1 && parentRowKey.getUpdateCount() > 1) { CacheKey combinedKey; try { combinedKey = rowKey.clone(); } catch (CloneNotSupportedException e) { throw new ExecutorException("Error cloning cache key. Cause: " + e, e); } combinedKey.update(parentRowKey); return combinedKey; } return CacheKey.NULL_CACHE_KEY; } private List getResultMappingsForRowKey(ResultMap resultMap) { List resultMappings = resultMap.getIdResultMappings(); if (resultMappings.isEmpty()) { resultMappings = resultMap.getPropertyResultMappings(); } return resultMappings; } private void createRowKeyForMappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey, List resultMappings, String columnPrefix) throws SQLException { for (ResultMapping resultMapping : resultMappings) { if (resultMapping.isSimple()) { final String column = prependPrefix(resultMapping.getColumn(), columnPrefix); final TypeHandler th = resultMapping.getTypeHandler(); Collection mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix); // Issue #114 if (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) { final Object value = th.getResult(rsw.getResultSet(), column); if (value != null || configuration.isReturnInstanceForEmptyRow()) { cacheKey.update(column); cacheKey.update(value); } } } } } private void createRowKeyForUnmappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey, String columnPrefix) throws SQLException { final MetaClass metaType = MetaClass.forClass(resultMap.getType(), reflectorFactory); List unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix); for (String column : unmappedColumnNames) { String property = column; if (columnPrefix != null && !columnPrefix.isEmpty()) { // When columnPrefix is specified, ignore columns without the prefix. if (!column.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) { continue; } property = column.substring(columnPrefix.length()); } if (metaType.findProperty(property, configuration.isMapUnderscoreToCamelCase()) != null) { String value = rsw.getResultSet().getString(column); if (value != null) { cacheKey.update(column); cacheKey.update(value); } } } } private void createRowKeyForMap(ResultSetWrapper rsw, CacheKey cacheKey) throws SQLException { List columnNames = rsw.getColumnNames(); for (String columnName : columnNames) { final String value = rsw.getResultSet().getString(columnName); if (value != null) { cacheKey.update(columnName); cacheKey.update(value); } } } private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue) { final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject); if (collectionProperty != null) { final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty); targetMetaObject.add(rowValue); } else { metaObject.setValue(resultMapping.getProperty(), rowValue); } } private Object instantiateCollectionPropertyIfAppropriate(ResultMapping resultMapping, MetaObject metaObject) { final String propertyName = resultMapping.getProperty(); Object propertyValue = metaObject.getValue(propertyName); if (propertyValue == null) { Class type = resultMapping.getJavaType(); if (type == null) { type = metaObject.getSetterType(propertyName); } try { if (objectFactory.isCollection(type)) { propertyValue = objectFactory.create(type); metaObject.setValue(propertyName, propertyValue); return propertyValue; } } catch (Exception e) { throw new ExecutorException( "Error instantiating collection property for result '" + resultMapping.getProperty() + "'. Cause: " + e, e); } } else if (objectFactory.isCollection(propertyValue.getClass())) { return propertyValue; } return null; } private boolean hasTypeHandlerForResultObject(ResultSetWrapper rsw, Class resultType) { if (rsw.getColumnNames().size() == 1) { return typeHandlerRegistry.hasTypeHandler(resultType, rsw.getJdbcType(rsw.getColumnNames().get(0))); } return typeHandlerRegistry.hasTypeHandler(resultType); } }