Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Micronaut controller with pagination using Pageable

I am trying to use a Micronaut controller with pagination. Micronaut-Data has this Spring inspired way to access the repositories using the Pageable class and returning a Page

The problem comes when you want to show this paginated data. I have not been able create a call the controller with pagination. Here I have a simple controller:

    @Controller
    public class PageableController {

        private static final Logger LOGGER = LoggerFactory.getLogger(PageableController.class);

        @Get(produces = APPLICATION_JSON, value = "/test{?pageable}")
        public Page<String> getNames(@Nullable Pageable pageable) {

            LOGGER.info("pageable {}", pageable);
            if( pageable == null){
                return Page.of(Arrays.asList("foo", "bar"), Pageable.UNPAGED, 2);
            }else{
                return Page.of(Arrays.asList("foo", "bar"), pageable, 2);
            }
        }
    }

I would expect to be able to call it with something like this. But currently the logger shows that pageable is always null:

    @MicronautTest
    class PageableControllerTest {

        @Inject
        @Client("/")
        private RxHttpClient client;

        @Test
        void callsWithPageable() {
            String uri = "/test?size=20&number=2";
            String orders = client.toBlocking().retrieve(HttpRequest.GET(uri));

            //TODO, assert orders and pagination
        }

It would be even better if we could test it with something like:

    @Test
    void callsWithPageableParsingJson() {
        String uri = "/test?size=20&number=2";

        //This fails to parse as it can't build pages.
        Page<String> pages = client.toBlocking().retrieve(HttpRequest.GET(uri), pageOf(String.class));

        assertThat(pages.getSize(),   is(2));
        assertThat(pages.getContent(), contains("foo", "bar"));
    }

    // Inspired by Argument.listOf
    private static <T> Argument<Page<T>> pageOf(Class<T> type) {
        return Argument.of((Class<Page<T>>) ((Class) Page.class), type);
    }

And this Micronaut bug shows that the right way to paginate is with Micronaut Data

like image 694
borjab Avatar asked Jul 01 '26 15:07

borjab


2 Answers

The problem was solved by adding the following dependecy:

    <dependency>
       <groupId>io.micronaut.data</groupId>
       <artifactId>micronaut-data-runtime</artifactId>
       <version>1.0.0.M1</version>
    </dependency>

My controller layer had access to the micronaut-data-model but this jar contains the important class PageableRequestArgumentBinder. Just by being i the classpath it will automatically be injected as a binder with no need for extra configuration.

And yes, Free See was right and now I can remove the pageable argument from the path and the argument from the method does not need to be @Nullable :

    @Controller
    public class PageableController {

        private static final Logger LOGGER = LoggerFactory.getLogger(PageableController.class);

        @Get(produces = APPLICATION_JSON, value = "/test")
        public Page<String> getNames(Pageable pageable) {

            LOGGER.info("pageable {}", pageable);
            return Page.of(Arrays.asList("foo", "bar", "baz"), pageable, 3);
        }

To call it we new to use the standard parameters names defined in DataConfiguration.PageableConfiguration.

  • DEFAULT_PAGE_PARAMETER "page"
  • DEFAULT_SIZE_PARAMETER "size"
  • DEFAULT_SORT_PARAMETER "sort"

If you want to use different parameters you can change it with properties:

micronaut:
  data:
    pageable:
     max-page-size: 500

And you can test it with

    @Test
    void callsWithPageable() {
        String uri = "/test?page=1&size=2";

        Page<String> pages = client.toBlocking().retrieve(HttpRequest.GET(uri), pageOf(String.class));

        assertThat(pages.getPageNumber(),   is(1));
        assertThat(pages.getTotalPages(),   is(2));
        assertThat(pages.getSize(),   is(2));
        assertThat(pages.getContent(), contains("foo", "bar", "baz"));
    }

And, to make things even better, the client can convert the result to a page using the pageOf method that returns Argument>

like image 193
borjab Avatar answered Jul 03 '26 04:07

borjab


In my application, I accept Pageable as well and I dont have any issues with it. The difference between mine and yours are:

  1. My path is just @Get("/test") i.e. no {pageable} part.
  2. I dont make my Pageable as @Nullable. From what I can trace from the micronaut code, it does not treat Pageable like any other object. It has special handling for it (like testing if the argument type is Pageable and if it is, then do certain things).

Can you try those two things?

like image 28
Franz See Avatar answered Jul 03 '26 05:07

Franz See