wongakim-99 / Spring-study

스프링 공부방 (백엔드 공부)
0 stars 0 forks source link

HTTP와 REST 컨트롤러 #12

Closed wongakim-99 closed 1 month ago

wongakim-99 commented 1 month ago

게시판 데이터를 CRUD(생성, 조회, 수정, 삭제)하기 위한 REST API를 구현해 보겠다.

wongakim-99 commented 1 month ago

이전 장에서 연습용 REST API 서버에 접속해 HTTP 요청을 보내고 응답받는 예제를 실습했다. 이때 사용하는 JSON 개념도 함께 살폈다. 클라이언트가 보내는 HTTP 요청 메시지의 첫 줄에는 시작 라인인 요청 라인(request line)이 있고, 그 아래에는 헤더(header)와 본문(body)가 있다.

결국 REST API란 REST 기반으로 API를 구현한 것이라고 할 수 있다. REST API를 잘 구현하면 클라이언트가 기기에 구애받지 않고 서버의 자원을 이용할 수 있을 뿐만 아니라, 서버가 클라이언트의 요청에 체계적으로 대응할 수 있어서 서버 프로그램의 재사용성과 확장성이 좋아진다.

wongakim-99 commented 1 month ago

REST API의 구현 과정

REST API를 구현하려면 REST API의 주소, 즉 URL을 설계해야 한다. 게시판의 Article 데이터를 CRUD(생성, 조회, 수정, 삭제) 하기 위해 REST API 의 주소를 다음과 같이 설계하겠다.

GET : /articles GET : /articles/id POST : /articles PATCH : /articles/id DELETE : /articles/id

주소 설계가 끝났다면 URL 요청을 받아 그결과를 JSON으로 반환해 줄 컨트롤러도 만들어야 함. 게시판을 만들 때는 일반 컨트롤러(ArticleController)를 사용했지만, REST API로 요청과 응답을 주고받을 때는 REST 컨트롤러를 사용해야 한다. 그리고 응답할 때 적절한 상태 코드를 반환하기 위해 ResponseEntity라는 클래스도 활용.

Image

wongakim-99 commented 1 month ago

간단한 문제 해결

저번주에 웹서버 프로그래밍 수업에서 jakarta EE로 Maven을 활용해서 어쩌구 저쩌구 하면서 프로젝트 생성을 진행하다 보니 뭔가 의존성 문제가 생겼다.

그러다 보니 간단하게 REST API 에서 FirstApiController.java를 추가하고 문제를 살펴보니...

org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaConfiguration': Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'dataSource' defined in class path resource [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class]: Failed to instantiate [com.zaxxer.hikari.HikariDataSource]: Factory method 'dataSource' threw exception with message: Failed to load driver class org.h2.Driver in either of HikariConfig class loader or Thread context classloader

이런 문제가 나왔다....대충 한글로 번역해보자니...

"클래스 경로 리소스 [org/springframework/boot/autoconfigure/jdbc/DataSourceConfiguration$Hikari.class]에 정의된 이름이 'dataSource'인 빈을 생성하는 중 오류가 발생했습니다. [com.zaxxer.hikari.HikariDataSource]를 인스턴스화하는 데 실패했습니다. 팩토리 메서드 'dataSource'가 다음 메시지와 함께 예외를 발생시켰습니다. HikariConfig 클래스 로더 또는 Thread 컨텍스트 클래스 로더에서 드라이버 클래스 org.h2.Driver를 로드하는 데 실패했습니다."

대충 살펴보니 드라이버 클래스가 로드되지 않아서 발생한 문제같다. HikariCP가 H2 드라이버를 로드하려고 시도하는데, H2 Driver가 명시되지 않았거나, 올바르게 설정되지 않았던 문제였던 것이다.

하지만...스린이였던 내가 이걸 알리 없었다...폭풍 구글링, 폭풍 GPT 질문 후...나온 결론이 위의 내용으로 추정된다.

1. 첫 번째 시도 -> 인텔리제이 다시 껐다켜기

당연히 인생에서 날로 먹을 수 있는건 회만 있을 뿐...스프링 부트는 호락호락 하지 않았다. 당연히 실패

2. 두 번재 시도 -> refresh gradle dependencies

gradle 의존성 파일을 다시 새로고침하고 껐다 켜보기...당연히 두번째 날먹 실패

3. pom.xml 파일 찾아서 수정

GPT에게 물어보니 pom.xml 파일에서

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <version>2.5.4</version>
    </dependency>
</dependencies>

위와 같이 수정하면 된다고 했다. 그러나 눈씻고 찾아봐도 지금 현재 프로젝트에서 pom.xml을 찾을 수 없었다.

