Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

issue-#321 : Mybatis doesn't get correct return type when the mapper interface extends another one. #396

Closed
wants to merge 10 commits into from
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,8 @@
import java.io.IOException;
import java.io.InputStream;
import java.lang.annotation.Annotation;
import java.lang.reflect.Array;
import java.lang.reflect.GenericArrayType;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.lang.reflect.*;
import java.util.*;

import org.apache.ibatis.annotations.Arg;
import org.apache.ibatis.annotations.CacheNamespace;
Expand Down Expand Up @@ -94,12 +82,14 @@ public class MapperAnnotationBuilder {
private Configuration configuration;
private MapperBuilderAssistant assistant;
private Class<?> type;
private Map<String, Map<String, Type>> parentTypeMap = new HashMap<String,Map<String,Type>>();

public MapperAnnotationBuilder(Configuration configuration, Class<?> type) {
String resource = type.getName().replace('.', '/') + ".java (best guess)";
this.assistant = new MapperBuilderAssistant(configuration, resource);
this.configuration = configuration;
this.type = type;
collectTypeParameters(type);

sqlAnnotationTypes.add(Select.class);
sqlAnnotationTypes.add(Insert.class);
Expand Down Expand Up @@ -364,7 +354,7 @@ private Class<?> getParameterType(Method method) {
}

private Class<?> getReturnType(Method method) {
Class<?> returnType = method.getReturnType();
Class<?> returnType = resolveReturnType(method);
// issue #508
if (void.class.equals(returnType)) {
ResultType rt = method.getAnnotation(ResultType.class);
Expand All @@ -386,6 +376,8 @@ private Class<?> getReturnType(Method method) {
Class<?> componentType = (Class<?>) ((GenericArrayType) returnTypeParameter).getGenericComponentType();
// (issue #525) support List<byte[]>
returnType = Array.newInstance(componentType, 0).getClass();
} else if (returnTypeParameter instanceof TypeVariable<?>) {
returnType = resolveTypeVariable(method, returnTypeParameter);
}
}
}
Expand Down Expand Up @@ -605,4 +597,59 @@ private KeyGenerator handleSelectKeyAnnotation(SelectKey selectKeyAnnotation, St
return answer;
}

private Class<?> resolveTypeVariable(Method m, Type typeVariable) {
if (typeVariable instanceof TypeVariable) {
Map<String, Type> genericTypes = parentTypeMap.get(m.getDeclaringClass().getName());
if (null != genericTypes) {
String genericTypeName = ((TypeVariable) typeVariable).getName();
Type returnClass = genericTypes.get(genericTypeName);
if (null != returnClass) {
return (Class<?>)returnClass;
}
}
}
return null;
}
private Class<?> resolveReturnType(Method m) {
Class<?> retClass = resolveTypeVariable(m, m.getGenericReturnType());
if (null != retClass) {
return retClass;
}
return m.getReturnType();
}

public void collectTypeParameters(Class<?> offspring, Type... actualArgs) {
assert offspring != null;

Map<String, Type> typeVariables = new HashMap<String, Type>();
for (int i = 0; i < actualArgs.length; i++) {
TypeVariable<?> typeVariable = (TypeVariable<?>) offspring.getTypeParameters()[i];
typeVariables.put(typeVariable.getName(), actualArgs[i]);
}
if (!typeVariables.isEmpty()) {
parentTypeMap.put(offspring.getName(), typeVariables);
}

// because offspring is a interface which only extends from other interface, it is no need
// get getGenericSuperClass
for (Type parent: offspring.getGenericInterfaces()) {
if (parent instanceof ParameterizedType) {
ParameterizedType parameterizedType = (ParameterizedType) parent;
Type rawType = parameterizedType.getRawType();
if (rawType instanceof Class<?>) {
Class<?> rawTypeClass = (Class<?>) rawType;
List<Type> resolvedTypes = new LinkedList<Type>();
for (Type t : parameterizedType.getActualTypeArguments()) {
if (t instanceof TypeVariable<?>) {
Type resolvedType = typeVariables.get(((TypeVariable<?>) t).getName());
resolvedTypes.add(resolvedType != null ? resolvedType : t);
} else {
resolvedTypes.add(t);
}
}
collectTypeParameters(rawTypeClass, resolvedTypes.toArray(new Type[]{}));
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,22 @@ public void shouldGetAllCarsNonUnique() {
}
}

@Test
public void getSubGenericReturnType() {
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
NoMapper mapper = sqlSession.getMapper(NoMapper.class);
Car car = mapper.getElement(1);
Assert.assertEquals(1, car.getId());

String carType = mapper.getMobileType("VW");
Assert.assertEquals("VW", carType);

List<String> carTypes = mapper.getAllCarTypes();
Assert.assertTrue(!carTypes.isEmpty());
} finally {
sqlSession.close();
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Copyright 2009-2015 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
*
* http://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.associationtest;

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

public interface AutomoblieMapper<T> {
@Select("select cartype from cars where cartype=#{type}")
T getMobileType(@Param("type") String carType);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
/**
* Copyright 2009-2015 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
*
* http://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.associationtest;

public interface CarMapper<E> extends GenericMapper<Car,E>, AutomoblieMapper<E> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* Copyright 2009-2015 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
*
* http://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.associationtest;

import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface GenericMapper<T, E> {
@Select("select carid as id, cartype as type from cars where carid=#{id}")
T getElement(@Param("id") int id);

@Select("select cartype from cars")
List<E> getAllCarTypes();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/**
* Copyright 2009-2015 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
*
* http://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.associationtest;

import org.apache.ibatis.annotations.Select;

import java.util.List;

public interface NoMapper extends CarMapper<String>{

}
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@

<mappers>
<mapper resource="org/apache/ibatis/submitted/associationtest/Mapper.xml" />
<mapper class="org.apache.ibatis.submitted.associationtest.NoMapper"/>
</mappers>

</configuration>