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 if (FlexGlobalConfig.getUnMappedColumnHandler() != null) { 598 // 增加未匹配列自定义处理 599 final List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix); 600 for (String unmappedColumnName : unmappedColumnNames) { 601 // 不明确类型,直接取object 602 final Object value = typeHandlerRegistry.getMappingTypeHandler(UnknownTypeHandler.class).getResult(rsw.getResultSet(), unmappedColumnName); 603 // 自定义处理未匹配列 604 FlexGlobalConfig.getUnMappedColumnHandler().handleUnMappedColumn(metaObject, unmappedColumnName, value); 605 } 606 } 607 } 608 return foundValues; 609 } 610 611 // MULTIPLE RESULT SETS 612 613 private void linkToParents(ResultSet rs, ResultMapping parentMapping, Object rowValue) throws SQLException { 614 CacheKey parentKey = createKeyForMultipleResults(rs, parentMapping, parentMapping.getColumn(), 615 parentMapping.getForeignColumn()); 616 List<PendingRelation> parents = pendingRelations.get(parentKey); 617 if (parents != null) { 618 for (PendingRelation parent : parents) { 619 if (parent != null && rowValue != null) { 620 linkObjects(parent.metaObject, parent.propertyMapping, rowValue); 621 } 622 } 623 } 624 } 625 626 private void addPendingChildRelation(ResultSet rs, MetaObject metaResultObject, ResultMapping parentMapping) 627 throws SQLException { 628 CacheKey cacheKey = createKeyForMultipleResults(rs, parentMapping, parentMapping.getColumn(), 629 parentMapping.getColumn()); 630 PendingRelation deferLoad = new PendingRelation(); 631 deferLoad.metaObject = metaResultObject; 632 deferLoad.propertyMapping = parentMapping; 633 List<PendingRelation> relations = MapUtil.computeIfAbsent(pendingRelations, cacheKey, k -> new ArrayList<>()); 634 // issue #255 635 relations.add(deferLoad); 636 ResultMapping previous = nextResultMaps.get(parentMapping.getResultSet()); 637 if (previous == null) { 638 nextResultMaps.put(parentMapping.getResultSet(), parentMapping); 639 } else if (!previous.equals(parentMapping)) { 640 throw new ExecutorException("Two different properties are mapped to the same resultSet"); 641 } 642 } 643 644 private CacheKey createKeyForMultipleResults(ResultSet rs, ResultMapping resultMapping, String names, String columns) 645 throws SQLException { 646 CacheKey cacheKey = new CacheKey(); 647 cacheKey.update(resultMapping); 648 if (columns != null && names != null) { 649 String[] columnsArray = columns.split(","); 650 String[] namesArray = names.split(","); 651 for (int i = 0; i < columnsArray.length; i++) { 652 Object value = rs.getString(columnsArray[i]); 653 if (value != null) { 654 cacheKey.update(namesArray[i]); 655 cacheKey.update(value); 656 } 657 } 658 } 659 return cacheKey; 660 } 661 662 // 663 // INSTANTIATION & CONSTRUCTOR MAPPING 664 // 665 666 private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, ResultLoaderMap lazyLoader, 667 String columnPrefix) throws SQLException { 668 this.useConstructorMappings = false; // reset previous mapping result 669 final List<Class<?>> constructorArgTypes = new ArrayList<>(); 670 final List<Object> constructorArgs = new ArrayList<>(); 671 Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix); 672 if (resultObject != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { 673 final List<ResultMapping> propertyMappings = resultMap.getPropertyResultMappings(); 674 for (ResultMapping propertyMapping : propertyMappings) { 675 // issue gcode #109 && issue #149 676 if (propertyMapping.getNestedQueryId() != null && propertyMapping.isLazy()) { 677 resultObject = configuration.getProxyFactory().createProxy(resultObject, lazyLoader, configuration, 678 objectFactory, constructorArgTypes, constructorArgs); 679 break; 680 } 681 } 682 } 683 this.useConstructorMappings = resultObject != null && !constructorArgTypes.isEmpty(); // set current mapping result 684 return resultObject; 685 } 686 687 private Object createResultObject(ResultSetWrapper rsw, ResultMap resultMap, List<Class<?>> constructorArgTypes, 688 List<Object> constructorArgs, String columnPrefix) throws SQLException { 689 final Class<?> resultType = resultMap.getType(); 690 final MetaClass metaType = MetaClass.forClass(resultType, reflectorFactory); 691 final List<ResultMapping> constructorMappings = resultMap.getConstructorResultMappings(); 692 if (hasTypeHandlerForResultObject(rsw, resultType)) { 693 return createPrimitiveResultObject(rsw, resultMap, columnPrefix); 694 } 695 if (!constructorMappings.isEmpty()) { 696 return createParameterizedResultObject(rsw, resultType, constructorMappings, constructorArgTypes, constructorArgs, 697 columnPrefix); 698 } else if (resultType.isInterface() || metaType.hasDefaultConstructor()) { 699 return objectFactory.create(resultType); 700 } else if (shouldApplyAutomaticMappings(resultMap, false)) { 701 return createByConstructorSignature(rsw, resultMap, columnPrefix, resultType, constructorArgTypes, 702 constructorArgs); 703 } 704 throw new ExecutorException("Do not know how to create an instance of " + resultType); 705 } 706 707 Object createParameterizedResultObject(ResultSetWrapper rsw, Class<?> resultType, 708 List<ResultMapping> constructorMappings, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, 709 String columnPrefix) { 710 boolean foundValues = false; 711 for (ResultMapping constructorMapping : constructorMappings) { 712 final Class<?> parameterType = constructorMapping.getJavaType(); 713 final String column = constructorMapping.getColumn(); 714 final Object value; 715 try { 716 if (constructorMapping.getNestedQueryId() != null) { 717 value = getNestedQueryConstructorValue(rsw.getResultSet(), constructorMapping, columnPrefix); 718 } else if (constructorMapping.getNestedResultMapId() != null) { 719 final ResultMap resultMap = configuration.getResultMap(constructorMapping.getNestedResultMapId()); 720 value = getRowValue(rsw, resultMap, getColumnPrefix(columnPrefix, constructorMapping)); 721 } else { 722 final TypeHandler<?> typeHandler = constructorMapping.getTypeHandler(); 723 value = typeHandler.getResult(rsw.getResultSet(), prependPrefix(column, columnPrefix)); 724 } 725 } catch (ResultMapException | SQLException e) { 726 throw new ExecutorException("Could not process result for mapping: " + constructorMapping, e); 727 } 728 constructorArgTypes.add(parameterType); 729 constructorArgs.add(value); 730 foundValues = value != null || foundValues; 731 } 732 return foundValues ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null; 733 } 734 735 private Object createByConstructorSignature(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix, 736 Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs) throws SQLException { 737 return applyConstructorAutomapping(rsw, resultMap, columnPrefix, resultType, constructorArgTypes, constructorArgs, 738 findConstructorForAutomapping(resultType, rsw).orElseThrow(() -> new ExecutorException( 739 "No constructor found in " + resultType.getName() + " matching " + rsw.getClassNames()))); 740 } 741 742 private Optional<Constructor<?>> findConstructorForAutomapping(final Class<?> resultType, ResultSetWrapper rsw) { 743 Constructor<?>[] constructors = resultType.getDeclaredConstructors(); 744 if (constructors.length == 1) { 745 return Optional.of(constructors[0]); 746 } 747 Optional<Constructor<?>> annotated = Arrays.stream(constructors) 748 .filter(x -> x.isAnnotationPresent(AutomapConstructor.class)).reduce((x, y) -> { 749 throw new ExecutorException("@AutomapConstructor should be used in only one constructor."); 750 }); 751 if (annotated.isPresent()) { 752 return annotated; 753 } 754 if (configuration.isArgNameBasedConstructorAutoMapping()) { 755 // Finding-best-match type implementation is possible, 756 // but using @AutomapConstructor seems sufficient. 757 throw new ExecutorException(MessageFormat.format( 758 "'argNameBasedConstructorAutoMapping' is enabled and the class ''{0}'' has multiple constructors, so @AutomapConstructor must be added to one of the constructors.", 759 resultType.getName())); 760 } else { 761 return Arrays.stream(constructors).filter(x -> findUsableConstructorByArgTypes(x, rsw.getJdbcTypes())).findAny(); 762 } 763 } 764 765 private boolean findUsableConstructorByArgTypes(final Constructor<?> constructor, final List<JdbcType> jdbcTypes) { 766 final Class<?>[] parameterTypes = constructor.getParameterTypes(); 767 if (parameterTypes.length != jdbcTypes.size()) { 768 return false; 769 } 770 for (int i = 0; i < parameterTypes.length; i++) { 771 if (!typeHandlerRegistry.hasTypeHandler(parameterTypes[i], jdbcTypes.get(i))) { 772 return false; 773 } 774 } 775 return true; 776 } 777 778 private Object applyConstructorAutomapping(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix, 779 Class<?> resultType, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, Constructor<?> constructor) 780 throws SQLException { 781 boolean foundValues = false; 782 if (configuration.isArgNameBasedConstructorAutoMapping()) { 783 foundValues = applyArgNameBasedConstructorAutoMapping(rsw, resultMap, columnPrefix, constructorArgTypes, 784 constructorArgs, constructor, foundValues); 785 } else { 786 foundValues = applyColumnOrderBasedConstructorAutomapping(rsw, constructorArgTypes, constructorArgs, constructor, 787 foundValues); 788 } 789 return foundValues || configuration.isReturnInstanceForEmptyRow() 790 ? objectFactory.create(resultType, constructorArgTypes, constructorArgs) : null; 791 } 792 793 private boolean applyColumnOrderBasedConstructorAutomapping(ResultSetWrapper rsw, List<Class<?>> constructorArgTypes, 794 List<Object> constructorArgs, Constructor<?> constructor, boolean foundValues) throws SQLException { 795 796 // fixed IndexOutOfBoundsException https://gitee.com/mybatis-flex/mybatis-flex/issues/I98ZO9 797 List<String> columnNames = rsw.getColumnNames(); 798 if (columnNames.size() < constructor.getParameterCount()) { 799 throw new IllegalArgumentException("Can not invoke the constructor[" + buildMethodString(constructor) + "] with value names: " 800 + Arrays.toString(columnNames.toArray()) + ",\n" 801 + "Perhaps you can add a default (no parameters) constructor to fix it." 802 ); 803 } 804 805 Class<?>[] parameterTypes = constructor.getParameterTypes(); 806 for (int i = 0; i < parameterTypes.length; i++) { 807 Class<?> parameterType = parameterTypes[i]; 808 String columnName = columnNames.get(i); 809 TypeHandler<?> typeHandler = rsw.getTypeHandler(parameterType, columnName); 810 Object value = typeHandler.getResult(rsw.getResultSet(), columnName); 811 constructorArgTypes.add(parameterType); 812 constructorArgs.add(value); 813 foundValues = value != null || foundValues; 814 } 815 return foundValues; 816 } 817 818 819 private static String buildMethodString(Executable method) { 820 StringBuilder sb = new StringBuilder() 821 .append(method.getDeclaringClass().getName()) 822 .append(".") 823 .append(method.getName()) 824 .append("("); 825 826 Class<?>[] params = method.getParameterTypes(); 827 int in = 0; 828 for (Class<?> clazz : params) { 829 sb.append(clazz.getName()); 830 if (++in < params.length) { 831 sb.append(","); 832 } 833 } 834 835 return sb.append(")").toString(); 836 } 837 838 839 private boolean applyArgNameBasedConstructorAutoMapping(ResultSetWrapper rsw, ResultMap resultMap, 840 String columnPrefix, List<Class<?>> constructorArgTypes, List<Object> constructorArgs, Constructor<?> constructor, 841 boolean foundValues) throws SQLException { 842 List<String> missingArgs = null; 843 Parameter[] params = constructor.getParameters(); 844 for (Parameter param : params) { 845 boolean columnNotFound = true; 846 Param paramAnno = param.getAnnotation(Param.class); 847 String paramName = paramAnno == null ? param.getName() : paramAnno.value(); 848 for (String columnName : rsw.getColumnNames()) { 849 if (columnMatchesParam(columnName, paramName, columnPrefix)) { 850 Class<?> paramType = param.getType(); 851 TypeHandler<?> typeHandler = rsw.getTypeHandler(paramType, columnName); 852 Object value = typeHandler.getResult(rsw.getResultSet(), columnName); 853 constructorArgTypes.add(paramType); 854 constructorArgs.add(value); 855 final String mapKey = resultMap.getId() + ":" + columnPrefix; 856 if (!autoMappingsCache.containsKey(mapKey)) { 857 MapUtil.computeIfAbsent(constructorAutoMappingColumns, mapKey, k -> new ArrayList<>()).add(columnName); 858 } 859 columnNotFound = false; 860 foundValues = value != null || foundValues; 861 } 862 } 863 if (columnNotFound) { 864 if (missingArgs == null) { 865 missingArgs = new ArrayList<>(); 866 } 867 missingArgs.add(paramName); 868 } 869 } 870 if (foundValues && constructorArgs.size() < params.length) { 871 throw new ExecutorException(MessageFormat.format( 872 "Constructor auto-mapping of ''{1}'' failed " + "because ''{0}'' were not found in the result set; " 873 + "Available columns are ''{2}'' and mapUnderscoreToCamelCase is ''{3}''.", 874 missingArgs, constructor, rsw.getColumnNames(), configuration.isMapUnderscoreToCamelCase())); 875 } 876 return foundValues; 877 } 878 879 private boolean columnMatchesParam(String columnName, String paramName, String columnPrefix) { 880 if (columnPrefix != null) { 881 if (!columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) { 882 return false; 883 } 884 columnName = columnName.substring(columnPrefix.length()); 885 } 886 return paramName 887 .equalsIgnoreCase(configuration.isMapUnderscoreToCamelCase() ? columnName.replace("_", "") : columnName); 888 } 889 890 protected Object createPrimitiveResultObject(ResultSetWrapper rsw, ResultMap resultMap, String columnPrefix) 891 throws SQLException { 892 final Class<?> resultType = resultMap.getType(); 893 final String columnName; 894 if (!resultMap.getResultMappings().isEmpty()) { 895 final List<ResultMapping> resultMappingList = resultMap.getResultMappings(); 896 final ResultMapping mapping = resultMappingList.get(0); 897 columnName = prependPrefix(mapping.getColumn(), columnPrefix); 898 } else { 899 columnName = rsw.getColumnNames().get(0); 900 } 901 final TypeHandler<?> typeHandler = rsw.getTypeHandler(resultType, columnName); 902 return typeHandler.getResult(rsw.getResultSet(), columnName); 903 } 904 905 // 906 // NESTED QUERY 907 // 908 909 private Object getNestedQueryConstructorValue(ResultSet rs, ResultMapping constructorMapping, String columnPrefix) 910 throws SQLException { 911 final String nestedQueryId = constructorMapping.getNestedQueryId(); 912 final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId); 913 final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType(); 914 final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, constructorMapping, 915 nestedQueryParameterType, columnPrefix); 916 Object value = null; 917 if (nestedQueryParameterObject != null) { 918 final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject); 919 final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, 920 nestedBoundSql); 921 final Class<?> targetType = constructorMapping.getJavaType(); 922 final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, 923 nestedQueryParameterObject, targetType, key, nestedBoundSql); 924 value = resultLoader.loadResult(); 925 } 926 return value; 927 } 928 929 private Object getNestedQueryMappingValue(ResultSet rs, MetaObject metaResultObject, ResultMapping propertyMapping, 930 ResultLoaderMap lazyLoader, String columnPrefix) throws SQLException { 931 final String nestedQueryId = propertyMapping.getNestedQueryId(); 932 final String property = propertyMapping.getProperty(); 933 final MappedStatement nestedQuery = configuration.getMappedStatement(nestedQueryId); 934 final Class<?> nestedQueryParameterType = nestedQuery.getParameterMap().getType(); 935 final Object nestedQueryParameterObject = prepareParameterForNestedQuery(rs, propertyMapping, 936 nestedQueryParameterType, columnPrefix); 937 Object value = null; 938 if (nestedQueryParameterObject != null) { 939 final BoundSql nestedBoundSql = nestedQuery.getBoundSql(nestedQueryParameterObject); 940 final CacheKey key = executor.createCacheKey(nestedQuery, nestedQueryParameterObject, RowBounds.DEFAULT, 941 nestedBoundSql); 942 final Class<?> targetType = propertyMapping.getJavaType(); 943 if (executor.isCached(nestedQuery, key)) { 944 executor.deferLoad(nestedQuery, metaResultObject, property, key, targetType); 945 value = DEFERRED; 946 } else { 947 final ResultLoader resultLoader = new ResultLoader(configuration, executor, nestedQuery, 948 nestedQueryParameterObject, targetType, key, nestedBoundSql); 949 if (propertyMapping.isLazy()) { 950 lazyLoader.addLoader(property, metaResultObject, resultLoader); 951 value = DEFERRED; 952 } else { 953 value = resultLoader.loadResult(); 954 } 955 } 956 } 957 return value; 958 } 959 960 private Object prepareParameterForNestedQuery(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType, 961 String columnPrefix) throws SQLException { 962 if (resultMapping.isCompositeResult()) { 963 return prepareCompositeKeyParameter(rs, resultMapping, parameterType, columnPrefix); 964 } 965 return prepareSimpleKeyParameter(rs, resultMapping, parameterType, columnPrefix); 966 } 967 968 private Object prepareSimpleKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType, 969 String columnPrefix) throws SQLException { 970 final TypeHandler<?> typeHandler; 971 if (typeHandlerRegistry.hasTypeHandler(parameterType)) { 972 typeHandler = typeHandlerRegistry.getTypeHandler(parameterType); 973 } else { 974 typeHandler = typeHandlerRegistry.getUnknownTypeHandler(); 975 } 976 return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix)); 977 } 978 979 private Object prepareCompositeKeyParameter(ResultSet rs, ResultMapping resultMapping, Class<?> parameterType, 980 String columnPrefix) throws SQLException { 981 final Object parameterObject = instantiateParameterObject(parameterType); 982 final MetaObject metaObject = configuration.newMetaObject(parameterObject); 983 boolean foundValues = false; 984 for (ResultMapping innerResultMapping : resultMapping.getComposites()) { 985 final Class<?> propType = metaObject.getSetterType(innerResultMapping.getProperty()); 986 final TypeHandler<?> typeHandler = typeHandlerRegistry.getTypeHandler(propType); 987 final Object propValue = typeHandler.getResult(rs, prependPrefix(innerResultMapping.getColumn(), columnPrefix)); 988 // issue #353 & #560 do not execute nested query if key is null 989 if (propValue != null) { 990 metaObject.setValue(innerResultMapping.getProperty(), propValue); 991 foundValues = true; 992 } 993 } 994 return foundValues ? parameterObject : null; 995 } 996 997 private Object instantiateParameterObject(Class<?> parameterType) { 998 if (parameterType == null) { 999 return new HashMap<>(); 1000 } 1001 if (ParamMap.class.equals(parameterType)) { 1002 return new HashMap<>(); // issue #649 1003 } else { 1004 return objectFactory.create(parameterType); 1005 } 1006 } 1007 1008 // 1009 // DISCRIMINATOR 1010 // 1011 1012 @Override 1013 public ResultMap resolveDiscriminatedResultMap(ResultSet rs, ResultMap resultMap, String columnPrefix) 1014 throws SQLException { 1015 Set<String> pastDiscriminators = new HashSet<>(); 1016 Discriminator discriminator = resultMap.getDiscriminator(); 1017 while (discriminator != null) { 1018 final Object value = getDiscriminatorValue(rs, discriminator, columnPrefix); 1019 final String discriminatedMapId = discriminator.getMapIdFor(String.valueOf(value)); 1020 if (!configuration.hasResultMap(discriminatedMapId)) { 1021 break; 1022 } 1023 resultMap = configuration.getResultMap(discriminatedMapId); 1024 Discriminator lastDiscriminator = discriminator; 1025 discriminator = resultMap.getDiscriminator(); 1026 if (discriminator == lastDiscriminator || !pastDiscriminators.add(discriminatedMapId)) { 1027 break; 1028 } 1029 } 1030 return resultMap; 1031 } 1032 1033 private Object getDiscriminatorValue(ResultSet rs, Discriminator discriminator, String columnPrefix) 1034 throws SQLException { 1035 final ResultMapping resultMapping = discriminator.getResultMapping(); 1036 final TypeHandler<?> typeHandler = resultMapping.getTypeHandler(); 1037 return typeHandler.getResult(rs, prependPrefix(resultMapping.getColumn(), columnPrefix)); 1038 } 1039 1040 1041 protected String prependPrefix(String columnName, String prefix) { 1042 if (columnName == null || columnName.length() == 0 || prefix == null || prefix.length() == 0) { 1043 return columnName; 1044 } 1045 return prefix + columnName; 1046 } 1047 1048 // 1049 // HANDLE NESTED RESULT MAPS 1050 // 1051 1052 private void handleRowValuesForNestedResultMap(ResultSetWrapper rsw, ResultMap resultMap, 1053 ResultHandler<?> resultHandler, RowBounds rowBounds, ResultMapping parentMapping) throws SQLException { 1054 final DefaultResultContext<Object> resultContext = new DefaultResultContext<>(); 1055 ResultSet resultSet = rsw.getResultSet(); 1056 skipRows(resultSet, rowBounds); 1057 Object rowValue = previousRowValue; 1058 while (shouldProcessMoreRows(resultContext, rowBounds) && !resultSet.isClosed() && resultSet.next()) { 1059 final ResultMap discriminatedResultMap = resolveDiscriminatedResultMap(resultSet, resultMap, null); 1060 final CacheKey rowKey = createRowKey(discriminatedResultMap, rsw, null); 1061 Object partialObject = nestedResultObjects.get(rowKey); 1062 // issue #577 && #542 1063 if (mappedStatement.isResultOrdered()) { 1064 if (partialObject == null && rowValue != null) { 1065 nestedResultObjects.clear(); 1066 storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet); 1067 } 1068 rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject); 1069 } else { 1070 rowValue = getRowValue(rsw, discriminatedResultMap, rowKey, null, partialObject); 1071 if (partialObject == null) { 1072 storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet); 1073 } 1074 } 1075 } 1076 if (rowValue != null && mappedStatement.isResultOrdered() && shouldProcessMoreRows(resultContext, rowBounds)) { 1077 storeObject(resultHandler, resultContext, rowValue, parentMapping, resultSet); 1078 previousRowValue = null; 1079 } else if (rowValue != null) { 1080 previousRowValue = rowValue; 1081 } 1082 } 1083 1084 // 1085 // NESTED RESULT MAP (JOIN MAPPING) 1086 // 1087 1088 private boolean applyNestedResultMappings(ResultSetWrapper rsw, ResultMap resultMap, MetaObject metaObject, 1089 String parentPrefix, CacheKey parentRowKey, boolean newObject) { 1090 boolean foundValues = false; 1091 for (ResultMapping resultMapping : resultMap.getPropertyResultMappings()) { 1092 final String nestedResultMapId = resultMapping.getNestedResultMapId(); 1093 if (nestedResultMapId != null && resultMapping.getResultSet() == null) { 1094 try { 1095 final String columnPrefix = getColumnPrefix(parentPrefix, resultMapping); 1096 final ResultMap nestedResultMap = getNestedResultMap(rsw.getResultSet(), nestedResultMapId, columnPrefix); 1097 if (resultMapping.getColumnPrefix() == null) { 1098 // try to fill circular reference only when columnPrefix 1099 // is not specified for the nested result map (issue #215) 1100 Object ancestorObject = ancestorObjects.get(nestedResultMapId); 1101 if (ancestorObject != null) { 1102 if (newObject) { 1103 linkObjects(metaObject, resultMapping, ancestorObject); // issue #385 1104 } 1105 continue; 1106 } 1107 } 1108 final CacheKey rowKey = createRowKey(nestedResultMap, rsw, columnPrefix); 1109 final CacheKey combinedKey = combineKeys(rowKey, parentRowKey); 1110 Object rowValue = nestedResultObjects.get(combinedKey); 1111 boolean knownValue = rowValue != null; 1112 instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject); // mandatory 1113 if (anyNotNullColumnHasValue(resultMapping, columnPrefix, rsw)) { 1114 rowValue = getRowValue(rsw, nestedResultMap, combinedKey, columnPrefix, rowValue); 1115 if (rowValue != null && !knownValue) { 1116 linkObjects(metaObject, resultMapping, rowValue); 1117 foundValues = true; 1118 } 1119 } 1120 } catch (SQLException e) { 1121 throw new ExecutorException( 1122 "Error getting nested result map values for '" + resultMapping.getProperty() + "'. Cause: " + e, e); 1123 } 1124 } 1125 } 1126 return foundValues; 1127 } 1128 1129 private String getColumnPrefix(String parentPrefix, ResultMapping resultMapping) { 1130 final StringBuilder columnPrefixBuilder = new StringBuilder(); 1131 if (parentPrefix != null) { 1132 columnPrefixBuilder.append(parentPrefix); 1133 } 1134 if (resultMapping.getColumnPrefix() != null) { 1135 columnPrefixBuilder.append(resultMapping.getColumnPrefix()); 1136 } 1137 return columnPrefixBuilder.length() == 0 ? null : columnPrefixBuilder.toString().toUpperCase(Locale.ENGLISH); 1138 } 1139 1140 private boolean anyNotNullColumnHasValue(ResultMapping resultMapping, String columnPrefix, ResultSetWrapper rsw) 1141 throws SQLException { 1142 Set<String> notNullColumns = resultMapping.getNotNullColumns(); 1143 if (notNullColumns != null && !notNullColumns.isEmpty()) { 1144 ResultSet rs = rsw.getResultSet(); 1145 for (String column : notNullColumns) { 1146 rs.getObject(prependPrefix(column, columnPrefix)); 1147 if (!rs.wasNull()) { 1148 return true; 1149 } 1150 } 1151 return false; 1152 } 1153 if (columnPrefix != null) { 1154 for (String columnName : rsw.getColumnNames()) { 1155 if (columnName.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix.toUpperCase(Locale.ENGLISH))) { 1156 return true; 1157 } 1158 } 1159 return false; 1160 } 1161 return true; 1162 } 1163 1164 private ResultMap getNestedResultMap(ResultSet rs, String nestedResultMapId, String columnPrefix) 1165 throws SQLException { 1166 ResultMap nestedResultMap = configuration.getResultMap(nestedResultMapId); 1167 return resolveDiscriminatedResultMap(rs, nestedResultMap, columnPrefix); 1168 } 1169 1170 // 1171 // UNIQUE RESULT KEY 1172 // 1173 1174 private CacheKey createRowKey(ResultMap resultMap, ResultSetWrapper rsw, String columnPrefix) throws SQLException { 1175 final CacheKey cacheKey = new CacheKey(); 1176 cacheKey.update(resultMap.getId()); 1177 List<ResultMapping> resultMappings = getResultMappingsForRowKey(resultMap); 1178 if (resultMappings.isEmpty()) { 1179 if (Map.class.isAssignableFrom(resultMap.getType())) { 1180 createRowKeyForMap(rsw, cacheKey); 1181 } else { 1182 createRowKeyForUnmappedProperties(resultMap, rsw, cacheKey, columnPrefix); 1183 } 1184 } else { 1185 createRowKeyForMappedProperties(resultMap, rsw, cacheKey, resultMappings, columnPrefix); 1186 } 1187 if (cacheKey.getUpdateCount() < 2) { 1188 return CacheKey.NULL_CACHE_KEY; 1189 } 1190 return cacheKey; 1191 } 1192 1193 private CacheKey combineKeys(CacheKey rowKey, CacheKey parentRowKey) { 1194 if (rowKey.getUpdateCount() > 1 && parentRowKey.getUpdateCount() > 1) { 1195 CacheKey combinedKey; 1196 try { 1197 combinedKey = rowKey.clone(); 1198 } catch (CloneNotSupportedException e) { 1199 throw new ExecutorException("Error cloning cache key. Cause: " + e, e); 1200 } 1201 combinedKey.update(parentRowKey); 1202 return combinedKey; 1203 } 1204 return CacheKey.NULL_CACHE_KEY; 1205 } 1206 1207 private List<ResultMapping> getResultMappingsForRowKey(ResultMap resultMap) { 1208 List<ResultMapping> resultMappings = resultMap.getIdResultMappings(); 1209 if (resultMappings.isEmpty()) { 1210 resultMappings = resultMap.getPropertyResultMappings(); 1211 } 1212 return resultMappings; 1213 } 1214 1215 private void createRowKeyForMappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey, 1216 List<ResultMapping> resultMappings, String columnPrefix) throws SQLException { 1217 for (ResultMapping resultMapping : resultMappings) { 1218 if (resultMapping.isSimple()) { 1219 final String column = prependPrefix(resultMapping.getColumn(), columnPrefix); 1220 final TypeHandler<?> th = resultMapping.getTypeHandler(); 1221 Collection<String> mappedColumnNames = rsw.getMappedColumnNames(resultMap, columnPrefix); 1222 // Issue #114 1223 if (column != null && mappedColumnNames.contains(column.toUpperCase(Locale.ENGLISH))) { 1224 final Object value = th.getResult(rsw.getResultSet(), column); 1225 if (value != null || configuration.isReturnInstanceForEmptyRow()) { 1226 cacheKey.update(column); 1227 cacheKey.update(value); 1228 } 1229 } 1230 } 1231 } 1232 } 1233 1234 private void createRowKeyForUnmappedProperties(ResultMap resultMap, ResultSetWrapper rsw, CacheKey cacheKey, 1235 String columnPrefix) throws SQLException { 1236 final MetaClass metaType = MetaClass.forClass(resultMap.getType(), reflectorFactory); 1237 List<String> unmappedColumnNames = rsw.getUnmappedColumnNames(resultMap, columnPrefix); 1238 for (String column : unmappedColumnNames) { 1239 String property = column; 1240 if (columnPrefix != null && !columnPrefix.isEmpty()) { 1241 // When columnPrefix is specified, ignore columns without the prefix. 1242 if (!column.toUpperCase(Locale.ENGLISH).startsWith(columnPrefix)) { 1243 continue; 1244 } 1245 property = column.substring(columnPrefix.length()); 1246 } 1247 if (metaType.findProperty(property, configuration.isMapUnderscoreToCamelCase()) != null) { 1248 String value = rsw.getResultSet().getString(column); 1249 if (value != null) { 1250 cacheKey.update(column); 1251 cacheKey.update(value); 1252 } 1253 } 1254 } 1255 } 1256 1257 private void createRowKeyForMap(ResultSetWrapper rsw, CacheKey cacheKey) throws SQLException { 1258 List<String> columnNames = rsw.getColumnNames(); 1259 for (String columnName : columnNames) { 1260 final String value = rsw.getResultSet().getString(columnName); 1261 if (value != null) { 1262 cacheKey.update(columnName); 1263 cacheKey.update(value); 1264 } 1265 } 1266 } 1267 1268 private void linkObjects(MetaObject metaObject, ResultMapping resultMapping, Object rowValue) { 1269 final Object collectionProperty = instantiateCollectionPropertyIfAppropriate(resultMapping, metaObject); 1270 if (collectionProperty != null) { 1271 final MetaObject targetMetaObject = configuration.newMetaObject(collectionProperty); 1272 targetMetaObject.add(rowValue); 1273 } else { 1274 metaObject.setValue(resultMapping.getProperty(), rowValue); 1275 } 1276 } 1277 1278 private Object instantiateCollectionPropertyIfAppropriate(ResultMapping resultMapping, MetaObject metaObject) { 1279 final String propertyName = resultMapping.getProperty(); 1280 Object propertyValue = metaObject.getValue(propertyName); 1281 if (propertyValue == null) { 1282 Class<?> type = resultMapping.getJavaType(); 1283 if (type == null) { 1284 type = metaObject.getSetterType(propertyName); 1285 } 1286 try { 1287 if (objectFactory.isCollection(type)) { 1288 propertyValue = objectFactory.create(type); 1289 metaObject.setValue(propertyName, propertyValue); 1290 return propertyValue; 1291 } 1292 } catch (Exception e) { 1293 throw new ExecutorException( 1294 "Error instantiating collection property for result '" + resultMapping.getProperty() + "'. Cause: " + e, 1295 e); 1296 } 1297 } else if (objectFactory.isCollection(propertyValue.getClass())) { 1298 return propertyValue; 1299 } 1300 return null; 1301 } 1302 1303 private boolean hasTypeHandlerForResultObject(ResultSetWrapper rsw, Class<?> resultType) { 1304 if (rsw.getColumnNames().size() == 1) { 1305 return typeHandlerRegistry.hasTypeHandler(resultType, rsw.getJdbcType(rsw.getColumnNames().get(0))); 1306 } 1307 return typeHandlerRegistry.hasTypeHandler(resultType); 1308 } 1309 1310}