春季是否可以在没有数据休息的情况下简单实现HATEOAS +分页?

问题描述

我一直在寻找有关如何实现包含HATEOAS链接+使用Spring Boot和JPA分页的Spring REST API(无Spring数据静态)的几天,

{
    "_links": {
        "first": {
            "href": "http://localhost:8080/api/albums-list?page=0&size=2&sort=title,desc"
        },"prev": {
            "href": "http://localhost:8080/api/albums-list?page=0&size=2&sort=title,"self": {
            "href": "http://localhost:8080/api/albums-list?page=1&size=2&sort=title,"next": {
            "href": "http://localhost:8080/api/albums-list?page=2&size=2&sort=title,"last": {
            "href": "http://localhost:8080/api/albums-list?page=4&size=2&sort=title,desc"
        }
    },"page": {
        "size": 2,"totalElements": 10,"totalPages": 5,"number": 1
    },"_embedded": {
        "albums": [
            {
                "id": 7,"title": "Top Hits Vol 7","description": "Top hits vol 7. description","releaseDate": "10-03-1987","actors": [
                    {
                        "id": 4,"firstName": "Janice","lastName": "Preston","_links": {
                            "self": {
                                "href": "http://localhost:8080/api/actors/4"
                            }
                        }
                    }
                ],"_links": {
                    "self": {
                        "href": "http://localhost:8080/api/actors/7"
                    }
                }
            },{
                "id": 6,"title": "Top Hits Vol 6","description": "Top hits vol 6. description","releaseDate": "10-03-1986","actors": [
                    {
                        "id": 3,"firstName": "Laverne","lastName": "Mann","_links": {
                            "self": {
                                "href": "http://localhost:8080/api/actors/3"
                            }
                        }
                    }
                ],"_links": {
                    "self": {
                        "href": "http://localhost:8080/api/actors/6"
                    }
                }
            }
        ]
    }
}

但是,到目前为止,我发现的解决方案非常复杂,或者样板数量可笑 例如此解决方案: https://howtodoinjava.com/spring5/hateoas/pagination-links/页面上的教程并未完整介绍它,但需要您创建实体,该实体的模型以及一个很长的样板样的长RepresentationModelAssemblerSupport

我也尝试过这样做: https://spring.io/guides/tutorials/rest/ 但是,嵌套类(我的关系是一对多/很多一对一的关系)无法获得HATEOAS的链接

{
    "id": 3,"nome": "Amazonas","uf": "AM","cidades": [
        {
        //no HATEOAS in here
            "id": 10003,"nome": null,"instituicoes": [],"uf": "AM"
        },{
            "id": 219,"nome": "Alvarães","uf": "AM"
        }
    ],"_links": {
        "self": {
            "href": "http://localhost:8080/api/v1/estados/estadoes/3"
        },"estadoes": {
            "href": "http://localhost:8080/api/v1/estados/estadoes"
        }
    }
}

我的意思是,没有更简单的解决方案吗?幸运的是,对于分页,PagingAndSortingRepository很有用,但是同时拥有分页和HATEOAS,真是一场噩梦。

解决方法

需要RepresentationModelAssembler。如果您不需要其他字段,则不需要模型类extends EntityModel<T>

我的示例实体类为Inventory

RepresentationModelAssembler。您可以在此处添加更多链接。

@Component
public class InventoryModelAssembler implements RepresentationModelAssembler<Inventory,EntityModel<Inventory>> {

    @Override
    public EntityModel<Inventory> toModel(Inventory inventory) {

        return EntityModel.of(inventory,linkTo(methodOn(InventoryController.class).get(inventory.getId()))
                        .withSelfRel());
    }
}

控制器

@RestController
@RequestMapping("/inventories")
@RequiredArgsConstructor
public class InventoryController {

    private final InventoryRepository inventoryRepository;

    private final PagedResourcesAssembler pagedResourcesAssembler;

    private final InventoryModelAssembler inventoryModelAssembler;

    @GetMapping
    public ResponseEntity get(Pageable pageable) {

        Page<Inventory> inventories = inventoryRepository.findAll(pageable);

        return ResponseEntity
                .ok()
                .contentType(MediaTypes.HAL_JSON)
                .body(pagedResourcesAssembler.toModel(inventories,inventoryModelAssembler));
    }
}

HAL JSON响应。在我的HTML doc中可以找到完整的答复。下载并使用浏览器打开。

{
  "_embedded" : {
    "inventories" : [ {
      "carrier" : "SG","fltNum" : "001","serviceType" : "PAX","fltDate" : "2020-01-20","fltDow" : 1,"createdDate" : "2020-06-11T13:21:44.992Z","lastModifiedDate" : "2020-06-11T13:21:44.992Z","_links" : {
        "self" : {
          "href" : "http://localhost:8080/inventories/5ee22fe8853b0f45ae5fca27"
        }
      }
    }]
  },"_links" : {
    "first" : {
      "href" : "http://localhost:8080/inventories?carrier=SG&fltNum=001&page=0&size=10&sort=fltNum,asc&sort=fltDate,desc"
    },"self" : {
      "href" : "http://localhost:8080/inventories?carrier=SG&fltNum=001&page=0&size=10&sort=fltNum,"next" : {
      "href" : "http://localhost:8080/inventories?carrier=SG&fltNum=001&page=1&size=10&sort=fltNum,"last" : {
      "href" : "http://localhost:8080/inventories?carrier=SG&fltNum=001&page=1&size=10&sort=fltNum,desc"
    }
  },"page" : {
    "size" : 10,"totalElements" : 20,"totalPages" : 2,"number" : 0
  }
}

完整代码在Github中共享。如果我的项目除此答案以外还有帮助,请考虑在Github中给它加星号。