diff --git a/src/main/java/org/apache/ibatis/builder/ResultMappingConstructorResolver.java b/src/main/java/org/apache/ibatis/builder/ResultMappingConstructorResolver.java index 4fe821fc170..95347a2f478 100644 --- a/src/main/java/org/apache/ibatis/builder/ResultMappingConstructorResolver.java +++ b/src/main/java/org/apache/ibatis/builder/ResultMappingConstructorResolver.java @@ -103,7 +103,7 @@ public List resolveWithConstructor() { .filter(Objects::nonNull).collect(Collectors.toCollection(LinkedHashSet::new)); // arg order can only be 'fixed' if all mappings have property names - final boolean allMappingsHavePropertyNames = constructorResultMappings.size() == constructorArgsByName.size(); + final boolean allMappingsHavePropertyNames = verifyPropertyNaming(constructorArgsByName); // only do this if all property mappings were set if (allMappingsHavePropertyNames) { @@ -147,6 +147,19 @@ public List resolveWithConstructor() { return resultMappings; } + private boolean verifyPropertyNaming(Set constructorArgsByName) { + final boolean allMappingsHavePropertyNames = constructorResultMappings.size() == constructorArgsByName.size(); + + // If property names have been partially specified, throw an exception, as this case does not make sense + // either specify all names and (optional random order), or type info. + if (!allMappingsHavePropertyNames && !constructorArgsByName.isEmpty()) { + throw new BuilderException("Error in result map '" + resultMapId + + "'. We do not support partially specifying a property name. Either specify all property names, or none."); + } + + return allMappingsHavePropertyNames; + } + List retrieveConstructorCandidates(int withLength) { return Arrays.stream(resultType.getDeclaredConstructors()) .filter(constructor -> constructor.getParameterTypes().length == withLength).map(ConstructorMetaInfo::new) diff --git a/src/test/java/org/apache/ibatis/builder/ResultMappingConstructorResolverTest.java b/src/test/java/org/apache/ibatis/builder/ResultMappingConstructorResolverTest.java index 9880e635abe..dce9990aa63 100644 --- a/src/test/java/org/apache/ibatis/builder/ResultMappingConstructorResolverTest.java +++ b/src/test/java/org/apache/ibatis/builder/ResultMappingConstructorResolverTest.java @@ -177,18 +177,16 @@ void testReturnOriginalMappingsWhenNoPropertyNamesDefinedAndCannotResolveConstru } @Test - void testCanResolveWithMissingPropertyNameAndPartialTypeInfo() { + void testThrowExceptionWithPartialPropertyNameSpecified() { ResultMapping mappingA = createConstructorMappingFor(Object.class, "a", "a"); ResultMapping mappingB = createConstructorMappingFor(Object.class, null, "b"); ResultMapping mappingC = createConstructorMappingFor(LocalDate.class, null, "c"); - ResultMapping[] constructorMappings = new ResultMapping[] { mappingA, mappingB, mappingC }; - final ResultMappingConstructorResolver resolver = createResolverFor(ResultType1.class, TEST_ID, - constructorMappings); - final List mappingList = resolver.resolveWithConstructor(); + final ResultMappingConstructorResolver resolver = createResolverFor(ResultType1.class, TEST_ID, mappingA, mappingB, + mappingC); - assertThat(mappingList).extracting(ResultMapping::getProperty, mapping -> mapping.getJavaType().getSimpleName()) - .containsExactly(tuple("a", "long"), tuple(null, "String"), tuple(null, "LocalDate")); + assertThatThrownBy(resolver::resolveWithConstructor).isInstanceOf(BuilderException.class) + .hasMessageContaining("Either specify all property names, or none."); } @Test diff --git a/src/test/java/org/apache/ibatis/submitted/auto_type_from_non_ambiguous_constructor/Mapper1.java b/src/test/java/org/apache/ibatis/submitted/auto_type_from_non_ambiguous_constructor/Mapper1.java index 053bdbe0aad..ed595439cb2 100644 --- a/src/test/java/org/apache/ibatis/submitted/auto_type_from_non_ambiguous_constructor/Mapper1.java +++ b/src/test/java/org/apache/ibatis/submitted/auto_type_from_non_ambiguous_constructor/Mapper1.java @@ -26,11 +26,11 @@ public interface Mapper1 { String SELECT_SQL = "select a.id, a.name, a.type from account a where a.id = #{id}"; String SELECT_WITH_DOB_SQL = "select id, name, type, date '2025-01-05' dob from account where id = #{id}"; - @ConstructorArgs({ @Arg(column = "id"), @Arg(column = "name"), @Arg(column = "type")}) + @ConstructorArgs({ @Arg(column = "id"), @Arg(column = "name"), @Arg(column = "type") }) @Select(SELECT_SQL) Account getAccountJavaTypesMissing(long id); - @ConstructorArgs({ @Arg(column = "id"), @Arg(column = "name", javaType = String.class), @Arg(column = "type")}) + @ConstructorArgs({ @Arg(column = "id"), @Arg(column = "name", javaType = String.class), @Arg(column = "type") }) @Select(SELECT_SQL) Account3 getAccountPartialTypesProvided(long id);