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

Spring boot Restful API: DTO with relationships convert to entity using ModelMapper?

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

让我解释一下,我有两条通往 POST 和 PUT 实体的路线。我为此创建了两个 DTO createPostRequestupdatePostRequest。因为在添加时,属性不能为空,而在更新时可以(忽略空属性)。

问题一:

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

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

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

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

问题二:

基于我的问题 1,我怎样才能轻松地将我的两个 DTO 映射到同一个实体而不重复代码 2x?

代码示例- 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();
    }
}

创建发布请求:

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

更新发布请求:

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

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

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