# 댓글 서비스와 컨트롤러(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