스프링 부트, 입문!

스프링 부트, 입문!

쉽고 빠르게 배우는, 스프링 부트 첫걸음!

23 댓글 서비스와 컨트롤러(feat. REST API)

# 댓글 서비스와 컨트롤러(feat. REST API) ## 미션 댓글 REST API를 완성하기 위해, 컨트롤러와 서비스를 구현하시오. ![홍팍-스프링-부트-입문--미션](http://drive.google.com/thumbnail?export=view&sz=w960&id=1eDmqpDVNgVEqymr4IVCA8Yij6Ju-9Oj5) ## 01:07 REST 컨트롤러 생성 - @RestController #### ../api/CommentApiController ``` package com.example.firstproject.api; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RestController; @RestController public class CommentApiController { @Autowired private CommentService commentService; // 댓글 목록 조회 // 댓글 생성 // 댓글 수정 // 댓글 삭제 } ``` ## 01:54 Comment 서비스 생성 - @Service #### ../service/CommentService ``` package com.example.firstproject.service; import com.example.firstproject.repository.ArticleRepository; import com.example.firstproject.repository.CommentRepository; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @Service public class CommentService { @Autowired private CommentRepository commentRepository; @Autowired private ArticleRepository articleRepository; } ``` ## 03:40 댓글 목록 조회 #### ../api/CommentApiController ``` ... @RestController public class CommentApiController { @Autowired private CommentService commentService; // 댓글 목록 조회 @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); } ... } ``` #### ../dto/CommentDto ``` package com.example.firstproject.dto; import lombok.*; @AllArgsConstructor @NoArgsConstructor @Getter @ToString public class CommentDto { private Long id; private Long articleId; private String nickname; private String body; } ``` #### ../service/CommentService ``` ... @Service public class CommentService { @Autowired private CommentRepository commentRepository; @Autowired private ArticleRepository articleRepository; public List<CommentDto> comments(Long articleId) { // 조회: 댓글 목록 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; } } ``` #### ../dto/CommentDto ``` ... public class CommentDto { ... public static CommentDto createCommentDto(Comment comment) { return new CommentDto( comment.getId(), comment.getArticle().getId(), comment.getNickname(), comment.getBody() ); } } ``` ## 13:44 반복을 스트림으로 - stream(), map(), collent(), Collectors.toList() #### ../service/CommentService ``` ... public class CommentService { ... public List<CommentDto> comments(Long articleId) { // 반환 return commentRepository.findByArticleId(articleId) .stream() .map(comment -> CommentDto.createCommentDto(comment)) .collect(Collectors.toList()); } } ``` ## 15:22 댓글 생성하기 - HTTP POST, @PostMapping, IllegalArgumentException, #### ../api/CommentApiController ``` ... @RestController public class CommentApiController { ... // 댓글 생성 @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); } ... } ``` #### ../service/CommentService ``` ... public class CommentService { ... @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); } } ``` #### ../entity/Comment ``` ... public class Comment { ... 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() ); } } ``` ## 25:00 디버깅 - @JsonProperty #### ../dto/CommentDto ``` package com.example.firstproject.dto; ... public class CommentDto { private Long id; @JsonProperty("article_id") private Long articleId; private String nickname; private String body; ... } ``` ## 27:09 댓글 수정하기 - HTTP PATCH, @PatchMapping, IllegalArgumentException #### ../api/CommentApiController ``` ... @RestController public class CommentApiController { ... // 댓글 수정 @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); } ... } ``` #### ../service/CommentService ``` ... @Service public class CommentService { ... @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/Comment ``` ... public class Comment { ... 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(); } } ``` ## 34:23 댓글 삭제하기 - HTTP DELETE, @DeleteMapping, IllegalArgumentException #### ../api/CommentApiController ``` ... @RestController public class CommentApiController { ... // 댓글 삭제 @DeleteMapping("/api/comments/{id}") public ResponseEntity<CommentDto> delete(@PathVariable Long id) { // 서비스에게 위임 CommentDto deletedDto = commentService.delete(id); // 결과 응답 return ResponseEntity.status(HttpStatus.OK).body(deletedDto); } } ``` #### ../service/CommentService ``` ... public class CommentService { ... @Transactional public CommentDto delete(Long id) { // 댓글 조회(및 예외 발생) Comment target = commentRepository.findById(id) .orElseThrow(() -> new IllegalArgumentException("댓글 삭제 실패! 대상이 없습니다.")); // 댓글 삭제 commentRepository.delete(target); // 삭제 댓글을 DTO로 반환 return CommentDto.createCommentDto(target); } } ``` ## 🔥 구글링 훈련하기 - 롬복 @Builder 사용법 - 자바8 스트림이란 - 자바8 스트림 map - 자바8 스트림 collect - @JsonProperty