Skip to content

Commit 37eee0e

Browse files
schaudermp911de
authored andcommitted
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.
1 parent c9cdc93 commit 37eee0e

16 files changed

+80
-158
lines changed

src/main/asciidoc/repositories.adoc

+26-8
Original file line numberDiff line numberDiff line change
@@ -58,13 +58,13 @@ public interface CrudRepository<T, ID> extends Repository<T, ID> {
5858
NOTE: We also provide persistence technology-specific abstractions, such as `JpaRepository` or `MongoRepository`.
5959
Those interfaces extend `CrudRepository` and expose the capabilities of the underlying persistence technology in addition to the rather generic persistence technology-agnostic interfaces such as `CrudRepository`.
6060

61-
On top of the `CrudRepository`, there is a https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/PagingAndSortingRepository.html[`PagingAndSortingRepository`] abstraction that adds additional methods to ease paginated access to entities:
61+
Additional to the `CrudRepository`, there is a https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/PagingAndSortingRepository.html[`PagingAndSortingRepository`] abstraction that adds additional methods to ease paginated access to entities:
6262

6363
.`PagingAndSortingRepository` interface
6464
====
6565
[source,java]
6666
----
67-
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
67+
public interface PagingAndSortingRepository<T, ID> {
6868
6969
Iterable<T> findAll(Sort sort);
7070
@@ -210,17 +210,35 @@ The sections that follow explain each step in detail:
210210

211211
To define a repository interface, you first need to define a domain class-specific repository interface.
212212
The interface must extend `Repository` and be typed to the domain class and an ID type.
213-
If you want to expose CRUD methods for that domain type, extend `CrudRepository` instead of `Repository`.
213+
If you want to expose CRUD methods for that domain type, you may extend `CrudRepository`, or one of its variants instead of `Repository`.
214214

215215
[[repositories.definition-tuning]]
216216
=== Fine-tuning Repository Definition
217217

218-
Typically, your repository interface extends `Repository`, `CrudRepository`, or `PagingAndSortingRepository`.
219-
Alternatively, if you do not want to extend Spring Data interfaces, you can also annotate your repository interface with `@RepositoryDefinition`.
220-
Extending `CrudRepository` exposes a complete set of methods to manipulate your entities.
221-
If you prefer to be selective about the methods being exposed, copy the methods you want to expose from `CrudRepository` into your domain repository.
218+
There are a few variants how you can get started with your repository interface.
222219

223-
NOTE: Doing so lets you define your own abstractions on top of the provided Spring Data Repositories functionality.
220+
The typical approach is to extend `CrudRepository`, which gives you methods for CRUD functionality.
221+
CRUD stands for Create, Read, Update, Delete.
222+
With version 3.0 we also introduced `ListCrudRepository` which is very similar to the `CrudRepository` but for those methods that return multiple entities it returns a `List` instead of an `Iterable` which you might find easier to use.
223+
224+
If you are using a reactive store you might choose `ReactiveCrudRepository`, or `RxJava3CrudRepository` depending on which reactive framework you are using.
225+
226+
If you are using Kotlin you might pick `CoroutineCrudRepository` which utilizes Kotlin's coroutines.
227+
228+
Additional you can extend `PagingAndSortingRepository`, `ReactiveSortingRepository`, `RxJava3SortingRepository`, or `CoroutineSortingRepository` if you need methods that allow to specify a `Sort` abstraction or in the first case a `Pageable` abstraction.
229+
Note that the various sorting repositories no longer extended their respective CRUD repository as they did in Spring Data Versions pre 3.0.
230+
Therefore, you need to extend both interfaces if you want functionality of both.
231+
232+
If you do not want to extend Spring Data interfaces, you can also annotate your repository interface with `@RepositoryDefinition`.
233+
Extending one of the CRUD repository interfaces exposes a complete set of methods to manipulate your entities.
234+
If you prefer to be selective about the methods being exposed, copy the methods you want to expose from the CRUD repository into your domain repository.
235+
When doing so, you may change the return type of methods.
236+
Spring Data will honor the return type if possible.
237+
For example, for methods returning multiple entities you may choose `Iterable<T>`, `List<T>`, `Collection<T>` or a VAVR list.
238+
239+
If many repositories in your application should have the same set of methods you can define your own base interface to inherit from.
240+
Such an interface must be annotated with `@NoRepositoryBean`.
241+
This prevents Spring Data to try to create an instance of it directly and failing because it can't determine the entity for that repository, since it still contains a generic type variable.
224242

225243
The following example shows how to selectively expose CRUD methods (`findById` and `save`, in this case):
226244

src/main/java/org/springframework/data/repository/PagingAndSortingRepository.java

+6-3
Original file line numberDiff line numberDiff line change
@@ -20,16 +20,19 @@
2020
import org.springframework.data.domain.Sort;
2121

2222
/**
23-
* Extension of {@link CrudRepository} to provide additional methods to retrieve entities using the pagination and
24-
* sorting abstraction.
23+
* Repository fragment to provide methods to retrieve entities using the pagination and sorting abstraction. In many
24+
* cases this will be combined with {@link CrudRepository} or similar or with manually added methods to provide CRUD
25+
* functionality.
2526
*
2627
* @author Oliver Gierke
28+
* @author Jens Schauder
2729
* @see Sort
2830
* @see Pageable
2931
* @see Page
32+
* @see CrudRepository
3033
*/
3134
@NoRepositoryBean
32-
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {
35+
public interface PagingAndSortingRepository<T, ID> extends Repository<T, ID> {
3336

3437
/**
3538
* Returns all entities sorted by the given options.

src/main/java/org/springframework/data/repository/reactive/ReactiveSortingRepository.java

+7-3
Original file line numberDiff line numberDiff line change
@@ -20,20 +20,24 @@
2020

2121
import org.springframework.data.domain.Sort;
2222
import org.springframework.data.repository.NoRepositoryBean;
23+
import org.springframework.data.repository.Repository;
2324

2425
/**
25-
* Extension of {@link ReactiveCrudRepository} to provide additional methods to retrieve entities using the sorting
26-
* abstraction.
26+
* Repository fragment to provide methods to retrieve entities using the sorting abstraction. In many cases it
27+
* should be combined with {@link ReactiveCrudRepository} or a similar repository interface in order to add CRUD
28+
* functionality.
2729
*
2830
* @author Mark Paluch
2931
* @author Christoph Strobl
32+
* @author Jens Schauder
3033
* @since 2.0
3134
* @see Sort
3235
* @see Mono
3336
* @see Flux
37+
* @see ReactiveCrudRepository
3438
*/
3539
@NoRepositoryBean
36-
public interface ReactiveSortingRepository<T, ID> extends ReactiveCrudRepository<T, ID> {
40+
public interface ReactiveSortingRepository<T, ID> extends Repository<T, ID> {
3741

3842
/**
3943
* Returns all entities sorted by the given options.

src/main/java/org/springframework/data/repository/reactive/RxJava3SortingRepository.java

+7-4
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,21 @@
1919

2020
import org.springframework.data.domain.Sort;
2121
import org.springframework.data.repository.NoRepositoryBean;
22+
import org.springframework.data.repository.Repository;
2223

2324
/**
24-
* Extension of {@link RxJava3CrudRepository} to provide additional methods to retrieve entities using the sorting
25-
* abstraction.
26-
*
25+
* Repository fragment to provide methods to retrieve entities using the sorting abstraction. In many cases this
26+
* should be combined with {@link RxJava3CrudRepository} or a similar interface to provide CRUD functionality.
27+
*
2728
* @author Mark Paluch
29+
* @author Jens Schauder
2830
* @since 2.4
2931
* @see Sort
3032
* @see Flowable
33+
* @see RxJava3CrudRepository
3134
*/
3235
@NoRepositoryBean
33-
public interface RxJava3SortingRepository<T, ID> extends RxJava3CrudRepository<T, ID> {
36+
public interface RxJava3SortingRepository<T, ID> extends Repository<T, ID> {
3437

3538
/**
3639
* Returns all entities sorted by the given options.

src/main/java/org/springframework/data/repository/support/DefaultRepositoryInvokerFactory.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
*
3636
* @author Oliver Gierke
3737
* @author Christoph Strobl
38+
* @author Jens Schauder
3839
* @since 1.10
3940
*/
4041
public class DefaultRepositoryInvokerFactory implements RepositoryInvokerFactory {
@@ -93,7 +94,7 @@ private RepositoryInvoker prepareInvokers(Class<?> domainType) {
9394
@SuppressWarnings("unchecked")
9495
protected RepositoryInvoker createInvoker(RepositoryInformation information, Object repository) {
9596

96-
if (repository instanceof PagingAndSortingRepository) {
97+
if (repository instanceof PagingAndSortingRepository && repository instanceof CrudRepository) {
9798
return new PagingAndSortingRepositoryInvoker((PagingAndSortingRepository<Object, Object>) repository, information,
9899
conversionService);
99100
} else if (repository instanceof CrudRepository) {

src/main/java/org/springframework/data/repository/support/PagingAndSortingRepositoryInvoker.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.springframework.core.convert.ConversionService;
2222
import org.springframework.data.domain.Pageable;
2323
import org.springframework.data.domain.Sort;
24+
import org.springframework.data.repository.CrudRepository;
2425
import org.springframework.data.repository.PagingAndSortingRepository;
2526
import org.springframework.data.repository.core.RepositoryMetadata;
2627

@@ -29,6 +30,7 @@
2930
* avoid reflection overhead introduced by the superclass.
3031
*
3132
* @author Oliver Gierke
33+
* @author Jens Schauder
3234
* @since 1.10
3335
*/
3436
class PagingAndSortingRepositoryInvoker extends CrudRepositoryInvoker {
@@ -47,7 +49,7 @@ class PagingAndSortingRepositoryInvoker extends CrudRepositoryInvoker {
4749
public PagingAndSortingRepositoryInvoker(PagingAndSortingRepository<Object, Object> repository,
4850
RepositoryMetadata metadata, ConversionService conversionService) {
4951

50-
super(repository, metadata, conversionService);
52+
super((CrudRepository<Object, Object>) repository, metadata, conversionService);
5153

5254
var crudMethods = metadata.getCrudMethods();
5355

src/main/kotlin/org/springframework/data/repository/kotlin/CoroutineSortingRepository.kt

+7-2
Original file line numberDiff line numberDiff line change
@@ -18,18 +18,23 @@ package org.springframework.data.repository.kotlin
1818
import kotlinx.coroutines.flow.Flow
1919
import org.springframework.data.domain.Sort
2020
import org.springframework.data.repository.NoRepositoryBean
21+
import org.springframework.data.repository.Repository
2122

2223
/**
23-
* Extension of [CoroutineCrudRepository] to provide additional methods to retrieve entities using the sorting
24+
* Repository fragment to provide methods to retrieve entities using the sorting
2425
* abstraction.
2526
*
27+
* In many cases this should be combined with [CoroutineCrudRepository] or similar or manually added methods to provide CRUD functionality.
28+
*
2629
* @author Mark Paluch
30+
* @author Jens Schauder
2731
* @since 2.3
2832
* @see Flow
2933
* @see Sort
34+
* @see CoroutineCrudRepository
3035
*/
3136
@NoRepositoryBean
32-
interface CoroutineSortingRepository<T, ID> : CoroutineCrudRepository<T, ID> {
37+
interface CoroutineSortingRepository<T, ID> : Repository<T, ID> {
3338

3439
/**
3540
* Returns all entities sorted by the given options.

src/test/java/org/springframework/data/repository/core/support/AbstractRepositoryMetadataUnitTests.java

+6-1
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.springframework.data.domain.Page;
2828
import org.springframework.data.domain.Pageable;
2929
import org.springframework.data.querydsl.User;
30+
import org.springframework.data.repository.CrudRepository;
3031
import org.springframework.data.repository.PagingAndSortingRepository;
3132
import org.springframework.data.repository.Repository;
3233
import org.springframework.data.repository.core.RepositoryMetadata;
@@ -95,7 +96,7 @@ void handlesGenericTypeInReturnedCollectionCorrectly() throws SecurityException,
9596
@Test // DATACMNS-471
9697
void detectsArrayReturnTypeCorrectly() throws Exception {
9798

98-
RepositoryMetadata metadata = new DefaultRepositoryMetadata(PagedRepository.class);
99+
RepositoryMetadata metadata = new DefaultRepositoryMetadata(CompletePageableAndSortingRepository.class);
99100
var method = PagedRepository.class.getMethod("returnsArray");
100101

101102
assertThat(metadata.getReturnedDomainClass(method)).isEqualTo(User.class);
@@ -169,4 +170,8 @@ abstract class Container implements Iterable<Element> {}
169170
interface ContainerRepository extends Repository<Container, Long> {
170171
Container someMethod();
171172
}
173+
174+
interface CompletePageableAndSortingRepository extends PagingAndSortingRepository<Container, Long> {
175+
}
176+
172177
}

src/test/java/org/springframework/data/repository/core/support/DefaultCrudMethodsUnitTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ interface Domain {}
197197

198198
interface DomainCrudRepository extends CrudRepository<Domain, Long> {}
199199

200-
interface DomainPagingAndSortingRepository extends PagingAndSortingRepository<Domain, Long> {}
200+
interface DomainPagingAndSortingRepository extends PagingAndSortingRepository<Domain, Long>, CrudRepository<Domain, Long> {}
201201

202202
interface RepositoryWithCustomSave extends Repository<Domain, Serializable> {
203203

src/test/java/org/springframework/data/repository/core/support/DefaultRepositoryInformationUnitTests.java

+5-17
Original file line numberDiff line numberDiff line change
@@ -111,27 +111,11 @@ void considersIntermediateMethodsAsFinderMethods() {
111111
assertThat(information.hasCustomMethod()).isFalse();
112112
}
113113

114-
@Test
115-
void discoversIntermediateMethodsAsBackingMethods() throws NoSuchMethodException, SecurityException {
116-
117-
var metadata = new DefaultRepositoryMetadata(CustomRepository.class);
118-
var information = new DefaultRepositoryInformation(metadata,
119-
PagingAndSortingRepository.class, RepositoryComposition.empty());
120-
121-
var method = CustomRepository.class.getMethod("findAll", Pageable.class);
122-
assertThat(information.isBaseClassMethod(method)).isTrue();
123-
124-
method = getMethodFrom(CustomRepository.class, "existsById");
125-
126-
assertThat(information.isBaseClassMethod(method)).isTrue();
127-
assertThat(information.getQueryMethods()).isEmpty();
128-
}
129-
130114
@Test // DATACMNS-151
131115
void doesNotConsiderManuallyDefinedSaveMethodAQueryMethod() {
132116

133117
RepositoryMetadata metadata = new DefaultRepositoryMetadata(CustomRepository.class);
134-
RepositoryInformation information = new DefaultRepositoryInformation(metadata, PagingAndSortingRepository.class,
118+
RepositoryInformation information = new DefaultRepositoryInformation(metadata, CompletePageableAndSortingRepository.class,
135119
RepositoryComposition.empty());
136120

137121
assertThat(information.getQueryMethods()).isEmpty();
@@ -427,4 +411,8 @@ public Sample save(Sample entity) {
427411
return entity;
428412
}
429413
}
414+
415+
interface CompletePageableAndSortingRepository<T, ID> extends CrudRepository<T, ID>, PagingAndSortingRepository<T, ID> {
416+
}
417+
430418
}

src/test/java/org/springframework/data/repository/core/support/ReactiveRepositoryInformationUnitTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ void discoversRxJava3MethodWithConvertibleArguments() throws Exception {
6666
@Test // DATACMNS-836
6767
void discoversMethodAssignableArguments() throws Exception {
6868

69-
var reference = extractTargetMethodFromRepository(ReactiveSortingRepository.class, "saveAll", Publisher.class);
69+
var reference = extractTargetMethodFromRepository(ReactiveCrudRepository.class, "saveAll", Publisher.class);
7070

7171
assertThat(reference.getDeclaringClass()).isEqualTo(ReactiveCrudRepository.class);
7272
assertThat(reference.getName()).isEqualTo("saveAll");

src/test/java/org/springframework/data/repository/core/support/ReactiveWrapperRepositoryFactorySupportUnitTests.java

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import io.reactivex.rxjava3.core.Completable;
2121
import io.reactivex.rxjava3.core.Maybe;
2222
import io.reactivex.rxjava3.core.Single;
23+
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
2324
import reactor.core.publisher.Mono;
2425

2526
import java.io.Serializable;
@@ -47,7 +48,7 @@ class ReactiveWrapperRepositoryFactorySupportUnitTests {
4748

4849
DummyRepositoryFactory factory;
4950

50-
@Mock ReactiveSortingRepository<Object, Serializable> backingRepo;
51+
@Mock ReactiveCrudRepository<Object, Serializable> backingRepo;
5152
@Mock ObjectRepositoryCustom customImplementation;
5253

5354
@BeforeEach

src/test/java/org/springframework/data/repository/core/support/RepositoryFactorySupportUnitTests.java

+5-5
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.springframework.data.domain.Sort;
5252
import org.springframework.data.projection.ProjectionFactory;
5353
import org.springframework.data.querydsl.QuerydslPredicateExecutor;
54+
import org.springframework.data.repository.CrudRepository;
5455
import org.springframework.data.repository.PagingAndSortingRepository;
5556
import org.springframework.data.repository.Repository;
5657
import org.springframework.data.repository.RepositoryDefinition;
@@ -90,7 +91,7 @@ class RepositoryFactorySupportUnitTests {
9091

9192
DummyRepositoryFactory factory;
9293

93-
@Mock PagingAndSortingRepository<Object, Object> backingRepo;
94+
@Mock CrudRepository<Object, Object> backingRepo;
9495
@Mock ObjectRepositoryCustom customImplementation;
9596

9697
@Mock MyQueryCreationListener listener;
@@ -167,12 +168,11 @@ void invokesCustomMethodCompositionMethodIfItRedeclaresACRUDOne() {
167168
void createsRepositoryInstanceWithCustomIntermediateRepository() {
168169

169170
var repository = factory.getRepository(CustomRepository.class);
170-
Pageable pageable = PageRequest.of(0, 10);
171171

172-
when(backingRepo.findAll(pageable)).thenReturn(new PageImpl<>(Collections.emptyList()));
173-
repository.findAll(pageable);
172+
when(backingRepo.findAll()).thenReturn(new PageImpl<>(Collections.emptyList()));
173+
repository.findAll();
174174

175-
verify(backingRepo, times(1)).findAll(pageable);
175+
verify(backingRepo, times(1)).findAll();
176176
}
177177

178178
@Test

src/test/java/org/springframework/data/repository/support/CrudRepositoryInvokerUnitTests.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ interface OrderRepository extends CrudRepository<Order, Long> {
153153

154154
static class Person {}
155155

156-
interface PersonRepository extends PagingAndSortingRepository<Person, Long> {
156+
interface PersonRepository extends PagingAndSortingRepository<Person, Long>, CrudRepository<Person, Long> {
157157

158158
Page<Person> findByFirstName(@Param("firstName") String firstName, Pageable pageable);
159159

src/test/java/org/springframework/data/repository/support/DefaultRepositoryInvokerFactoryIntegrationTests.java

+1-2
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,6 @@ void createsCrudRepositoryInvokerForRepositoryExtendingCrudRepository() {
9393
var invoker = factory.getInvokerFor(User.class);
9494

9595
assertThat(invoker)//
96-
.isInstanceOf(CrudRepositoryInvoker.class)//
97-
.isNotInstanceOf(PagingAndSortingRepositoryInvoker.class);
96+
.isInstanceOf(CrudRepositoryInvoker.class);
9897
}
9998
}

0 commit comments

Comments
 (0)