반응형

↓↓↓ 이전내용

2024.01.05 - [Web Application/Backend] - [REST API] Spring Boot로 REST API CRUD 간단 구현 (7) - JUnit TEST service 계층 test 예제

 

[REST API] Spring Boot로 REST API CRUD 간단 구현 (7) - JUnit TEST service 계층 test 예제

↓↓↓ 이전 내용 2023.12.30 - [Web Application/Backend] - [REST API] Spring Boot로 REST API CRUD 간단 구현 (6) - JUnit TEST 기본 설정 및 repository 계층 test 예제 [REST API] Spring Boot로 REST API CRUD 간단 구현 (6) - JUnit TEST

im-gonna.tistory.com

 

이전 내용에 이어서 이번에는 최상단 layer인 Controller의 test를 진행해보자.

 

main에 있는 controller class 이름에 ctrl+shift+t를 눌러서 test 클래스를 자동 생성해준다.

 

💡 Controller test class

 

import 되어있는 jupiter assertion은 제거한다. (이전에 설명했지만 기본 assertion 사용!)

 

  • 클래스 어노테이션 추가

외부 data와 직접 상호작용하는 계층이기 때문에 test class에 어노테이션을 추가해준다.

 

그리고 컨트롤러 클래스를 인자로 가져온다.

 

  • mockMvc 주입

 

web layer url test이기 때문에, 모의 MVC가 필요하다.

따라서 MockMvc 객체를 하나 선언하여 사용한다.

 

  • 필요한 객체 선언

controller 레이어는 service 레이어와 통신하기 때문에 모의 service를 만들어주어야 한다.

그리고 cloudvendor 인스턴스 2개를 각각 만들어 주고, 이들을 담을 리스트로 하나 선언해준다.

 

  • setUp 메서드

 cloudVendorOne 인스턴스와 cloudVendorTwo 인스턴스를 생성해주고, list에 담아준다.

 

 

  • getCloudVendorDetails()의 test 메서드

  • controller에 정의된 getCloudVendorDetails()의 로직을 보면, cloudVendorService의 getCloudVendor()를 호출한 결과를 반환한다.
  • 따라서 cloudVendorService의 getCloudVendor()에 cloudVendorId가 1로 전달하며, 이 메서드를 호출하면 cloudVendorOne을 return하여 성공적으로 보이도록 한다.
  • 그리고 mockMvc의 perform메서드를 통해 get 동작을 수행하도록 하고 url은 controller에서 지정한대로 cloudVendorId에 따라 달라지므로, /cloudvendor/1 로 가져오도록 한다.
  • andDo(print()): 이 메서드는 테스트 결과를 출력한다. 테스트가 실행될 때 컨트롤러가 반환하는 HTTP 응답 등의 정보를 콘솔에 출력하여 디버깅이나 테스트 결과 확인을 도와준다.
  • andExpect(status().isOk()): 이 메서드는 특정한 조건을 검증하는데, 여기서는 HTTP 응답의 상태 코드가 "200 OK"인지 확인한다. 만약 상태 코드가 200이 아니면 테스트는 실패하는 것이다.
  • 여기까지하면, perform get에서 빨간 줄이 쳐질 것이다. 예외처리가 필요하다. 메서드에 throws Exception을 추가해주자.

getCloudVendorDetails test 결과

 

테스트 결과 성공이다.

getCloudVendorDetails()의 test를 위해 모의(mock) http 서블릿 요청이 제대로 구축되는 것을 확인할 수 있다.

(이는 마치 레포지토리 레이어에서 내장된 데이터베이스 h2를 사용하는 것과 유사한 상황이다)

파라미터와 헤더는 아무것도 필요로 하지 않았다.

 

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Type:"application/json"]
     Content type = application/json
             Body = {"data":{"vendorId":"1","vendorName":"Amazon","vendorAddress":"USA","vendorPhoneNumber":"xxxxx"},"httpStatus":"OK","message":"Requested Vendor Details are given here"}
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

 

그 아래 더 내려서 response 부분을 보면, status는 200, 에러 사항 없음, body data에 controller에서 반환하는 CloudVendorOne에 대한 data가 담겨져 있는 것을 확인할 수 있다.

 

 

  • getAllCloudVendorDetails()의 test 메서드

  • 앞의 get메서드와 로직은 유사하나, service 메서드를 getAll로 수정해주고, 가져오는 단위가 list이기 때문에, 초기에 설정해주었던 cloudVendorList를 return하도록 한다.
  • 모든 cloudVendor를 가져올 때에는 기본 url을 사용하기 때문에, /cloudvendor로 mapping되도록 한다. 

getAllCloudVendorDetails test 결과

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Type:"application/json"]
     Content type = application/json
             Body = [{"vendorId":"1","vendorName":"Amazon","vendorAddress":"USA","vendorPhoneNumber":"xxxxx"},{"vendorId":"2","vendorName":"GCP","vendorAddress":"UK","vendorPhoneNumber":"yyyyy"}]
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

 

body를 보면 one, tow 모두 값을 가져오는 것을 확인할 수 있다.

 

 

  • deleteCloudVendorDetails()의 test 메서드

  • controller의 delete 메서드 로직을 보면, service의 delete메서드를 호출하고, 이 안에서는 Success 메시지를 반환하도록 되어있다.
  • 따라서 cloudvendorId가 1인 cloudvendor를 delete하도록 service 메서드가 호출되면, Success를 return하도록 하여, service의 delete 메서드 호출을 성공적으로 보이게 한다.
  • 그리고 mockMvc의 perform을 통해 delete mapping시 url은 /cloudvendor/1에 매핑되도록 한다.

deleteCloudVendorDetails test 결과

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Type:"text/plain;charset=UTF-8", Content-Length:"33"]
     Content type = text/plain;charset=UTF-8
             Body = Cloud Vendor Deleted Successfully
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

 

