web +a

스프링부트 ~23 | 댓글 CRUD를 위한 Controller & Service (+ 댓글 Rest API 완성하기)

냥냥체뤼 2022. 7. 27. 12:16

Intro

 

지난 시간 : Comment Entity를 추가했다! 그리고 Repository까지 준비해뒀다.

이번 시간 : Comment CRUD를 위한 Controller와 Service를 추가해보자.

이로써 REST api가 완성된다 >v <

 

Article 기능 만들 때 다 했던 거다. 똑같은 과정을 거칠 것이다.

지금까지 배운 것을 상기하면서 다시 보자. 


컨트롤러와 서비스 - DI

 

컨트롤러 DI

∨ REST API로 설계하고 있음을 기억하면서, 컨트롤러에 @RestController를 사용해주자. 

 컨트롤러는 CRUD 요청을 받고, 실제 CRUD 처리는 서비스에게 위임한다는 것을 상기하자. 

@RestController
public class CommentApiController {
    @Autowired
    private CommentService commentService;
}

 

 

서비스 DI

서비스는 리포지토리에 접근해서 실제 데이터 처리를 담당한다. 

댓글서비스에서는 댓글리포뿐만 아니라 게시글리포도 사용할 일이 있기 때문에 두개 다 땡겨와준다.

@Service
public class CommentService {
    @Autowired
    private CommentRepository commentRepository;
    @Autowired
    private ArticleRepository articleRepository;
}

 

 


컨트롤러 - CRUD 구현

 

 댓글 목록 조회 / 댓글 생성 / 댓글 수정 / 댓글 삭제 기능을 추가해보자.

순서대로 @GetMapping / @PostMapping / @PatchMapping / @DeleteMapping으로 요청을 받아냈던 것을 기억하자.

∨ 반환타입도 배운대로 신경써서 정해주자.

∨ 잘못된 요청에 대한 처리 : 이전에는 컨트롤러에서 경우를 다뤄줬는데, 이번에는 서비스에서 예외를 발생시키도록 했다.

@GetMapping("/api/articles/{articleId}/comments")
public ResponseEntity<List<CommentDto>> comments(@PathVariable Long articleId) {

    return null;
}
@PostMapping("/api/articles/{articleId}/comments")
public ResponseEntity<CommentDto> create(@PathVariable Long articleId,
                                         @RequestBody CommentDto dto) {

    return null;
}
@PatchMapping("/api/comments/{id}")
public ResponseEntity<CommentDto> update(@PathVariable Long id,
                                         @RequestBody CommentDto dto) {
                                         
    return null;
}
@DeleteMapping("/api/comments/{id}")
public ResponseEntity<CommentDto> delete(@PathVariable Long id) {
      
    return null;
}

 

 

recall) @RequestBody

더보기

 rest API에서 json으로 데이터를 던질 때 @RequestBody 어노테이션을 표시해주어야 받을 수 있다. (반면 폼데이터 dto는 저 어노테이션 없이 받아올 수 있었다)

※ @RequestBody : JSON 데이터 받기

ㄴ 요청의 body에서 데이터를 받아오란 의미

 


컨트롤러 역할 작성하기

 

대충 잡은 CRUD의 틀을 컨트롤러에서 완성해보자.

일단 컨트롤러에서 쓱쓱 작성하고 서비스에서 원하던 작업을 처리하도록 구현하도록 하자.

+ 부가적으로 필요한 형식이나 기능은 그때그때 만든다.

 

 

댓글 목록 조회

    @GetMapping("/api/articles/{articleId}/comments")
    public ResponseEntity<List<CommentDto>> comments(@PathVariable Long articleId) {
        // 서비스에게 위임
        List<CommentDto> dtos = commentService.comments(articleId);
        
        // 결과 응답
        return ResponseEntity.status(HttpStatus.OK).body(dtos);
    }

조회 결과를 Comment가 아니라 CommentDto의 List로 받아오도록 정했다.

그래서 이때 CommentDto 형식을 새로 추가해주었다.

더보기
@AllArgsConstructor
@NoArgsConstructor
@Getter
@ToString
public class CommentDto {
    private Long id;
    @JsonProperty("article_id")
    private Long articleId;
    private String nickname;
    private String body;
}

 

참고) DB의 컬럼명은 article_id이고 CommentDto의 필드명은 articleId이다.

이럴 경우 Dto를 받아올 때 매치가 되지 않는 문제가 있다. 

이런 경우 위와 같이 @JsonProperty("article_id")를 통해 매핑가능하다.

 

 

 

 

댓글 생성

∨ 생성요청시 들어오는 Json 데이터는 CommentDto 타입으로 들어온다.

@PostMapping("/api/articles/{articleId}/comments")
public ResponseEntity<CommentDto> create(@PathVariable Long articleId,
                                         @RequestBody CommentDto dto) {
    // 서비스에게 위임
    CommentDto createdDto = commentService.create(articleId, dto);
    // 결과 응답
    return ResponseEntity.status(HttpStatus.OK).body(createdDto);
}

 

 

댓글 수정

∨ 수정요청시 들어오는 Json 데이터는 CommentDto 타입으로 들어온다.

@PatchMapping("/api/comments/{id}")
public ResponseEntity<CommentDto> update(@PathVariable Long id,
                                         @RequestBody CommentDto dto) {
    // 서비스에게 위임
    CommentDto updatedDto = commentService.update(id, dto);
    // 결과 응답
    return ResponseEntity.status(HttpStatus.OK).body(updatedDto);
}

 

 

