(이미지 출처)

REST(Representational State Transfer) API
REST ControllerREST API를 위한 컨트롤러로서 서비스와 협업.요청을 받아서 응답하고, 뷰(view)가 아닌 데이터를 반환.ServiceREST 컨트롤러와 리파지터리 사이에서 비즈니스 로직을 담당.@Transactional로 변경된 데이터를 롤백.DTO(Data Transfer Object)클라이언트와 서버간에 댓글 JSON 데이터를 전송.Entity엔터티를 기반으로 테이블이 생성됨.Repository엔터티를 관리하는 인터페이스.CRUD 등의 기능을 제공함.서비스로부터 CRUD 등의 명령을 받아서 DB에 보내고 응답을 받음.댓글 CRUD를 위한 REST API 주소 설계
<======> REST API| method | URL |
|---|---|
| GET | /articles/articleId/comments |
| POST | /articles/articleId/comments |
| PATCH | /comments/id |
| DELETE | /comments/id |
REST API를 구현하려면 Controller가 아닌 REST Controller를 만들어야 함.@RestController
public class CommentApiController {
@Autowired
private CommentService commentService;
}
@RestController@Autowired private CommentService commentService;@Service
public class CommentService {
@Autowired
private ArticleRepository articleRepository;
@Autowired
private CommentRepository commentRepository;
}
@Service@Autowired CommentRepository 뿐만 아니라 ArticleRepository까지 필요한 이유.ArticleRepository가 있어야 댓글을 생성할 때 해당 게시글의 존재 여부를 파악할 수 있기 때문.GET : /articles/articleId/comments
@AllArgsConstructor
@NoArgsConstructor
@Getter
@ToString
public class CommentDto {
private Long id;
private Long articleId; // 댓글의 부모(article) id
private String nickname;
private String body;
public static CommentDto createCommentDto(Comment comment) {
return new CommentDto(
comment.getId()
, comment.getArticle().getId()
, comment.getNickname()
, comment.getBody());
}
}
createCommentDto 메서드.static, 즉, 정적 메서드로 선언되어 있음.생성 메서드 ≒ 정적 팩토리 메서드라 함.return new CommentDtocomment.getId()comment.getArticle().getId()comment.getNickname()comment.getBody())// controller
@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);
}
{articleId}URL에서 조회하는 게시글의 id를 변수화해서 유연한 코드.@PathVariable Long articleIdURL에 있는 변수 articleId값을 매개변수와 매핑.List<CommentDto> dtos = commentService.comments(articleId);comments메서드를 호출하고 argument로 articleId를 넘겨줌.List<commentDto>타입의 dtos 변수에 저장함.return200, 본문(Body)에는 조회한 댓글의 목록인 DTOs를 실어 보냄. public List<CommentDto> comments(Long articleId) {
List<Comment> comments = commentRepository.findByArticleId(articleId);
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;
}
Long articleId값을 받음.List<Comment> comments = commentRepository.findByArticleId(articleId);리파지토리의 findByArticleId메서드를 이용해서 articleId번에 있는 모든 댓글을 가져옴.List<Comment> 타입의 comments 변수에 저장.List<CommentDto> dtos = new ArrayList<CommentDto>();엔터티 목록을 DTO 목록으로 변환하기 위해서 CommentDto를 저장하는 빈 ArrayList를 만들고 List<CommentDto>타입의 dtos변수에 저장.for 문comments.size()comments.get(i);CommentDto.createCommentDto(c);dtos.add(dto);return dtos;for 문 -----> 스트림(stream).↑, 코드의 양 ↓ public List<CommentDto> comments(Long articleId) {
return commentRepository.findByArticleId(articleId)
.stream().map(comment -> CommentDto.createCommentDto(comment))
.collect(Collectors.toList());
}
commentRepository.findByArticleId(articleId).stream()스트림(stream)은 컬렉션이나 리스트에 저장된 요소들을 하나씩 참조하며 반복처리할 때 사용..map(comment -> CommentDto.createCommentDto(comment)).map(a -> b) : 스트림의 각 요소(a)를 꺼내 b를 수행한 결과로 매핑.collect(Collectors.toList());POST : /articles/articleId/comments
@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);
}
@PostMapping("/api/articles/{articleId}/comments"){articleID}로 변수 처리해서 유연하게.(@PathVariable Long articleId, @RequestBody CommentDto dto)@PathVariable로 요청 URL의 {articleID}을 받음.@RequestBody는 HTTP 요청 본문에 실린 데이터(JSON, XML, YAML)을 Java객체로 변환해줌.@RequestBody을 이용해서 dto로 생성할 댓글 정보를 받음.CommentDto createdDto = commentService.create(articleId, dto);create(articleId, dto) 메서드 호출.CommentDto.return200, 본문(body)에는 생성한 댓글 데이터인 createdDto를 실어서 보냄. @Transactional
public CommentDto create(Long articleId, CommentDto dto) {
// 1번.
Article article = articleRepository.findById(articleId).orElseThrow(() -> new IllegalArgumentException("댓글 생성 실패 " + "대상 게시글이 없음"));
// 2번.
Comment comment = Comment.createComment(dto, article);
Comment created = commentRepository.save(comment);
// 3번.
return CommentDto.createCommentDto(created); // Entity -> DTO 변환해서 리턴.
}
create의 경우 데이터를 추가, 즉 DB의 데이터에 변동을 주기 때문에 @Transactional을 추가해서 실패했을 경우 롤백하도록.1번articleId를 통해 DB에 데이터를 조회함.orElseThrow()메서드로 예외를 발생시킴.orElseThrow()메서드는 Optional 객체(= null이 될수도 있는 객체)에 값이 존재하면 그 값을 반환하고, 존재 하지 않는다면 전달값으로 보낸 예외를 발생시키는 메서드.IllegalArgumentException 클래스를 사용하여 메서드가 잘못 됐거나, 전달값이 잘못 보냈음을 뜻함.2번createComment(dto, article)를 호출해서 댓글 엔터티를 반환받음.댓글 DTO, 게시글 엔터티를 argument로 받아서 댓글 엔터티를 만듦.3번created 엔터티를 DTO로 변환한 후 반환. 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()
);
}
엔터티의 id는 DB가 자동으로 생성해주니깐.JSON 데이터와 URL 요청 정보가 다르다는 뜻.new Comment() 생성자 호출.PATCH : /comments/id닉네임, 내용 둘 다 수정.
닉네임만 수정.