response의 body를 보면 controller의 해당 메서드에서 반환하는 값인 메시지를 반환하는 것을 확인할 수 있다.

 

 

  • createCloudVendorDetails()의 test 메서드

 

  • controller에서 create메서드 로직을 살펴보면, cloudVendorService의 create메서드를 호출한다. 그 메서드 안에서는 success를 return한다.
  • 따라서 create메서드가 호출되면 Sucess를 return하도록 한다.
  • perform의 post mapping 시 /cloudvendor url과 mapping되도록 한다.
  • 그런데, 내용으로 전달되는 data를 json형식으로 전달하여야 하기 때문에, contentType을 json으로 지정해준다.
  • 그런데, cloudvendor는 entity이기 때문에, json형식으로 바꾸어주기 위해 위 코드를 작성해준다.
  • json 형식의 cloudvendor를 requestJson에 저장해주고, requestJson을 content에 넣는다.

createCloudVendorDetails test 결과

 

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Type:"text/plain;charset=UTF-8", Content-Length:"33"]
     Content type = text/plain;charset=UTF-8
             Body = Cloud Vendor Created Successfully
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

 

response의 body를 보면 controller의 해당 메서드에서 반환하는 값인 메시지를 반환하는 것을 확인할 수 있다.

 

 

  • updateCloudVendorDetails()의 test 메서드

  • 앞의 create 메서드와 로직이 유사하기 때문에, service 메서드의 create를 update로 변경하고, perform메서드는 post를 put으로 변경한다.

updateCloudVendorDetails test 결과

 

MockHttpServletResponse:
           Status = 200
    Error message = null
          Headers = [Content-Type:"text/plain;charset=UTF-8", Content-Length:"33"]
     Content type = text/plain;charset=UTF-8
             Body = Cloud Vendor Updated Successfully
    Forwarded URL = null
   Redirected URL = null
          Cookies = []

 

역시 response의 body에는 controller의 해당 메서드에서 반환하는 값인 메시지를 담고 있다.

 

모든 unit test를 마쳤으므로, 전체 test class의 test coverage를 확인해보자.

 

controller의 test coverage 결과

test coverage가 100% 인것을 확인하였다.

 

summery

 

 

↓↓↓ 다음 내용

2024.01.25 - [Web Application/Backend] - [REST API] Spring Boot로 REST API 프로젝트 (9) - Swagger로 api document 생성하기

 

[REST API] Spring Boot로 REST API 프로젝트 (9) - Swagger로 api document 생성하기

↓↓↓이전내용 2024.01.10 - [Web Application/Backend] - [REST API] Spring Boot로 REST API 프로젝트 (8) - JUnit TEST controller 계층 test 예제 [REST API] Spring Boot로 REST API 프로젝트 (8) - JUnit TEST controller 계층 test 예제 ↓

im-gonna.tistory.com

반응형
반응형

 

↓↓↓ 이전 내용

2023.12.15 - [Web Application/Backend] - [REST API] Spring Boot로 REST API CRUD 간단 구현 (5)-사용자 정의 ResponseEntity

 

[REST API] Spring Boot로 REST API CRUD 간단 구현 (5)-사용자 정의 ResponseEntity

이전 내용↓↓↓ 2023.11.22 - [Web Application/Backend] - [REST API] Spring Boot로 REST API CRUD 간단 구현 (4)-예외처리/handle Exception [REST API] Spring Boot로 REST API CRUD 간단 구현 (4)-예외처리/handle Exception 이전 내용↓

im-gonna.tistory.com

💡 JUnit test 기본 설정

 

작성한 코드가 백에서 잘 동작하는 지를 확인하기 위해 TEST하는 기능이 있다.

스프링 부트에서는 JUnit을 활용하여 테스트 한다.

 

JUnit을 이용한 테스트 시 다음 네가지 종속성을 확인해야 한다.

JUNIT5, AssertJ, Mockito, H2Database

 

기본적으로 spring initializer로 프로젝트 생성 시 앞에 3개는 자동으로 종속되어 있다.

build.gradle의 dependency를 보면 확인이 가능하다.

 

따라서 h2 database만 따로 종속성을 추가해준다.

test시에 사용할 것이므로 다음 코드를 추가한다.

testImplementation 'com.h2database:h2:1.4.200'

 

test 사용 시 필요한 기능을 갖춘 종속성은 모두 추가해주었다.

 

그렇다면 test는 어디서 진행되는 것일까?

 

 

디렉토리를 보면 main과 test가 나란히 있는 것을 알 수 있다.

우리는 지금까지 main 이하에 디렉토리와 클래스를 생성해가며 실행했었지만, 나란히 존재하는 test 디렉토리에서는 unit test를 진행할 수 있다. 여기서 test를 실행한다.

 

실제 프로젝트가 돌아가는 동작은 main에서 일어난다. main에도 java와 resources 하위 디렉토리가 존재한다.

따라서 test를 위한 test 디렉토리 하에도 java와 병렬하게 resources 디렉토리를 생성해주자.

resources 디렉토리를 생성하였으면 그 안에 application.yml 파일을 추가해준다.

 

테스트 시 사용하는 데이터베이스는 h2이기 때문에, 스프링부트에 내장된 h2 데이터베이스를 연동하기 위해서 아래 코드를 yml파일에 추가해준다.

spring:
  datasource:
    url: jdbc:h2://mem:db;DB_CLOSE_DELAY=-1
    username: sa
    password: sa
    driver-class-name: org.h2.Driver

  # The SQL dialect makes Hibernate generate better SQL for the chosen database
  jpa:
    properties:
      hibernate:
        dialect: org.hibernate.dialect.H2Dialect
    show-sql: true

  #JPA settings
  jpa.hibernate.ddl-auto: create-drop

 

create-drop으로 하는 이유는 스프링부트를 종료했을 때 모두 drop하고 새로운 애플리케이션이 돌아갔을 때 새로 받기 위함이다.

 

💡 Repository test

 

이제 테스트를 할 기초 준비를 마쳤으니, repository의 동작부터 test해보자.

 

main의 형태와 동일하게 test에서도 repository 디렉토리를 만들어주고, 그 아래 CloudVendorRepositoryTest 클래스를 추가하여 CloudVendorRepository를 test한다.

 

 

이제 클래스를 test클래스로 정의해주자.

 

repository test 클래스를 test하기 위해서 @DataJpaTest 어노테이션을 추가해 식별해준다.

이때, 테스트 작업을 할 때, 모든 초기화 작업을 수행하는 것이 모범 사례이다.

무슨 의미이냐 하면, 테스트를 할 때도 비교할 만한 대상이 있어야 하기 때문에, 테스트 용 데이터를 넣어준다는 것이다.

 

