Mission
Comment "컨트롤러"와 "서비스"를 만들어 댓글 "REST API" 를 완성하시오
-요약
댓글 CRUD 기능을 REST API로 구현을 했다.
이를 위해서 RestController를 생성 하였고, 서비스와 협업 할수 있도록 CommentService를 생성하여 구현을 했다.
이때 서비스가 2개의 레파지토리인 CommentRepository와 ArticleRepository와 협업을 했다.
추가로 Controller에서 JSON을 받기위해 CommentDto를 생성 했고, CommentDto안에는 JSON에서 던져진 키 값을 @JsonProperty 어노테이션을 통해서 지정할 수 있었다.
추가로 Comment 엔티티에서 2개의 메소드를 만들었다.
첫번째는 dto와 article을 입력받아서 Commnet 엔티티를 반환하는 생성 메소드를 클래스 메소드로 만들어 보았고,
추가로 수정을 위해서 인스턴스 메소드인 patch 메소드를 작성했다.
그리고 끝으로 CommentService에서 스트림 문법을 연습할수 있는 문법도 보았고, 예외를 발생시키는 코드또한 작성을 했다.
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;
// 댓글 목록 조회
// 댓글 생성
// 댓글 수정
// 댓글 삭제
}
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;
}
댓글 목록 조회
- ../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()
);
}
}
반복을 스트림으로 - 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());
}
}
댓글 생성하기 - 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()
);
}
}
디버깅 - @JsonProperty
- ../dto/CommentDto
@JsonProperty
에러
CommentDto에서 articleId 가 카멜 형식으로 잡혀있는데 데이터는 article_id 로 보낸거다
json 에서 데이터 받아 올 때는 @JsonProperty("article_id") 이렇게 써줘야 한다. "article_id" 이렇게 날라온다 선언해주는 거고 자동으로 articleId 로 매핑시켜준다.
package com.example.firstproject.dto;
...
public class CommentDto {
private Long id;
@JsonProperty("article_id")
private Long articleId;
private String nickname;
private String body;
...
}
댓글 수정하기 - 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();
}
}
댓글 삭제하기 - HTTP DELETE, @DeleteMapping, IllegalArgumentException4
- ../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
내용참고
https://www.youtube.com/channel/UCpW1MaTjw4X-2Y6MwAVptcQ
'Spring Boot > Spring Boot 입문 홍팍' 카테고리의 다른 글
[Spring boot] (22) 댓글 엔티티와 레파지토리 (0) | 2023.08.18 |
---|---|
[Spring boot] (21) 테스트 작성하기 (0) | 2023.08.18 |
[Spring boot] (20) 서비스 계층과 트랜잭션 (0) | 2023.08.18 |
[Spring boot] (19) Rest Controller와 HTTP (0) | 2023.08.09 |
[Spring boot] (18) Rest API 와 JSON (0) | 2023.08.08 |