Spring data r2dbc and pagination - spring-data-r2dbc

I'm using the new spring data r2dbc module and i'm able to extract the data using a ReactiveCrudRepository.
Now i need to introduce pagination but i cannot manage to do it.
I tried with this
public interface TestRepository extends ReactiveCrudRepository<MyEntity, Long> {
Flux<MyEntity> findByEntityId(Long entityId, Pageable page);
}
but when i try to execute this i obtain this error
org.springframework.data.repository.query.ParameterOutOfBoundsException: Invalid parameter index! You seem to have declared too little query method parameters!
at org.springframework.data.repository.query.Parameters.getParameter(Parameters.java:237)
Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
is there a way to use pagination with this module?

The new R2dbcEntityTemplate Spring Data R2dbc 1.2 contains pagination operations like this.
private final R2dbcEntityTemplate template;
public Flux<Post> findByTitleContains(String name) {
return this.template.select(Post.class)
.matching(Query.query(where("title").like("%" + name + "%")).limit(10).offset(0))
.all();
}
Spring Data R2dbc 1.2 (not released yet) will accept a Pageable parameter as in Repository.
public Flux<PostSummary> findByTitleLike(String title, Pageable pageable);
The complete code examples, check here, test codes.

No, there currently is no way to use implicit pagination. You should specify your whole queries to use it.
Here is an example:
#Query("SELECT * FROM my_entity WHERE entity_id = :entityId OFFSET :offset LIMIT :limit")
Flux<MyEntity> findByEntityId(Long entityId, int offset, int limit);

The newer versions of Spring Data R2dbc accepts Pageable as #Hantsy mentioned, but there is a catch.
If you are fetching all the records without any WHERE clause then following is NOT working:
public interface MyEntityRepository extends ReactiveCrudRepository<MyEntity, Long> {
Flux<MyEntity> findAll(Pageable pageable);
}
Changing findAll() to findBy() is working fine.
public interface MyEntityRepository extends ReactiveCrudRepository<MyEntity, Long> {
Flux<MyEntity> findBy(Pageable pageable);
}

I was able to achieve this using spring-boot-starter-data-r2dbc.2.4.3
As #Hantsy said the ReactiveCrudRepository will accept Pageable as a parameter inside of the queries but this won't solve the paging issue. In hibernate you expect to be returned a Page of an Object but for Reactive it's going to be a Flux.
I was however able to achieve this by using the PageImpl class and using the count method from the ReactiveCrudRepository interface.
For example
public interface TestRepository extends ReactiveCrudRepository<MyEntity, Long> {
Flux<MyEntity> findByEntityId(Long entityId, Pageable page);
}
public Mono<<Page<MyEntity>> getMyEntities(Long entityId, PageRequest request) {
return testRepository.findByEntityId(entityId, request)
.collectList()
.zipWith(testRepository.count())
.flatMap(entityTuples ->
new PageImpl<>(entityTuples.getT1(), request, entityTuples.getT2()));
}

Related

How to remove/handle irrelevant or bad sort parameters from http url using Pageable interface in spring boot?

How to remove/handle irrelevant or bad sort parameters from http url using Pageable interface in spring boot?
For e.g. I have a query like
http://localhost:8080/all?sort=firstName,asc&sort=nosuchfield,asc
How can I handle or remove the irrelevant field "nosuchfield"?
Also, how can I limit sort parameters in URL?
If the sorting field doesn't present in the database then below exception will be thrown by Spring JPA.
org.springframework.data.mapping.PropertyReferenceException: No property nosuchfield found for type <TYPE>!
at org.springframework.data.mapping.PropertyPath.<init>(PropertyPath.java:94)
at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:382)
at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:358)
However, the exception can be handled using various types. Ultimately, you can just log it or transform it into any custom exception. As per my requirement, I have transformed it into a custom exception.
Using AOP
#Aspect
#Component
public class UnKnownColumnSortingExceptionHandler {
#AfterThrowing(pointcut = "execution(* com.repositorypackage.*.*(..))", throwing = "exception")
public void executeWhenExceptionThrowninRepository(JoinPoint jp, Throwable ex) {
if (ex instanceof PropertyReferenceException) {
throw new CustomException("Invalid Database operation");
}
}
}
Using #ControllerAdvice(Exception handling in Application wise)
#ControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
public GlobalExceptionHandler() {}
#ExceptionHandler({PropertyReferenceException.class})
public ResponseEntity<Void> handleAllExceptions(Exception ex, WebRequest req) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}
}
Exception handling in Controller wise
Add the below piece of code to your controller
#ExceptionHandler({PropertyReferenceException.class})
public ResponseEntity<Void> handleAllExceptions(Exception ex, WebRequest req)
{
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
}

