Jak poprawnie korzystać z PagedResourcesAssembler z danych Spring?

Używam Spring 4.0.0.Wydanie, Spring Data Commons 1.7.0.M1, Spring Hate 0.8.0.RELEASE

Mój zasób to proste POJO:

public class UserResource extends ResourceSupport { ... }

Mój asembler zasobów konwertuje Obiekty użytkownika do obiektów UserResource:

@Component
public class UserResourceAssembler extends ResourceAssemblerSupport<User, UserResource> { 
    public UserResourceAssembler() {
        super(UserController.class, UserResource.class);
    }

    @Override
    public UserResource toResource(User entity) {
        // map User to UserResource
    }
}

Wewnątrz mojego kontrolera UserController Chcę pobrać Page<User> z mojego serwisu, a następnie przekonwertować go do PagedResources<UserResource> za pomocą PagedResourcesAssembler, Jak pokazano tutaj: https://stackoverflow.com/a/16794740/1321564

@RequestMapping(value="", method=RequestMethod.GET)
PagedResources<UserResource> get(@PageableDefault Pageable p, PagedResourcesAssembler assembler) {
    Page<User> u = service.get(p)
    return assembler.toResource(u);
}

To nie wywołuje UserResourceAssembler i po prostu Zawartość User są zwracane zamiast mojego zwyczaju UserResource.

Zwracanie pojedynczego zasobu działa:

@Autowired
UserResourceAssembler assembler;

@RequestMapping(value="{id}", method=RequestMethod.GET)
UserResource getById(@PathVariable ObjectId id) throws NotFoundException {
    return assembler.toResource(service.getById(id));
}

The PagedResourcesAssembler chce jakiegoś ogólnego argumentu, ale wtedy nie mogę użyć T toResource(T), ponieważ nie chcę konwertować mojego Page<User> na PagedResources<User>, zwłaszcza, że User jest POJO i nie ma zasobów.

Więc pytanie brzmi: Jak to działa?

EDIT:

My WebMvcConfigurationSupport:

@Configuration
@ComponentScan
@EnableHypermediaSupport
public class WebMvcConfig extends WebMvcConfigurationSupport {
    @Override
    protected void addArgumentResolvers(List<HandlerMethodArgumentResolver> argumentResolvers) {
        argumentResolvers.add(pageableResolver());
        argumentResolvers.add(sortResolver());
        argumentResolvers.add(pagedResourcesAssemblerArgumentResolver());
    }

    @Bean
    public HateoasPageableHandlerMethodArgumentResolver pageableResolver() {
        return new HateoasPageableHandlerMethodArgumentResolver(sortResolver());
    }

    @Bean
    public HateoasSortHandlerMethodArgumentResolver sortResolver() {
        return new HateoasSortHandlerMethodArgumentResolver();
    }

    @Bean
    public PagedResourcesAssembler<?> pagedResourcesAssembler() {
        return new PagedResourcesAssembler<Object>(pageableResolver(), null);
    }

    @Bean
    public PagedResourcesAssemblerArgumentResolver pagedResourcesAssemblerArgumentResolver() {
        return new PagedResourcesAssemblerArgumentResolver(pageableResolver(), null);
    }

    /* ... */
}

Rozwiązanie:

@Autowired
UserResourceAssembler assembler;

@RequestMapping(value="", method=RequestMethod.GET)
PagedResources<UserResource> get(@PageableDefault Pageable p, PagedResourcesAssembler pagedAssembler) {
    Page<User> u = service.get(p)
    return pagedAssembler.toResource(u, assembler);
}
Author: Community, 2014-01-25

2 answers

Wydaje się, że już dowiedziałeś się o właściwym sposobie użycia, ale chciałbym przejść do niektórych szczegółów tutaj trochę dla innych, aby znaleźć, jak również. O PagedResourceAssembler w tej odpowiedzi zająłem się podobnymi szczegółami.

Modele reprezentacji

Spring HATEOAS oferuje różnorodne klasy bazowe dla modeli reprezentacji, które ułatwiają tworzenie reprezentacji wyposażonych w linki. Istnieją trzy rodzaje klas dostarczane po wyjęciu z pudełka:

  • Resource - zasób przedmiotów. Skutecznie owijać wokół jakiegoś DTO lub podmiotu, który przechwytuje pojedynczy element i wzbogaca go o linki.
  • Resources - zbiór zasobów, który może być zbiorem czegoś, ale zazwyczaj jest zbiorem Resource instancji.
  • PagedResources - rozszerzenie Resources, które przechwytuje dodatkowe informacje o paginacji, takie jak liczba wszystkich stron itp.

