Springboot JPA Pageable not working for page number greater then 0 - spring

I want to get the Results Paginated, The below code works fine if the request is made with page parameter set to 0 but it doesn't work for page>0 like page=1 or page=2 or page=3 etc.
Here is my RequestMapping
MyResponse getSample(#ModelAttribute MyRequest MyRequest) {
Pageable pageRequest = new PageRequest(MyRequest.page, MyRequest.size)
MyModule.findSamples(MyRequest, pageRequest)
}
class MyRequest {
MyQueryType queryType
String searchTerm
#Min(value = 0L, message = 'Offset must be greater than or equal to 0')
int offset = 0
#Min(value = 0L, message = 'Offset must be greater than or equal to 0')
int page = 0
#Min(value = 1L, message = 'Limit must be greater than or equal to 1')
int limit = 100
#Min(value = 1L, message = 'Limit must be greater than or equal to 1')
int size = 5
}
MyModule:Code inside my Module
MyResponse findSamples(MyRequest MyRequest, Pageable pageRequest) {
log.info("Page Information Set "+pageRequest.pageNumber+pageRequest.pageSize)
Page<SamplesPO> pages = null
pages = MyRepository.findAllById(MyRequest.Id, pageRequest)
}
Repository Code:
public interface SampleRepository extends JpaRepository<Sample, Long> {
#Query('''
select e.Samples
from ParentSampleTable e
where e.Id = :Id
''')
Page<Sample> findAllById(#Param('Id') String Id, Pageable pageRequest)
}

Read PageRequest java docs before Using below code. PageRequest
request works on page number(starts from index 0 to 1,2,3 and so
on) and the size( limit you want)
Sort sort = new Sort(Sort.Direction.DESC, "mfgDate");
Pageable pageable = new PageRequest(pageNumber, pageSize, sort);
#Repository
public interface BikeRepository extends MongoRepository<Bike, String> {
Page<Bike> findByCompanyId(String companyId, Pageable pageable);
}

You could try extracting the id with #PathParam
Method getSample annotated with #GetMapping with params (#PathParam("id") String id, Pageable pageable) and it could return sampleRepository.findAllById(id, pageable)
Url might look like: /samples/id?page=pageNr&size=nrOfElemOnPage
Your repository should return
Page<Sample> findAllById(String id, Pageable pageable)
Also please consider refactoring(indention) the code before posting it to stackoverflow
Also this might be a duplicate: Using findAll PagingAndSortingRepository with filter

Related

Handling multiple possible #RequestParam values when making request

I have an endpoint to get all Posts, I also have multiple #RequestParams used to filter and search for values etc.
The issue I'm having is that when trying to filter based on specific #RequestParams, I would need to have multiple checks to see whether that specific parameter is passed when calling the endpoint, so in my Controller I have something like this. The parameters are optional, I also have parameters for Pagination etc, but I left it out below.
I have these criteria:
#RequestParam(required=false) List<String> brand - Used to filter by multiple brands
#RequestParam(required=false) String province - Used to filter by province
#RequestParam(required=false) String city - Used to filter by city
// Using these 2 for getting Posts within a certain price range
#RequestParam(defaultValue = "0", required = false) String minValue - Used to filter by min price
#RequestParam(defaultValue = "5000000", required = false) String maxValue - Used to filter by max price
I also have this in my Controller when checking which of my service methods to call based on the parameters passed.
if(query != null) {
pageTuts = postService.findAllPosts(query, pagingSort);
} else if(brand != null) {
pageTuts = postService.findAllByBrandIn(brand, pagingSort);
} else if(minValue != null && maxValue != null) {
pageTuts = postService.findAllPostsByPriceBetween(minValue, maxValue, pagingSort);
} else if(brand != null & minValue != null & maxValue != null) {
pageTuts = postService.findAllPostsByPriceBetween(minValue, maxValue, pagingSort);
} else {
// if no parameters are passed in req, just get all the Posts available
pageTuts = postService.findAllPosts(pagingSort);
}
// I would need more checks to handle all parameters
The issue is that I'm struggling to find out, if I need this condition for each and every possible parameter, which will be a lot of checks and Repository/Service methods based on that parameter.
For example in my Repository I have abstract methods like these:
Page<Post> findAllByProvince(String province, Pageable pageable);
Page<Post> findAllByCity(String city, Pageable pageable);
Page<Post> findAllByProvinceAndCity(String province, String city, Pageable pageable);
Page<Post> findAllByBrandInAndProvince(List<String> brand, String province, Pageable pageable);
And I'd need much more so I could handle the other potential values, ie. findAllByPriceBetween(), findAllByCityAndPriceBetween(), findAllByProvinceAndPriceBetween()...
So I'd like some suggestions on how to handle this?.
Edit
Managed to get it working by overriding the toPredicate method as shown by #M. Deinum with some small tweaks according to my use case.
#Override
public Predicate toPredicate(Root root, CriteriaQuery query, CriteriaBuilder builder) {
List<Predicate> predicates = new ArrayList<>();
// min/max is never not set as they have default values
predicates.add(builder.between(root.get("price"), params.getMinValue(), params.getMaxValue()));
if (params.getProvince() != null) {
predicates.add(builder.equal(root.get("province"), params.getProvince()));
}
if (params.getCity() != null) {
predicates.add(builder.equal(root.get("city"), params.getCity()));
}
if (!CollectionUtils.isEmpty(params.getBrand())) {
Expression<String> userExpression = root.get("brand");
Predicate p = userExpression.in(params.getBrand());
predicates.add(p);
}
return builder.and(predicates.toArray(new Predicate[0]));
}
Create an object to hold your variables instead of individual elements.
Move the logic to your service and pass the object and pageable to the service
Ditch those findAll methods from your repository and add the JpaSpecificationExecutor in your extends clause.
In the service create Predicate and use the JpaSpecificationExecutor.findAll to return what you want.
public class PostSearchParameters {
private String province;
private String city;
private List<String> brand;
private int minValue = 0;
private int maxValue = 500000;
//getters/setters or when on java17+ use a record instead of class
}
Predicate
public class PostSearchParametersSpecification implements Specification {
private final PostSearchParameters params;
PostSearchParametersPredicate(PostSearchParameters params) {
this.params=params;
}
#Override
public Predicate toPredicate(Root<T> root, CriteriaQuery<?> query, CriteriaBuilder builder) {
List<Predicate> predicates = new ArrayList<>();
// min/max is never not set as they have default values
predicates.add(builder.between(root.get("price", params.getMinValue(), params.getMaxValue());
if (params.getProvince() != null) {
predicates.add(builder.equal(root.get("province"), params.getProvince());
}
if (params.getCity() != null) {
predicates.add(builder.equal(root.get("city"), params.getCity());
}
if (!CollectionUtils.isEmpty(params.getBrand()) {
predicates.add(builder.in(root.get("brand")).values( params.getBrand());
}
return builder.and(predicates.toArray(new Predicate[0]));
}
}
Repository
public interface PostRepository extends JpaRepository<Post, Long>, JpaSpecificationExecutor<Post> {}
Service method
public Page<Post> searchPosts(PostSearchParameters params, Pageable pageSort) {
PostSearchParametersSpecification specification =
new PostSearchParametersSpecification(params)
return repository.findAll(specification, pageSort);
}
Now you can query on all available parameters, adding one is extending/modifying the predicate and you are good to go.
See also the Spring Data JPA Reference guide on Specifications

Spring boot repository query to return a ordered result

public interface StudentRepository extends MongoRepository<Student, String> {
Page<Student> findByIdInAndNameLike(List<String> ids, String name, Pageable pageable);
}
In Service layer :
Pageable pageable = PageRequest.of(page, size);
studentRepository.findByIdInAndNameLike(idList, name, pageable );
Here this idList is an ordered list. And I need to get the search results according to the order of that idList.
eg : List<String> idList= Arrays.asList("123", "111", "213");
So here the Student with id 123 should come first. And student with id 213 should come last.
As per this tutorial u have to provide the 3rd parameter to PageRequest which exactly does the sorting for you
Pageable sortedByName = PageRequest.of(page, size, Sort.by("id"));

Spring Data JDBC - Pageable on custom Query

On my Project I have a Repository that extends CrudRepository. Inside there I have a custom query:
public interface CustomerRepository extends CrudRepository<Customer, Long> {
#Query("select * from person where firstname = :firstname")
List<Customer> findByFirstname(#Param("firstname") String firstname, Pageable pageable);
}
in my Service-Class I try to put the List in a Pageable - Object like:
... getPageableCustomer(String firstname, Pageable pageable){
// => By using "Sol" I got 90 matching entries
List<Customer> custList = customerRepository.findByFirstname(firstname, pageable);
Page<Customer> custPage = new PageImpl<Customer>(custList, pageable, custList.size());
return custPage;
}
the return value includes the complete List "custList". What would be the best way to get a pageable object with specified offset and size?
One option could be to use
customer.subList(fromIndex, toIndex)
but that feels wrong. Also because of Loading all Data inside the list instead of just getting data by size and offset as parameterized with pageable.
Remark: In case of using Page inside the Repository I ´ll get org.springframework.dao.IncorrectResultSizeDataAccessException: Incorrect result size: expected 1, actual 88
There is also a open Improvement on Jira that could be found here:
https://jira.spring.io/browse/DATAJDBC-554?filter=-3
hope for some help...
I got a response on the JIRA-Issue from Dirk Luijk (Thx Dirk :))
https://jira.spring.io/browse/DATAJDBC-554?filter=-3
interface FooRepository extends PagingAndSortingRepository<FooEntity, Long> {
List<FooEntity> findAllByBar(String bar, Pageable pageable);
Long countAllByBar(String bar);
}
And then combining those 2 queries like this:
List<FooEntity> fooList = repository.findAllByBar("...", pageable);
Long fooTotalCount = repository.countAllByBar("...");
Page<FooEntity> fooPage = PageableExecutionUtils.getPage(fooList, pageable, () -> fooTotalCount);
"the mistake in your workaround is your custom query. In Spring Data JDBC 2.0 you don't need to use that, except for special queries but they won't support pageables."
Possible Parameters could be found:
https://docs.spring.io/spring-data/jdbc/docs/current/reference/html/#jdbc.query-methods
Thx Dirk,
I also find a workaround to get it running with a custom Query. Just use limit, offset and orderBy as additional Parameter like so:
#Query("select * from person where firstname = :name order by :order limit :size offset :offset")
List<Customer> findByFirstNameCustomQuery(#Param("name") String name, Pageable page, #Param("offset") long offset,
#Param("size") long size, #Param("order") String order);
And than change the call inside the Service like:
List<Customer> custList = customerRepository.findByFirstNameCustomQuery(firstname, pageable, ....

multiple requestparam in springboot pageable

i know that this question may be duplicated but i tried a lot with no success
I need to make multiple RequestParam in spring boot rest controller as following :
#GetMapping("/prd-products/test")
#Timed
public ResponseEntity<List<Test>> getAllTests(#RequestParam (required = false) String search, #RequestParam (required = false) Pageable pageable) {
Page<Test> page = prdProductsService.findAllTest(search, pageable);
HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/api/prd-products");
return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK);
}
when i try to call this service by url like :
http://localhost:9116/api/prd-products/test?search=id==1,id==2&pageable=0&size=2
it give me following error
"title": "Bad Request",
"status": 400,
"detail": "Failed to convert value of type 'java.lang.String' to required type 'org.springframework.data.domain.Pageable'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'org.springframework.data.domain.Pageable': no matching editors or conversion strategy found",
when i try to send one request param it successfully work .
Note : id==1,id==2 is the way that fields is parsed in search
With Spring Boot you can just register PageableHandlerMethodArgumentResolver bean
#Bean
public PageableHandlerMethodArgumentResolver pageableResolver() {
return new PageableHandlerMethodArgumentResolver();
}
In controller
public ResponseEntity<List<Test>> getAllTests(
#RequestParam (required = false) String search,
#PageableDefault(page = 0, size = 100) Pageable pageable
) {
...
}
And then adjust your query parameters to
...&page=0&size=2
Spring cannot convert a String to a Pageable. You should create a Pageable object from the request parameters, for example, with PageRequest.of.
Example:
#GetMapping("/prd-products/test")
#Timed
public ResponseEntity<List<Test>> getAllTests(
#RequestParam (required = false) String search,
#RequestParam (required = false) Integer pageIndex,
#RequestParam (required = false) Integer pageSize) {
Pageable pageable = PageRequest.of(pageIndex, pageSize);
Page<Test> page = prdProductsService.findAllTest(search, pageable);
HttpHeaders headers = PaginationUtil.generatePaginationHttpHeaders(page, "/api/prd-products");
return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK);
}
Your QueryString seems strange.
In order to use Spring Data REST to create and inject a Pageable, you need to do something like this:
?page=0&size=2&ids=1,2
Your REST Controller:
public ResponseEntity <List<Test>> getAllTests(#RequestParam(required = false) String search, #RequestParam(required = false) Pageable pageable) {
Page<Test> page = prdProductsService.findAllTest(search, pageable);
return new ResponseEntity<>(page.getContent(), headers, HttpStatus.OK);
}
This way, Spring Data REST will create a Pageable object.
See reference documentation.

Spring rest controller and paging

I use spring 4.2 and rest and I would like to use paging.
What is the way to use paging with spring rest controller?
#RequestMapping(value = "/members/{memberId}/payments", method = RequestMethod.GET)
public Page<PaymentDto> getPaymentByMemberId(#PathVariable("memberId") Long memberId, Pageable pageable) {
return paymentService.getPaymentByMemberId(memberId, pageable);
}
Is it a good way to manage this?
If for some area in the application, we don't want to use paging, We need to create another url?
if I want all payments for a member, I will do:
/members/{memberId}/payments
and for the paging, it's there a way to said to spring to do something like:
/members/{memberId}/payments?pageNumber=1&PageSize=10
One way to do this is:
#RequestMapping(value = "/members/{memberId}/payments", method = RequestMethod.GET)
public List<PaymentDto> getPaymentByMemberId(#PathVariable("memberId") Long memberId, #RequestParam(value = "pageNumber", required = false) final Integer pageNumber,#RequestParam(value = "pageSize", required = false) final Integer pageSize) {
PageRequest pageReq = new PageRequest((pageNumber == null ? 0 : pageNumber), (pageSize == null ? 0 : pageSize));
Page<PaymentDto> page = paymentService.getPaymentByMemberId(memberId, pageReq);
return page.getContent();
}
You need write annotation #RestController for your controller
#RestController
public class PaymentController {
...
#RequestMapping(value = "/members/{memberId}/payments", method = RequestMethod.GET)
public Page<PaymentDto> getPaymentByMemberId(#PathVariable("memberId") Long memberId, Pageable pageable) {
return paymentService.getPaymentByMemberId(memberId, pageable);
}
}
Request example: /members/12345/payments?page=0&size=50

Resources