4. 블로그 전체 글 목록 조회를 위한 API 구현하기
- 클라이언트는 데이터베이스에 직접 접근 불가 → API를 구현해볼 수 있도록 해야 함
- 모든 글을 조회하는 API를 구현해보자
1) 서비스 메서드 코드 작성하기
◉ BlogService.java 파일에 데이터베이스에 저장되어 있는 글을 모두 가져오는 findAll() 메서드 추가
▪︎ BlogService.java
- JPA 지원 메서드인 findAll( )을 호출해 article 테이블에 저장되어 있는 모든 데이터 조회
@RequiredArgsConstructor // final이 붙거나 @NotNull이 붙은 필드의 생성자 추가
@Service // 빈으로 등록
public class BlogService {
// 데이터베이스와 작업 모델 ( 특히 JPA의 모델은 리포지토리 ) 멤버변수 선언
// 의존성 주입 방법
private final BlogRepository blogRepository; // 보통 클래스명의 소문자로 객체변수 선언
// * save () 메서드 생략 *
// * 데이터베이스에 저장되어 있는 글을 모두 가져오는 메서드
public List<Article> findAll() {
// ① 전체 흐름 만들기
// : 서비스는 레포지토리로부터 데이터를 가져온다
// blogRepository.findAll();
// ② 리턴값 지정
// : JPA repository는 entity 자바객체와 테이블의 레코드가 1 : 1 연결됨
// * findAll()의 결과, 모든 글 반환 → List 컬렉션 이용
// : List<Article>
return blogRepository.findAll();
}
}
2) 컨트롤러 메서드 코드 작성하기
◉ /api/articles GET 요청이 오면, 글 목록을 조회할 findAllArticles() 메서드 작성
◎ dto 패키지에 ArticleResponse.java 파일 생성
- 글은 제목과 내용 구성 → 해당 필드( title, content )를 가지는 클래스를 만든 다음, 엔티티를 인수로 받는 생성자 추가
package org.choongang.dto;
import org.choongang.domain.Article;
import lombok.Getter;
// * ArticleResponse 객체 : Article 엔터티의 응답용 객체로 임시 저장용
// → 데이터 변수 구조는 같고 메서드는 생성자만 있으면 됨
// * 임시 객체이므로 VO 객체가 아니라 DTO 객체라고 함
// - VO 객체 : 생성 후 변하지 않을 때 사용
// - DTO 객체 : 생성 후 내용이 변할 수 있거나 임시 사용으로 만들어 질때 사용 / SQL의 뷰에 해당함
@Getter
public class ArticleResponse {
// * 임시이지만 생성되고 고치면 안되므로, final로 지정
private final String title;
private final String content;
// * 데이터를 최초 한번 초기화하기 위한 메서드 -> 생성자
// : 매개인자로 엔티티인 Article이 사용
public ArticleResponse(Article article) {
this.title = article.getTitle();
this.content = article.getContent();
}
}
◎ controller 디렉터리에 있는 BlogApiController.java에 전체 글을 조회한 뒤 반환하는 findAllArticles() 메서드 추가
// * 글 전체 목록 조회
@GetMapping("/api/articles")
// : GET METHOD /api/articles로 요청하면 이 메서드가 실행
public ResponseEntity<List<ArticleResponse>> findAllArticles() {
// 지금은 JPA 공부 중이므로 서비스 메서드 이름과 리포지토리 메소드 이름을 같게 설정
// JPA의 findAll()과 동일하게 작성함
// ① 글 목록 요청 URL 주소 라우팅
// 서비스로 요청 - 서비스 객체의 메서드 호출
log.info("GET MAPPING /api/articles");
log.info("브라우저에서 JSON 방식으로 아티클 목록 요청");
// ② 서비스로부터 데이터 리턴 후 응답 준비
// 1) blogService.findAll()의 반환값은 List<Article> 타입
// 따라서 List<Article> articleList = blogService.findAll()
// 2) 기존의 고전적 방법 : 향상된 for구문을 이용한 절차적 프로그래밍을 이용
// ArticleResponse 객체를 응답용 json 또는 맵 데이터를 보관하는 배열이라고 하면
// List<ArticleResponse> articles = new ArrayList<>();
// for(Article item : articleList){
// ArticleResponse articleResponse =
// new ArticleResponse(
// item.get(0).title, item.get(0).content) ;
// articles.add(item);
// }
// return ResponseEntity.ok().body(articleResponse);
// 3) 앞으로는 간단한 컬렉션은 스트림 객체로 대신할 수 있음
// for( 한개의 item : list객체 ) => List객체의 stream() 메서드로 대체 가능
// new 변환객체(item.get(0).개별멤버변수 ... ) => stream() 객체의 map(변환방법)으로 대체 가능
// 최종적으로 루프의 개별 항목들을 toList() 메서드로 List<변환객체>로 바꿀 수 있음
// ArticleResponse : 응답할 레코드 객체 하나
// ArticleResponse의 데이터는 Article 객체의 데이터부분과 같다
// blogService.findAll() => List<Article>
// blogService.findAll().stream() =>
// for(Article item : blogService.findAll())
// .map(Article::new) =>
// new ArticleResponse(item.get(0).title, item.get(0).content)
// .toList() => List<ArticleResponse> 객체 반환
List<ArticleResponse> articles = blogService.findAll()
.stream()
.map(ArticleResponse::new)
.toList();
// ArticleResponse 객체를 생성하라.
// 파라미터로 blogService.findAll()의 리턴값에
// 요소 데이터(Article 객체)를 대입한다
// blogService.findAll() => List<Article>의 요소
// => Article 객체
// ③ 응답데이터 웹 브라우저로 전송
// 1) 응답데이터는 뷰 페이지(HTML코드)거나 JSON 형태의 데이터 자체
// rest서버이므로 JSON 형태 / ㄹ우리가 직접 만들 필요 없음
// 일반적인 경우는 스프링 부트가 대 해줌
// ResponseEntity 객체가 바로 그 주인공
// ResponseEntity 객체 : 엔터티 객체의 멤버 내용을 맵 방식으로 변환 가능
// 자바의 맵 데이터 형식은 json형식과 유사
// resongseEntity 객체는ㄴ 맵을 응답 데이터로 보냄
// 그 메서드가 responseenetity.body()
// ResponseEntity는 기본적으로 응답코드가 필요함
// ok() 메서드가 바로 응답코드 200을 의미
//ResponseEntity.ok().body(map 또는 json 데이터);
//컨트롤러가 브라우저로 rest 데이터를 보ㅓ내게 됩니다.
//위의 List<ArticleResponse> articles를 .body(articles)하면 끝
return ResponseEntity.ok()
.body(articles);
}
- /api/articles GET 요청이 오면, 글 전체를 요청하는 findAll( ) 메서드를 호출
→ 응답용 객체인 ArticleRepsonse로 파싱해 body에 담아 클라이언트에게 전송함
( 이 코드에는 스트림을 적용 )
3) 실행 테스트하기
◉ resources 폴더에 data.sql 생성
INSERT INTO article(title, content) VALUES ('제목1', '내용1')
INSERT INTO article(title, content) VALUES ('제목2', '내용2')
INSERT INTO article(title, content) VALUES ('제목3', '내용3')
◉ 포스트맨에서 확인
- 포스트맨에서 HTTP 메서드 : GET / [Params] 탭으로 변경 후, URL에 'http://localhost/api/articles' 입력 → Send 클릭
★ 오라클의 경우 ★
- 일련번호의 자동 생성 기능 지원 안됨 → SEQUENCE 객체 생성해야 함
SQL : CREATE SEQUENCE 시퀀스명;
- SEQUENCE 객체 생성 후, '시퀀스명.nextval'로 새로운 일련번호 생성 가능
- '시퀀스명.currval' : 현재 번호 의미
INSERT INTO ARTICLE
(id, title, content, created_at, updated_at)
VALUES
(article_seq.nextval, '제목1', '내용1', SYSDATE, SYSDATE);
INSERT INTO ARTICLE
(id, title, content, created_at, updated_at)
VALUES
(article_seq.nextval, '제목2', '내용2', SYSDATE, SYSDATE);
INSERT INTO ARTICLE
(id, title, content, created_at, updated_at)
VALUES
(article_seq.nextval, '제목3', '내용3', SYSDATE, SYSDATE);
4) 테스트 코드 작성하기
▪︎ BlogApiController.java
given | 블로그 글을 저장함 |
when | 목록 조회 API를 호출함 |
then | 응답 코드가 200 Ok이고, 반환받은 값 중에 0번째 요소의 content와 title이 저장된 값과 같은지 확인함 |
@DisplayName("findAllArticles() : 블로그 글 목록 조회 실행 성공")
@Test
void testFindAllArticles() throws Exception {
// * given : 조건 설정
// - 요청 주소 uri 및 입력 데이터 변수 선언
// -> 이 부분은 조건으로 중간에 변경할 수 없도록 상수로 설정
final String url = "/api/articles";
final String title = "테스트용 title";
final String content = "테스트용 content";
// - 입력데이터 추가 : JPA에서 제공하는 save() 메서드 이용
// INSERT INTO 테이블 (컬럼, ...) VALUES (값, ...);
blogRepository.save(Article.builder()
.title(title)
.content(content)
.build());
// * when : 설정된 조건을 이용하여 실행
// - ResultActions
// : 테스트에서 어떤 테스트 명령을 수행하여 그 결과를 저장하는 객체
// 이 객체의 andExpert()를 가지고 우리가 기대하는 값과 결과를 비교함
// -> 값이 일치하면 테스트 성공, 아니면 실패
// - mockMvc.perform : 가상서버의 주어진 url로 접속하여
// 데이터(MediaType)의 결과값을 json형식으로 전송
final ResultActions resultActions = mockMvc.perform(get(url)
.accept(MediaType.APPLICATION_JSON));
// * then : 그 실행한 결과가 예상하는 데이터와 같은가
// - isOk() : 데이터 응답코드가 올바른지 체크
// 실제 Article 테이블에 저장하는 데이터와 같은지 확인
// jsonPath : resultActions에 가져온 json 데이터 리소스
resultActions
.andExpect(status().isOk())
.andExpect(jsonPath("$[0].content").value(content))
.andExpect(jsonPath("$[0].title").value(title));
}
5. 블로그 글 조회 API 구현하기
1) 서비스 메서드 코드 작성하기
◉ BlogService.java 파일에 블로그 글 하나를 조회하는 메서드인 findById() 메서드 추가
- 이 메서드는 데이터베이스에 저장되어 있는 글의 ID를 이용해 글을 조회
// * 블로그 글 하나를 조회하는 메서드
public Article findById() {
return blogRepository.findById(id)
.orElseThrow(() -> new IllegalArgumentException("not found : " + id));
// * Optional<T>의 객체 값 가져올 때, null일 경우 지정된 예외 발생
}
▪︎ JPA에서 제공하는 findById() 메서드를 사용해 ID를 받아 엔티티를 조회하고, 없으면 IllegalArgumentException 예외 발생
▪︎ Optional<T> 객체의 값 가져올 때, null일 경우 지정된 예외 발생시킴
2) 컨트롤러 메서드 코드 작성하기
◉ /api/articles/{id} GET 요청이 오면 블로그 글을 조회하기 위해 매핑할 findArticle( ) 메서드 작성
// * 게시물 하나 구하기
// GET /api/articles/{id}
// GET + ... id = 게시물 하나 검색해주세요
// CRUD에서 상세보기 기능에 해당
@GetMapping("/api/articles/{id}")
// URI 경로에서 값 추출
public ResponseEntity<ArticleResponse> findArticle(@PathVariable long id){
Article article = blogService.findById(id);
return ResponseEntity.ok()
.body(new ArticleResponse(article));
}
▪︎ @PathVariable 애너테이션
- URL에서 값을 가져오는 애너테이션
- 동작원리
/api/articles/3 GET 요청을 받으면, id에 3이 들어옴
→ 이 값이 서비스 클래스의 findById( ) 메서드로 번호가 3번 블로그 글을 찾게 됨
→ 3번 글의 정보를 body에 담아 웹 브라우저로 전송함
3) 테스트 코드 작성하기
▪︎ BlogApiControllerTest.java
given | 블로그 글을 저장함 |
when | 저장한 블로그 글의 Id값으로 API를 호출함 |
then | 응답 코드가 200 Ok이고, 반환받은 content와 title이 저장된 값과 같은지 확인함 |
@DisplayName("findArticle : 블로그 글 조회에 성공한다.")
@Test
public void findArticle() throws Exception{
// * given
// - 테스트로 사용할 데이터 입력
final String url = "/api/articles/{id}";
final String title = "테스트 제목";
final String content = "테스트 내용";
// - 테스트용 데이터를 BlogRepository의 save() 메서드를 이용해 저장
// -> 그 결과 Article 객체가 반환됨
Article savedArticle = blogRepository.save(Article.builder()
.title(title)
.content(content)
.build());
// * when
// - 실제 웹 서버로 실행시킨 것과 같은 결과를 반환하게 해서
// ResultActions 객체에 결과정보를 저장함
final ResultActions resultActions
= mockMvc.perform(get(url, savedArticle.getId()));
// * then
resultActions
.andExpect(status().isOk())
.andExpect(jsonPath("$.content").value(content))
.andExpect(jsonPath("$.title").value(title));
}
'백엔드 > Spring Boot' 카테고리의 다른 글
7. 블로그 화면 구성하기 (1) - 타임리프 (0) | 2023.08.02 |
---|---|
6. 블로그 기획하고 API 만들기 (3) - 블로그 글 작성을 위한 API 구현❸ (1) | 2023.07.31 |
6. 블로그 기획하고 API 만들기 (3) - 블로그 글 작성을 위한 API 구현❶ (0) | 2023.07.26 |
6. 블로그 기획하고 API 만들기 - (2) 블로그 개발을 위한 엔티티 구성하기 (0) | 2023.07.26 |
JSP에 CSS 및 Javascript 연결 (0) | 2023.07.25 |