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