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"
}
}
}
]
}
}
我正在开发 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"
}
}
}
]
}
}