내용만 수정

@PatchMapping("/api/comments/{id}")
public ResponseEntity<CommentDto> update(@PathVariable Long id, @RequestBody CommentDto dto) {
// 1번.
CommentDto updatedDto = commentService.update(id, dto);
// 2번.
return ResponseEntity.status(HttpStatus.OK).body(updatedDto);
}
@PatchMapping("/api/comments/{id}"){id}는 수정 대상 댓글의 id@PathVariable Long id, @RequestBody CommentDto dto{id}변수를 @PathVariable을 이용해서 매개변수 id로 받아옴.@RequestBody를 통해 JSON데이터를 dto로 받음.1번update() 메서드를 호출하고 argument로 id, dto를 넘겨줌.CommentDto.2번 @Transactional
public CommentDto update(Long id, CommentDto dto) {
// 1번.
Comment target = commentRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("댓글 수정 실패 " + "대상 댓글이 없음."));
// 2번.
target.patch(dto);
Comment updated = commentRepository.save(target);
// 3번.
return CommentDto.createCommentDto(updated);
}
@Transactional을 사용.1번id를 통해 데이터를 조회하고 값이 있다면 변수에 저장,orElseThrow()메서드로 예외 발생시킴.2번patch(CommentDto dto)메서드를 호출해서 기존 댓글 엔터티에 수정 요청 정보를 추가함.3번 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();
}
}
this 객체의 id와 dto의 id가 다른 경우.DELETE : /comments/id
@DeleteMapping("/api/comments/{id}")
public ResponseEntity<CommentDto> delete(@PathVariable Long id) {
CommentDto deleteDto = commentService.delete(id);
// 1번.
return ResponseEntity.status(HttpStatus.OK).body(deleteDto);
}
@DeleteMapping("/api/comments/{id}")로 삭제 요청을 받음.@PathVariable Long id{id}변수를 받아오기 위해서.1번 @Transactional
public CommentDto delete(Long id) {
// 1번
Comment target = commentRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("'댓글 삭제 실패. 대상이 없음."));
commentRepository.delete(target);
// 2번
return CommentDto.createCommentDto(target);
}
@Transactional처리.1번rElseThrow()메서드를 통해 예외 발생시킴.2번JSON 데이터의 키(key) 이름과 이를 받아 저장하는 DTO에 선언된 필드의 변수명이 다를 경우 DTO 필드 위에 @JsonProperty("키 이름")을 작성해줘야 됨.키와 변수가 자동으로 매핑됨.@AllArgsConstructor
@NoArgsConstructor
@Getter
@ToString
public class CommentDto {
private Long id;
@JsonProperty("article_id")
private Long articleId;
private String nickname;
private String body;
// 코드 생략.
}


"article_id"로 하니 정상적으로 통신이 됨.

JSON 데이터의 articleId를 article_id로 썼음.