댓글 삭제

 @DeleteMapping("/api/comments/{id}")
    public ResponseEntity<CommentDto> delete(@PathVariable Long id) {
        // 서비스에게 위임
        CommentDto deletedDto = commentService.delete(id);
        // 결과 응답
        return ResponseEntity.status(HttpStatus.OK).body(deletedDto);
}

 

 

 

이렇게 컨트롤러의 역할 명시는 다 끝났다. 

이제 컨트롤러가 서비스에게 넘긴 저러한 기능들을 서비스에서 마저 구현해주면 된다.

 


서비스층 기능 구현하기

 

댓글 목록 조회

public List<CommentDto> comments(Long articleId) {
    // 반환 : stream 문법을 사용하여 깔끔하게 반환 가능하다. for문을 이용해도 된다.
    return commentRepository.findByArticleId(articleId)
            .stream()
            .map(comment -> CommentDto.createCommentDto(comment))
            .collect(Collectors.toList());
}

참고) 위 stream 문법은 다음 for문과 동일한 기능을 한다. 참고해서 이해하자.

더보기
        // 조회: 댓글 목록
        List<Comment> comments = commentRepository.findByArticleId(articleId);
        // 변환: 엔티티 -> DTO
        List<CommentDto> dtos = new ArrayList<CommentDto>();
        for (int i = 0; i < comments.size(); i++) {
            Comment c = comments.get(i);
            CommentDto dto = CommentDto.createCommentDto(c);
            dtos.add(dto);
        }
        // 반환
        return dtos;

 

참고) Entity를 Dto로 변환하기 위해 Dto에다가 static인 클래스메서드를 뒀다. 

더보기

 

public static CommentDto createCommentDto(Comment comment) {
    return new CommentDto(
            comment.getId(),
            comment.getArticle().getId(),
            comment.getNickname(),
            comment.getBody()
    );
}

 

 

댓글 생성

댓글생성은 DB 변경을 수반하는 작업이기 때문에 @Transactional 처리를 잊지 말자. 

    @Transactional
    public CommentDto create(Long articleId, CommentDto dto) {
        // 게시글 조회 및 예외 발생
        Article article = articleRepository.findById(articleId)
                .orElseThrow(() -> new IllegalArgumentException("댓글 생성 실패: 대상 게시글이 없습니다"));
        // 댓글 엔티티 생성
        Comment comment = Comment.createComment(dto, article);
        // 댓글 엔티티를 DB로 저장
        Comment created = commentRepository.save(comment);
        // DTO로 변경하여 반환
        return CommentDto.createCommentDto(created);
    }

주의) 이렇게 Comment Entity를 생성할 때 유효한 경우에 대해서만 생성을 해야 한다. 

∴ 적절하게 예외 발생을 시켜주자. 

더보기
    public static Comment createComment(CommentDto dto, Article article) {
        // 예외 발생
        if (dto.getId() != null)
            throw new IllegalArgumentException("댓글 생성 실패! 댓글의 id가 없어야 합니다.");
        if (dto.getArticleId() != article.getId())
            throw new IllegalArgumentException("댓글 생성 실패! 게시글의 id가 잘못되었습니다.");
        // 엔티티 생성 및 반환
        return new Comment(
                dto.getId(),
                article,
                dto.getNickname(),
                dto.getBody()
        );
    }

 

 

댓글 수정

∨ 역시 DB 변경을 요하는 수정작업이므로 트랜잭션 처리를 잊지 말자. 

@Transactional
public CommentDto update(Long id, CommentDto dto) {
    // 댓글 조회 및 예외 발생
    Comment target = commentRepository.findById(id)
            .orElseThrow(() -> new IllegalArgumentException("댓글 수정 실패! 대상 댓글이 없습니다."));
    // 댓글 수정
    target.patch(dto);
    // DB로 갱신
    Comment updated = commentRepository.save(target);
    // 댓글 엔티티를 DTO로 변환 및 반환
    return CommentDto.createCommentDto(updated);
}

참고) Entity를 수정하는 인스턴스 메서드 patch()는 아래와 같다.

더보기

파라미터로 들어온 dto의 내용이 유효하다면, dto의 내용대로 적절히 patch시켜주면 된다.

public void patch(CommentDto dto) {
        // 예외 발생
        if (this.id != dto.getId())
            throw new IllegalArgumentException("댓글 수정 실패! 잘못된 id가 입력되었습니다.");
        // 객체를 갱신
        if (dto.getNickname() != null)
            this.nickname = dto.getNickname();
        if (dto.getBody() != null)
            this.body = dto.getBody();
}

 

 

댓글 삭제

∨ 역시 삭제는 DB를 건드리는 작업이니까 트랜잭션 처리를 잊지 말자.

@Transactional
    public CommentDto delete(Long id) {
        // 댓글 조회(및 예외 발생)
        Comment target = commentRepository.findById(id)
                .orElseThrow(() -> new IllegalArgumentException("댓글 삭제 실패! 대상이 없습니다."));
        // 댓글 삭제
        commentRepository.delete(target);
        // 삭제 댓글을 DTO로 반환
        return CommentDto.createCommentDto(target);
}

 

 


와우~~!! 

rest api 설계방식으로 crud 구현하기.. 이런거구나 한번 더 복습완뇨..  

빨리 스스로도 해봐야게따 느낌굿

 

반응형