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