Converting List of objects into pages in spring - java

I am trying to get page with a number of items in each page. For some reason, this method returns the complete list instead of page of 5 items.
public Page<Item> searchPagedCategoryByName(#RequestParam String name)
{
Category category;
category = categoryRepository.findCategoryByCategoryName(name);
List<Item> items = category.getItems();
Pageable pageable = PageRequest.of(0,5);
Page<Item> page = new PageImpl<Item>(items, pageable, items.size());
return page;
}

Create the repository extending PagingAndSortingRepository which provides perform pagination and sorting capability and then you can use like this,
Here is a code snippet from my practice workspace.
public interface CategoryRepository extends PagingAndSortingRepository< Category, Long> {
List<Category> findCategoryByCategoryName(String categoryName, Pageable pageable);
}
#Bean
public CommandLineRunner pagingAndSortingRepositoryDemo(CategoryRepository repository) {
return (args) -> {
log.info("Category found with Paging Request PageRequest.of(page [zeroBased Page index], Size)");
repository. findCategoryByCategoryName(name , PageRequest.of(0, 5)).forEach(category -> log.info(" :=> " + category));
};
}

Related

Spring Boot: Slice/Pageable not returning proper chunk based on page

In my PSQL DB, I have two objects stored and I would like to retrieve each item on their separate page/slice. I am trying to achieve this by passing in the following Page objects:
PageRequest.of(0,1) for the first item and PageRequest.of(1, 1) for the second item.
However, when I create the Pageable object via PageRequest.of(1, 1), this always results in only the first item being returned every time, but I have confirmed that both items indeed does exist by calling repo.findAll().
What am I doing incorrectly?
My service layer call looks like the following:
#Transactional
public Slice<Foo> findAllInactive(Pageable pageable) {
return repo.findAllInactive(new Date(), pageable));
}
And my repo is:
#Repository
public interface FooRepository extends JpaRepository<Foo, String> {
value =
"SELECT * FROM fooschema.foo i WHERE i.valid_until < :currentDate OR i.valid_until IS NULL --#pageable\n",
nativeQuery = true,
countQuery = "SELECT count(*) FROM fooschema.foo i")
Slice<Foo> findAllInactive(#Param("currentDate") Date currentDate, Pageable pageable);
}
If it makes any difference, here is the test call
#Autowired private MockMvc mvc;
#Test
void testStuff() throws Exception {
// two elements added....
ResultActions resultActions =
mvc.perform(
get("/foo")
.param("page", "1")
.param("size", "1"))// should return the second element, but returns the first
.andExpect(status().isOk())
.andExpect(content().contentType("application/json"));
}
and the controller
#RestController
#RequestMapping("/foo")
public class FooController {
#GetMapping
#ApiImplicitParams({
#ApiImplicitParam(
name = "page",
dataType = "int",
paramType = "query",
value = "Page you want to retrieve",
defaultValue = "0"),
#ApiImplicitParam(
name = "size",
dataType = "int",
paramType = "query",
value = "Number of foo per page.",
defaultValue = "10"))
public Slice<Foo> getFoo(Pageable pageable) {
return service.findAllInactive(pageable);
}
}
Anshul's comment got me on the right track, and in the end, it appears that creating a derived query, as noted here: https://www.baeldung.com/spring-data-derived-queries, works.
In the end, the following got it working for me:
Slice<Foo> findByValidUntilIsNullOrValidUntilBefore(Date currentDate, Pageable pageable); // or can return a List<Foo>
Can you try with Page object instead Slice.
Step-1 - Create a page size
Pageable page1 = PageRequest.of(0, 1);
Pageable page2 = PageRequest.of(1, 1);
Step-2
Page <Foo> findAllInactive(Date currentDate, Pageable page2);

Pagable Sort.Order ignore case

