Index: src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java IDEA additional info: Subsystem: com.intellij.openapi.diff.impl.patch.CharsetEP <+>UTF-8 =================================================================== diff --git a/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java b/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java --- a/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java (revision 580daedb5e4491611105e23b9fbfab09f0631acc) +++ b/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java (revision cd1a8e8c43440f929407010e9299074dfdee39cb) @@ -57,6 +57,7 @@ import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; +import java.sql.Types; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Arrays; @@ -67,6 +68,7 @@ import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.concurrent.atomic.AtomicInteger; /** * @author Clinton Begin @@ -125,6 +127,31 @@ } } + private final AtomicInteger current = new AtomicInteger(0); + private final boolean debug = true; + private final boolean debugColumns = false; + + private void log(ResultMap rs, String ident, int type) { + int indents; + String sym; + if (type == 1) { + indents = current.incrementAndGet(); + sym = "▶"; + } else if (type == 2) { + indents = current.getAndDecrement(); + sym = "⏹"; + } else { + indents = current.get(); + sym = "ⓘ"; + } + + if (debug) { + final String id = rs.getId(); + System.out.printf("%s[%s - %s] - %s\n", "\t".repeat(Math.max(0, type == 4 ? (indents + 1) : indents)), sym, + id.substring(id.lastIndexOf('.') + 1), ident); + } + } + public DefaultResultSetHandler(Executor executor, MappedStatement mappedStatement, ParameterHandler parameterHandler, ResultHandler resultHandler, BoundSql boundSql, RowBounds rowBounds) { this.executor = executor; @@ -433,11 +460,14 @@ final String resultMapId = resultMap.getId(); Object rowValue = partialObject; if (rowValue != null) { + log(resultMap, "GET ROW VALUE (existing)", 1); final MetaObject metaObject = configuration.newMetaObject(rowValue); putAncestor(rowValue, resultMapId); applyNestedResultMappings(rsw, resultMap, metaObject, columnPrefix, combinedKey, false); ancestorObjects.remove(resultMapId); + log(resultMap, "GET ROW VALUE (existing)", 2); } else { + log(resultMap, "GET ROW VALUE (new)", 1); final ResultLoaderMap lazyLoader = new ResultLoaderMap(); rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix, combinedKey); if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { @@ -455,8 +485,10 @@ rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null; } if (combinedKey != CacheKey.NULL_CACHE_KEY) { + log(resultMap, "add nestedResultObject: " + rowValue + " for key: " + combinedKey, 4); nestedResultObjects.put(combinedKey, rowValue); } + log(resultMap, "GET ROW VALUE (new)", 2); } return rowValue; } @@ -693,8 +725,19 @@ return createPrimitiveResultObject(rsw, resultMap, columnPrefix); } if (!constructorMappings.isEmpty()) { - return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, + log(resultMap, "createResultObject", 1); + + final var obj = createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, columnPrefix, resultMap.hasResultMapsUsingConstructorCollection(), parentRowKey); + + if (obj != null) { + log(resultMap, obj.toString(), 4); + } else { + log(resultMap, "obj is null", 4); + } + + log(resultMap, "createResultObject", 2); + return obj; } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) { return objectFactory.create(resultType); } else if (shouldApplyAutomaticMappings(resultMap, false)) { @@ -1052,6 +1095,17 @@ lastHandledCreation = null; } + log(resultMap, "DEALING WITH " + (foundNewUniqueRow ? "NEW" : "EXISTING") + " ROW - " + rowKey, 3); + + if (debugColumns) { + for (int i = 1; i <= resultSet.getMetaData().getColumnCount(); i++) { + String colName = resultSet.getMetaData().getColumnName(i); + int colType = resultSet.getMetaData().getColumnType(i); + Object colVal = colType != Types.CLOB && colType != Types.BLOB ? resultSet.getObject(i) : "clob"; + log(resultMap, "column[" + colName + "]" + (" ".repeat(30 - colName.length())) + ": value: " + colVal, 4); + } + } + // issue #577 && #542 if (mappedStatement.isResultOrdered()) { if (partialObject == null && rowValue != null) { @@ -1100,10 +1154,13 @@ private void linkNestedPendingCreations(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix, CacheKey parentRowKey, PendingConstructorCreation pendingCreation, Object resultObject, List constructorArgs) throws SQLException { + log(resultMap, "POST PROCESS PCC", 1); + final CacheKey rowKey = createRowKey(resultMap, rsw, columnPrefix); final CacheKey combinedKey = combineKeys(rowKey, parentRowKey); if (combinedKey != CacheKey.NULL_CACHE_KEY) { + log(resultMap, "adding nested for key (post process): " + combinedKey, 4); nestedResultObjects.put(combinedKey, resultObject); } @@ -1130,10 +1187,13 @@ final boolean isInnerCreation = actualValue instanceof PendingConstructorCreation; final boolean alreadyCreatedCollection = hasValue && objectFactory.isCollection(actualValue.getClass()); + log(nestedResultMap, "trying to link: " + constructorArgs.get(index), 1); + if (!isInnerCreation) { final var value = pendingCreation.initializeCollectionForResultMapping(objectFactory, nestedResultMap, constructorMapping, index); if (!alreadyCreatedCollection) { + log(nestedResultMap, "setting initial collection", 4); // override values with empty collection constructorArgs.set(index, value); } @@ -1143,14 +1203,20 @@ final CacheKey nestedCombinedKey = combineKeys(nestedRowKey, combinedKey); if (nestedCombinedKey != CacheKey.NULL_CACHE_KEY) { + log(nestedResultMap, "adding linked nested result object for key:" + nestedCombinedKey, 4); nestedResultObjects.put(nestedCombinedKey, pendingCreation); } if (hasValue) { + log(nestedResultMap, "linking actualValue", 4); pendingCreation.linkCollectionValue(constructorMapping, actualValue); + } else { + log(nestedResultMap, "no value to link, null", 4); } } else { final PendingConstructorCreation innerCreation = (PendingConstructorCreation) actualValue; + log(nestedResultMap, + "linking inner creation : " + pendingCreation.hashCode() + " -> " + innerCreation.hashCode(), 4); final var value = pendingCreation.initializeCollectionForResultMapping(objectFactory, nestedResultMap, constructorMapping, index); @@ -1159,16 +1225,23 @@ // link the creation for building later pendingCreation.linkCreation(nestedResultMap, innerCreation); } + + log(nestedResultMap, "try link end", 2); } + + log(resultMap, "POST PROCESS PCC", 2); } private boolean applyNestedPendingConstructorCreations(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, String parentPrefix, CacheKey parentRowKey, boolean newObject, boolean foundValues) { + log(resultMap, "APPLY NESTED", 1); + for (ResultMapping constructorMapping : resultMap.getConstructorResultMappings()) { final String nestedResultMapId = constructorMapping.getNestedResultMapId(); final Class parameterType = constructorMapping.getJavaType(); if (nestedResultMapId == null || constructorMapping.getResultSet() != null || parameterType == null || !objectFactory.isCollection(parameterType)) { + log(resultMap, "skip non-nested collection mapping - " + constructorMapping, 4); continue; } @@ -1201,33 +1274,47 @@ } pendingConstructorCreation = (PendingConstructorCreation) parentObject; + log(nestedResultMap, + "new value for nestedResultMap detected, using parentObject pcc: " + pendingConstructorCreation, 4); } + log(nestedResultMap, "trying to link [" + combinedKey + "] with " + pendingConstructorCreation, 1); rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, newValueForNestedResultMap ? null : pendingConstructorCreation); if (rowValue == null) { + log(nestedResultMap, "skip, new value is null, nothing to link", 4); continue; } + log(resultMap, "new value to be applied: " + rowValue, 4); if (rowValue instanceof PendingConstructorCreation) { - if (newValueForNestedResultMap) { + if (!newValueForNestedResultMap) { + log(nestedResultMap, "already dealt with this creation, not doing anything", 4); + } else { // we created a brand new pcc. this is a new collection value + log(nestedResultMap, "new pcc to be added to parent pcc collection: " + rowValue, 4); pendingConstructorCreation.linkCreation(nestedResultMap, (PendingConstructorCreation) rowValue); foundValues = true; } } else { + log(nestedResultMap, "linking to existing creation!", 4); pendingConstructorCreation.linkCollectionValue(constructorMapping, rowValue); foundValues = true; if (combinedKey != CacheKey.NULL_CACHE_KEY) { + log(nestedResultMap, "We found a new nested value, so link it in nested for key:" + combinedKey, 4); nestedResultObjects.put(combinedKey, pendingConstructorCreation); } } + + log(nestedResultMap, "linking end", 2); } catch (SQLException e) { throw new ExecutorException("Error getting experimental nested result map values for '" + constructorMapping.getProperty() + "'. Cause: " + e, e); } + + log(resultMap, "APPLY NESTED", 2); } return foundValues; }