Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Multiple Sort Optional query - Spring REST Controller configuration with Pagination

I want to build a spring controller that can handle multiple, optional sort queries. According to spring.io spec these queries should be formatted thus

&sort=name,desc&sort=code,desc

As discussed by EduardoFernandes

I know this can be done with one instance of sort with value to be sorted and the direction give separately as per Gregg but that doesn't match the Spring spec or handle multiple sort values.

I'm not sure how to turn multiple queries in the spring spec format into a Sort that I can pass to my PageRequest and then on to my repository. Also I would like the ability to make these optional and if possible, it would be great if I could use @Anotation based config if defaults are necessary to achieve this as per Rasheed Amir (@SortDefault)

Here is the basics of what I'm working with..

Domain

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Subject {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String code; 
...

Repository

public interface SubjectRepository extends JpaRepository<Subject, Long> {
}

Service

@Override
public Page<SubjectDTO> listSubjectsPageble(PageRequest pageableRequest) {
    return subjectRepository.findAll(pageableRequest)
            .map(subjectMapper::subjectToSubjectDTO);
}

Controller

@GetMapping
@ResponseStatus(HttpStatus.OK)
@PreAuthorize("hasRole('LECTURER')")
public Page<SubjectDTO> getSubjects(
        @RequestParam("page") int page,
        @RequestParam("size") int size,
        @RequestParam("sort") String sort

) {

    return subjectService.listSubjectsPageble(PageRequest.of(page, size, new Sort(sort)));
}

So here in the controller I don't know how to deal with\populate the Sort from the RequestParam at all, according to Ralph I should be able to use something like the below to get multiple values from one param, but I don't know how to then pass that to a Sort.

I know a Sort can take more than one parameter but only one sort direction. And then of coarse I would like to make them optional.

@RequestParam MultiValueMap<String, String> params

Please help, I'm still quite a noob :) Thanks

EDIT

I solved some of my issues thanks to a post by Dullimeister But the approach feels a little messy and still doesn't handle multiple sort parameters. Does anyone know of better approach or is this the way to go?

@GetMapping
    @ResponseStatus(HttpStatus.OK)
    @PreAuthorize("hasRole('LECTURER')")
    public Page<SubjectDTO> getSubjects(
            @RequestParam(value = "page", defaultValue = "0", required = false) int page,
            @RequestParam(value = "size", defaultValue = "10", required = false) int size,
            @RequestParam(value = "sort", defaultValue = "name,ASC", required = false) String sortBy

    ) {
        String[] sort = sortBy.split(",");
        String evalSort = sort[0];
        String sortDirection = sort[1];
        Sort.Direction evalDirection = replaceOrderStringThroughDirection(sortDirection);
        Sort sortOrderIgnoreCase = Sort.by(new Sort.Order(evalDirection,evalSort).ignoreCase());

        return subjectService.listSubjectsPageble(PageRequest.of(page, size, sortOrderIgnoreCase));
    }

    private Sort.Direction replaceOrderStringThroughDirection(String sortDirection) {
        if (sortDirection.equalsIgnoreCase("DESC")){
            return Sort.Direction.DESC;
        } else {
            return Sort.Direction.ASC;
        }
    }

Final Solution Thanks everyone, this is what I ended up with. Not sure if its the perfect way but it works :) I had to replace the comma with a semi-colon in the end as the FormattingConversionService was automatically parsing a single sort param to a string instead of an Sting[]

@GetMapping
@ResponseStatus(HttpStatus.OK)
@PreAuthorize("hasRole('LECTURER')")
public Page<SubjectDTO> getSubjects(
        @RequestParam(value = "page", defaultValue = "0", required = false) int page,
        @RequestParam(value = "size", defaultValue = "10", required = false) int size,
        @RequestParam(value = "sort", defaultValue = "name;ASC", required = false) String[] sortBy

Sort allSorts = Sort.by(
        Arrays.stream(sortBy)
                .map(sort -> sort.split(";", 2))
                .map(array ->
                        new Sort.Order(replaceOrderStringThroughDirection(array[1]),array[0]).ignoreCase()
                ).collect(Collectors.toList())
);
return subjectService.listSubjectsPageble(PageRequest.of(page, size, allSorts));

private Sort.Direction replaceOrderStringThroughDirection(String sortDirection) {
    if (sortDirection.equalsIgnoreCase("DESC")){
        return Sort.Direction.DESC;
    } else {
        return Sort.Direction.ASC;
    }
like image 492
Rinus van Heerden Avatar asked Feb 19 '19 20:02

Rinus van Heerden


1 Answers

Why don't you use Pageable in your controller ?

Pageable can handle many sort queries, each of them will be stored in orders list. Moreover, any of pageable parameters aren't required. When you don't pass them in url, pageable will contains default values (page = 0, size = 20). You can change default values by using @PageableDefault annotation.

GET .../test?sort=name,desc&sort=code,desc enter image description here

like image 131
Mateusz Jaszewski Avatar answered Nov 03 '22 23:11

Mateusz Jaszewski