Spring rest controller and paging - spring

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

Related

Spring JPA: find by multiple IDs with Pagination

How is it possible to apply the pagination to the below query:
#Repository
public interface PostRepository extends JpaRepository<Post, Long> {
#Query("select b from Building b where b.id in :ids" )
Page<Post> findByIds(#Param("ids") List<Long> postIdsList);
...
}
All the existing examples are based on the standard findAll method that accepts a Pageable object: public Page findAll(Pageable pageable);.
The questions are:
what the controller method signature should be
what the repository method parameters should be
how and what parameters should be passed into the controller method
should I always split the post IDs for every request
will Spring make a single query and keep all the found posts in memory or it will hit a query every time for every next/previous page? If so, how can it figure out the IDs to use to find the next/previous posts?
The initial implementation was as follows:
#RestController
class PostsController {
#Autowired
private PostService postService;
#GetMapping("/posts", params = "ids")
public List<Post> getPaginatedPosts(#RequestParam List<Long> ids) {
return postService.findPaginatedPosts(ids);
}
}
#Repository
#Repository
public interface PostRepository extends JpaRepository<Post, Long> {
#Query("select b from Building b where b.id in :ids" )
Page<Post> findByIds(#Param("ids") List<Long> postIdsList);
...
}
I omitted the code from the PostServiceImpl qui implements the PostService and just calls the PostRepository#findByIds method.
Try this:
#Repository
public interface PostRepository extends JpaRepository<Post, Long> {
#Query( "select o from Building b where id in :ids" )
Page<Post> findByIds(#Param("ids") List<Long> postIdsList,Pageable pageRequest);
...
}
In controller ask for pageSize and pageNo, if it is empty set a default value like pageNo = 0, pageSize=10.
pass these values to to service layer service should create pageable object call findByIds(ids, pagable); and return the page to controller.
you can refer this:
https://www.petrikainulainen.net/programming/spring-framework/spring-data-jpa-tutorial-part-seven-pagination/
Here is the solution I came to you coupled with the above comments suggestions.
Define a repository either extending JpaRepository or PagingAndSortingRepositoryas follows:
#Repository
public interface PostRepository extends JpaRepository<Post, Long> {
#Query("select p from Post p where p.id in :ids" )
Page<Post> findByIds(#Param("ids") List<Long> postIdsList);
...
}
Create a service class and its implementation:
public interface PostService {
List<PostDTO> getPostsList(List<Long> ids, Pageable pageable);
...
}
#Service
#Slf4j
public class PostServiceImpl implements PostService {
...
#Autowired
private PostRepository postRepository;
...
#Override
public List<PostDTO> getPostsList(List<Long> ids, Pageable pageable) {
List<PostDTO> resultList = new ArrayList<>();
Page<Post> paginatedPosts = postRepository.findByIds(ids, pageable);
List<Post> posts = paginatedPosts.getContent();
posts.forEach(post -> resultList.add(convertToPostDTO(post)));
return resultList;
}
And finally, the PostsController part:
#RestController
#RequestMapping("/api")
class PostsController {
#Autowired
private PostService postService;
...
#GetMapping(value = "/posts", params = "ids")
public ResponseEntity <List<PostDTO>>getPostsList(#RequestParam List<Long> ids, Pageable pageable) {
List<PostDTO> postsList = postService.getPostsList(ids, pageable);
return new ResponseEntity<>(postsList, HttpStatus.OK);
}
The request should contain page and size URL parameters (by default, page is 0 and size is 20):
http://localhost:8080/api/posts?ids=1050,1049,1048,1043,1042,1041,1040,1039,1038&size=5&page=1&sort=id
In the above example, I had 9 records total and I put the parameters explicitly to limit the result list to 5 and display the second page only as well as to sort them by id.
If you don't provide them, the default values will be used (page = 0, size = 20).
To anyone coming here looking to pass a list of ids as a url-parameter like the question asker wants to do and the answer of belgoros explains:
Be aware of the url-max-length of 2048 characters.
So if your list of ids is long enough to require pagination, you probably also want to make the ids a body-parameter. This answer explains how to create body-parameters with spring: https://stackoverflow.com/a/22163492/7465516
I think this is important, because solutions that work on small data but unexpectedly fail on big data are the kind of thing that gets through testing and fails in production.
(I do not have the reputation to make this a comment, I hope this post is acceptable)
#Query( "select o from Building b where id in :ids", nativeQuery=true )
Page findByIds(#Param("ids") List postIdsList,Pageable pageRequest);

Hibernate Criteria FetchMode.JOIN is doing lazy loading

I have a paginated endpoint which internally uses Hibernate Criteria to fetch certain objects and relations. The FetchMode is set as FetchMode.JOIN.
When I am trying to hit the endpoint, the request seems to work fine for few pages but is then erring out with :
could not initialize proxy - no Session
Method is as as below:
#Override
public Page<Person> findAllNotDeleted(final Pageable pageable)
{
final var criteria = createCriteria();
criteria.add(Restrictions.or(Restrictions.isNull(DELETED), Restrictions.eq(DELETED, false)));
criteria.setFetchMode(PERSON_RELATION, FetchMode.JOIN);
criteria.setFetchMode(DEPARTMENT_RELATION, FetchMode.JOIN);
criteria.setFirstResult((int) pageable.getOffset());
criteria.setMaxResults(pageable.getPageSize());
criteria.addOrder(asc("id"));
final var totalResult = getTotalResult();
return new PageImpl<>(criteria.list(), pageable, totalResult);
}
private int getTotalResult()
{
final Criteria countCriteria = createCriteria();
countCriteria.add(Restrictions.or(Restrictions.isNull(DELETED), Restrictions.eq(DELETED, false)));
return ((Number) countCriteria.setProjection(Projections.rowCount()).uniqueResult()).intValue();
}
Also, the call to findAllNotDeleted is done from a method anotated with #Transactional.
Not sure what is going wrong.
Any help would be highly appreciated.
EDIT
I read that FetchMode.Join does not work with Restrictions. So I tried implementing it using CriteriaBuilder but again stuck with the issue.
#Override
public Page<Driver> findAllNotDeleted(final Pageable pageable)
{
final var session = getCurrentSession();
final var builder = session.getCriteriaBuilder();
final var query = builder.createQuery(Person.class);
final var root = query.from(Driver.class);
root.join(PERSON_RELATION, JoinType.INNER)
.join(DEPARTMENT_RELATION,JoinType.INNER);
//flow does not reach here.....
var restrictions_1 = builder.isNull(root.get(DELETED));
var restrictions_2 = builder.equal(root.get(DELETED), false);
query.select(root).where(builder.or(restrictions_1,restrictions_2));
final var result = session.createQuery(query).getResultList();
return new PageImpl<>(result, pageable, result.size());
}
The flow does not seem to reach after root.join.
EDIT-2
The relations are as follows:
String PERSON_RELATIONSHIP = "person.address"
String DEPARTMENT_RELATION = "person.department"
and both person, address, department themselves are classes which extend Entity
I guess the associations you try to fetch i.e. PERSON_RELATION or DEPARTMENT_RELATION are collections? In such a case, it is not possible to directly do pagination on the entity level with Hibernate. You would have to fetch the ids first and then do a second query to fetch just the entities with the matching ids.
You could use Blaze-Persistence on top of Hibernate though which has a special pagination API that does these tricks for you behind the scenes. Here is the documentation about the pagination: https://persistence.blazebit.com/documentation/core/manual/en_US/index.html#pagination
There is also a Spring Data integration, so you could also use the Spring Data pagination convention along with Blaze-Persistence Entity-Views which are like Spring Data Projections on steroids. You'd use Page<DriverView> findByDeletedFalseOrDeletedNull(Pageable p) with
#EntityView(Driver.class)
interface DriverView {
Long getId();
String getName();
PersonView getPersonRelation();
DepartmentView getDepartmentRelation();
}
#EntityView(Person.class)
interface PersonView {
Long getId();
String getName();
}
#EntityView(Department.class)
interface DepartmentView {
Long getId();
String getName();
}
Using entity views will only fetch what you declare, nothing else. You could also use entity graphs though:
#EntityGraph(attributePaths = {"personRelation", "departmentRelation"})
Page<Driver> findByDeletedFalseOrDeletedNull(Pageable p);

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.

Springboot JPA Pageable not working for page number greater then 0

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

Spring Data Pageable breaking Spring Data JPA OrderBy

I have a simple JpaRepository with a finder that returns records ordered by a property named "number" in descending order. The "number" property is also the #Id of my entity. This works just fine, however there's thousands of records, so I want to return a Page instead of a List.
#Repository
public interface ReceiptRepository extends JpaRepository<Receipt, BigDecimal> {
#Query
public List<Receipt> findByStorerOrderByNumberDesc(String storer);
}
If I change my finder to something like the following, the sorting no longer works. I've tried using the sort capability of the Pageable argument, but it didn't work. Also removed the OrderByNumberDesc, but same result.
#Repository
public interface ReceiptRepository extends JpaRepository<Receipt, BigDecimal> {
#Query
public Page<Receipt> findByStorerOrderByNumberDesc(String storer, Pageable pageable);
}
EDIT - added controller method
The following is my controller method.
#RequestMapping(method = RequestMethod.GET, produces = {"application/json"})
public PagedResources<Receipt> receipts(Pageable pageable, PagedResourcesAssembler assembler) {
Page<Receipt> receipts = receiptRepository.findByStorer("003845", pageable);
return assembler.toResource(receipts, receiptResourceAssembler);
}
I feel I'm missing something very basic here.
I'm using Spring Data JPA 1.5.2 and Commons 1.7.2.
Thanks :)
Add the sort to your Pageable when you create it:
e.g.
Pageable pageable ...;
pageable.getSort().and(new Sort(Direction.ASC, "prop1", "prop1"));
or in Spring MVC you can do:
#RequestMapping(method = RequestMethod.GET, produces = {"application/json"})
public PagedResources<Receipt> receipts(#PageableDefaults(sort = { "x",
"y", "z" }, value = 10)Pageable pageable, PagedResourcesAssembler assembler) {
Page<Receipt> receipts = receiptRepository.findByStorer("003845", pageable);
return assembler.toResource(receipts, receiptResourceAssembler);
}

Resources