Announcing ListCrudRepository & Friends for Spring Data 3.0
The Spring Data CrudRepository
has various methods that return multiple instances of the entity managed by the repository.
It does so by using Iterable
and not List
, as one might expect.
In many cases, that is of no consequence, since you typically want to iterate over the result anyway.
However, you might occasionally prefer a List
. In these cases, Iterable
is annoying.
I will write more about why that choice was made in the first place and how you can deal with it as long as you are on Spring Data 2.x. However, let me get the good news out first:
Repositories returning Lists
Spring Data 3.0.0 now offers a ListCrudRepository
in the latest snapshot releases,
which returns a List
where CrudRepository
returns an Iterable
.
@NoRepositoryBean
public interface CrudRepository<T, ID> extends Repository<T, ID> {
<S extends T> S save(S entity);
<S extends T> Iterable<S> saveAll(Iterable<S> entities);
Optional<T> findById(ID id);
boolean existsById(ID id);
Iterable<T> findAll();
Iterable<T> findAllById(Iterable<ID> ids);
long count();
void deleteById(ID id);
void delete(T entity);
void deleteAllById(Iterable<? extends ID> ids);
void deleteAll(Iterable<? extends T> entities);
void deleteAll();
}
@NoRepositoryBean
public interface ListCrudRepository<T, ID> extends CrudRepository<T, ID> {
<S extends T> List<S> saveAll(Iterable<S> entities);
List<T> findAll();
List<T> findAllById(Iterable<ID> ids);
}
Splitting the Sorting Repositories
The popular PagingAndSortingRepository
used to extend from CrudRepository
, but it no longer does.
This lets you combine it with either CrudRepository
or ListCrudRepository
or a base interface of your own creation.
This means you now have to explicitly extend from a CRUD fragment, even when you already extend from PagingAndSortingRepository
.
public interface PersonRepository<Person, Long> extends PagingAndSortingRepository<Person, Long> {}
public interface PersonRepository<Person, Long> extends PagingAndSortingRepository<Person, Long>, ListCrudRepository<Person, Long> {}
There are also other interfaces that return Iterable<T>
and that now got a companion interface that returns List<T>
.
Fragment Interface returning Iterable |
New fragment interface returning List |
---|---|
|
|
|
|
Also, similar to PagingAndSortingRepository
, other sorting repository interfaces used to extend their respective CRUD variant but no longer do so.
Sorting fragment Interface | CRUD repository it no longer extends |
---|---|
|
|
|
|
|
This means that, when you use any of these interfaces as the basis for your Spring Data repositories, you now need to additionally extend the respective CRUD repository (assuming you are actually interested in using the CRUD functionality of it).
What about 2.x?
If you are not ready to jump onto 3.0.0 snapshots, you still have all the existing options
to avoid dealing with Iterable
as a return value.
-
You need not extend
CrudRepository
. You can instead useRepository
, which does not have any methods. Now you can add only the methods that you actually want with the return types you want. If they match those inCrudRepository
but returnCollection
orList
instead ofIterable
Spring Data, takes care of the conversion for you. Also, in most cases, you probably do not really needdeleteAll
in production, right? -
If you want all methods of
CrudRepository
but wantIterable
to be replaced by something else, you may do so by extendingCrudRepository
and overwriting the methods that you want to have changed. -
Instead of doing this in every repository interface that you declare, you can create you own base repository interface. For this approach, use the same approach as above but annotate it with
@NoRepositoryBean
so that Spring Data does not try to create an implementation for it. Your actual repository interfaces can now extend from this interface. The popularJpaRepository
is one such interface. -
In the same way as above, you might use
Streamable
as the return type, which is anIterable
but offers direct conversion methods toStream
,List
, andSet
. -
If you do not want to touch the repositories, you can use
Streamable
to convert fromIterable
to an interface ofStream
,List
, orSet
.
Why Iterable in the first place?
-
The methods of
CrudRepository
need to get implemented by every Spring Data implementation. Therefore, it is not only an API for users of Spring Data but also an SPI for those that provide a Spring Data module. Also, such a module might not want to populate a full list before returning it but, instead, return anIterable
quickly while still loading and processing data. -
If
CrudRepository
returns aList
, you could not overwrite its methods to return aStreamable
,Set
,Collection
, orIterable
. -
Streamable
would have been a great return type, since it combines flexibility with usability. Unfortunately it would force a Spring interface into your domain model, which many consider a no go or at least a code smell.
Also, once Iterable
was out there, it became hard or even impossible to change the API without breaking existing code.
So finding a solution with better usability while limiting the breakage caused by the change took some time.
We hope you like the solution, and we are confident that you will let us know what you think.