main에서 만들어준 CloudVendorRepository가 정상 동작하는 지 확인해야 하기 때문에, repository를 하나와, 데이터를 저장해주기 위해 CloudVendor 객체를 하나 정의해 준다.

 

  • 설정 메서드

 

설정 메서드를 통해 각 test케이스가 실행되기 전에, 미리 데이터베이스에 테스트 용 객체를 생성해준다.

위와 같은 값을 가지는 CloudVendor 객체를 생성해주고, 데이터 베이스에 저장해준다.

 

  • 해제 메서드

 

해제 메서드를 통해 각 test케이스가 실행되고 난 후에는, 다시 객체를 반환하여 메모리를 반환해준다.

 

 

  • 성공 테스트

먼저, 성공적으로 조회가 된 경우에 대한 테스트를 정의해보자.

  • 테스트 케이스를 사용할 때마다 @Test 어노테이션을 사용하여, 아래 메서드가 일반 메서드가 아니라, 테스트 케이스임을 식별할 수 있다. 따라서 이 표시가 있는 메서드 옆에는 개별적으로 실행할 수 있도록 재생 표시가 있는 것을 확인할 수 있다.
  • 이 test의 메서드 이름은 testFindByVendorName_Found이다.
  • cloudVendorRepository의 findByVendorName을 통해 "Amazon"을 이름으로 가진 cloudVendor를 찾도록 하여 조회 결과를 list에 저장한다.
  • assertThat을 통해 실제로 예상되는 값을 반환하는지 확인할 수 있다.
  • asserThat을 통해 list에 저장된 cloudVendor의 id가, 우리가 설정해준 Amazon의 id와 동일한지 확인한다.
  • asserThat을 통해 list에 저장된 cloudVendor의 Address가, 우리가 설정해준 Amazon의 Address와 동일한지 확인한다.
  • 모두 동일하다면 이 테스트는 성공적인 결과를 반환할 것이다.

 

첫번째 테스트에 대한 실행 결과 로그를 살펴보자.

 

select와 insert 쿼리가 실행되며 데이터베이스에 입력하고, 주어진 입력에 대해 조회한 것을 확인할 수 있다.

 

또한 EmbeddedDatabaseFactory 사용을 시작하는 것과 함께, 스프링부트에 내장된 jdbc h2 데이터베이스를 사용한다는 것을 확인할 수 있다.

즉, 테스트 시에는 내 pc에 있는 mysql db가 아닌, 내장된 h2 데이터베이스를 사용하여 테스트 한다.

 

  • 실패 테스트

다음으로, 조회에 실패한 경우에 대한 테스트를 정의해보자.

 

 

  • 데이터베이스에 존재하지 않는 cloudvendor 이름을 찾으려고 하는 실패 테스트 케이스에 대한 테스트이다.
  • 현재는 내장 db에 Amazon 객체만 들어가 있는 상태임을 명심하자.
  • 이 메서드는 testFindByVendorName_NotFound이다.
  • 마찬가지로, repository 조회를 GCP로 조회하고 그 결과를 list에 저장하도록 한다.
  • 당연히 내장 db에는 Amazon에 대한 데이터밖에 없기 때문에 GCP로 조회한 결과는 비어있어야 한다.
  • 이를 확인하기 위해 asserThat 메서드를 통해서 lsit가 비어 있음이 true임을 확인하도록 한다.
  • 이 assertion이 통과하면 리스트는 비어있다는 것이고, 이 테스트는 성공적으로 통과하게 된다.

 

💡 respository test 클래스에 대한 실행 결과

 레포지토리 테스트 클래스를 실행한 결과, 내부에 있는 unit 테스트 두개(성공사례, 실패사례) 모두 성공적으로 잘 테스트 되었음을 ✔표시를 통해 확인할 수 있다.

 

만약 test case가 잘못 적용된다면 어떻게 될까? 다음과 같이 실패 케이스에서 isTrue 부분을 isFalse로 바꾸어 보자.

 

 

테스트 결과 NotFound 테스트에서는 실패 표시가 나타나고, 왜 실패했는지 옆에 로그를 통해 설명해주고 있다.

테스트 로직 내에서는 FALSE를 기대했으나, 실제 값은 TRUE를 반환하므로 테스트 결과가 실패로 뜨고 있다.

따라서 이런 로그를 통해 테스트 내에서 어떤 값을 예상하고 출력하는지 확인함으로써 수정할 수 있다.

 

이렇게 Repository test를 수행할 수 있다!

 

 

↓↓↓ 다음 내용

2024.01.05 - [Web Application/Backend] - [REST API] Spring Boot로 REST API CRUD 간단 구현 (7) - JUnit TEST service 계층 test 예제

 

[REST API] Spring Boot로 REST API CRUD 간단 구현 (7) - JUnit TEST service 계층 test 예제

↓↓↓ 이전 내용 2023.12.30 - [Web Application/Backend] - [REST API] Spring Boot로 REST API CRUD 간단 구현 (6) - JUnit TEST 기본 설정 및 repository 계층 test 예제 [REST API] Spring Boot로 REST API CRUD 간단 구현 (6) - JUnit TEST

im-gonna.tistory.com

 

반응형
반응형

이전 내용↓↓↓

2023.11.22 - [Web Application/Backend] - [REST API] Spring Boot로 REST API CRUD 간단 구현 (4)-예외처리/handle Exception

 

[REST API] Spring Boot로 REST API CRUD 간단 구현 (4)-예외처리/handle Exception

이전 내용↓↓↓ 2023.11.13 - [Web Application/Backend] - [REST API] Spring Boot로 REST API CRUD 간단 구현 (3) [REST API] Spring Boot로 REST API CRUD 간단 구현 (3) 이전 내용↓↓↓ 2023.11.11 - [Web Application/Backend] - [REST API] S

im-gonna.tistory.com

 

 

응답 형태를 사용자가 원하는 형태로 받을 수 있도록 ResponseEntity를 커스텀하기!

 

이전에는 get을 통해 특정 cloudvendor의 정보를 불러들일 때 아래와 같이 4가지 속성에 대한 정보만 나왔다.

왜냐하면, 컨트롤러의 get 메서드에서 CloudVendor 서비스의 get메서드를 통해 데이터베이스에 있는 데이터를 가져오기 때문이다.

 