(I'm using Micronaut)
I have a list of students and I want to sort it by name. I am using Pageable and sort as query parameters
http://localhost:8080/admin/students?page=0&size=10&sort=name,asc&status=graduated
and I get this result:
Samantha
Tim
antonio
david
sofia
Instead of:
antonio
david
Samantha
sofia
Tim
I have tried the options using Sort.Order, based on
PagingAndSortingRepository how to sort case insensitive?
Spring Data JPA: case insensitive orderBy
I removed the sorting from my url and tried if Sort.Order works
Sort.Order order = new Sort.Order("name",Sort.Order.Direction.ASC, true);
http://localhost:8080/admin/students?page=0&size=10&status=graduated
but it didn't, it doesn't do any sorting neither
Steps:
Repository, added Sort.Order
#Repository
public interface StudentRepository extends PageableRepository<Student, Long> {
Page<Student> findByGraduatedIsNull(Student.Status status, #Nullable Pageable p, Sort.Order sort);
}
Service
public Page<Student> getStudents(Student.Status status, Pageable pageable){
Sort.Order order = new Sort.Order("name",Sort.Order.Direction.ASC, true);
Page<Student> studentPage =
studentRepository.findByGraduatedIsNull(status, pageable, order);
return studentPage.map(s -> studentTransform.toDto(s));
}
What could be the problem?
The Pageable interface supports sorting, so not sure why you would pass both a Pageable and Sort.Order parameter (I don't know if that is even supported). If no sort was defined on the URL, the getSort() on Pageable should return UNSORTED. So that may be taking precedent over the Sort.Order.
Why don't you check if a sort was passed in on the request, and if not default it to the sort you want or simply override it if you don't want to allow a sort to be passed in.
if (pageable.getSort == Sort.unsorted()) {
List<Sort.Order> orders = new ArrayList<>();
orders.add(new Sort.Order(Sort.Direction.ASC, "name").ignoreCase());
pageable = PageRequest.of(pageable.getPageNumber(), pageable.getPageSize(), Sort.by(orders));
}
Page<Student> studentPage = studentRepository.findByGraduatedIsNull(status, pageable)

Sort passed in Pageable to Spring controller appends direction to property name, causing a SQL error

I need to call a pageable endpoint in my Spring Boot app from another Spring Boot app. I'm trying to pass the pageable options through from the first app to the second, but I'm having an issue where the property name comes in as firstName: ASC. When the direction is appended to that, it becomes firstName: ASC: ASC which causes the JPA query to throw an exception.
What's the proper way to pass the pageable options from my first endpoint to my second?
Calling app
#GetMapping("/v1/users")
public Flux<User> getUsersByAccount(#RequestParam Long accountId,
#PageableDefault(size = 10, sort = "firstName") Pageable pageable) {
return userService.getUsersByAccount(accountId, pageable);
}
public Flux<User> getUsersByAccount(Long accountId, Pageable pageable) {
int page = pageable.getPageNumber();
int size = pageable.getPageSize();
Sort sort = pageable.getSort();
return webClient.backendService()
.get().uri(builder -> builder
.path("/rest/users")
.queryParam("accountId", accountId)
.queryParam("page", page)
.queryParam("size", size)
.queryParam("sort", sort)
.build())
.retrieve()
.bodyToFlux(ContactInfo.class);
}
I am splitting the Pageable out into its components because I wasn't sure how to pass the whole object at once since it's not a named parameter in the second app. Note that at this point, the sort looks fine and appears as it should with firstName and ASC as separate values for property name and direction, respectively.
Called app
#GetMapping("/rest/users")
public List<User> getUsersByAccount(#RequestParam Long accountId, Pageable pageable) {
return userService.getUsersByAccount(accountId, pageable);
}
As #M. Deinum mentioned, Sort's toString() doesn't produce a representation that can be directly serialized back into a Sort object, (and there is no such method on the object to do that).
You can convert it to the proper form like this:
List<String> sorts = new ArrayList<>();
sort.forEach(order -> sorts.add(String.join(",", order.getProperty(), order.getDirection().toString())));
builder.queryParam("sort", sorts.toArray());
This produces the correct representation of ["propertyName,direction"].
Controller we can capture HttpServletRequest as follows
public ResponseEntity<Page<PriceBook>> getAll(HttpServletRequest servletRequest) {
return new ResponseEntity<>(priceBookService.findAll(servletRequest), HttpStatus.OK);
}
use queryString as follows to pass all request parameters to the backend service
public PageableResponse<PriceBook> getPbAllWithFilter(HttpServletRequest servletRequest) {
return webClient.get().uri(pbUrl.concat("?").concat(servletRequest.getQueryString()))
.accept(MediaType.APPLICATION_JSON)
.header(CORRELATION_ID, MDC.get(CORRELATION_ID_MDC))
.exchange()
.flatMap(this::getSubscriptionAll)
.block();
}

How am I supposed to pass an unpaged but sorted Pageable to a Spring JPA repository?

Spring JPA has the Pageable interface. I can pass this to a repository method. Some of its implementations like PageRequest contain a page, a size and a sort. However, there is also an unpaged instance that I can create with Pageable.unpaged() and that returns an org.springframework.data.domain.Unpaged which is an enum that implements Pageable.
The problem comes when you want to make use of the sorting abilities of this pageable object, but do not want paging. In that case you should want the Pageable.isPaged() to return false, however the only instance of this that you can normally create is the PageRequest (with Pagerequest.of(size, page, sort)) which always returns true for that method.
If you have a JPA repository method like this:
Page<EventTriggerVersion> findByProjectIdAndEventTriggerId(
#Param("projectId") UUID projectId, #Param("eventTriggerId") UUID eventTriggerId, Pageable pageable);
Then you cannot create a Pageable for it that DOES sort but DOES NOT page, i.e. returns only 1 page with all results.
However, you can ALSO not just give it both a unpaged Pageable AND a Sort param, if you do then it throws this exception: java.lang.IllegalStateException: Method must not have Pageable *and* Sort parameter. Use sorting capabilities on Pageable instead!
So how am I supposed to do this without duplicating all my repository methods (which mostly have custom queries) to have a version with Pageable and with Sort?
Our users simply want to pass size=0 in their request and then get all data (not limited to some arbitrary maximum but a genuine unpaged request). This is annoying enough because it means I need to remove the Pageable request param from the controller and manually construct it from page, size and Sort, but now it doesn't even seem possible without serious code duplication.
For now I implemented my own Pageable class as following:
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
public class SortedUnpaged implements Pageable {
private final Sort sort;
private SortedUnpaged(Sort sort) {
this.sort = sort;
}
public static SortedUnpaged getInstance(Sort sort) {
return new SortedUnpaged(sort);
}
public boolean isPaged() {
return false;
}
public Pageable previousOrFirst() {
return this;
}
public Pageable next() {
return this;
}
public boolean hasPrevious() {
return false;
}
public Sort getSort() {
return sort;
}
public int getPageSize() {
throw new UnsupportedOperationException();
}
public int getPageNumber() {
throw new UnsupportedOperationException();
}
public long getOffset() {
throw new UnsupportedOperationException();
}
public Pageable first() {
return this;
}
}
And have this in my controller:
#GetMapping("/{projectId}" + SUBJECTS_PATH)
public ResponseEntity<SubjectWrapperDTO> getSubjects(
#RequestParam(name = "size", required = false, defaultValue = "20") Integer size,
#RequestParam(name = "page", required = false, defaultValue = "0") Integer page,
#SortDefault(sort = "name") Sort sort) {
Pageable pageable;
if (size > 0) {
pageable = PageRequest.of(page, size, sort);
}
else {
pageable = SortedUnpaged.getInstance(sort);
}
...
}
But this seems to be a bit too much for such a basic requirement and I'd prefer not implementing my own classes of Spring interfaces.

Spring Data Pagination & AJAX

I have the following Controller and I've just included pagination into my returned results
#RequestMapping(value = "/search/{person}", produces="application/json", method = RequestMethod.GET)
public Page<Person> findAllPersons(#PathVariable String person) {
Page<Person> list = personRepo.findAll(new PageRequest(1, PAGE_SIZE));
return list;
}
I'm now trying to figure out how to actually tab through these results - The search on the Person table has is it's own AJAX request, where as selecting "next" or "previous" on my UI tool can launch it's own GET
<a id="previous" href="onclick="setPageNumber(1)">
<a id="next" href="onclick="setPageNumber(2)">
function setPageNumber(num) { //relaunch request with page number value retrieved from previous or next}
Should I include a pageNumber as a #PathVariable like so:
#RequestMapping(value = "/search/{person}/{pageNumber}", produces="application/json", method = RequestMethod.GET)
public Page<Person> findAllPersons(#PathVariable String person, #PathVariable int pageNumber) {
Page<Person> list = personRepo.findAll(new PageRequest(pageNumber, PAGE_SIZE));
return list;
}
or should setting the pageNumber be a completely separate controller method that somehow invokes findAllPersons with the pageNumber argument? I may be confusing myself here - any input is welcome thanks!
For REST service I would put it to the parameters rather then to URI page_start=X&page_size=Y.
I know this post isn't active for a long time but for anyone still looking for a way to use Spring pagination with Ajax, here are some possible solutions:
1. If your repository is an instance of JpaRepository (or more precisely PagingAndSortingRepository) then you can simply pass a Pageable to it:
#Controller
public class FooController {
//...
#GetMapping("/foo/list")
public List<Foo> handleList(Pageable pageable) {
return fooRepository.findAll(pageable);
}
//...
}
2. Instead of Pageable, you can also retrieve your pagination parameters as #RequestParam and create a PageRequest yourself. This approach might be useful if the project does not use Spring Data and JPA:
#Controller
public class FooController {
//...
#GetMapping("/foo/list")
public List<Foo> handleList(
#RequestParam(value = "size", required = false) Optional<Integer> pageSize,
#RequestParam(value = "page", required = false) Optional<Integer> pageNumber,
#RequestParam(value = "sort", required = false) Sort sort,
) {
PageRequest pageable = new PageRequest(pageNumber.orElse(0), pageSize.orElse(10), sort);
return fooRepository.customfindAll(pageable);
}
//...
}
(e.g. this repository above might be a class extending a JDBCRepository such as this one )
And for the AJAX part of these two possible solutions, something like that can be used:
// ... handle pageNo, listSize etc.
var url = '/yourprj/foo/list?page=' + pageNo + '&size=' + listSize
$.ajax({
type: "GET",
contentType: "application/json",
url: url,
success: function(result) {
// handle Foo list...
}
});
3. Alternatively, if you are using Thymeleaf+Spring Data there is a dialect to automatically add pagination.

Categories