도대체 이 파일이 왜 안보이지...찾고 찾다가 C:\Users\kgw19.gradle\caches\modules-2\files-2.1\com.zaxxer\HikariCP\5.0.1\a74c7f0a37046846e88d54f7cb6ea6d565c65f9c\HikariCP-5.0.1.jar!\META-INF\maven\com.zaxxer\HikariCP\pom.xml 경로에 있다는거다. 아무리 봐도 느낌상 여기는 아닌것 같아서 GPT에게 물어봤다. 진짜로 여기 수정하는게 맞냐 물어봤지만 알고보니 위의 경로에 있는 pom.xml 파일은 Gradle의 캐시 디렉토리 안에 있는 파일이었다. 즉, Gradle이 의존성으로 다운로드한 라이브러리와 관련된 메타정보일 뿐, 내 프로젝트와 직접적으로 관련된 pom.xml 파일이 아니다. 수정할 필요도, 수정한다 해도 무의미했을 뿐이다.

머리를 계속 싸매다가 GPT 아래 답변이 킬포였다. 프로젝트 내에서 pom.xml 파일을 찾을 수 없는 경우 Gradle 기반 프로젝트일 가능성이 높다고 했다. Gradle 프로젝트는 pom.xml 대신 build.gradle 파일을 사용하여 의존성을 관리한다. 즉!!!! 이 파일을 찾아서 수정하면 되는 것이었다.

확인해보니 내 프로젝트는 Gradle 기반 프로젝트 였고, 여기에 H2 데이터베이스 의존성을 추가했다. 그러고 나서 다시 오류 목록을 보자하니... 드라이버 클래스 어쩌구 저쩌구를 로드하는데 실패, ",,,정의된 이름이 'dataSource'인 빈을 생성하는 중 오류가 발생했습니다."

그래서 application.properties 파일에서 spring.datasource.driver-class-name 설정을 해주었다.

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driver-class-name=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.platform=h2

그러더니 정상 작동ㅎ

wongakim-99 commented 1 month ago

배운 점

Maven 과 Gradle

Maven 과 Gradle 은 모두 빌드 자동화 도구로, 자바 프로젝트에서 의존성 관리, 빌드, 테스트, 배포 등을 쉽게 할 수 있게 도와준다. 하지만 이 둘은 구조와 철학에서 큰 차이가 있다.

1. 언어 및 스크립트 방식

2. 의존성 관리 방식

3. 빌드 속도

4. 구성 방식 및 유연성

wongakim-99 commented 1 month ago

다시 본론으로

postman을 켜서 메서드를 GET으로 선택하고 URL을 http://localhost:8080/api/hello 를 입력하고 send 버튼을 클릭 Image

이처럼 REST 컨트롤러를 사용하면 REST API로 설계된 URL 요청을 받아 처리할 수 있다. 그렇다면 REST 컨트롤러와 일반 컨트롤러의 차이점은 무엇일까?

REST 컨트롤러와 일반 컨트롤러의 차이

REST 컨트롤러(FirstApiController)는 JSON이나 텍스트 같은 데이터를 반환하는 반면 일반 컨트롤러(FirstController)는 뷰 페이지 반환

wongakim-99 commented 1 month ago

REST API 구현

1. GET 요청 처리 (전체 게시글 조회)

그 다음에 http://localhost:8080/api/articles 로 GET요청 날리면 기존에 우리가 저장해놨던 더미 데이터 3개가 아래와 같이 잘 뜰거임 Image

2. GET 요청 처리 (단일 게시글 조회)

wongakim-99 commented 1 month ago

POST 구현하기

  1. @PostMpping 으로 "/api/articles" 주소로 오는 URL 요청을 받는다.
  2. 반환형이 Article인 create() 라는 메서드를 정의하고 수정할 데이터를 dto 매개변수로 받아 온다. 이렇게 받아 온 dto는 DB에서 활용할 수 있도록 엔티티로 변환해 article 변수에 넣고, articleRepository를 통해 DB에 저장한 후 반환한다.
  3. 서버를 재시작하고 postman에서 POST 요청을 보내면 200OK 로 성공응답이 돌아오지만 응답 본문에 title 과 content가 null로 나온다. Image
  4. 웹 페이지에 게시판 폼을 만들고 데이터를 생성할 때는 컨트롤러의 메서드의 매개변수로 dto를 받아 오기만 하면 됐다. 그러나 REST API 에서 데이터를 생성할 때는 JSON 데이터를 받아 와야 하므로 단순히 매개변수로 dto를 쓴다고 해서 받아 올 수 있는 게 아니다. 해결방법 : dto 매개변수 앞에 @RequestBody라는 어노테이션을 추가해 줘야 한다. 이렇게 하면 요청 시 본문(BODY)에 실어 보내는 데이터를 create() 메서드의 매개변수로 받아 올 수 있다.
