Pagable Sort.Order ignore case - java

(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)

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);

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.

Converting List of objects into pages in spring

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));
};
}

Spring Data REST - Override repository findAll without creating /search/findAll URL

Is there any way to prevent Spring Data REST from creating a /search URLs for overridden repository methods?
For example the following code results in a /search/findAll URL being generated which duplicates the functionality of the collection resource:
public interface EmployeeRepository extends CrudRepository<Employee, Long>
{
#Override
#Query("SELECT e FROM Empolyee e")
Iterable<Employee> findAll();
}
This is only a cosmetic issue when overriding a single method but if you attempt to override multiple methods with the same function name and different parameters, for example both findAll methods in PagingAndSortingRepository then spring throws an exception because it's attempting to map 2 functions to the same path.
public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Long>
{
#Override
#Query("SELECT e FROM Employee e")
Iterable<Employee> findAll();
#Override
#Query("SELECT e FROM Employee e")
Iterable<Employee> findAll(Sort sort);
#Override
#Query("SELECT e FROM Employee e")
Page<Employee> findAll(Pageable pageable);
}
Results in:
java.lang.IllegalStateException: Ambiguous search mapping detected. Both public abstract java.lang.Iterable uk.co.essl.roster.entity.employee.EmployeeRepository.findAll(org.springframework.data.domain.Sort) and public abstract java.lang.Iterable uk.co.essl.roster.entity.employee.EmployeeRepository.findAll() are mapped to /findAll! Tweak configuration to get to unambiguous paths!
at org.springframework.data.rest.core.mapping.SearchResourceMappings.<init>(SearchResourceMappings.java:60)
at org.springframework.data.rest.core.mapping.RepositoryResourceMappings.getSearchResourceMappings(RepositoryResourceMappings.java:128)
at springfox.documentation.spring.data.rest.EntityContext.searchMappings(EntityContext.java:107)
...
Is there any way to prevent Spring Data REST from creating a /search URLs for overridden repository methods?
I found following trick to solve this issue:
#Override
default Page<Employee> findAll(Pageable pageable) {
return findBy(pageable);
}
#RestResource(exported = false)
Page<Employee> findBy(Pageable pageable);
More other this trick allows you to set default sort order for 'get all records' request:
#Override
default Page<Employee> findAll(Pageable p) {
if (p.getSort() == null) {
// The default sort order
return findBy(new PageRequest(p.getPageNumber(), p.getPageSize(), Sort.Direction.DESC, "myField"));
}
return findBy(pageable);
}
Enjoy! ))
#RestResource(exported=false) just for overridden method will not help 'cause this blocks GET 'all records' request (
#RestResource(exported = false)

Categories