Spring boot Restful API:使用ModelMapper将具有关系的DTO转换为实体



我现在对如何在带有Spring的Rest API中进行CRUD感到困惑。

让我解释一下,我有两条途径来POST和PUT实体。我为此创建了两个DTOcreatePostRequestupdatePostRequest。因为添加时,属性不能为null,而更新时可以为null(忽略为null的属性(。

问题1:

在我的前端,用户被要求从数据库中选择一个标签列表(多选html(。这就是为什么createPostRequest具有类型为TagDTOtags属性。但是,我如何使用modelMapper将createPostRequest映射到Post实体,以确保标签存在于数据库中?

例如,如果一个用户试图插入一个不存在的标签,我想做这样的事情:

postEntity.setTags(tagService.findAllByIds(postEntity.getTagsId()));

这使得代码中有很多重复,因为在我的实体在服务中的创建和更新方法之间,有很多相同的代码。

问题2:

基于我的问题1,如何在不重复代码2x的情况下轻松地将我的两个DTO映射到同一实体?

代码示例-PostService(请参阅注释(

这是更新的一个示例,但create将有几乎相同的代码,那么我该如何继续?

@Transactional
public Post update(Integer postId, UpdatePostRequest request) {
return Optional.ofNullable(this.getById(postId)).map(post -> {
// here how to map non-null properties of my request 
// into my post taking in consideration my comment above?   
postDAO.save(post);
return post;
}).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
}

========================================

更新:

根据要求,找到以下代码。

控制器:

@RestController
@RequestMapping("/v1/posts")
public class PostController {
RequestMapping(method = RequestMethod.POST, consumes = "application/json", produces = "application/json; charset=UTF-8")
public ResponseEntity<Object> update(@Valid @RequestBody CreatePostRequest createPostRequest) {
Post post = postService.create(createPostRequest);
return new ApiResponseHandler(new PostDTO(post), HttpStatus.OK).response();
}
RequestMapping(value = "/{postId}", method = RequestMethod.PUT, consumes = "application/json", produces = "application/json; charset=UTF-8")
public ResponseEntity<Object> update(@Valid @RequestBody UpdatePostRequest updatePostRequest, @PathVariable Integer postId) {
Post post = postService.update(postId, updatePostRequest);
return new ApiResponseHandler(new PostDTO(post), HttpStatus.OK).response();
}
}

CreatePostRequest:

@Data
public class CreatePostRequest {
@NotNull
@Size(min = 10, max = 30)
private Sting title;
@NotNull
@Size(min = 50, max = 600)
private String description
@NotNull
@ValidDateString
private String expirationDate;
@NotNull
private List<TagDTO> tags;
public List<Integer> getTagIds() {
return this.getTags().stream().map(TagDTO::getId).collect(Collectors.toList());
}
}

UpdatePostRequest:

@Data
public class UpdatePostRequest {
@Size(min = 10, max = 30)
private Sting title;
@Size(min = 50, max = 600)
private String description
@ValidDateString
private String expirationDate;
private List<TagDTO> tags;
public List<Integer> getTagIds() {
return this.getTags().stream().map(TagDTO::getId).collect(Collectors.toList());
}
}

服务:

@Service
@Transactional
public class PostService {
@Transactional
public Post create(CreatePostRequest request) {
ModelMapper modelMapper = new ModelMapper();
Post post = modelMapper.map(request, Post.class);
// map will not work for tags : how to check that tags exists in database ?
return postDAO.save(post);
}
@Transactional
public Post update(Integer postId, UpdatePostRequest request) {
return Optional.ofNullable(this.getById(postId)).map(post -> {
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration().setSkipNullEnabled(true);
modelMapper.map(request, post);

// map will not work for tags : how to check that tags exists in database ?

postDAO.save(post);
return post;
}).orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND));
}
}

为了避免重复两个类似的DTO,可以使用@Validated组验证。这允许您主动设置要对每个属性进行哪些验证。您可以在以下在线资源中了解更多信息https://www.baeldung.com/spring-valid-vs-validated.您将从创建两个市场接口开始:

interface OnCreate {}
interface OnUpdate {}

然后,您可以将这些标记接口与通用DTO:中的任何约束注释一起使用

@Data
public class CreateOrUpdatePostRequest {
@NotNull(groups = OnCreate.class)
@Size(min = 10, max = 30, groups = {OnCreate.class, OnUpdate.class})
private Sting title;
@NotNull(groups = OnCreate.class)
@Size(min = 50, max = 600, groups = {OnCreate.class, OnUpdate.class})
private String description
@NotNull(groups = OnCreate.class)
@ValidDateString(groups = {OnCreate.class, OnUpdate.class})
private String expirationDate;
@NotNull(groups = OnCreate.class)
private List<TagDTO> tags;
public List<Integer> getTagIds() {
return this.getTags().stream().map(TagDTO::getId).collect(Collectors.toList());
}
}

最后,您只需要在Controller中相应地注释您的方法:

@RestController
@RequestMapping("/v1/posts")
@Validated
public class PostController {
@RequestMapping(method = RequestMethod.POST, consumes = "application/json", produces = "application/json; charset=UTF-8")
public ResponseEntity<Object> update(@Validated(OnCreate.class) @RequestBody CreateOrUpdatePostRequest createPostRequest) {
Post post = postService.create(createPostRequest);
return new ApiResponseHandler(new PostDTO(post), HttpStatus.OK).response();
}
@RequestMapping(value = "/{postId}", method = RequestMethod.PUT, consumes = "application/json", produces = "application/json; charset=UTF-8")
public ResponseEntity<Object> update(@Validated(OnUpdate.class) @RequestBody CreateOrUpdatePostRequest updatePostRequest, @PathVariable Integer postId) {
Post post = postService.update(postId, updatePostRequest);
return new ApiResponseHandler(new PostDTO(post), HttpStatus.OK).response();
}
}

这样,您就可以拥有一个映射函数。

尽管如此,请记住,鉴于我们混合了不同的关注点,使用验证组很容易成为一种反模式。对于验证组,被验证的实体必须知道它所使用的所有用例的验证规则。话虽如此,我通常避免使用验证组,除非真的有必要。


关于tags,我想您唯一的选择就是查询数据库。那些不存在的,你应该创建它们(我想(,所以沿着以下路线:

List<Integer> tagsId = createOrUpdatePostRequest.getTagsId();
List<Tag> tags = tagService.findAllByIds(tagsId);
List<Integer> nonExistentTagsId = tagsId.stream().filter(id -> tags.stream().noneMatch(tag -> tag.getId().equals(id)));
if (!nonExistentTagsId.isEmpty()) {
// create Tags and add them to tags List
}

最新更新