wongakim-99 commented 1 month ago

REST API : PATCH 구현하기

  1. @PatchMapping 으로 "/api/articles/{id}" 주소로 오는 URL 요청을 받는다.
  2. 반환형이 Article인 update() 라는 메서드를 정의하고 매개변수로 요청 URL의 id와 요청 메시지의 본문 데이터를 받아 온다.
  3. 메서드의 본문은 네 부분으로 나누어 작성
    • 수정용 엔티티 생성
    • DB에 대상 엔티티가 있는지 조회
    • 대상 엔티티가 없거나 수정하려는 id가 잘못됐을 경우 처리
    • 대상 엔티티가 있으면 수정 내용으로 업데이트하고 정상응답 보내기
Article article = dto.toEntity();

클라이언트에서 받은 수정 데이터가 담긴 dto를 DB에서 활용할 수 있도록 엔티티로 변환해 article 변수에 저장

Article target = articleRepository.findById(id).orElse(null);

DB에서 대상 엔티티를 조회해 가져옴. articleRepository.findById(id) 를 통해서 DB에서 해당 id를 가진 엔티티를 가져오되 없다면 null을 반환. 이렇게 반환한 값은 target이라는 이름의 변수에 저장

wongakim-99 commented 1 month ago

잘못된 요청 처리하기

잘못된 요청이 들어온 경우를 처리해보자. 예를 들어, id 가 1,2,3 번까지 있는데 100번 데이터의 수정 요청이 들어왔다면 DB에 대상이 없을 것이다. 또 수정 요청은 1번으로 했는데, 요청 본문의 id가 3번이라면 요청 자체가 잘못된 것이다. 이런 경우를 처리하는 코드를 작성해보자.

  1. 대상 엔티티가 없거나 (target == null) 수정 요청 id와 본문 id 가 다를(id != article.getId) 경우 잘못된 요청이므로 조건문을 실행한다.
  2. 잘못된 요청임을 확인할 수 있도록 id 와 article의 내용을 로그로 찍는다.
  3. 클라이언트 요청 오류이므로 상태 코드 400을 반환해야 한다. 그런데 update() 메서드의 반환형을 단순히 Article로 하면 안된다. Article을 ResponseEntity에 담아서 반환해야만 반환하는 데이터의 상태코드를 실어 보낼 수 있다.
wongakim-99 commented 1 month ago

DELETE 구현하기

@DeleteMapping 어노테이션을 쓰고 메서드 안에 크게 3부분으로 나누어 작성하겠다.

  1. DB에서 대상 엔티티가 있는지 조회
  2. 대상 엔티티가 없어서 요청 자체가 잘못됐을 경우 처리
  3. 대상 엔티티가 있으면 삭제하고 정상 응답(200) 반환
wongakim-99 commented 1 month ago

정리

  1. REST API REST 는 HTTP URL로 서버의 자원(resource)을 명시하고 HTTP 메서드(GET, POST, PATCH/PUT, DELETE)로 해당 자원에 대해 CRUD(생성, 조회, 수정, 삭제)하는 것을 말한다. 그리고 API는 클라이언트가 서버의 자원을 요청할 수 있도록 서버 측에서 제공하는 인터페이스(interface)이다. 결국 REST API란 REST 기반으로 API를 구현한 것이라고 할 수 있다.

  2. REST 컨트롤러 REST API로 설계된 URL 요청을 받아 처리하는 컨트롤러이다. 일반 컨트롤러가 뷰 페이지를 반환한다면 REST컨트롤러는 JSON이나 텍스트같은 데이터를 반환한다.

  3. REST 컨트롤러의 특징

    • @RestController 어노테이션을 붙여 선언
    • 클라이언트의 데이터 조회, 생성, 수정, 삭제 요청을 HTTP 메서드에 맞게 각각 @GetMapping, @PostMapping, @PatchMapping, @DeleteMapping 으로 받아 처리한다.
  4. ResponseEntity REST 컨트롤러의 반환형, 즉 REST API의 응답을 위해 사용하는 클래스이다.REST API 요청을 받아 응답할 때 이 클래스에 HTTP 상태 코드, 헤더, 본문을 실어 보낼 수 있다.

  5. HttpStatus HTTP 상태 코드를 관리하는 클래스로, 다양한 Enum 타입과 관련한 메서드를 가진다. 상태 코드 200은 HttpStatus.OK, 201은 HttpStatus.CREATED, 400은 HttpStatus.BAD_REQUEST 등으로 관리됨