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