Wszystkie te klasy wywodzą się z ResourceSupport, który jest podstawowym kontenerem dla instancji Link.

Resource assemblers

A ResourceAssembler jest teraz komponentem łagodzącym do konwersji obiektów domeny lub DTOs na takie instancje zasobów. Ważne jest to, że zamienia jeden obiekt źródłowy w jeden obiekt docelowy.

Tak więc PagedResourcesAssembler pobierze instancję Spring Data Page i przekształci ją w instancję PagedResources, oceniając Page i tworząc niezbędne PageMetadata, a także linki prev i next do nawigacji po stronach. Przez domyślna - i to jest prawdopodobnie interesująca część tutaj-będzie używać zwykłej SimplePagedResourceAssembler (wewnętrznej klasy PRA) do przekształcania poszczególnych elementów strony w zagnieżdżone instancje Resource.

Aby umożliwić dostosowanie tego, PRA ma dodatkowe metody toResource(…), które biorą delegata ResourceAssembler do przetwarzania poszczególnych elementów. Więc kończysz z czymś takim:

 class UserResource extends ResourceSupport { … }

 class UserResourceAssembler extends ResourceAssemblerSupport<User, UserResource> { … }

A kod klienta wygląda teraz mniej więcej tak:

 PagedResourcesAssembler<User> parAssembler = … // obtain via DI
 UserResourceAssembler userResourceAssembler = … // obtain via DI

 Page<User> users = userRepository.findAll(new PageRequest(0, 10));

 // Tell PAR to use the user assembler for individual items.
 PagedResources<UserResource> pagedUserResource = parAssembler.toResource(
   users, userResourceAssembler);

Outlook

Od nadchodzące Spring Data Commons 1.7 RC1 (i Spring HATEOAS 0.9 Transitional) linki prev i next będą generowane jako szablony Uri zgodne z RFC6540 , aby ujawnić parametry żądania paginacji skonfigurowane w HandlerMethodArgumentResolvers dla Pageable i Sort.

Konfiguracja, którą pokazałeś powyżej, może zostać uproszczona przez dodanie adnotacji do klasy config za pomocą @EnableSpringDataWebSupport, co pozwoli Ci pozbyć się wszystkich jawnych deklaracji bean.

 60
Author: Oliver Gierke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:46:51

ALTERNATYWNY SPOSÓB

Innym sposobem jest użycie nagłówka HTTP Range (więcej w RFC 7233). Możesz zdefiniować nagłówek HTTP w ten sposób:

Range: resources=20-41

Oznacza to, że chcesz uzyskać zasoby od 20 do 41 (włącznie). W ten sposób użytkownicy API otrzymują dokładnie zdefiniowane zasoby.

To po prostu alternatywny sposób. Range jest często używany z innymi jednostkami (jak bajty itp.)

SPOSÓB ZALECANY

Jeśli chcesz pracować z paginacją i mieć naprawdę odpowiednie API (Hypermedia / HATEOAS włączone) następnie polecam dodać stronę i PageSize do adresu URL. Przykład:

http://host.loc/articles?Page=1&PageSize=20

Następnie możesz odczytać te dane w swoim Baseapicontrolle i utworzyć jakiś obiekt QueryFilter we wszystkich żądaniach:

{
    var requestHelper = new RequestHelper(Request);

    int page = requestHelper.GetValueFromQueryString<int>("page");
    int pageSize = requestHelper.GetValueFromQueryString<int>("pagesize");

    var filter = new QueryFilter
    {
        Page = page != 0 ? page : DefaultPageNumber,
        PageSize = pageSize != 0 ? pageSize : DefaultPageSize
    };

    return filter;
}

Twój api powinien zwrócić jakąś specjalną kolekcję z informacją o liczbie elementów.

public class ApiCollection<T>
{
    public ApiCollection()
    {
        Data = new List<T>();
    }

    public ApiCollection(int? totalItems, int? totalPages)
    {
        Data = new List<T>();
        TotalItems = totalItems;
        TotalPages = totalPages;
    }

    public IEnumerable<T> Data { get; set; }

    public int? TotalItems { get; set; }
    public int? TotalPages { get; set; }
}

Twoje klasy modelowe mogą dziedziczyć jakąś klasę z obsługą paginacji:

public abstract class ApiEntity
{
    public List<ApiLink> Links { get; set; }
}

public class ApiLink
{
    public ApiLink(string rel, string href)
    {
        Rel = rel;
        Href = href;
    }

    public string Href { get; set; }

    public string Rel { get; set; }
}
 -6
Author: Miroslav Holec,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-10-21 08:46:04