나는 컨트롤러의 get 메서드를 호출하면, 위의 데이터 뿐만아니라 httpStatus와, 특정 메시지도 가져오도록 하고 싶다면??

ResponseEntity를 세가지를 담도록 커스텀해주면 된다!

 

다른 형태로 담고싶다면 그것대로 커스텀해주면 된다.

 

아래 절차를 따라 ResponseEntity를 커스텀하고 다시 get을 통해 확인해보자!

 

💡 ResponseEntity 커스텀하기

 

1. response 패키지를 생성해준 후, ResponseHandler 클래스를 생성해준다.

 

2. ResponseHandler 클래스안에 reponseBuilder 메서드를 정의해준다.

public static ResponseEntity<Object> responseBuilder(
            String message, HttpStatus httpStatus, Object responseObject
    )

 

  • 이 메서드는 responseHandler 객체를 생성하지 않아도 사용할 수 있도록, static으로 선언해준다.
  • ResponseEntity를 사용자 입맛대로 커스텀해줄 것이기 때문에, ResponseEntity를 반환하도록 한다.
  • 이 메서드의 인자로는 string타입의 message와, HttpStstus와, 임의의 데이터인 Object타입의 responseObject를 갖는다.

3. responseBuilder 메서드의 로직은 다음과 같이 동작할 수 있다.

{
        Map<String, Object> response = new HashMap<>();
        response.put("message", message);
        response.put("httpStatus", httpStatus);
        response.put("data", responseObject);

        return new ResponseEntity<>(response, httpStatus);
    }
  • string과 object를 속성으로 갖는 Map을 하나 생성하여 HashMap으로 생성한다.
  • 인자로 받은 것들을 Map에 message, httpStatus, reponseObject로 갖도록 한다.
  • 그리고 ResponseEntity로 map과 httpStatus를 담아 반환한다. 

4. ResponseHandler 클래스의 코드 전문이다.

 

 

💡 커스텀한 ResponseEntity를 반환하도록 컨트롤러 수정하기

 

  • getCloudVendorDetails함수는 원래 url로 특정 vendorId가 들어오면, CloudVendor 엔티티 객체를 반환하도록 되어있었다.
  • 그러나, 지금은 커스텀한 ResponseEntity를 통해, 위와 같이 "특정 메시지"와 HttpStaus.OK와 기존의 데이터인 CloudVendor 엔티티 객체를 responseBuilder메서드의 인자로 넣어 커스텀한 ResponseEnity로 반환되도록 수정해주었다.
  • 이 get 메서드에서 바뀐 부분은 반환타입이 커스텀한 ResponseEnity라는 것이다.

 

💡 결과 확인
  • spring boot를 실행하고, postman에서 C5에 대해서 get mapping 해준다
  • mapping 결과 다음과 같이 data, httpStatus, message가 모두 반환되었다.

  • 이로써 내가 커스텀한대로 Response의 형태가 바뀌어 반환된 것을 확인할 수 있다.

 

↓↓↓ 다음 내용

2023.12.30 - [Web Application/Backend] - [REST API] Spring Boot로 REST API CRUD 간단 구현 (6) - JUnit TEST 기본 설정 및 repository 계층 test 예제

 

[REST API] Spring Boot로 REST API CRUD 간단 구현 (6) - JUnit TEST 기본 설정 및 repository 계층 test 예제

↓↓↓ 이전 내용 2023.12.15 - [Web Application/Backend] - [REST API] Spring Boot로 REST API CRUD 간단 구현 (5)-사용자 정의 ResponseEntity [REST API] Spring Boot로 REST API CRUD 간단 구현 (5)-사용자 정의 ResponseEntity 이전 내

im-gonna.tistory.com

 

반응형
반응형

이전 내용↓↓↓

2023.11.13 - [Web Application/Backend] - [REST API] Spring Boot로 REST API CRUD 간단 구현 (3)

 

[REST API] Spring Boot로 REST API CRUD 간단 구현 (3)

이전 내용↓↓↓ 2023.11.11 - [Web Application/Backend] - [REST API] Spring Boot로 REST API CRUD 간단 구현 (2) [REST API] Spring Boot로 REST API CRUD 간단 구현 (2) 지난 시간, 간단한 model을 생성하고, 해당 model을 클래스

im-gonna.tistory.com

 

이전 포스팅에서 cloudVendor의 기본적인 CRUD가 동작하는 것을 확인하였다.

 

그런데 get mapping에서 cloud vendor가 존재하지 않을 때는 출력 시 오류가 발생하는 것을 확인하였다.

 

오늘은 이러한 오류가 발생했을 때, 즉 예외에 대해서는 어떻게 처리해야 하는지에 대해 알아볼 것이다.

 


✔ 웹 개발에 있어서 중요한 점

✅ 오류나 예외 상황 발생 시 사용자가 이해하기 쉽도록 상황에 대한 정보를 충분히 전달할 수 있어야 한다.

 

따라서 개발자는 로직 구현에 있어서 예외가 발생하는 부분을 찾아서, 예외처리를 해주어야 한다.

 

이제 프로젝트에서 발생한 오류를 가지고 예외처리를 해보자.

 

예외처리 전 cloud vendor get시 에러 발생

예외 처리를 하기 전에는 위와 같이 데이터베이스에 C3가 없을 때, C3를 get하는 mapping과정에서 반환 값이 없어서 에러가 발생하였다.

우리는 이를 CloudVendorNotFoundException이라고 할 것이다.

이 예외를 처리하기 위해서 다음 단계를 따른다.

 

 

1. 예외 처리를 위한 클래스들을 담은 exception 패키지를 하나 생성한다.

2. exception 패키지에 CloudVendorNotFoundException 클래스를 생성한다.

CloudVendorNotFoundException class

  • 이 클래스를 예외 클래스로 인식하기 위해서 RuntimeException 클래스를 extends한다.
  • 이 클래스에 대한 두 타입의 생성자를 자동생성해준다.
    - 인자로 message만 담은 생성자
    - 인자로 message와 Throwable 객체인 cause를 담은 생성자

*Throwable이 뭐야? (접은글을 확인하세요)

더보기

- Throwable은 java에서 오류나 예외를 처리하는 최상위 클래스로, exception과 error 클래스를 하위에 둔다.

