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