FrameWork/Spring Boot
블로그 기획하고 API 만들기 - 블로그 글 조회(개별 글), 삭제, 수정
soooy0
2025. 3. 28. 23:50
블로그 글 전체 목록을 조회하는 API를 만들었으니, 이제는 글 하나의 상세 정보를 조회하는 api를 구현해보자.
블로그 글 조회 API 구현
서비스 메서드 코드 작성
1. BlogService.java에 글 하나를 조회하는 메서드인 findById()메서드를 추가한다. 이 메서드는 DB에 저장되어 있는 ID를 이용하여 글을 조회한다.
/*
* 메서드: DB에 저장된 ID를 사용하여 개별 글을 조회하는 메서드
* param: Long id (DB의 번호)
* return: Article 엔티티 반환, 없으면 IllegalAccessException 예외 발생
* */
public Article findById(long id) {
return blogRepository.findById(id)
// orElseThrow는 Optional이 비어있을 경우, 예외를 던지는 메서드
// IllegalAccessException 예외를 던지고, 예외 메시지로 "not found: " + id를 포함시켜, 해당 ID의 Article을 찾을 수 없다는 정보를 제공
.orElseThrow(() -> new IllegalArgumentException("not found: " + id));
}
컨트롤러 메서드 코드 작성
1. "/api/articles/{id}" GET 요청이 오면 블로그 개별 글을 조회하기 위해 매핑할 findArticle()메서드 작성
/*
* GET 요청이 들어오면, 개별 글을 조회하는 메서드
* parma: long id,(URL에서 {id}에 해당하는 값이 파마리터에 들어옴)
* return:
* */
@GetMapping("/api/articles/{id}")
// URL경로에서 값 추출, URL에서 {id}에 해당하는 값이 파마리터에 들어옴
// @PathVariable: URL에서 값을 가져오는 애너테이션 ex) "/api/articles/3" GET요청 들어오면, 3이 파라미터로 들어감
public ResponseEntity<ArticleResponse> findArticle(@PathVariable long id){
Article article = blogService.findById(id);
// article 엔티티를 articleResponse 객체로 변환 후, JSON 형태로 변환,body에 담아서 데이터 전송
return ResponseEntity.ok()
.body(new ArticleResponse(article));
}
}
테스트 코드 작성
@DisplayName("findArticle: 블로그 글 조회에 성공한다.")
@Test
public void findArticle() throws Exception{
// given
// 블로그 글을 저장할 데이터
final String url = "/api/articles/{id}";
final String title = "title";
final String content = "content";
// Article 엔티티 생성해서 위 데이터 DB에 저장
Article savedArticle = blogRepository.save(Article.builder()
.title(title)
.content(content)
.build());
// when
// 저장한 블로그 글을 id값으로 get api 호출
final ResultActions resultActions = mockMvc.perform(get(url, savedArticle.getId()));
//then
// 응답코드가 200 OK이고, 반환받은 content와 title이 저장된 값과 같은지 확인
resultActions
.andExpect(status().isOk())
.andExpect(jsonPath("$.content").value(content))
.andExpect(jsonPath("$.title").value(title));
}
정상적으로 테스트가 진행됨을 확인할 수 있다.
블로그 삭제 API 구현
서비스 메서드 코드 작성
1. BlogService.java 파일에 delete()메서드 추가, 이 메서드는 블로그의 ID를 받은 뒤 JPA에서 제공하는 deleteById() 메서드를 이용해 데이터베이스에서 데이터를 삭제한다.
/*
* 메서드: 블로그의 ID를 받은 뒤 JPA에서 제공하는 deleteById() 메서드를 이용해 데이터베이스에서 데이터를 삭제
* param: long id (글의 ID)
* return: void
* 삭제 이후, return할 값이 따로 없으므로 return 타입은 void
* */
public void delete(long id){
blogRepository.deleteById(id);
}
컨트롤러 메서드 코드 작성
1. "/api/articles/{id} DELETE요청이 오면 글을 삭제하기 위한 findArticles() 메서드 작성
/*
* DELETE 요청이 들어오면, 요청된 ID를 받아 해당 글을 삭제하는 메서드
* param: long id (글 번호, id)
* return: void, 반환값 없음을 의미하며 HTTP 응답상태만 클라이언트에게 전달함
* */
@DeleteMapping("/api/articles/{id}")
public ResponseEntity<Void> deleteArticle(@PathVariable long id){
blogService.delete(id);
// body 없이 전달함
return ResponseEntity.ok()
.build();
}
2. 포스트맨 테스트
- delete로 요청 보냄
- 다시 get 요청 보냄
- 1이 사라져있음을 볼 수 있음
테스트 코드 작성
@DisplayName("deleteArticle: 블로그 글 삭제에 성공한다.")
@Test
public void deleteArticle() throws Exception{
// given
// 블로그 글을 저장할 데이터
final String url = "/api/articles/{id}";
final String title = "title";
final String content = "content";
// Article 엔티티 생성해서 위 데이터 DB에 저장
Article savedArticle = blogRepository.save(Article.builder()
.title(title)
.content(content)
.build());
// when
// 삭제 API 호출
mockMvc.perform(delete(url, savedArticle.getId()))
.andExpect(status().isOk());
// then, 응답 코드가 200 OK 이고, 블로그 글 리스트 전체를 조회해 조회한 배열의 크기가 0인지 확인한다.
// articles 에 모든 글 조회해서 담음
List<Article> articles = blogRepository.findAll();
// articles 가 비어있는지 확인함, 비어있으면 테스트 성공
assertThat(articles).isEmpty();
}
블로그 수정 API 구현
Articles.java 파일에 코드 작성
// 요청받은 내용으로 값을 수정하는 메서드
public void update(String title, String content){
this.title = title;
this.content = content;
}
글 수정 요청을 받을 DTO 작성, dto 디텍터리에 UpdateArticleRequest.java 파일 생성 후 코드 작성
@NoArgsConstructor
@AllArgsConstructor
@Getter
public class UpdateArticleRequest {
private String title;
private String content;
}
service 메서드 작성
/*
* 메서드: id 값을 받아 특정 아이디의 글을 수정
* param: long id(글 번호, id), UpdateArticleRequest request(수정한 데이터를 담고 있는 DTO)
* 클라이언트는 수정할 글의 제목과 내용을 입력하고, 이 수정된 데이터를 request 에 담아서 서버로 보내는 것!
* return: 수정된 article 엔티티 객체
* */
// @Transactional : 메서드 전체가 하나의 트랜잭션으로 실행됨.
// 즉, 메서드 실행 도중 오류가 발생하면 롤백돼서 DB 변경 사항이 취소됨.
// 글 수정 중 예외 발생 시, DB가 변경되지 않도록 보호하는 역할.
@Transactional
public Article update(long id, UpdateArticleRequest request){
// DB에서 id에 해당하는 글을 찾음, 없으면 not found 예외 발생
Article article = blogRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("not found: " + id));
// UpdateArticleRequest DTO 에서 title 과 content 값을 가져와서 Article 엔티티의 데이터를 변경.
// -> 서버는 이 수정된 데이터를 request.getTitle()과 request.getContent()로 꺼내어 기존의 글을 업데이트한다.
// 쉽게 말하자면 사용자가 수정한 제목과 내용이 request 에 담겨있을 것이고, 그 데이터를 article 엔티티에 update 해서 수정하는 것
article.update(request.getTitle(), request.getContent());
// 수정된 Article 엔티티 객체를 반환함.
// 엔티티 객체(article)의 값만 바꿔도 JPA 가 자동으로 변경을 감지해서 DB에 반영해줌. save() 메서드 필요x
return article;
}
컨트롤러 메서드 작성
/*
* DELETE 요청이 들어오면, 요청된 ID를 받아 해당 글을 삭제하는 메서드
* param: long id (글 번호, id)
* return: void, 반환값 없음을 의미하며 HTTP 응답상태만 클라이언트에게 전달함
* */
@DeleteMapping("/api/articles/{id}")
public ResponseEntity<Void> deleteArticle(@PathVariable long id){
blogService.delete(id);
// body 없이 전달함
return ResponseEntity.ok()
.build();
}
/*
* 메서드: PUT 요청이 들어오면, 요청된 id를 받아 해당 글을 수정하는 메서드
* param: long id(클라이언트가 수정하려는 글번호, id), @RequestBody UpdateArticleRequest request
* @RequestBody: 요청 본문(request body)에 포함된 데이터를 메서드의 파라미터로 매핑
* 즉, 클라이언트가 PUT 요청으로 보내는 제목과 내용이 담긴 새로운 수정값 JSON 데이터를 UpdateArticleRequest 객체로 변환해서 request 로 받게 됨
* 클라이언트에서 보내는 JSON 형식의 데이터가 @RequestBody를 통해 서버에서 지정한 UpdateArticleRequest 객체로 변환되는 것
*
* */
@PutMapping("/api.articles/{id}")
// put 요청이 오면 request body 정보가 request 로 넘어온다. 그리고 다시 service 클래스의 update() 메서드에 id, request를 넘겨줌
// 응답값은 body에 담아 다시 전송
public ResponseEntity<Article> updateArticle(@PathVariable long id, @RequestBody UpdateArticleRequest request){
// 수정된 데이터를 service 메서드에 보내고, 반환되는 엔티티 값을 updateArticle에 저장
Article updateArticle = blogService.update(id, request);
// HTTP 상태코드 200을 반환, 수정된 article 객체를 응답 본문(body)에 담아서 클라이언트에게 보냄
return ResponseEntity.ok()
.body(updateArticle);
}
포스트맨으로 확인
- Body에서 수정해서 보내봤더니, 잘 되는 모습
전체 조회 api로 확인해보자.
- 잘 수정된 것을 확인 가능!
테스트 코드 작성
@DisplayName("updateArticle: 블로그 글 수정에 성공한다.")
@Test
public void updateArticle() throws Exception{
// given
// 블로그 글을 저장할 데이터와 글 수정에 필요한 요청 객체를 만듬
// 기존 데이터
final String url = "/api/articles/{id}";
final String title = "title";
final String content = "content";
// Article 엔티티 생성해서 위 데이터 DB에 저장
Article savedArticle = blogRepository.save(Article.builder()
.title(title)
.content(content)
.build());
// 수정 데이터
final String newTitle = "new Title";
final String newContent = "new Content";
// 수정할 데이터를 UpdateArticleRequest 객체를 생성해서 해당 필드에 저장해줌
UpdateArticleRequest request = new UpdateArticleRequest(newTitle, newContent);
// when
// Update API로 수정 요청을 보냄. 요청 타입은 JSON, given 절에서 만들어둔 수정 데이터 객체를 요청 본문으로 함꼐 보냄
ResultActions result = mockMvc.perform(put(url, savedArticle.getId())
// 요청 본문이 JSON형식임을 지정
.contentType(MediaType.APPLICATION_JSON_VALUE)
// UpdateArticleRequest 객체를 JSON으로 변환하고, objectMapper.writeValueAsString를 사용해 JSON -> 문자열 변환
.content(objectMapper.writeValueAsString(request)));
// then
// 응답 코드가 200 OK인지 확인함. 블로그 글 id로 조회한 후에 값이 수정되었는지 확인
result.andExpect(status().isOk());
// 수정되어 article 에 저장된 데이터가 위에서 생성한 수정데이터와 동일한지 확인하여, 수정이 제대로 이루어졌는지 확인
Article article = blogRepository.findById(savedArticle.getId()).get();
assertThat(article.getTitle()).isEqualTo(newTitle);
assertThat(article.getContent()).isEqualTo(newContent);
}