diff --git a/src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.java b/src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.java index af32cafc2dc..3a8c533bed6 100644 --- a/src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.java +++ b/src/main/java/org/apache/ibatis/builder/annotation/MapperAnnotationBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2009-2022 the original author or authors. + * Copyright 2009-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -138,7 +138,7 @@ public void parse() { parsePendingMethods(); } - private boolean canHaveStatement(Method method) { + private static boolean canHaveStatement(Method method) { // issue #237 return !method.isBridge() && !method.isDefault(); } @@ -224,7 +224,7 @@ private void parseCacheRef() { } private String parseResultMap(Method method) { - Class returnType = getReturnType(method); + Class returnType = getReturnType(method, type); Arg[] args = method.getAnnotationsByType(Arg.class); Result[] results = method.getAnnotationsByType(Result.class); TypeDiscriminator typeDiscriminator = method.getAnnotation(TypeDiscriminator.class); @@ -366,7 +366,7 @@ void parseStatement(Method method) { null, parameterTypeClass, resultMapId, - getReturnType(method), + getReturnType(method, type), resultSetType, flushCache, useCache, @@ -408,7 +408,7 @@ private Class getParameterType(Method method) { return parameterType; } - private Class getReturnType(Method method) { + private static Class getReturnType(Method method, Class type) { Class returnType = method.getReturnType(); Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, type); if (resolvedReturnType instanceof Class) { @@ -669,6 +669,23 @@ private Optional getAnnotationWrapper(Method method, boolean return Optional.ofNullable(annotationWrapper); } + public static Class getMethodReturnType(String mapperFqn, String localStatementId) { + if (mapperFqn == null || localStatementId == null) { + return null; + } + try { + Class mapperClass = Resources.classForName(mapperFqn); + for (Method method : mapperClass.getMethods()) { + if (method.getName().equals(localStatementId) && canHaveStatement(method)) { + return getReturnType(method, mapperClass); + } + } + } catch (ClassNotFoundException e) { + // No corresponding mapper interface which is OK + } + return null; + } + private class AnnotationWrapper { private final Annotation annotation; private final String databaseId; diff --git a/src/main/java/org/apache/ibatis/builder/xml/XMLStatementBuilder.java b/src/main/java/org/apache/ibatis/builder/xml/XMLStatementBuilder.java index 82316bb49c9..5c2187c9ce7 100644 --- a/src/main/java/org/apache/ibatis/builder/xml/XMLStatementBuilder.java +++ b/src/main/java/org/apache/ibatis/builder/xml/XMLStatementBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2009-2022 the original author or authors. + * Copyright 2009-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import org.apache.ibatis.builder.BaseBuilder; import org.apache.ibatis.builder.MapperBuilderAssistant; +import org.apache.ibatis.builder.annotation.MapperAnnotationBuilder; import org.apache.ibatis.executor.keygen.Jdbc3KeyGenerator; import org.apache.ibatis.executor.keygen.KeyGenerator; import org.apache.ibatis.executor.keygen.NoKeyGenerator; @@ -101,6 +102,9 @@ public void parseStatementNode() { String resultType = context.getStringAttribute("resultType"); Class resultTypeClass = resolveClass(resultType); String resultMap = context.getStringAttribute("resultMap"); + if (resultTypeClass == null && resultMap == null) { + resultTypeClass = MapperAnnotationBuilder.getMethodReturnType(builderAssistant.getCurrentNamespace(), id); + } String resultSetType = context.getStringAttribute("resultSetType"); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); if (resultSetTypeEnum == null) { diff --git a/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java b/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java index 22af84642e8..3ff2cdf77e3 100644 --- a/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java +++ b/src/main/java/org/apache/ibatis/executor/resultset/DefaultResultSetHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2009-2022 the original author or authors. + * Copyright 2009-2023 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -294,7 +294,7 @@ private void cleanUpAfterHandlingResultSet() { private void validateResultMapsCount(ResultSetWrapper rsw, int resultMapCount) { if (rsw != null && resultMapCount < 1) { throw new ExecutorException("A query was run and no Result Maps were found for the Mapped Statement '" + mappedStatement.getId() - + "'. It's likely that neither a Result Type nor a Result Map was specified."); + + "'. 'resultType' or 'resultMap' must be specified when there is no corresponding method."); } } diff --git a/src/test/java/org/apache/ibatis/submitted/no_result_type_map/Mapper.java b/src/test/java/org/apache/ibatis/submitted/no_result_type_map/Mapper.java new file mode 100644 index 00000000000..18b3926ed04 --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/no_result_type_map/Mapper.java @@ -0,0 +1,28 @@ +/* + * Copyright 2009-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ibatis.submitted.no_result_type_map; + +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +public interface Mapper extends ParentMapper{ + + User getUser(@Param("id") Integer id); + + List getAllUsers(); + +} diff --git a/src/test/java/org/apache/ibatis/submitted/no_result_type_map/NoResultTypeMapTest.java b/src/test/java/org/apache/ibatis/submitted/no_result_type_map/NoResultTypeMapTest.java new file mode 100644 index 00000000000..04b27bca2bc --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/no_result_type_map/NoResultTypeMapTest.java @@ -0,0 +1,92 @@ +/* + * Copyright 2009-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ibatis.submitted.no_result_type_map; + +import static org.junit.jupiter.api.Assertions.*; + +import java.io.Reader; +import java.util.List; + +import org.apache.ibatis.BaseDataTest; +import org.apache.ibatis.exceptions.PersistenceException; +import org.apache.ibatis.executor.ExecutorException; +import org.apache.ibatis.io.Resources; +import org.apache.ibatis.session.SqlSession; +import org.apache.ibatis.session.SqlSessionFactory; +import org.apache.ibatis.session.SqlSessionFactoryBuilder; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +class NoResultTypeMapTest { + + private static SqlSessionFactory sqlSessionFactory; + + @BeforeAll + static void setUp() throws Exception { + // create a SqlSessionFactory + try (Reader reader = Resources + .getResourceAsReader("org/apache/ibatis/submitted/no_result_type_map/mybatis-config.xml")) { + sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); + sqlSessionFactory.getConfiguration().addMapper(Mapper.class); + } + + // populate in-memory database + BaseDataTest.runScript(sqlSessionFactory.getConfiguration().getEnvironment().getDataSource(), + "org/apache/ibatis/submitted/no_result_type_map/CreateDB.sql"); + } + + @Test + void shouldGetAUser() { + try (SqlSession sqlSession = sqlSessionFactory.openSession()) { + Mapper mapper = sqlSession.getMapper(Mapper.class); + User user = mapper.getUser(1); + Assertions.assertEquals("User1", user.getName()); + } + } + + @Test + void shouldGetAllUsers() { + try (SqlSession sqlSession = sqlSessionFactory.openSession()) { + Mapper mapper = sqlSession.getMapper(Mapper.class); + List users = mapper.getAllUsers(); + Assertions.assertEquals(3, users.size()); + } + } + + @Test + void shouldResolveInheritedReturnType() { + try (SqlSession sqlSession = sqlSessionFactory.openSession()) { + Mapper mapper = sqlSession.getMapper(Mapper.class); + List users = mapper.getAllUsersInParent(); + Assertions.assertEquals(3, users.size()); + } + } + + @Test + void shouldFailIfNoMatchingMethod() { + try (SqlSession sqlSession = sqlSessionFactory.openSession()) { + PersistenceException ex = assertThrows(PersistenceException.class, + () -> sqlSession.selectList("org.apache.ibatis.submitted.no_result_type_map.Mapper.noMatchingMethod")); + ExecutorException cause = (ExecutorException) ex.getCause(); + assertEquals("A query was run and no Result Maps were found for the Mapped Statement " + + "'org.apache.ibatis.submitted.no_result_type_map.Mapper.noMatchingMethod'. " + + "'resultType' or 'resultMap' must be specified when there is no corresponding method.", + cause.getMessage()); + } + } + +} diff --git a/src/test/java/org/apache/ibatis/submitted/no_result_type_map/ParentMapper.java b/src/test/java/org/apache/ibatis/submitted/no_result_type_map/ParentMapper.java new file mode 100644 index 00000000000..b2d333138fb --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/no_result_type_map/ParentMapper.java @@ -0,0 +1,24 @@ +/* + * Copyright 2009-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ibatis.submitted.no_result_type_map; + +import java.util.List; + +public interface ParentMapper { + + List getAllUsersInParent(); + +} diff --git a/src/test/java/org/apache/ibatis/submitted/no_result_type_map/User.java b/src/test/java/org/apache/ibatis/submitted/no_result_type_map/User.java new file mode 100644 index 00000000000..f38715c94da --- /dev/null +++ b/src/test/java/org/apache/ibatis/submitted/no_result_type_map/User.java @@ -0,0 +1,38 @@ +/* + * Copyright 2009-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.apache.ibatis.submitted.no_result_type_map; + +public class User { + + private Integer id; + private String name; + + public Integer getId() { + return id; + } + + public void setId(Integer id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } +} diff --git a/src/test/resources/org/apache/ibatis/submitted/no_result_type_map/CreateDB.sql b/src/test/resources/org/apache/ibatis/submitted/no_result_type_map/CreateDB.sql new file mode 100644 index 00000000000..54418630e28 --- /dev/null +++ b/src/test/resources/org/apache/ibatis/submitted/no_result_type_map/CreateDB.sql @@ -0,0 +1,26 @@ +-- +-- Copyright 2009-2023 the original author or authors. +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- https://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +drop table users if exists; + +create table users ( + id int, + name varchar(20) +); + +insert into users (id, name) values(1, 'User1'); +insert into users (id, name) values(2, 'User2'); +insert into users (id, name) values(3, 'User3'); diff --git a/src/test/resources/org/apache/ibatis/submitted/no_result_type_map/Mapper.xml b/src/test/resources/org/apache/ibatis/submitted/no_result_type_map/Mapper.xml new file mode 100644 index 00000000000..c31893c1e63 --- /dev/null +++ b/src/test/resources/org/apache/ibatis/submitted/no_result_type_map/Mapper.xml @@ -0,0 +1,41 @@ + + + + + + + + + + + + + + + diff --git a/src/test/resources/org/apache/ibatis/submitted/no_result_type_map/mybatis-config.xml b/src/test/resources/org/apache/ibatis/submitted/no_result_type_map/mybatis-config.xml new file mode 100644 index 00000000000..fb5e8a9f06d --- /dev/null +++ b/src/test/resources/org/apache/ibatis/submitted/no_result_type_map/mybatis-config.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + +