댓글 엔티티와 리포지토리를 만들었는데, 컨트롤러와 서비스를 추가해서 Rest API를 완성해 보자
@RestController
public class CommentApiController {
@Autowired private CommentService commentService;
}
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;
}
CommentRepository뿐만 아니라 ArticleRepository도 @Autowired로 가져와준다.
//댓글 목록 조회
@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);
}
컨트롤러에서 api/articles/4/comments 같은 요청을 처리하는 comments 메소드다.
반환형 : CommentDto List를 Response로 감싸서 응답코드랑 같이 반환할 것이다.
따라서 CommendDto 를 만들어야함
package com.example.firstproject.dto;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
@AllArgsConstructor
@NoArgsConstructor
@Getter
@ToString
public class CommentDto {
private Long id;
private Long articleId;
private String nickname;
private String body;
}
이렇게 만들어준다.
만들고 나서 이제 실제 작업을 할 CommentService 클래스에 comments 메소드를 만들어주면 된다.
package com.example.firstproject.service;
import com.example.firstproject.dto.CommentDto;
import com.example.firstproject.entity.Comment;
import com.example.firstproject.repository.ArticleRepository;
import com.example.firstproject.repository.CommentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
@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;
}
}
CommentService에 comments 메소드를 만들어준다.
먼저 commentRepository를 이용해서 findByArticleId로 Comment 엔티티의 리스트를 가져온다. 리스트에 대해 각 원소를 엔티티->dto로 형변환해서 리스트에 담고 반환한다.
이 때 CommentDto 클래스의 createCommentDto 메소드를 만들어서 엔티티를 dto로 형변환한다.
따라서 createCommentDto 메소드를 만들어야 한다.
package com.example.firstproject.dto;
import com.example.firstproject.entity.Comment;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
@AllArgsConstructor
@NoArgsConstructor
@Getter
@ToString
public class CommentDto {
private Long id;
private Long articleId;
private String nickname;
private String body;
public static CommentDto createCommentDto(Comment comment) {
return new CommentDto(
comment.getId(),
comment.getArticle().getId(),
comment.getNickname(),
comment.getBody()
);
}
}
이렇게 CommentDto 클래스에 createCommentDto 메소드를 만들어준다.
매개변수로 받은 Comment 객체를 CommentDto로 바꿔서 반환해준다.
package com.example.firstproject.service;
import com.example.firstproject.dto.CommentDto;
import com.example.firstproject.entity.Comment;
import com.example.firstproject.repository.ArticleRepository;
import com.example.firstproject.repository.CommentRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
@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 commentRepository.findByArticleId(articleId)
.stream()
.map(comment->CommentDto.createCommentDto(comment))
.collect(Collectors.toList());
}
}
CommentService 클래스에서 이렇게 스트림 문법을 사용해서 for 문을 대체할 수도 있다.
이렇게 POST 요청을 보냈을 때 댓글을 생성해주는 요청을 처리해보자
//댓글 생성
@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);
}
컨트롤러의 create 메소드다.
매개변수에 @RequestBody 어노테이션으로 request에 담겨있는 json 데이터를 받는다
그리고 서비스의 create를 호출해서 dto를 만들고 반환해준다.
이 때 실패할 경우는 create 메소드 안에서 처리한다.
@Transactional //DB에 접근하므로 트랙잭션 어노테이션으로 문제가 발생하면 롤백되도록 해줘야함
public CommentDto create(Long articleId, CommentDto dto) {
// 게시글 조회 및 예외 처리
Article article = articleRepository.findById(articleId)
.orElseThrow(()->new IllegalArgumentException("댓글 생성 실패! 대상 게시글이 없습니다.")); //해당 id 없을경우 예외 발생
// 댓글 엔티티 생성
Comment comment = Comment.createComment(dto,article);
// 댓글 엔티티를 DB로 저장
Comment created = commentRepository.save(comment);
// DTO로 변경하여 반환
return CommentDto.createCommentDto(created);
}
create 메소드는 이렇게 만들어준다.
findById로 먼저 게시글을 조회하는데 없으면 orElseThrow로 예외처리 해준다
댓글 엔티티를 생성하고 리포지토리의 save 메소드를 이용해서 DB에 저장해준다
그리고 반환할 때는 만든 엔티티를 DTO로 변환해서 반환해준다.
이제 Comment 엔티티에 createComment 메소드를 만들어줘야 한다.
public static Comment createComment(CommentDto dto, Article article) {
//예외 처리
if (dto.getId() != null){ //id 생성할때는 json에 id 를 넣으면 안되는데 넣었을 경우
throw new IllegalArgumentException("댓글 생성 실패! 댓글의 id가 없어야 합니다.");
}
if (dto.getArticleId()!=article.getId()){
throw new IllegalArgumentException("댓글 생성 실패! 게시글의 id가 잘못되었습니다.");
}
//엔티티 생성 및 반환
return new Comment(
dto.getId(),
article,
dto.getNickname(),
dto.getBody()
);
}
Comment 엔티티에 createComment 메소드를 만들어 준다.
두가지 경우에 대해 예외 처리를 해주고 엔티티를 생성하고 반환해준다.
이렇게 하고 서버 돌려서 POST 요청 보내면 에러 발생
디버깅 결과
request 보낼 때 json 데이터에 article_id로 4를 보내서 CommentDto로 받는데
package com.example.firstproject.dto;
import com.example.firstproject.entity.Comment;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
@AllArgsConstructor
@NoArgsConstructor
@Getter
@ToString
public class CommentDto {
private Long id;
@JsonProperty("article_id")
private Long articleId;
private String nickname;
private String body;
public static CommentDto createCommentDto(Comment comment) {
return new CommentDto(
comment.getId(),
comment.getArticle().getId(),
comment.getNickname(),
comment.getBody()
);
}
}
JSON에서 보내는것과 이름을 같게 해서 article_id로 필드명을 받아야 하는데 articleId이기 때문
이럴 대는 @JsonProperty 어노테이션으로 article_id로 받겠다고 해주면 된다.
//댓글 수정
@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);
}
컨트롤러에 댓글 수정 하기 위한 updatge 메소드를 만들어 준다.
서비스에 update 메소드를 만들어야 한다.
@Transactional // DB 건드리니까 문제 발생시 롤백 되도록
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);
}
서비스에 update 메소드를 만들어준다.
Comment 엔티티에 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();
}
}
엔티티에 patch 메소드를 만들어준다.
target.patch 로 호출하니까 this를 사용해서 값 업데이트 해준다.
//댓글 삭제
@DeleteMapping("/api/comments/{id}")
public ResponseEntity<CommentDto> delete(@PathVariable Long id){
// 서비스에게 위임
CommentDto deletedDto = commentService.delete(id);
// 결과 응답
return ResponseEntity.status(HttpStatus.OK).body(deletedDto);
}
컨트롤러의 delete 메소드
@Transactional
public CommentDto delete(Long id) {
// 댓글 조회(및 예외 발생)
Comment target = commentRepository.findById(id)
.orElseThrow(()-> new IllegalArgumentException("댓글 삭제 실패! 대상이 없습니다") );
// DB에서 댓글 삭제
commentRepository.delete(target); //delete는 반환값 없음
// 삭제 댓글 DTO로 반환
return CommentDto.createCommentDto(target);
}
서비스의 delete 메소드
리포지토리의 delete 메소드는 반환값이 없고 위에서 조회한 target을 DTO로 변환해서 반환해줌