【问题标题】:Spring boot Restful API: DTO with relationships convert to entity using ModelMapper?Spring Boot Restful API:具有关系的 DTO 使用 ModelMapper 转换为实体?
【发布时间】:2021-11-16 19:54:58
【问题描述】:

我现在对如何使用 Spring 在 Rest API 中执行 CRUD 感到困惑。

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

问题一:

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

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

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

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

问题 2:

根据我的问题 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());
    }
}

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

【问题讨论】:

  • 您需要添加更多代码以便我们为您提供帮助。从UpdatePostRequestCreatePostRequest 开始,公开两个端点的控制器以及updatecreate 方法的代码。谢谢!
  • @JoãoDias 我按要求添加了代码,但请注意服务方法尚未完成,因为我只是不知道如何在验证标签关系时正确注释我作为实体的请求确保它存在。感谢您的帮助。

标签: spring spring-boot hibernate modelmapper


【解决方案1】:

为避免两个相似的 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
}

【讨论】:

  • 我试试这个!非常感谢您的回答。对于这两个市场接口,您将它们存储在您的架构中的什么位置?在特定文件夹中?
  • 你试过了吗?它奏效了吗?我会将它们放在dto 子包中的特定文件夹中。类似dto.validation 或类似的东西。
  • 是的,但它不起作用。我已经通过@Validated(OnCreate.class) 注释更改了@RequestBody 之前的@Valid 注释,现在可以了。如果我应用@ValidDateString 之类的规则,如果我希望验证有效,我必须添加这两个组。该规则默认不适用于所有组,我不知道为什么。
  • 好吧,现在我很困惑。它是否有效?关于@Validated(OnCreate.class),那是我的错误。我复制了您的代码并将其放在错误的位置。我已经改变了答案。关于@ValidDateString,这是自定义注解吧?
  • 是的,它现在可以工作了,是的,它是一个自定义验证。但例如 @Size 也是同样的问题。 @Size 不适用于创建或更新。我必须添加@Size(groups = {onCreate.class, onUpdate.class})
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多