Decouple Paging and Sorting repositories from CrudRepository.

This way they can be combined with different variants of CrudRepositories.

This affects
`PagingAndSortingRepository`, `ReactiveSortingRepository`, `CoroutineSortingRepository`, and `RxJavaSortingRepository`.

Any repository implementing those interfaces now needs to also implement a suitable CRUD repository, or needs to manually add the methods from a CRUD repository as needed.

Closes #2537
Original pull request: #2540.
This commit is contained in:
Jens Schauder
2022-01-28 14:35:20 +01:00
committed by Mark Paluch
parent c9cdc93809
commit 37eee0e35d
16 changed files with 80 additions and 158 deletions

View File

@@ -27,6 +27,7 @@ import org.springframework.core.ResolvableType;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.querydsl.User;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.core.RepositoryMetadata;
@@ -95,7 +96,7 @@ class AbstractRepositoryMetadataUnitTests {
@Test // DATACMNS-471
void detectsArrayReturnTypeCorrectly() throws Exception {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(PagedRepository.class);
RepositoryMetadata metadata = new DefaultRepositoryMetadata(CompletePageableAndSortingRepository.class);
var method = PagedRepository.class.getMethod("returnsArray");
assertThat(metadata.getReturnedDomainClass(method)).isEqualTo(User.class);
@@ -169,4 +170,8 @@ class AbstractRepositoryMetadataUnitTests {
interface ContainerRepository extends Repository<Container, Long> {
Container someMethod();
}
interface CompletePageableAndSortingRepository extends PagingAndSortingRepository<Container, Long> {
}
}

View File

@@ -197,7 +197,7 @@ class DefaultCrudMethodsUnitTests {
interface DomainCrudRepository extends CrudRepository<Domain, Long> {}
interface DomainPagingAndSortingRepository extends PagingAndSortingRepository<Domain, Long> {}
interface DomainPagingAndSortingRepository extends PagingAndSortingRepository<Domain, Long>, CrudRepository<Domain, Long> {}
interface RepositoryWithCustomSave extends Repository<Domain, Serializable> {

View File

@@ -111,27 +111,11 @@ class DefaultRepositoryInformationUnitTests {
assertThat(information.hasCustomMethod()).isFalse();
}
@Test
void discoversIntermediateMethodsAsBackingMethods() throws NoSuchMethodException, SecurityException {
var metadata = new DefaultRepositoryMetadata(CustomRepository.class);
var information = new DefaultRepositoryInformation(metadata,
PagingAndSortingRepository.class, RepositoryComposition.empty());
var method = CustomRepository.class.getMethod("findAll", Pageable.class);
assertThat(information.isBaseClassMethod(method)).isTrue();
method = getMethodFrom(CustomRepository.class, "existsById");
assertThat(information.isBaseClassMethod(method)).isTrue();
assertThat(information.getQueryMethods()).isEmpty();
}
@Test // DATACMNS-151
void doesNotConsiderManuallyDefinedSaveMethodAQueryMethod() {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(CustomRepository.class);
RepositoryInformation information = new DefaultRepositoryInformation(metadata, PagingAndSortingRepository.class,
RepositoryInformation information = new DefaultRepositoryInformation(metadata, CompletePageableAndSortingRepository.class,
RepositoryComposition.empty());
assertThat(information.getQueryMethods()).isEmpty();
@@ -427,4 +411,8 @@ class DefaultRepositoryInformationUnitTests {
return entity;
}
}
interface CompletePageableAndSortingRepository<T, ID> extends CrudRepository<T, ID>, PagingAndSortingRepository<T, ID> {
}
}

View File

@@ -66,7 +66,7 @@ class ReactiveRepositoryInformationUnitTests {
@Test // DATACMNS-836
void discoversMethodAssignableArguments() throws Exception {
var reference = extractTargetMethodFromRepository(ReactiveSortingRepository.class, "saveAll", Publisher.class);
var reference = extractTargetMethodFromRepository(ReactiveCrudRepository.class, "saveAll", Publisher.class);
assertThat(reference.getDeclaringClass()).isEqualTo(ReactiveCrudRepository.class);
assertThat(reference.getName()).isEqualTo("saveAll");

View File

@@ -20,6 +20,7 @@ import static org.mockito.Mockito.*;
import io.reactivex.rxjava3.core.Completable;
import io.reactivex.rxjava3.core.Maybe;
import io.reactivex.rxjava3.core.Single;
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
import reactor.core.publisher.Mono;
import java.io.Serializable;
@@ -47,7 +48,7 @@ class ReactiveWrapperRepositoryFactorySupportUnitTests {
DummyRepositoryFactory factory;
@Mock ReactiveSortingRepository<Object, Serializable> backingRepo;
@Mock ReactiveCrudRepository<Object, Serializable> backingRepo;
@Mock ObjectRepositoryCustom customImplementation;
@BeforeEach

View File

@@ -51,6 +51,7 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.projection.ProjectionFactory;
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.Repository;
import org.springframework.data.repository.RepositoryDefinition;
@@ -90,7 +91,7 @@ class RepositoryFactorySupportUnitTests {
DummyRepositoryFactory factory;
@Mock PagingAndSortingRepository<Object, Object> backingRepo;
@Mock CrudRepository<Object, Object> backingRepo;
@Mock ObjectRepositoryCustom customImplementation;
@Mock MyQueryCreationListener listener;
@@ -167,12 +168,11 @@ class RepositoryFactorySupportUnitTests {
void createsRepositoryInstanceWithCustomIntermediateRepository() {
var repository = factory.getRepository(CustomRepository.class);
Pageable pageable = PageRequest.of(0, 10);
when(backingRepo.findAll(pageable)).thenReturn(new PageImpl<>(Collections.emptyList()));
repository.findAll(pageable);
when(backingRepo.findAll()).thenReturn(new PageImpl<>(Collections.emptyList()));
repository.findAll();
verify(backingRepo, times(1)).findAll(pageable);
verify(backingRepo, times(1)).findAll();
}
@Test

View File

@@ -153,7 +153,7 @@ class CrudRepositoryInvokerUnitTests {
static class Person {}
interface PersonRepository extends PagingAndSortingRepository<Person, Long> {
interface PersonRepository extends PagingAndSortingRepository<Person, Long>, CrudRepository<Person, Long> {
Page<Person> findByFirstName(@Param("firstName") String firstName, Pageable pageable);

View File

@@ -93,7 +93,6 @@ class DefaultRepositoryInvokerFactoryIntegrationTests {
var invoker = factory.getInvokerFor(User.class);
assertThat(invoker)//
.isInstanceOf(CrudRepositoryInvoker.class)//
.isNotInstanceOf(PagingAndSortingRepositoryInvoker.class);
.isInstanceOf(CrudRepositoryInvoker.class);
}
}

View File

@@ -1,107 +0,0 @@
/*
* Copyright 2014-2021 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.springframework.data.repository.support;
import static org.mockito.ArgumentMatchers.*;
import static org.mockito.Mockito.*;
import static org.springframework.data.repository.support.RepositoryInvocationTestUtils.*;
import org.junit.jupiter.api.Test;
import org.springframework.core.convert.support.GenericConversionService;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.core.RepositoryMetadata;
import org.springframework.data.repository.core.support.DefaultRepositoryMetadata;
import org.springframework.format.support.DefaultFormattingConversionService;
/**
* Unit tests for {@link PagingAndSortingRepositoryInvoker}.
*
* @author Oliver Gierke
*/
class PaginginAndSortingRepositoryInvokerUnitTests {
@Test // DATACMNS-589
void invokesFindAllWithPageableByDefault() throws Exception {
var repository = mock(Repository.class);
var method = PagingAndSortingRepository.class.getMethod("findAll", Pageable.class);
getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(PageRequest.of(0, 10));
getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(Pageable.unpaged());
}
@Test // DATACMNS-589
void invokesFindAllWithSortByDefault() throws Exception {
var repository = mock(Repository.class);
var method = PagingAndSortingRepository.class.getMethod("findAll", Sort.class);
getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(Sort.by("foo"));
getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(Sort.unsorted());
}
@Test // DATACMNS-589
void invokesRedeclaredFindAllWithPageable() throws Exception {
var repository = mock(RepositoryWithRedeclaredFindAllWithPageable.class);
var method = RepositoryWithRedeclaredFindAllWithPageable.class.getMethod("findAll", Pageable.class);
when(repository.findAll(any(Pageable.class))).thenReturn(Page.empty());
getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(PageRequest.of(0, 10));
getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(Pageable.unpaged());
}
@Test // DATACMNS-589
void invokesRedeclaredFindAllWithSort() throws Exception {
var repository = mock(RepositoryWithRedeclaredFindAllWithSort.class);
var method = RepositoryWithRedeclaredFindAllWithSort.class.getMethod("findAll", Sort.class);
getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(Sort.by("foo"));
getInvokerFor(repository, expectInvocationOf(method)).invokeFindAll(Sort.unsorted());
}
@SuppressWarnings({ "unchecked", "rawtypes" })
private static RepositoryInvoker getInvokerFor(Object repository) {
RepositoryMetadata metadata = new DefaultRepositoryMetadata(repository.getClass().getInterfaces()[0]);
GenericConversionService conversionService = new DefaultFormattingConversionService();
return new PagingAndSortingRepositoryInvoker((PagingAndSortingRepository) repository, metadata, conversionService);
}
private static RepositoryInvoker getInvokerFor(Object repository, VerifyingMethodInterceptor interceptor) {
return getInvokerFor(getVerifyingRepositoryProxy(repository, interceptor));
}
interface Repository extends PagingAndSortingRepository<Object, Long> {}
interface RepositoryWithRedeclaredFindAllWithPageable extends PagingAndSortingRepository<Object, Long> {
Page<Object> findAll(Pageable pageable);
}
interface RepositoryWithRedeclaredFindAllWithSort extends PagingAndSortingRepository<Object, Long> {
Page<Object> findAll(Sort sort);
}
}