- Throwable에는 두개의 메서드가 있는데,

  • getMessage(): 예외 또는 오류에 대한 상세한 메시지를 반환합니다.
  • printStackTrace(): 예외 또는 오류의 추적 정보를 출력합니다.

3. 클라이언트에게 예외 정보를 전달하기 위해서, 정보를 담는 CloudVendorException 클래스를 생성한다.

  • 위 3개의 속성을 추가해준다. 
  • 사용자에게 보여질 오류 메시지 = message
  • 예외 정보 = throwable
  • http 상태 정보 = httpStatus

  • 기본 생성자와, getter들을 자동 생성해준다.

 

4. 이제 예외 처리를 할 CloudVendorExceptionHandler 클래스를 생성해준다. 프론트와 직접 연결되는 컨트롤러라고 생각하면 된다.

  • 우리는 지금 CloudVendor를 찾을 수 없다는 예외에 대한 처리를 다루어야 하기 때문에, handleCloudVendorNotFoundException 메서드를 선언해주자.
  • rest api이기 때문에 반환타입은 ResponseEntity로 하고, Object유형으로 반환하도록 한다.
  • 그리고 예외 발생 시의 CloudVendorNotFoundException 객체를 인자로 받아서 이 메서드의 인수로 매핑되도록 한다.
  • 로직 내에서는 예외 정보를 받아서 적절히 처리 후 반환해주어야 하기 때문에, 예외 정보를 담아 보낼 CloudVendorException 객체를 하나 생성한다.
  • 인자로 받은 cloudVendorNotFoundException의 message와, cause, 그리고 HttpStatus를 not_found로 하여 생성되도록 한다.

  • return 시에는 ResponseEntity에 로직내에서 생성한 cloudVendorException 클래스와 HttpStatus를 인자로 넣어 반환한다.

  • 그리고 이 메서드가 어떤 예외를 처리할 지 알려줘야 하기 때문에, 메서드 위에 @ExceptionHandler라는 어노테이션을 추가해주고, 이는 메서드에 의해 처리될 예외의 목록을 value로 갖는다.
  • 즉 CloudVendorNotFoundException이 발생하면 value에 명시되어 있기 때문에, 해당 메서드의 인자로 매핑될 수 있는 것이다. 만약 여러개의 Exception을 한 메서드에서 처리하는 경우, value가 {} 리스트 형태이기 때문에, 쉼표를 하고 여러 개를 추가로 작성해주면 된다.

  • 마지막으로 CloudVedorExceptionHandler 클래스에 대해서 @ControllerAdvice 어노테이션을 달아준다
  • 이 컨트롤러는 이 프로젝트 전반에 걸쳐서 전역적으로 여러 예외처리를 해야 하기 때문이다.

 

5. 이제 이러한 예외가 발생할 수 있는 위치로 돌아가서 어떻게 했을 때 예외가 발생하는 지 확인해야 한다.

  • 서비스 레이어의 get 메서드에서 오류가 발행하였는데, 로직을 보면 return에서 findId를 호출하도록 되어 있다.
  • findId를 통해 반환할 내용이 있다면, cloudVendor에 대한 정보가 출력되겠지만, 찾을 수 없다면 예외를 발생시키고 일반 내부 서버 오류 메시지가 표시된다.
  • 따라서 예외 처리 기능을 추가함으로써 출력되는 메시지를 사용자가 오류에 대해 이해하기 쉽게 더 나은 메시지로 표시하고자 한다.
  • 예외 처리 기능을 추가해준다.

  • if문을 추가하여, findId 호출의 return이 비어있다면 예외를 표시하도록 한다.
    CloudVendorNotFoundException클래스를 throw 문법을 통해 발생시키고, 이때의 message는 오류에 대한 상황 설명을 친숙한 메시지로 제공해준다.
    => "requested cloud vendor does not exist"

6. 실행 결과

  • 클라이언트가 제대로 수신한 적절한 오류 응답 404 not found와 함께 적절한 오류 메시지인 cloud vendor를 찾을 수 없다는 메시지가 출력된 것을 확인할 수 있다.

 

위 과정이 바로 Spring boot rest api 애플리케이션으로 예외 처리를 하는 방식이다.

= 사용자 정의 예외 처리

 

 

다음 내용↓↓↓

2023.12.15 - [Web Application/Backend] - [REST API] Spring Boot로 REST API CRUD 간단 구현 (5)-사용자 정의 ResponseEntity

 

[REST API] Spring Boot로 REST API CRUD 간단 구현 (5)-사용자 정의 ResponseEntity

이전 내용↓↓↓ 2023.11.22 - [Web Application/Backend] - [REST API] Spring Boot로 REST API CRUD 간단 구현 (4)-예외처리/handle Exception [REST API] Spring Boot로 REST API CRUD 간단 구현 (4)-예외처리/handle Exception 이전 내용↓

im-gonna.tistory.com

 

반응형
반응형

이전 내용↓↓↓

2023.11.11 - [Web Application/Backend] - [REST API] Spring Boot로 REST API CRUD 간단 구현 (2)

 

[REST API] Spring Boot로 REST API CRUD 간단 구현 (2)

지난 시간, 간단한 model을 생성하고, 해당 model을 클래스 내에서 직접 생성하고, 클라인언트로부터 받은 정보를 통해 CRUD를 구현해 보았다. ↓↓↓ 2023.11.08 - [Web Application/Backend] - [REST API] Spring Boot

im-gonna.tistory.com

 

DB연동에 이어서, db에 있는 정보를 CRUD 처리해보자.

 

💡 Model을 entity로 설정
  • model인 CloudVendor를 entity로써 하나의 테이블로 선언해주기 위해, CloudVendor 클래스를 수정해준다.

  • CloudVendor를 Entity로 선언해주고, Table 이름은 "cloud_vendor_info"로 설정하여 생성 시 이 이름으로 table이 생성된다.
  • 이 테이블의 primary key를 vendorId로 설정해주기 위해, vendorId위에 @Id 를 추가해준다.

 

