Spring HATEOAS和实体DTO-ModelAssembler的问题

时间:2020-06-19 16:34:17

标签: spring spring-boot dto spring-rest spring-hateoas

我正在开发Spring HATEOAS REST API。 现在,我需要隐藏Employee类的某些字段(我想隐藏其中的一些字段)。 据我了解,必须使用DTO(数据传输对象)。因此,我仅使用此字段创建了新类(EmployeeDTO)。我正在使用ModelMapper来映射适当的字段。

所以现在,我对数据类型有疑问。我是否必须将Controller中的所有返回值从Employee更改为EmployeeDTO?然后更改ModelAssembler中的所有内容?

也许DTO类应该extends RepresentationModel? (这也迫使它在Entity中扩展)。 期待您的帮助,所有这些看起来都有些混乱(更改所有返回类型),我觉得应该有更明智的解决方案。

这就是我现在正在做的事情:

EmployeeController中的方法:

@GetMapping(value = "/{id}", produces = "application/hal+json")
    public EntityModel<Employee> getEmployeeById(@PathVariable Long id) {
        Optional<Employee> employee = employeeService.findById(id);
        return employeeModelAssembler.toModel(employee.get());
}

@GetMapping(value = "", produces = "application/hal+json")
    public CollectionModel<EntityModel<Employee>> getAllEmployees() {
        List<Employee> employeesList = EmployeeService.findall();
        return employeeModelAssembler.toCollectionModel(EmployeesList);
}

EmployeeModelAssembler(在Controller中自动连线):

public class EmployeeModelAssembler implements RepresentationModelAssembler<Employee, EntityModel<Employee>> {

    @Override
    public EntityModel<Employee> toModel(Employee employee) {
        EntityModel<Employee> employeeEntityModel = EntityModel.of(employee);

        Link selfLink = linkTo(methodOn(EmployeeController.class).getEmployeeById(employee.getId())).withSelfRel();
        employeeEntityModel.add(selfLink);

        return employeeEntityModel;
    }

    @Override
    public CollectionModel<EntityModel<Employee>> toCollectionModel(Iterable<? extends Employee> entities) {

        List<Employee> employeesList = (List<Employee>) entities;
        List<EntityModel<Employee>> employeeEML = employeesList.stream().map(this::toModel).collect(Collectors.toList());

        Link selfLink = linkTo(methodOn(EmployeeController.class).getAllEmployees()).withSelfRel();

        return CollectionModel.of(employeeEML, selfLink);
    }
}

编辑

这是我在另一个控制器中访问此hal + json响应的方式。目的是获取EmployeeDTO对象的列表:

String uri = "http://localhost:8080/api/employees";

Traverson traverson = new Traverson(URI.create(uri), MediaTypes.HAL_JSON);
Traverson.TraversalBuilder tb = traverson.follow("href");

ParameterizedTypeReference<CollectionModel<EmployeeDTO>> typeReference = new ParameterizedTypeReference<CollectionModel<EmployeeDTO>>() {};
CollectionModel<EmployeeDTO> resEmployees = tb.toObject(typeReference);
Collection<EmployeeDTO> employees = resEmployees.getContent();

ArrayList<EmployeesDTO> employeesList = new ArrayList<>(employees);

for(EmployeesDTO x : employeesList) {
    System.out.println(x);
    System.out.println(x.getLinks());
}

1 个答案:

答案 0 :(得分:1)

如果要将API公开的资源与JPA实体脱钩,这是一个好主意,则必须更新控制器以处理适当的类型。

请参见以下示例:

首先,让我们假设您具有以下DTO:

public class EmployeeDTO extends RepresentationModel<EmployeeDTO> {
      private long id;
      private String surname;
      private String departmentId;

      // getters and setters
}

请注意,我如何不将您的JPA实体(员工)与DTO混在一起。

现在应该更新您的控制器以返回适当的类型。

@GetMapping(value = "/{id}", produces = "application/hal+json")
public EmployeeDTO getEmployeeById(@PathVariable long id) {
       Optional<Employee> employee = employeeService.findById(id);
       return employeeModelAssembler.toModel(employee.get());
}

@GetMapping(value = "", produces = "application/hal+json")
public CollectionModel<EmployeeDTO> getAllEmployees() {
    List<Employee> employeesList = employeeService.findAll();
    return employeeModelAssembler.toCollectionModel(employeesList);
}

还有一个ModelAssembler的示例

@Component
public class EmployeeModelAssembler implements RepresentationModelAssembler<Employee, EmployeeDTO> {

    @Override
    public EmployeeDTO toModel(Employee employee) {
        ModelMapper modelMapper = new ModelMapper();
        EmployeeDTO employeeDto = modelMapper.map(employee, EmployeeDTO.class);

        Link selfLink = linkTo(methodOn(EmployeeController.class).getEmployeeById(employee.getId())).withSelfRel();
        employeeDto.add(selfLink);

        return employeeDto;
    }

    @Override
    public CollectionModel<EmployeeDTO> toCollectionModel(Iterable<? extends Employee> employeesList) {
       ModelMapper modelMapper = new ModelMapper();
       List<EmployeeDTO> employeeDTOS = new ArrayList<>();

       for (Employee employee : employeesList){
           EmployeeDTO employeeDto = modelMapper.map(employee, EmployeeDTO.class);
           employeeDto.add(linkTo(methodOn(EmployeeController.class)
                                .getEmployeeById(employeeDto.getId())).withSelfRel());
           employeeDTOS.add(employeeDto);
        }

        return new CollectionModel<>(employeeDTOS);
    }
}

我测试了它,并假设我有一个雇员端点,得到了以下答复

获取员工/ 1

{
"id": 1,
"surname": "teste",
"departmentId": "1",
"_links": {
    "self": {
        "href": "http://localhost:8080/employees/1"
    }
}
}

找雇员

{
    "_embedded": {
        "employeeDtoList": [
            {
                "id": 1,
                "surname": "teste",
                "departmentId": "1",
                "_links": {
                    "self": {
                        "href": "http://localhost:8080/employees/1"
                    }
                }
            },
            {
                "id": 2,
                "surname": "teste",
                "departmentId": "2",
                "_links": {
                    "self": {
                        "href": "http://localhost:8080/employees/2"
                    }
                }
            }
        ]
    }
}