(이미지 출처)
REST(Representational State Transfer) API
REST Controller
REST API
를 위한 컨트롤러로서 서비스와 협업.요청
을 받아서 응답
하고, 뷰(view)가 아닌 데이터
를 반환.Service
REST 컨트롤러
와 리파지터리
사이에서 비즈니스 로직
을 담당.@Transactional
로 변경된 데이터를 롤백
.DTO(Data Transfer Object)
클라이언트
와 서버
간에 댓글 JSON
데이터를 전송.Entity
엔터티
를 기반으로 테이블이 생성됨.Repository
엔터티
를 관리하는 인터페이스.CRUD
등의 기능을 제공함.서비스
로부터 CRUD
등의 명령을 받아서 DB에 보내고 응답
을 받음.댓글 CRUD를 위한 REST API 주소 설계
<======>
REST APImethod | 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 CommentDto
comment.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 articleId
URL
에 있는 변수 articleId
값을 매개변수와 매핑.List<CommentDto> dtos = commentService.comments(articleId);
comments
메서드를 호출하고 argument
로 articleId
를 넘겨줌.List<commentDto>
타입의 dtos
변수에 저장함.return
200
, 본문(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
.return
200
, 본문(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
로 썼음.