💡 CloudVendor Repository 생성하기
  • CloudVendor Entity에 대한 데이터베이스 작업인 CREATE, READ, UPDATE, DELETE를 수행하기 위해서 JpaRepository 인터페이스를 상속받아(extends) 사용한다.

  • 일단 repository 폴더를 생성해주고, 인터페이스로 CloudVendorRepository 파일을 추가한다.

  • CloudVendorRepository를 생성할 때, JpaRepository에 Entity이름과, Entity의 pk 데이터타입을 이용해서 define할 수 있다.

 

💡 CloudVendorService 생성하기
  • Service 단에서는 Repository에 접근하여 동작을 수행하도록 한다.

  • Service 폴더를 생성하고, CloudVendorService 라는 이름의 인터페이스를 먼저 생성한다.
  • 인터페이스를 implements하여 사용한 것이 CloudVendorServiceImpl이다.
  • 이렇게 하는 이유는, 인터페이스로 정의해서 서비스 구현체에 직접 의존하지 않고 인터페이스에 의존하게 하여 결합도를 낮추기 위함이다.
public interface CloudVendorService {//인터페이스로 정의해서 서비스 구현체에 직접 의존하지 않고 인터페이스에 의존하게 하여 결합도를 낮춤
    public String createCloudVendor(CloudVendor cloudVendor);
    public String updateCloudVendor(CloudVendor cloudVendor);
    public String deleteCloudVendor(String cloudVendorId);
    public CloudVendor getCloudVendor(String cloudVendorId);
    public List<CloudVendor> getAllCloudVendors();
}
  • CloudVendorService 인터페이스이다.
  • 구체적인 동작은 없고, 메서드 정의만 해두었다.
  • CloudVendor model을 CRUD하는 각각의 메서드이다.
    createCloudVendor : 입력으로 받은 cloudVendor 정보로 CloudVendor를 생성하는 메서드이다.
    updateCloudVendor : 입력으로 받은 cloudVendor 정보로 CloudVendor를 수정하는 메서드이다.
    deleteCloudVendor : 입력으로 받은 특정 cloudVendorId에 대한 CloudVendor 정보를 삭제하는 메서드이다.
    getCloudVendor : 입력으로 받은 특정 cloudVendorId에 대한 CloudVendor 정보를 읽어오는 메서드이다.
    getAllCloudVendor :  모든 CloudVendor 정보를 읽어오는 메서드로, List 형태로 가져온다.
@Service
public class CloudVendorServiceImpl implements CloudVendorService{
    CloudVendorRepository cloudVendorRepository;

    public CloudVendorServiceImpl(CloudVendorRepository cloudVendorRepository) {
        this.cloudVendorRepository = cloudVendorRepository;
    }

    @Override
    public String createCloudVendor(CloudVendor cloudVendor) {
        //more Business logic
        cloudVendorRepository.save(cloudVendor);
        return "Success";
    }

    @Override
    public String updateCloudVendor(CloudVendor cloudVendor) {
        //more Business logic
        cloudVendorRepository.save(cloudVendor);//jpa의 save 메서드의 경우, 이미 있는 값에 대해서는 변경된 내용만 추적해서 수정합니다
        return "Success";
    }

    @Override
    public String deleteCloudVendor(String cloudVendorId) {
        //more Business logic
        cloudVendorRepository.deleteById(cloudVendorId);
        return "Success";
    }

    @Override
    public CloudVendor getCloudVendor(String cloudVendorId) {
        //more Business logic
        return cloudVendorRepository.findById(cloudVendorId).get();
    }

    @Override
    public List<CloudVendor> getAllCloudVendors() {
        //more Business logic
        return cloudVendorRepository.findAll();
    }//이 안에 있는 모든 메서드를 implements 하기 전에는 빨간줄이 떠있음

}
  • CloudService를 implements한 것이 CloudVendorServiceImpl이다.
  • CloudVendorRepository 객체를 하나 생성하고, repository메서드를 이용해 각 메서드를 동작하도록 한다.
  • 처음에 모든 메서드를 implements하기 전에는 빨간 줄이 떠있게 된다. 빨간 표시를 누르면 자동으로 override할 수 있는 틀을 가져오는 버튼이 있다. 이 버튼을 누르면 override해야 할 메서드를 모두 가져와 준다.
  • 각 메서드에 맞는 repository의 메서드를 호출하여 데이터베이스 작업이 진행되도록 한다.
  • repository의 save() 메서드는 입력받은 Entity 정보를 삽입, 수정한다.
    해당하는 pk가 없었다면 삽입, 이미 있는 pk에 일부 정보만 바뀌었다면 수정 하도록 한다.
  • repository의 deleteById() 메서드는 입력받은 Entity의 pk를 통해 해당 정보를 삭제한다.
  • repository의  findById() 메서드는 입력받은 Entity의 pk를 통해 해당 정보를 가져온다.
  • repository의 findAll() 메서드는 모든 Entity 정보를 가져온다.

 

💡 CloudVendor Controller 수정하기
  • Controller의 CloudVendor Controller를 CloudVendorController로 클래스 이름을 수정한다.
  • Controller에서는 서비스 단의 메서드를 호출하여 CRUD를 수행하도록 한다.
public class CloudVendorController {
    CloudVendorService cloudVendorService;

    public CloudVendorController(CloudVendorService cloudVendorService) {
        this.cloudVendorService = cloudVendorService;
    }

    //Read Specific Cloud Vendor Details
    @GetMapping("{vendorId}")
    public CloudVendor getCloudVendorDetails(@PathVariable("vendorId") String vendorId)//위 url 경로로부터 값을 받아와서 사용하므로 어노테이션 추가
    {
        return cloudVendorService.getCloudVendor(vendorId);
    }

    //Read All Cloud Vendor Details
    @GetMapping
    public List<CloudVendor> getCloudVendorDetails()//위 url 경로로부터 값을 받아와서 사용하므로 어노테이션 추가
    {
        return cloudVendorService.getAllCloudVendors();
    }

    //Create Cloud Vendor
    @PostMapping
    public String postCloudVendorDetails(@RequestBody CloudVendor cloudVendor)
    {
        cloudVendorService.createCloudVendor(cloudVendor);
        return "Cloud Vendor Created Successfully";
    }

    //Update Cloud Vendor
    @PutMapping//수정이라 하더라도 특정 id 안받아도됨-> jpa의 save 메서드가 이미 있는 값에 대해서는 변경을 하고, 없으면 삽입을 하는 두가지 역할을 수행하기 때문
    public String updateCloudVendorDetails(@RequestBody CloudVendor cloudVendor)
    {
        cloudVendorService.updateCloudVendor(cloudVendor);
        return "Cloud Vendor Updated Successfully";
    }

