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