DATACMNS-151 - Improve CrudRepository method signature generics.
Method signatures of save(…) methods in CrudRepository now return the concrete type passed in. This way subtypes can be persisted without casting the result back.
class Contact { … }
class Person extends Contact { … }
interface ContactRepository extends CrudRepository<Contact, Long> { … }
Before:
Person person = new Person();
person = (Person) repo.save(person);
^^^^^^^^
Now:
Person person = new Person();
person = repo.save(person);
This commit is contained in:
@@ -33,7 +33,7 @@ public interface CrudRepository<T, ID extends Serializable> extends Repository<T
|
||||
* @param entity
|
||||
* @return the saved entity
|
||||
*/
|
||||
T save(T entity);
|
||||
<S extends T> S save(S entity);
|
||||
|
||||
/**
|
||||
* Saves all given entities.
|
||||
@@ -42,7 +42,7 @@ public interface CrudRepository<T, ID extends Serializable> extends Repository<T
|
||||
* @return the saved entities
|
||||
* @throws IllegalArgumentException in case the given entity is (@literal null}.
|
||||
*/
|
||||
Iterable<T> save(Iterable<? extends T> entities);
|
||||
<S extends T> Iterable<S> save(Iterable<S> entities);
|
||||
|
||||
/**
|
||||
* Retrives an entity by its id.
|
||||
|
||||
@@ -15,8 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.repository.core.support;
|
||||
|
||||
import static org.springframework.data.repository.util.ClassUtils.*;
|
||||
import static org.springframework.core.GenericTypeResolver.*;
|
||||
import static org.springframework.data.repository.util.ClassUtils.*;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Type;
|
||||
@@ -261,8 +261,7 @@ class DefaultRepositoryInformation extends AbstractRepositoryMetadata implements
|
||||
Class<?> parameterType = resolveParameterType(parameter, metadata.getRepositoryInterface());
|
||||
|
||||
if (type instanceof TypeVariable<?>) {
|
||||
String name = ((TypeVariable<?>) type).getName();
|
||||
if (!matchesGenericType(name, parameterType)) {
|
||||
if (!matchesGenericType((TypeVariable<?>) type, parameterType)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
@@ -284,16 +283,16 @@ class DefaultRepositoryInformation extends AbstractRepositoryMetadata implements
|
||||
* @param parameterType
|
||||
* @return
|
||||
*/
|
||||
private boolean matchesGenericType(String name, Class<?> parameterType) {
|
||||
private boolean matchesGenericType(TypeVariable<?> variable, Class<?> parameterType) {
|
||||
|
||||
Class<?> entityType = getDomainClass();
|
||||
Class<?> idClass = getIdClass();
|
||||
|
||||
if (ID_TYPE_NAME.equals(name) && parameterType.equals(idClass)) {
|
||||
if (ID_TYPE_NAME.equals(variable.getName()) && parameterType.isAssignableFrom(idClass)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (DOMAIN_TYPE_NAME.equals(name) && parameterType.equals(entityType)) {
|
||||
if (DOMAIN_TYPE_NAME.equals(variable.getBounds()[0].toString()) && parameterType.isAssignableFrom(entityType)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -8,6 +8,8 @@ import java.lang.reflect.Method;
|
||||
import java.util.List;
|
||||
|
||||
import org.hamcrest.Matcher;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.hamcrest.collection.IsEmptyIterable;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
@@ -96,6 +98,33 @@ public class DefaultRepositoryInformationUnitTests {
|
||||
assertThat(information.getQueryMethods(), is(empty));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-151
|
||||
*/
|
||||
@Test
|
||||
public void doesNotConsiderManuallyDefinedSaveMethodAQueryMethod() {
|
||||
|
||||
RepositoryMetadata metadata = new DefaultRepositoryMetadata(CustomRepository.class);
|
||||
RepositoryInformation information = new DefaultRepositoryInformation(metadata, PagingAndSortingRepository.class,
|
||||
null);
|
||||
assertThat(information.getQueryMethods(), is(IsEmptyIterable.<Method> emptyIterable()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATACMNS-151
|
||||
*/
|
||||
@Test
|
||||
public void doesNotConsiderRedeclaredSaveMethodAQueryMethod() throws Exception {
|
||||
|
||||
RepositoryMetadata metadata = new DefaultRepositoryMetadata(ConcreteRepository.class);
|
||||
RepositoryInformation information = new DefaultRepositoryInformation(metadata, CrudRepository.class, null);
|
||||
|
||||
Method saveMethod = BaseRepository.class.getMethod("save", Object.class);
|
||||
|
||||
assertThat(information.getQueryMethods(), is(Matchers.<Method> iterableWithSize(2)));
|
||||
assertThat(information.getQueryMethods(), not(hasItem(saveMethod)));
|
||||
}
|
||||
|
||||
private Method getMethodFrom(Class<?> type, String name) {
|
||||
for (Method method : type.getMethods()) {
|
||||
if (method.getName().equals(name)) {
|
||||
@@ -133,6 +162,8 @@ public class DefaultRepositoryInformationUnitTests {
|
||||
interface BaseRepository<T, ID extends Serializable> extends CrudRepository<T, ID> {
|
||||
|
||||
T findBySomething(String something);
|
||||
|
||||
<K extends T> K save(K entity);
|
||||
}
|
||||
|
||||
interface ConcreteRepository extends BaseRepository<User, Integer> {
|
||||
@@ -157,5 +188,6 @@ public class DefaultRepositoryInformationUnitTests {
|
||||
|
||||
interface CustomRepository extends ReadOnlyRepository<Object, Long> {
|
||||
|
||||
Object save(Object object);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user