Spring HATEOAS 和实体 DTO - ModelAssembler 的问题

Spring HATEOAS and Entity DTO - problem with ModelAssembler

我正在开发 Spring HATEOAS REST api。 现在遇到需要隐藏Employee class的部分字段(我想隐藏部分)。 据我了解,必须使用 DTO(数据传输对象)。所以我只用我需要的这个字段创建了新的 class (EmployeeDTO)。我正在使用 ModelMapper 来映射适当的字段。

所以现在,我遇到了数据类型方面的问题。我是否必须将控制器中的所有 return 值从 Employee 更改为 EmployeeDTO?然后更改 ModelAssembler 中的所有内容?

或者 DTO class 应该 extends RepresentationModel? (这也强制在实体中扩展它)。 期待您的帮助,所有这些看起来有点混乱(更改所有 return 类型),我觉得应该有更聪明的解决方案。

我现在就是这样做的:

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());
}

如果您想将 API 公开的资源与您的 JPA 实体分离(这是个好主意),您将必须更新您的控制器以处理适当的类型。

参见下面的示例:

首先,假设您有以下 DTO:

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

      // getters and setters
}

请注意我没有将您的 JPA 实体(员工)与 DTO 混合。

现在您的控制器应该更新为 return 合适的类型。

@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);
    }
}

我测试了它并得到了以下响应,假设我有一个员工端点

得到employees/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"
                    }
                }
            }
        ]
    }
}