    @DeleteMapping("{VendorId}")
    public String deleteCloudVendorDetails(@PathVariable("VendorId") String vendorId)
    {
        cloudVendorService.deleteCloudVendor(vendorId);
        return "Cloud Vendor Deleted Successfully";
    }
}
  • CloudVendorService 객체를 하나 생성한다.
  • @PathVariable은 url로 받는 값을 메서드의 인자로 받아 사용하기 위한 어노테이션이다.
  • @RequestBody는 프론트 단에서 입력한 값을 가져와 메서드의 인자로 받아 사용하기 위한 어노테이션이다.
  • 각각의 CRUD 메서드에 맞는 Service 단의 메서드를 호출하여 수행한다.

 

💡 실행 결과
  • 첫 실행 후에는, 데이터 베이스에 Cloud Vendor Entity에 대한 테이블이 처음 생성되므로, 해당 테이블의 레코드는 Empty set 상태임을 확인할 수 있다.
  • POST MAN을 사용하여 정보를 입력한다.
  • post 모드에서 C1-C5  총 5개의 레코드를 삽입한다.

  • 실행 결과, 잘 삽입되었다는 메시지가 출력된다
Cloud Vendor Created Successfully

 

  • GET 모드에서 C3에 대한 값을 가져온다.

  •  실행 결과 C3에 대한 모델 값이 잘 반환된다.

  • GET 모드에서 모든 모델 값을 가져온다.

  • 실행 결과 모든 모델 값이 잘 반환된다.
  • PUT 모드에서 C4에 대한 모델 값을 address를 London으로 변경해본다.

  • 실행 결과 잘 update되었다는 메시지가 출력된다.
Cloud Vendor Updated Successfully
  • 그리고 GET 모드를 통해 검색한 결과, 변경된 값이 잘 반영된 것을 확인할 수 있다.

  • DELETE 모드를 통해 C3를 삭제해본다.

  • 실행 결과 잘 삭제되었다는 메시지가 출력된다.
Cloud Vendor Deleted Successfully

 

  • GET 모드에서 C3를 읽어들이려고 하니, 예외처리를 하지 않아 에러가 발생한다. 없는 값을 읽으려고 해서 발생하는 오류이다.

  • 이로써 모든 CRUD가 잘 동작하는 것을 확인하였다.
  • 실시간으로 powershell을 통해 db 상태를 확인하며 잘 동작하는 것도 확인하였다.

 

다음시간에는 예외처리를 위한 코드를 추가할 예정이다.

 

 

다음 내용↓↓↓

2023.11.22 - [Web Application/Backend] - [REST API] Spring Boot로 REST API CRUD 간단 구현 (4)-예외처리/handle Exception

 

[REST API] Spring Boot로 REST API CRUD 간단 구현 (4)-예외처리/handle Exception

이전 내용↓↓↓ 2023.11.13 - [Web Application/Backend] - [REST API] Spring Boot로 REST API CRUD 간단 구현 (3) [REST API] Spring Boot로 REST API CRUD 간단 구현 (3) 이전 내용↓↓↓ 2023.11.11 - [Web Application/Backend] - [REST API] S

im-gonna.tistory.com

 

반응형
반응형
💡 REST API를 사용하여 간단한 CRUD 동작을 구현하기

 

✅ 프로젝트 세팅

✔ Spring Initializer를 이용하여 프로젝트 파일 생성
✔ Gradle 선택
✔ 스프링 버전 2.7.x
✔ 자바 버전 11
❗ 참고로 스프링 버전 3.x.x를 선택했다면 자바는 17버전 이상만 가능
✔ Spring Web 종속성 추가
✔ Generate 클릭 후 다운로드 받은 압축파일을 적절한 위치에 풀어준다.

✔ IntelliJ를 열고 프로젝트 파일을 불러온다
✔ File의 setting에서 프로젝트 언어를 gradle > IntelliJ IDEA로 모두 변경하고, 자바 11버전을 사용하도록 설정

 

  • Cloud Vendor(클라우드 공급업체)라는 객체가 있다고 가정한다.
  • 이 객체는 id, name, address, phone number를 정보로 갖는다.
  • 이 객체의 정보를 불러오고, 새로운 정보를 입력(새로운 객체를 생성)하고, 정보를 수정하고, 객체를 삭제하는 CRUD 동작을 rest api를 통해 구현할 것이다.

1. java 디렉토리 하에 아래와 같이 model 디렉토리를 생성한다.

  • model은 객체의 정보를 담는 역할을 한다.
  • model 디렉토리 아래에 CloudVendor라는 클래스를 정의해준다.
  • CloudVendor는 다음 네가지 속성을 갖는다.
    << private >>
    vendorId : String
    vendorName : String
    vendorAddress : String
    vendorPhoneNumber : String
  • 그리고 생성자와 getter/setter 메서드를 갖는다.
    이는 클래스 이름을 오른쪽 마우스로 누르면 generator 기능을 통해 자동 생성 가능하다.

2. java 디렉토리 하에 아래와 같이 controller 디렉토리를 생성한다.

  • 서버가 프론트 단과 정보를 주고 받게 되는 부분이다. 즉, 프론트 단과의 연결 역할을 한다.
  • CloudVendorAPIService라는 클래스를 만들어서 프론트로부터 받은 정보나 서버로부터 전달하고자 하는 정보를 CRUD 처리 하도록 할 것이다.
  • 이 클래스는 REST API로써 동작할 것이기 때문에, class에 @RestController를 붙여 rest api로 만든다.
  • rest api의 특징은 경로가 특정 이름으로 패턴을 가진다는 점이있다. 따라서 @RequestMapping("/cloudvendor")를 통해 8080이하 경로에서 /cloudvendor로 시작하는 경로에서는 이 클래스가 동작하도록 한다.
@RestController
@RequestMapping("/cloudvendor")
public class CloudVendorAPIService {

 

  • 이 클래스에서 사용할 CloudVendor 객체를 하나 생성한다.
CloudVendor cloudVendor;

 

3. Get