Spring Data JPA Querydsl doesn't work with projection

I have implemented Spring Data JPA and used Querydsl for search conditions. Which works fine with few changes as given in spring docs.
My REST controller method is given below
#RequestMapping(value = "/testdsl", method = RequestMethod.GET)
Iterable<User> index(
#QuerydslPredicate(root = User.class) Predicate predicate)
{
return userRepository.findAll(predicate);
}
and the repository is given below, commented methods give me projected objects nicely.
public interface UserRepository extends CrudRepository<User, Integer>,
QueryDslPredicateExecutor<User>, QuerydslBinderCustomizer<QUser>
{
//Collection<OnlyName> findAllProjectedBy();
//OnlyName findProjectedById(Integer id);
#Override
default public void customize(QuerydslBindings bindings, QUser root)
{
bindings.bind(String.class)
.first((StringPath path, String value) -> path.containsIgnoreCase(value));
}
}
And then I have this projection implemented where I get a subset of the whole entity class which is returned as the response.
public interface IUserProjection {
//...all required getters..
}
Now I want my Querydsl to return these projected objects.
Do we have any sample of such combination? I am using spring boot 1.4.0.RELEASE
You can do that but you'll need a concrete class...
class UserProjection {
#QueryProjection
public UserProjection(long id, String name){
...
}
}
And then your query would look like (in QueryDSL 3):
query.from(QTenant.tenant).list(new QUserProjection(QTenant.tenant.id, QTenant.tenant.name));
EDIT:
Query for queryDSL 4 would look like this:
List<UserProjection> dtos = query.select(new QUserProjection(QTenant.tenant.id, QTenant.tenant.name))
.from(tenant).fetch();

Mongo custom repository autowired is null

I try to autowire my custom mongo repository (and it seems the constructor is executed) but still the result is null
I've looked at some similar questions
Spring Data Neo4j - #Autowired Repository == null
and
spring data mongo repository is null
but I still don't know how to solve this.
public class TestRepo {
#Autowired
PersonRepository repository;
public void find(String name)
{
System.out.println(repository.findByName(name));
}
}
config
<mongo:repositories base-package="com.yyyy.zzz" />
PersonRepository
public interface PersonRepository extends Repository<Person, BigInteger> {
#Query("{name : ?0}")
public Person findByName(String name);
}
Implementation
public class PersonRepositoryImpl implements PersonRepository{
PersonRepositoryImpl()
{
System.out.println("constructing");
}
public Person findByName(String name) {
...
}
}
if I get the repository bean directly from context it works
Your repository setup looks suspicious. To execute query methods, you don't need to provide an implementation at all. I suspect in your current setup the custom implementation you have in PersonRepositoryImpl "overrides" the query method and thus will be preferred on execution.
If you simply drop your implementation class, Spring Data will automatically execute the query for you on invocation.
Generally speaking, custom implementation classes are only needed for functionality you cannot get through other means (query methods, Querydsl intergration etc.).

QueryDslMongoRepository Projection

I am using spring-data for mongodb with querydsl.
I have a repository
public interface DocumentRepository extends MongoRepository<Document, String> ,QueryDslPredicateExecutor<Document> {}
and an entity
#QueryEntity
public class Document {
private String id;
private String name;
private String description;
private boolean locked;
private String message;
}
I need to load a list of documents with id and name informations.
So only id and name should be loaded and set in my entity.
I think query projection is the right word for it.
Is this supported?
In addition I need to implement some lazy loading logic.
Is there anything like "skip" and "limit" features in a repository?
There's quite a few aspects to this, as it is - unfortunately - not a single question but multiple ones.
For the projection you can simply use the fields attribute of the #Query annotation:
interface DocumentRepository extends MongoRepository<Document, String>, QuerydslPredicateExecutor<Document> {
#Query(value = "{}", fields = "{ 'id' : 1, 'name' : 1 }")
List<Document> findDocumentsProjected();
}
You can combine this with the query derivation mechanism (by not setting query), with pagination (see below) and even a dedicated projection type in the return clause (e.g. a DocumentExcerpt with only id and name fields).
Pagination is fully supported on the repository abstraction. You already get findAll(Pageable) and a Querydsl specific version of the method by extending the base interfaces. You can also use the pagination API in finder methods adding a Pageable as parameter and returning a Page
Page<Document> findByDescriptionLike(String description, Pageable pageable)
See more on that in the reference documentation.
Projection
For all I know projections are not supported by the default Spring Data repositories. If you want to make sure only the projection is sent from the DB to your application (e.g. for performance reasons) you will have to implement the corresponding query yourself. Adding custom methods to extensions of the standard repo should not be too much effort.
If you just want to hide the content of certain fields from some client calling your application, you would typically use another set of entity objects with a suitable mapping in between. Using the same POJO for different levels of detail is always confusing as you will not know if a field is actually null or if the value was just suppressed in a certain context.
Pagination
I am currently not able to test any code, but according to the documentation of QueryDslPredicateExecutor the method findAll(predicate, pageable) should be what you want:
it returns a Page object that is a regular Iterable for your Document
you have to pass it a Pageable for which you can e.g. use a PageRequest; initializing it for known values of skip and limit should be trivial
I also found this approach for JPA
Spring Data JPA and Querydsl to fetch subset of columns using bean/constructor projection
I am currently trying to implement this for MongoDB.
According to the Answer of this -> Question <- I implemeted following solution.
Entity
#QueryEntity
public class Document extends AbstractObject {
}
Custom QuerydslMongoRepository
public interface CustomQuerydslMongoRepository<T extends AbstractObject,ID extends Serializable> extends MongoRepository<T, ID> ,QueryDslPredicateExecutor<T>{
Page<T> findAll(Predicate predicate, Pageable pageable,Path... paths);
Page<T> findAll(Predicate predicate, Pageable pageable,List<Path> projections);
}
Custom QuerydslMongoRepository Implementation
public class CustomQuerydslMongoRepositoryImpl<T extends AbstractObject,ID extends Serializable> extends QueryDslMongoRepository<T,ID> implements CustomQuerydslMongoRepository<T,ID> {
//All instance variables are available in super, but they are private
private static final EntityPathResolver DEFAULT_ENTITY_PATH_RESOLVER = SimpleEntityPathResolver.INSTANCE;
private final EntityPath<T> path;
private final PathBuilder<T> pathBuilder;
private final MongoOperations mongoOperations;
public CustomQuerydslMongoRepositoryImpl(MongoEntityInformation<T, ID> entityInformation, MongoOperations mongoOperations) {
this(entityInformation, mongoOperations,DEFAULT_ENTITY_PATH_RESOLVER);
}
public CustomQuerydslMongoRepositoryImpl(MongoEntityInformation<T, ID> entityInformation, MongoOperations mongoOperations, EntityPathResolver resolver) {
super(entityInformation, mongoOperations, resolver);
this.path=resolver.createPath(entityInformation.getJavaType());
this.pathBuilder = new PathBuilder<T>(path.getType(), path.getMetadata());
this.mongoOperations=mongoOperations;
}
#Override
public Page<T> findAll( Predicate predicate, Pageable pageable,Path... paths) {
Class<T> domainType = getEntityInformation().getJavaType();
MongodbQuery<T> query = new SpringDataMongodbQuery<T>(mongoOperations, domainType);
long total = query.count();
List<T> content = total > pageable.getOffset() ? query.where(predicate).list(paths) : Collections.<T>emptyList();
return new PageImpl<T>(content, pageable, total);
}
#Override
public Page<T> findAll(Predicate predicate, Pageable pageable, List<Path> projections) {
Class<T> domainType = getEntityInformation().getJavaType();
MongodbQuery<T> query = new SpringDataMongodbQuery<T>(mongoOperations, domainType);
long total = query.count();
List<T> content = total > pageable.getOffset() ? query.where(predicate).list(projections.toArray(new Path[0])) : Collections.<T>emptyList();
return new PageImpl<T>(content, pageable, total);
}
}
Custom Repository Factory
public class CustomQueryDslMongodbRepositoryFactoryBean<R extends QueryDslMongoRepository<T, I>, T, I extends Serializable> extends MongoRepositoryFactoryBean<R, T, I> {
#Override
protected RepositoryFactorySupport getFactoryInstance(MongoOperations operations) {
return new CustomQueryDslMongodbRepositoryFactory<T,I>(operations);
}
public static class CustomQueryDslMongodbRepositoryFactory<T, I extends Serializable> extends MongoRepositoryFactory {
private MongoOperations operations;
public CustomQueryDslMongodbRepositoryFactory(MongoOperations mongoOperations) {
super(mongoOperations);
this.operations = mongoOperations;
}
#SuppressWarnings({ "rawtypes", "unchecked" })
protected Object getTargetRepository(RepositoryMetadata metadata) {
return new CustomQuerydslMongoRepositoryImpl(getEntityInformation(metadata.getDomainType()), operations);
}
protected Class<?> getRepositoryBaseClass(RepositoryMetadata metadata) {
return CustomQuerydslMongoRepository.class;
}
}
}
Entity Repository
public interface DocumentRepository extends CustomQuerydslMongoRepository<Document, String>{
}
Usage in Service
#Autowired
DocumentRepository repository;
public List<Document> getAllDocumentsForListing(){
return repository.findAll( QDocument.document.id.isNotEmpty().and(QDocument.document.version.isNotNull()), new PageRequest(0, 10),QDocument.document.name,QDocument.document.version).getContent();
}

Spring-data #Query annotation and interface

Spring-data-mongodb 1.1.2-Released (Spring-data-common-core 1.4.1.Released)
I am having some trouble with using the #Query annotation with interface. For example, if I have the following interface defined:
public interface Person {
String getName();
Integer getAge();
}
and the following Repository defined:
public interface PersonRepository extends MongoRepository<Person, String> {
#Query(value="{ 'name': ?0}")
List<Person> findPeople(String name);
}
I get the following exception when trying to query:
java.lang.IllegalArgumentException: No property name found on com.abc.People!
at org.springframework.data.mapping.context.AbstractMappingContext.getPersistentPropertyPath(AbstractMappingContext.java:225)
at org.springframework.data.mongodb.core.convert.QueryMapper.getPath(QueryMapper.java:202)
at org.springframework.data.mongodb.core.convert.QueryMapper.getTargetProperty(QueryMapper.java:190)
at org.springframework.data.mongodb.core.convert.QueryMapper.getMappedObject(QueryMapper.java:86)
at org.springframework.data.mongodb.core.MongoTemplate.doFind(MongoTemplate.java:1336)
at org.springframework.data.mongodb.core.MongoTemplate.doFind(MongoTemplate.java:1322)
at org.springframework.data.mongodb.core.MongoTemplate.find(MongoTemplate.java:495)
at org.springframework.data.mongodb.repository.query.AbstractMongoQuery$Execution.readCollection(AbstractMongoQuery.java:123)
This exception does not occur if my #Query is updated to:
public interface PersonRepository extends MongoRepository<Person, String> {
#Query(value="{ 'abcd': ?0}")
List<Person> findPeople(String name);
}
This also does not occur if I remove the getName() function from the interface.
Has anyone encountered this issue and can tell me what I am doing wrong or if this is an known issue? I will open an JIRA in Spring-data project.
I think you are stumbling over this one. This has been fixed in the release announced here. You should see this working by upgrading to Spring Data MongoDB 1.2.1 (which pulls in Spring Data Commons 1.5.1 transitively).