Skip to content

Commit 03dc3e4

Browse files
committed
Merge branch 'master' into gh/566-oracle-nested-cursor
2 parents 0e07986 + d4af490 commit 03dc3e4

23 files changed

+1423
-116
lines changed

src/main/java/org/apache/ibatis/builder/MapperBuilderAssistant.java

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2009-2024 the original author or authors.
2+
* Copyright 2009-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -466,5 +466,4 @@ private Class<?> resolveParameterJavaType(Class<?> resultType, String property,
466466
}
467467
return javaType;
468468
}
469-
470469
}

src/main/java/org/apache/ibatis/builder/ResultMappingConstructorResolver.java

Lines changed: 382 additions & 0 deletions
Large diffs are not rendered by default.

src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.java

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2009-2024 the original author or authors.
2+
* Copyright 2009-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -63,6 +63,7 @@
6363
import org.apache.ibatis.builder.CacheRefResolver;
6464
import org.apache.ibatis.builder.IncompleteElementException;
6565
import org.apache.ibatis.builder.MapperBuilderAssistant;
66+
import org.apache.ibatis.builder.ResultMappingConstructorResolver;
6667
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
6768
import org.apache.ibatis.cursor.Cursor;
6869
import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator;
@@ -237,7 +238,7 @@ private String generateResultMapName(Method method) {
237238
private void applyResultMap(String resultMapId, Class<?> returnType, Arg[] args, Result[] results,
238239
TypeDiscriminator discriminator) {
239240
List<ResultMapping> resultMappings = new ArrayList<>();
240-
applyConstructorArgs(args, returnType, resultMappings);
241+
applyConstructorArgs(args, returnType, resultMappings, resultMapId);
241242
applyResults(results, returnType, resultMappings);
242243
Discriminator disc = applyDiscriminator(resultMapId, returnType, discriminator);
243244
// TODO add AutoMappingBehaviour
@@ -251,7 +252,7 @@ private void createDiscriminatorResultMaps(String resultMapId, Class<?> resultTy
251252
String caseResultMapId = resultMapId + "-" + c.value();
252253
List<ResultMapping> resultMappings = new ArrayList<>();
253254
// issue #136
254-
applyConstructorArgs(c.constructArgs(), resultType, resultMappings);
255+
applyConstructorArgs(c.constructArgs(), resultType, resultMappings, resultMapId);
255256
applyResults(c.results(), resultType, resultMappings);
256257
// TODO add AutoMappingBehaviour
257258
assistant.addResultMap(caseResultMapId, c.type(), resultMapId, null, resultMappings, null);
@@ -461,15 +462,15 @@ private void applyResults(Result[] results, Class<?> resultType, List<ResultMapp
461462

462463
private String findColumnPrefix(Result result) {
463464
String columnPrefix = result.one().columnPrefix();
464-
if (columnPrefix.length() < 1) {
465+
if (columnPrefix.isEmpty()) {
465466
columnPrefix = result.many().columnPrefix();
466467
}
467468
return columnPrefix;
468469
}
469470

470471
private String nestedResultMapId(Result result) {
471472
String resultMapId = result.one().resultMap();
472-
if (resultMapId.length() < 1) {
473+
if (resultMapId.isEmpty()) {
473474
resultMapId = result.many().resultMap();
474475
}
475476
if (!resultMapId.contains(".")) {
@@ -479,15 +480,15 @@ private String nestedResultMapId(Result result) {
479480
}
480481

481482
private boolean hasNestedResultMap(Result result) {
482-
if (result.one().resultMap().length() > 0 && result.many().resultMap().length() > 0) {
483+
if (!result.one().resultMap().isEmpty() && !result.many().resultMap().isEmpty()) {
483484
throw new BuilderException("Cannot use both @One and @Many annotations in the same @Result");
484485
}
485-
return result.one().resultMap().length() > 0 || result.many().resultMap().length() > 0;
486+
return !result.one().resultMap().isEmpty() || !result.many().resultMap().isEmpty();
486487
}
487488

488489
private String nestedSelectId(Result result) {
489490
String nestedSelect = result.one().select();
490-
if (nestedSelect.length() < 1) {
491+
if (nestedSelect.isEmpty()) {
491492
nestedSelect = result.many().select();
492493
}
493494
if (!nestedSelect.contains(".")) {
@@ -498,22 +499,24 @@ private String nestedSelectId(Result result) {
498499

499500
private boolean isLazy(Result result) {
500501
boolean isLazy = configuration.isLazyLoadingEnabled();
501-
if (result.one().select().length() > 0 && FetchType.DEFAULT != result.one().fetchType()) {
502+
if (!result.one().select().isEmpty() && FetchType.DEFAULT != result.one().fetchType()) {
502503
isLazy = result.one().fetchType() == FetchType.LAZY;
503-
} else if (result.many().select().length() > 0 && FetchType.DEFAULT != result.many().fetchType()) {
504+
} else if (!result.many().select().isEmpty() && FetchType.DEFAULT != result.many().fetchType()) {
504505
isLazy = result.many().fetchType() == FetchType.LAZY;
505506
}
506507
return isLazy;
507508
}
508509

509510
private boolean hasNestedSelect(Result result) {
510-
if (result.one().select().length() > 0 && result.many().select().length() > 0) {
511+
if (!result.one().select().isEmpty() && !result.many().select().isEmpty()) {
511512
throw new BuilderException("Cannot use both @One and @Many annotations in the same @Result");
512513
}
513-
return result.one().select().length() > 0 || result.many().select().length() > 0;
514+
return !result.one().select().isEmpty() || !result.many().select().isEmpty();
514515
}
515516

516-
private void applyConstructorArgs(Arg[] args, Class<?> resultType, List<ResultMapping> resultMappings) {
517+
private void applyConstructorArgs(Arg[] args, Class<?> resultType, List<ResultMapping> resultMappings,
518+
String resultMapId) {
519+
final List<ResultMapping> mappings = new ArrayList<>();
517520
for (Arg arg : args) {
518521
List<ResultFlag> flags = new ArrayList<>();
519522
flags.add(ResultFlag.CONSTRUCTOR);
@@ -527,12 +530,16 @@ private void applyConstructorArgs(Arg[] args, Class<?> resultType, List<ResultMa
527530
nullOrEmpty(arg.column()), arg.javaType() == void.class ? null : arg.javaType(),
528531
arg.jdbcType() == JdbcType.UNDEFINED ? null : arg.jdbcType(), nullOrEmpty(arg.select()),
529532
nullOrEmpty(arg.resultMap()), null, nullOrEmpty(arg.columnPrefix()), typeHandler, flags, null, null, false);
530-
resultMappings.add(resultMapping);
533+
mappings.add(resultMapping);
531534
}
535+
536+
final ResultMappingConstructorResolver resolver = new ResultMappingConstructorResolver(configuration, mappings,
537+
resultType, resultMapId);
538+
resultMappings.addAll(resolver.resolveWithConstructor());
532539
}
533540

534541
private String nullOrEmpty(String value) {
535-
return value == null || value.trim().length() == 0 ? null : value;
542+
return value == null || value.trim().isEmpty() ? null : value;
536543
}
537544

538545
private KeyGenerator handleSelectKeyAnnotation(SelectKey selectKeyAnnotation, String baseStatementId,

src/main/java/org/apache/ibatis/builder/xml/XMLMapperBuilder.java

Lines changed: 19 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2009-2024 the original author or authors.
2+
* Copyright 2009-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -31,6 +31,7 @@
3131
import org.apache.ibatis.builder.IncompleteElementException;
3232
import org.apache.ibatis.builder.MapperBuilderAssistant;
3333
import org.apache.ibatis.builder.ResultMapResolver;
34+
import org.apache.ibatis.builder.ResultMappingConstructorResolver;
3435
import org.apache.ibatis.cache.Cache;
3536
import org.apache.ibatis.executor.ErrorContext;
3637
import org.apache.ibatis.io.Resources;
@@ -223,12 +224,17 @@ private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> addi
223224
if (typeClass == null) {
224225
typeClass = inheritEnclosingType(resultMapNode, enclosingType);
225226
}
227+
228+
String id = resultMapNode.getStringAttribute("id", resultMapNode::getValueBasedIdentifier);
229+
String extend = resultMapNode.getStringAttribute("extends");
230+
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
231+
226232
Discriminator discriminator = null;
227233
List<ResultMapping> resultMappings = new ArrayList<>(additionalResultMappings);
228234
List<XNode> resultChildren = resultMapNode.getChildren();
229235
for (XNode resultChild : resultChildren) {
230236
if ("constructor".equals(resultChild.getName())) {
231-
processConstructorElement(resultChild, typeClass, resultMappings);
237+
processConstructorElement(resultChild, typeClass, resultMappings, id);
232238
} else if ("discriminator".equals(resultChild.getName())) {
233239
discriminator = processDiscriminatorElement(resultChild, typeClass, resultMappings);
234240
} else {
@@ -239,9 +245,7 @@ private ResultMap resultMapElement(XNode resultMapNode, List<ResultMapping> addi
239245
resultMappings.add(buildResultMappingFromContext(resultChild, typeClass, flags));
240246
}
241247
}
242-
String id = resultMapNode.getStringAttribute("id", resultMapNode::getValueBasedIdentifier);
243-
String extend = resultMapNode.getStringAttribute("extends");
244-
Boolean autoMapping = resultMapNode.getBooleanAttribute("autoMapping");
248+
245249
ResultMapResolver resultMapResolver = new ResultMapResolver(builderAssistant, id, typeClass, extend, discriminator,
246250
resultMappings, autoMapping);
247251
try {
@@ -265,16 +269,24 @@ protected Class<?> inheritEnclosingType(XNode resultMapNode, Class<?> enclosingT
265269
return null;
266270
}
267271

268-
private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings) {
272+
private void processConstructorElement(XNode resultChild, Class<?> resultType, List<ResultMapping> resultMappings,
273+
String id) {
269274
List<XNode> argChildren = resultChild.getChildren();
275+
276+
final List<ResultMapping> mappings = new ArrayList<>();
270277
for (XNode argChild : argChildren) {
271278
List<ResultFlag> flags = new ArrayList<>();
272279
flags.add(ResultFlag.CONSTRUCTOR);
273280
if ("idArg".equals(argChild.getName())) {
274281
flags.add(ResultFlag.ID);
275282
}
276-
resultMappings.add(buildResultMappingFromContext(argChild, resultType, flags));
283+
284+
mappings.add(buildResultMappingFromContext(argChild, resultType, flags));
277285
}
286+
287+
final ResultMappingConstructorResolver resolver = new ResultMappingConstructorResolver(configuration, mappings,
288+
resultType, id);
289+
resultMappings.addAll(resolver.resolveWithConstructor());
278290
}
279291

280292
private Discriminator processDiscriminatorElement(XNode context, Class<?> resultType,

src/main/java/org/apache/ibatis/mapping/ResultMap.java

Lines changed: 8 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -15,20 +15,13 @@
1515
*/
1616
package org.apache.ibatis.mapping;
1717

18-
import java.lang.annotation.Annotation;
19-
import java.lang.reflect.Constructor;
2018
import java.util.ArrayList;
2119
import java.util.Collections;
2220
import java.util.HashSet;
2321
import java.util.List;
2422
import java.util.Locale;
2523
import java.util.Set;
2624

27-
import org.apache.ibatis.annotations.Param;
28-
import org.apache.ibatis.builder.BuilderException;
29-
import org.apache.ibatis.logging.Log;
30-
import org.apache.ibatis.logging.LogFactory;
31-
import org.apache.ibatis.reflection.ParamNameUtil;
3225
import org.apache.ibatis.session.Configuration;
3326
import org.apache.ibatis.type.JdbcType;
3427

@@ -56,8 +49,6 @@ private ResultMap() {
5649
}
5750

5851
public static class Builder {
59-
private static final Log log = LogFactory.getLog(Builder.class);
60-
6152
private final ResultMap resultMap = new ResultMap();
6253

6354
public Builder(Configuration configuration, String id, Class<?> type, List<ResultMapping> resultMappings) {
@@ -86,12 +77,13 @@ public ResultMap build() {
8677
if (resultMap.id == null) {
8778
throw new IllegalArgumentException("ResultMaps must have an id");
8879
}
80+
8981
resultMap.mappedColumns = new HashSet<>();
9082
resultMap.mappedProperties = new HashSet<>();
9183
resultMap.idResultMappings = new ArrayList<>();
9284
resultMap.constructorResultMappings = new ArrayList<>();
9385
resultMap.propertyResultMappings = new ArrayList<>();
94-
final List<String> constructorArgNames = new ArrayList<>();
86+
9587
for (ResultMapping resultMapping : resultMap.resultMappings) {
9688
resultMap.hasNestedQueries = resultMap.hasNestedQueries || resultMapping.getNestedQueryId() != null;
9789
resultMap.hasNestedResultMaps = resultMap.hasNestedResultMaps || resultMapping.getNestedResultMapId() != null
@@ -107,10 +99,12 @@ public ResultMap build() {
10799
}
108100
}
109101
}
102+
110103
final String property = resultMapping.getProperty();
111104
if (property != null) {
112105
resultMap.mappedProperties.add(property);
113106
}
107+
114108
if (resultMapping.getFlags().contains(ResultFlag.CONSTRUCTOR)) {
115109
resultMap.constructorResultMappings.add(resultMapping);
116110

@@ -119,99 +113,27 @@ public ResultMap build() {
119113
resultMap.hasResultMapsUsingConstructorCollection = resultMap.hasResultMapsUsingConstructorCollection
120114
|| (resultMapping.getNestedQueryId() == null && resultMapping.getTypeHandler() == null && javaType != null
121115
&& resultMap.configuration.getObjectFactory().isCollection(javaType));
122-
123-
if (resultMapping.getProperty() != null) {
124-
constructorArgNames.add(resultMapping.getProperty());
125-
}
126116
} else {
127117
resultMap.propertyResultMappings.add(resultMapping);
128118
}
119+
129120
if (resultMapping.getFlags().contains(ResultFlag.ID)) {
130121
resultMap.idResultMappings.add(resultMapping);
131122
}
132123
}
124+
133125
if (resultMap.idResultMappings.isEmpty()) {
134126
resultMap.idResultMappings.addAll(resultMap.resultMappings);
135127
}
136-
if (!constructorArgNames.isEmpty()) {
137-
final List<String> actualArgNames = argNamesOfMatchingConstructor(constructorArgNames);
138-
if (actualArgNames == null) {
139-
throw new BuilderException("Error in result map '" + resultMap.id + "'. Failed to find a constructor in '"
140-
+ resultMap.getType().getName() + "' with arg names " + constructorArgNames
141-
+ ". Note that 'javaType' is required when there is no writable property with the same name ('name' is optional, BTW). There might be more info in debug log.");
142-
}
143-
resultMap.constructorResultMappings.sort((o1, o2) -> {
144-
int paramIdx1 = actualArgNames.indexOf(o1.getProperty());
145-
int paramIdx2 = actualArgNames.indexOf(o2.getProperty());
146-
return paramIdx1 - paramIdx2;
147-
});
148-
}
128+
149129
// lock down collections
150130
resultMap.resultMappings = Collections.unmodifiableList(resultMap.resultMappings);
151131
resultMap.idResultMappings = Collections.unmodifiableList(resultMap.idResultMappings);
152132
resultMap.constructorResultMappings = Collections.unmodifiableList(resultMap.constructorResultMappings);
153133
resultMap.propertyResultMappings = Collections.unmodifiableList(resultMap.propertyResultMappings);
154134
resultMap.mappedColumns = Collections.unmodifiableSet(resultMap.mappedColumns);
155-
return resultMap;
156-
}
157135

158-
private List<String> argNamesOfMatchingConstructor(List<String> constructorArgNames) {
159-
Constructor<?>[] constructors = resultMap.type.getDeclaredConstructors();
160-
for (Constructor<?> constructor : constructors) {
161-
Class<?>[] paramTypes = constructor.getParameterTypes();
162-
if (constructorArgNames.size() == paramTypes.length) {
163-
List<String> paramNames = getArgNames(constructor);
164-
if (constructorArgNames.containsAll(paramNames)
165-
&& argTypesMatch(constructorArgNames, paramTypes, paramNames)) {
166-
return paramNames;
167-
}
168-
}
169-
}
170-
return null;
171-
}
172-
173-
private boolean argTypesMatch(final List<String> constructorArgNames, Class<?>[] paramTypes,
174-
List<String> paramNames) {
175-
for (int i = 0; i < constructorArgNames.size(); i++) {
176-
Class<?> actualType = paramTypes[paramNames.indexOf(constructorArgNames.get(i))];
177-
Class<?> specifiedType = resultMap.constructorResultMappings.get(i).getJavaType();
178-
if (!actualType.equals(specifiedType)) {
179-
if (log.isDebugEnabled()) {
180-
log.debug("While building result map '" + resultMap.id + "', found a constructor with arg names "
181-
+ constructorArgNames + ", but the type of '" + constructorArgNames.get(i)
182-
+ "' did not match. Specified: [" + specifiedType.getName() + "] Declared: [" + actualType.getName()
183-
+ "]");
184-
}
185-
return false;
186-
}
187-
}
188-
return true;
189-
}
190-
191-
private List<String> getArgNames(Constructor<?> constructor) {
192-
List<String> paramNames = new ArrayList<>();
193-
List<String> actualParamNames = null;
194-
final Annotation[][] paramAnnotations = constructor.getParameterAnnotations();
195-
int paramCount = paramAnnotations.length;
196-
for (int paramIndex = 0; paramIndex < paramCount; paramIndex++) {
197-
String name = null;
198-
for (Annotation annotation : paramAnnotations[paramIndex]) {
199-
if (annotation instanceof Param) {
200-
name = ((Param) annotation).value();
201-
break;
202-
}
203-
}
204-
if (name == null && resultMap.configuration.isUseActualParamName()) {
205-
if (actualParamNames == null) {
206-
actualParamNames = ParamNameUtil.getParamNames(constructor);
207-
}
208-
if (actualParamNames.size() > paramIndex) {
209-
name = actualParamNames.get(paramIndex);
210-
}
211-
}
212-
paramNames.add(name != null ? name : "arg" + paramIndex);
213-
}
214-
return paramNames;
136+
return resultMap;
215137
}
216138
}
217139

0 commit comments

Comments
 (0)