  • 특정 id에 해당하는 정보를 읽어오는  READ 동작을 Get 메서드를 통해 구현하자.
  • @GetMapping을 통해서 {}안에 들어가는 vendorId에 해당하는 vendor 정보를 가져오도록 한다.
@GetMapping("{vendorId}")
  • rest api는 json("이름":"값")형식을 반환하기 때문에, json형식으로 값을 return 해야 한다.
public CloudVendor getCloudVendorDetails(String vendorId)
{
    return cloudVendor;
}
  • 현재 코드는 단순히 동작만을 확인하기 위함이므로, cloudVendor에 있는 vendorId랑 들어온 Id가 같은지 비교하는 과정은 생략할 것이다. 

4. POST

  • 새로운 vendor 정보를 생성하는 CREATE 동작을 post 메서드를 통해 구현하자.
  • 함수에 @PostMapping을 추가한다
@PostMapping
  • 프론트로부터 입력받은 CloudVendor를 인자로 받고, 이 클래스에 있는 cloudVendor 객체에 입력받은 값을 저장해준다.
public String postCloudVendorDetails(@RequestBody CloudVendor cloudVendor)
{
    this.cloudVendor = cloudVendor;
    return "Cloud Vendor Created Successfully";
}
  • 프론트로부터 받을 때 @RequestBody를 통해 받아온다.
  • 잘 create되었으면 string 타입의 메시지를 출력한다.

 

5. PUT

  • 기존의 객체의 정보를 수정하는 UPDATE 동작을 put 메서드를 통해 구현하자.
  • 함수에 @PutMapping을 추가한다.
@PutMapping("{VendorId}")
  • 프론트로부터 입력받은 CloudVendor를 인자로 받고, 이 클래스에 있는 cloudVendor 객체에 입력받은 값을 저장해준다.
public String updateCloudVendorDetails(@RequestBody CloudVendor cloudVendor)
{
    this.cloudVendor = cloudVendor;
    return "Cloud Vendor Updated Successfully";
}
  • 잘 update되었으면 string 타입의 메시지를 출력한다.

 

6. DELETE

  • 특정 id에 해당하는 객체 정보를 삭제하는 DELETE 동작을 Delete 메서드를 통해 구현하자.
  • 함수에 @DeleteMapping을 추가한다.
  • 특정 id에 대한 정보를 담은 경로를 요청받았을 경우, 해당 id의 객체를 삭제한다.
@DeleteMapping("{VendorId}")
  • 경로로 들어온 id는 인자에서 사용하고, 요청한 id의 객체를 삭제하기 위해, this. cloudVendor가 null을 가리키도록 한다.
public String deleteCloudVendorDetails(String vendorId)
{
    this.cloudVendor = null;
    return "Cloud Vendor Deleted Successfully";
}
  • 잘 delete되었으면 string 타입의 메시지를 출력한다.

 


프론트엔드를 구성하지 않았기 때문에, 실제로 값을 읽어오거나 할 수 없다. 따라서 POSTMAN 프로그램을 이용해서 직접 값을 json형태로 넣어주면서 CRUD가 잘 동작하는지 확인하였다.

 

일단 GET 메서드를 사용해보았다. 우리가 web에서 주소창에 입력하는 것처럼 똑같이 입력해주면 된다.

http://localhost:8080/cloudvendor/C1 경로를 통해서..

id가 C1인 객체에 대해서 조회해보려고 하였다.

 

아직 cloudvendor에 아무 값도 넣어 준적이 없기 때문에, 당연히 return 결과는 null이므로, 출력창에 아무것도 출력되지 않는 것을 확인할 수 있다.

그러나 잘 동작하였음은 200OK인 상태 코드를 통해 확인할 수 있다.

 

이제, POST 메서드를 사용하여, 값을 입력해보자.

 

실제로 프론트 단에서 값을 읽어올 수 없기 때문에, 위와 같이 요청 Body에 json형태로 vendor 정보를 입력해주었다.

json 형식은 {"이름":"값"} 쌍의 형태인 것을 잊지 말자.

여러 개의 쌍이 있을 경우 ,로 구분해 주어야 한다.

 

위와 같이 값을 입력하고 실행해보니, 아래 잘 create 되었다는 메시지가 출력되고, 200ok 상태임을 확인할 수 있다.

POST도 잘 동작한다.

 

이제 PUT 메서드를 이용해서, 프론트로부터 수정된 값을 받아서 vendor 정보를 수정해보자.

PUT의 경우 POST와 비슷한데, 특정 Id의 정보를 수정하기 위함이므로, url에 id 정보를 추가해주었다.

역시 JSON 형태로 받아올 것이고, 이전 정보에서 Address에 updated를 추가해 주었다.

실행 한 결과 잘 update되었다는 메시지가 출력되고, 200OK 상태인 것으로 보아 put 메서드도 잘 동작하는 것을 확인할 수 있다.

 

잘 수정되었는지 확인하려면 역시 get 메서드를 통해 반환된 값을 확인하면 된다.

 

잘 수정되었다!

 

이제 마지막으로 DELETE 메서드를 이용해 정보를 삭제해보자.

이 역시 특정 Id에 대한 정보를 삭제하기 위함이므로 url에 id정보를 함께 넘겨준다.

c3를 삭제한다. 

실행 결과 잘 delete되었다는 메시지가 출력되고, 200OK상태인 것을 통해, delete 메서드가 잘 동작했음을 확인할 수 있다.

 

잘 삭제 되었는지 get을 통해 확인해보자.

c3가 삭제되었기 때문에, get을 통해 불러도 아무것도 반환하지 않는 것을 확인할 수 있다.

 

이로써 간단한 CRUD를 구현해 보았고, 잘 동작하는 것을 확인하였다.

 

다음 회차↓↓↓

2023.11.11 - [Web Application/Backend] - [REST API] Spring Boot로 REST API CRUD 간단 구현 (2)

 

[REST API] Spring Boot로 REST API CRUD 간단 구현 (2)

지난 시간, 간단한 model을 생성하고, 해당 model을 클래스 내에서 직접 생성하고, 클라인언트로부터 받은 정보를 통해 CRUD를 구현해 보았다. ↓↓↓ 2023.11.08 - [Web Application/Backend] - [REST API] Spring Boot

im-gonna.tistory.com

 

반응형

+ Recent posts