Spring HATEOAS и Entity DTO - проблема с ModelAssembler - PullRequest
0 голосов
/ 19 июня 2020

Я разрабатываю Spring HATEOAS REST api. Теперь я столкнулся с необходимостью скрыть некоторые поля класса Employee (я хочу скрыть некоторые из них). Насколько я понимаю, необходимо использовать DTO (объекты передачи данных). Итак, я создал новый класс (EmployeeDTO) только с этими полями, которые мне нужны. Я использую ModelMapper для сопоставления соответствующих полей.

Итак, теперь у меня проблема с типами данных. Должен ли я изменить все возвращаемые значения в моем контроллере с 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 (автоматически подключается в контроллере):

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

EDIT

Таким образом я получаю доступ к этому ответу 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 Ответ

1 голос
/ 19 июня 2020

Если вы хотите отделить ресурс, предоставляемый вашим 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"
                    }
                }
            }
        ]
    }
}
...