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