상세 컨텐츠

본문 제목

[스프링2] 5-6장. API를 작성하는 다양한 방법 & 데이터베이스 연동

23-24/Spring 2

by hyom1n 2023. 11. 3. 10:00

본문

728x90

5장. API를 작성하는 다양한 방법

이번 장에서는 각 HTTP 메서드에 해당하는 API(Application Programming Interface)를 개발해 봅시다. 데이터베이스를 설치하기 전 간단한 외부 요청을 받아 응답하는 기능을 구현하여 컨트롤러의 구성을 살펴볼 것입니다.
 
 
 

5.1 프로젝트 설정

5장에서 실습할 프로젝트를 생성합니다. 4장  4.1.2 스프링 공식 사이트에서 프로젝트 생성하기의 방법과 동일하게 생성하면 됩니다. 그러나 4장과 달리 Group을 'com.springboot'로, Artifact와 Name을 'api'로 설정합니다.

스프링 공식 사이트 - https://start.spring.io

 

 
 

5.2 GET API 만들기

GET API서버에서 값을 가져올 때 사용하는 API입니다. GET API를 작성하는 방법은 매우 다양하며, 이번 장에서 다루는 것은 처리 방법 중 하나입니다. 실제 프로그래밍 시에는 HTTP 메서드에 따라 컨트롤러 클래스를 생성하지 않지만, 원활한 실습을 위하여 메서드별로 클래스를 생성하도록 하겠습니다.
우선, api/src/main/java/com.springboot.api/controller 패키지를 생성하고, GetController 클래스를 생성합니다.

api/src/main/java/com.springboot.api/controller 패키지 생성, GetController 클래스 생성

생성한 GetController 클래스를 수정하여 GET API를 작성해 봅시다.

@RestController
@RequestMapping("/api/v1/get-api")
public class GetController {}

GetController 클래스@RequestMapping을 추가하여 내부에 선언되는 메서드 URL 리소스 앞에 추가할 공통 URL을 설정합니다. 이후 GetController 클래스에 선언하는 메서드 주소는 모두 localhost:8080/api/v1/get-api로 시작합니다.
 
 
 

5.2.1 @RequestMapping으로 구현하기

@RequestMapping을 별도 설정 없이 선언하면 HTTP의 모든 요청을 받기 때문에 GET 형식의 요청만 받기 위하여 별도 설정이 요구됩니다. @RequestMappingmethod 요소의 값을 RequestMethod.GET으로 설정하면, 요청 형식을 GET으로 설정할 수 있습니다.

    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public String getHello() {
        return "Hello world";
    }

Talend API Tester, Postman 등의 API 플랫폼을 사용하여 작성한 메서드를 호출할 수 있습니다. Postman을 사용하여 호출해 보도록 하겠습니다. GET 방식을 선택 후 주소를 입력하여 Send 버튼을 클릭합니다. 그러면 getHello 메서드에서 반환된 'Hello World'가 출력되는 Response를 확인할 수 있습니다.

http://localhost:8080/api/v1/get-api/hello GET 호출

 
 
 

5.2.2 매개변수가 없는 GET 메서드 구현

스프링 4.3 버전 이후로 @GetMapping, @PostMapping,  @PutMapping,  @DeleteMapping 어노테이션이 추가되어 더 이상 @RequestMapping 어노테이션은 사용되지 않습니다. @GetMapping을 사용하여 별도의 매개변수가 없는 GET API를 구현해 봅시다.

    @GetMapping(value = "/name")
    public String getName() {
        return "Flature";
    }

매개변수가 없는 요청은 http://localhost:8080/api/v1/get-api/name URL을 그대로 입력하고, 정해진 반환값인 "Flature"를 출력합니다.

http://localhost:8080/api/v1/get-api/name GET 호출

 
 
 

5.2.3 @PathVariable을 활용한 GET 메서드 구현

실제로 매개변수가 없는 메서드는 거의 사용되지 않습니다. 웹 통신의 기본 목적은 데이터를 주고받고 그에 따른 처리를 하는 것이기 때문에, 대부분 매개변수가 존재하는 메서드를 작성하게 됩니다. 자주 쓰이는 방법은 URL에 값을 담아 요청하는 것입니다. URL에 값을 담아 전달하는 GET 메서드를 구현해 봅시다.

    @GetMapping(value = "/variable1/{variable}")
    public String getVariable1(@PathVariable String variable) {
        return variable;
    }

이 메서드는 중괄호({})로 표시된 위치의 값을 받아 요청합니다. 주로 값을 간단히 전달할 때 사용합니다. 이 방식으로 작성 시 지켜야 할 규칙이 있습니다.

  1. @GetMapping의 값으로 URL을 입력할 때 중괄호를 사용하여 값을 어디서 받을지 지정합니다.
  2. 메서드의 매개변수와 그 값을 연결하기 위해 @PathVariable을 명시합니다.
  3. @GetMapping@PathVariable의 변수의 이름이 동일해야 합니다.
  4. @GetMapping@PathVariable의 변수의 이름을 동일하게 맞추기 어렵다면, @PathVariable 뒤에 괄호(())를 사용하여 @GetMapping의 변수명을 지정합니다.
    @GetMapping(value = "/variable2/{variable}")
    public String getVariable2(@PathVariable("variable") String var) {
        return var;
    }

getVariable2 메서드는 @GetMapping의 변수명인 variable@PathVariable의 변수명인 var이 일치하지 않은 상황에서 두 값을 매핑하는 방법입니다. @PathVariable에는 변수의 이름을 특정할 수 있는 value 요소가 존재하여 변수 이름을 정의하면 매개변수와 매핑 가능합니다.
2번 줄의 getVariable2(@PathVariable("variable") String var)을 풀어쓰면 getVariable2(@PathVariable(value = "variable") String var)입니다.

http://localhost:8080/api/v1/get-api/variable/hello GET 호출

 
 
 

5.2.4 @RequestParam을 활용한 GET 메서드 구현

GET 요청을 구현할 때 쿼리 형식으로 값을 전달할 수도 있습니다. URI'?'를 기준으로 우측에 '{키}={값}' 형태로 구성된 요청을 전송하는 방법입니다. URI특정 리소스를 식별할 수 있는 식별자를 의미합니다. URL을 통해 리소스가 어느 서버에 위치해 있는지 알고, 그 서버에 접근하여 URI를 통해 리소스에 접근하는 것입니다. 이 같은 형식은 @RequestParam을 명시하여 쿼리 값과 매핑하여 구현합니다.

@GetMapping(value = "/request1")
    public String getRquestParam1(
        @RequestParam String name,
        @RequestParam String email,
        @RequestParam String organization) {
        return name + " " + email + " " + organization;
}

위와 같이 작성 시 http://localhost:8080/api/v1/get-api/request1?name={값1}&email={값2}&organization={값3}getRquestParam1 메서드를 호출할 수 있습니다. '?' 우측에 쿼리스트링(Query String)을 명시합니다. 쿼리스트링은 URL의 물음표 뒤 모든 문자열을 의미합니다. 쿼리스트링을 통하여 메서드의 매개변수에 이름을 매핑하면 값을 가져올 수 있습니다. 

http://localhost:8080/api/v1/get-api/request1?name=corner&email=corner@abc&organization=duksung GET 호출

만약 쿼리스트링에 어떤 값이 들어올지 모른다면, Map 객체를 활용할 수 있습니다.

    @GetMapping(value = "/request2")
    public String getRequestParam2(@RequestParam Map<String, String> param) {
        StringBuilder sb = new StringBuilder();
        param.entrySet().forEach(map -> {
            sb.append(map.getKey() + " : " + map.getValue() + "\n");
        });
        return sb.toString();
    }

Map 객체를 활용하면 값에 상관없이 요청을 받을 수 있습니다. 매개변수의 항목이 일정하지 않을 때 사용합니다.
 
 
 

5.2.5 DTO 객체를 활용한 GET 메서드 구현

DTO(Data Transfer Object)각 클래스 및 인터페이스를 호출하면서 전달하는 매개변수로 사용되는 데이터 객체입니다. 즉, 계층 간 데이터 전송을 위해 도메인 모델 대신 사용되는 객체라고 할 수 있습니다. 다른 레이어 간의 데이터 교환에 활용됩니다. 데이터를 교환하는 용도로만 사용하기 때문에, 별도의 로직이 포함되지 않습니다. DTO 클래스의 역할은 6장 데이터베이스 연동에서 자세히 다룰 예정입니다.
DTO 객체를 작성하기 위하여, api/src/main/java/com.springboot.api/dto 패키지를 생성하고, MemberDto 클래스를 생성합니다.

package com.springboot.api.dto;

public class MemberDto {
    private String name;
    private String email;
    private String organization;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getOrganization() {
        return organization;
    }

    public void setOrganization(String organization) {
        this.organization = organization;
    }

    @Override
    public String toString(){
        return "MemberDto{"+"name='"+name+'\''+", email='"+email+'\''+", organization='"+organization+'\''+'}';
    }
}

DTO 클래스에 필드 객체를 선언하고 getter, setter 메서드를 구현합니다. DTO 클래스에 선언된 필드는 컨트롤러의 메서드에서 쿼리 파라미터의 키와 매핑됩니다.

    @GetMapping(value = "/request3")
    public String getRequestParam3(MemberDto memberDto) {
        return memberDto.toString();
    }

 
위 코드는 5.2.4 @RequestParam을 활용한 GET 메서드 구현에서 작성한 메서드와 동일한 형태의 쿼리스트링을 가지지만 DTO 객체를 활용하여 코드의 가독성을 높였습니다.
 

http://localhost:8080/api/v1/get-api/request3?name=corner&amp;email=corner@abc&amp;organization=duksung GET 호출

 
 
 

5.3 POST API 만들기

POST API는 저장소에 리소스를 저장할 때 사용하는 API입니다. GET API는 URL의 경로나 파라미터에 변수를 넣어 요청을 보냈으나 POST API는 저장하고자 하는 리소스나 값을 HTTP 바디에 담아 서버에 전달합니다. 이에 따라 URI가 비교적 간단합니다.
POST API를 작성하기 위해, api/src/main/java/com.springboot.api/controller 패키지 아래에 PostController 클래스를 생성합니다. 이전의 GetController 클래스를 작성했을 때와 같이 PostController 클래스에 @RequestMapping을 이용하여 내부에 선언되는 메서드 URL 리소스 앞에 추가할 공통 URL을 설정합니다.

@RestController
@RequestMapping("/api/v1/post-api")
public class PostController {}

 
 
 
5.3.1 @RequestMapping으로 구현하기

POST API에서 @RequestMapping을 사용하는 방법은 GET API와 유사합니다. @RequestMapping의 method 요소의 값을 RequestMethod.POST으로 설정하면, 요청 형식을 POST으로 설정할 수 있습니다.

    @RequestMapping(value = "/domain", method = RequestMethod.POST)
    public String postExample(){
        return "Hello Post API";
    }

 
 
 

5.3.2 @RequestBody를 활용한 POST 메서드 구현

POST 형식의 요청은 주로 클라이언트가 서버에 리소스를 저장하는 데 사용합니다. 따라서 리소스를 담기 위하여 HTTP Body에 값을 넣어 전송합니다.
Body 영역에 작성하는 값은 일정 형태를 갖춥니다. 일반적으로 JSON(JavaScript Object Notaion) 형식으로 전송합니다. JSON자바스크립트 객체 문법을 따르는 문자 기반의 데이터 포맷입니다. 대체로 네트워크를 통해 데이터를 전달할 때 사용하며, 문자열 형태로 작성되기 때문에 파싱(구문 분석)하기 쉽습니다. 이렇게 서버에 들어온 요청은 다음과 같이 처리할 수 있습니다.

    @PostMapping(value = "/member")
    public String postMember(@RequestBody Map<String, Object> postData) {
        StringBuilder sb = new StringBuilder();

        postData.entrySet().forEach(map -> {
            sb.append(map.getKey() + " : " + map.getValue() + "\n");
        });

        return sb.toString();
    }

@GetMapping을 사용했던 것처럼, POST API 역시 @RequestMapping 대신 @PostMapping을 사용하였습니다. @RequestBody 어노테이션은 HTTP의 Body 내용을 지정된 객체에 매핑하는 역할을 합니다. 예제에서는 Body의 내용이 postData에 매핑됩니다.
Map 객체는 요청 메시지에 어떤 값이 들어올지 특정하기 어려울 때 사용하는데, 만약 특정된다면 DTO 객체를 매개변수로 작성할 수 있습니다. 대부분의 경우 API 명세를 통하여 올바른 사용법을 안내합니다.

    @PostMapping(value = "/member2")
    public String postMemberDto1(@RequestBody MemberDto memberDTO) {
        return memberDTO.toString();
    }
    
    @PostMapping(value = "/member3")
    public MemberDto postMemberDto2(@RequestBody MemberDto memberDTO) {
        return memberDTO;
    }

위 코드에서는 Body의 내용이 MemberDto의 멤버 변수에 매핑되어 값을 가져올 수 있습니다. postMemberDto1와 postMemberDto2의 차이는 반환 값입니다. postMemberDto1는 String을 반환하고, postMemberDto2는 DTO 객체를 반환합니다. 두 경우의 차이를 알아보기 위하여 Talend API Tester, Postman 등을 사용하여 작성한 메서드를 각각 호출해 봅시다.

http://localhost:8080/api/v1/post-api/member2와&amp;nbsp; http://localhost:8080/api/v1/post-api/member3 POST 호출

Body 영역의 값은 동일한데, 전달받는 값의 차이가 있습니다. member2의 경우 DTO 객체의 toString 메서드의 결괏값인 String 타입으로 전달받게 됩니다. toString 메서드를 통하여 형식이 갖추어져 있으나, 일반 문자열(Text)이 전달됐습니다. member3의 경우 DTO 객체를 전달받게 됩니다. JSON 형식으로 전달된 것을 확인할 수 있습니다. 줄 바꿈, 정렬 등은 Postman이 처리한 결괏값이며, 실제로는 형식만 유지되어 전달됩니다.
@ResponseBody 어노테이션은 자동으로 값을 JSON 형식으로 변환하여 전달하는 역할을 합니다.
 
 

 

5.4 PUT API 만들기

PUT API는 저장소에 저장한 리소스를 업데이트할 때 사용하는 API입니다. POST API와는 데이터베이스에 반영하는 서비스 로직에서 차이가 존재하지만, HTTP Body를 활용하는 점에서 컨트롤러 클래스를 구현하는 방법은 거의 동일합니다.
PUT API를 작성하기 위해, api/src/main/java/com.springboot.api/controller 패키지 아래에 PutController 클래스를 생성합니다. 이전의 GetController 클래스와  PostController  클래스를 작성했을 때와 같이, PutController 클래스에 @RequestMapping을 이용하여 내부에 선언되는 메서드 URL 리소스 앞에 추가할 공통 URL을 설정합니다.

@RestController
@RequestMapping("/api/v1/put-api")
public class PutController {}

 

 
 

5.4.1 @RequestBody를 활용한 PUT 메서드 구현

HTTP Body의 값을 받기 위해 @RequestBody를 사용합니다. POST API와 거의 동일합니다. 서버에 어떤 값이 들어오는지 모르는 경우에는 Map 객체를, 정해져 있는 경우에는 DTO 객체를 활용해 구현합니다.

    @PutMapping(value = "/member")
    public String postMember(@RequestBody Map<String, Object> putData) {
        StringBuilder sb = new StringBuilder();

        putData.entrySet().forEach(map -> {
            sb.append(map.getKey() + " : " + map.getValue() + "\n");
        });

        return sb.toString();
    }
    @PutMapping(value = "/member1")
    public String postMemberDto1(@RequestBody MemberDto memberDto) {
        return memberDto.toString();
    }
    
    @PutMapping(value = "/member2")
    public MemberDto postMemberDto2(@RequestBody MemberDto memberDto) {
        return memberDto;
    }

 
 
 

5.4.2 ResponseEntity를 활용한 PUT 메서드 구현

스프링 프레임워크에 포함된 HttpEntity 클래스는 헤더와 Body로 구성된 HTTP 요청과 응답을 구성하는 역할을 수행합니다. ResponseEntity는 HttpEntity를 상속받아 구현한 클래스로, 서버에 들어온 요청에 대해 응답 데이터를 구성하여 전달할 수 있도록 합니다. ResponseEntity 클래스를 활용하면 응답 코드를 변경할 수 있고, 헤더와 Body를 쉽게 구성할 수 있습니다. PUT 메서드 외 다른 메서드에서도 사용할 수 있습니다.

    @PutMapping(value = "/member3")
    public ResponseEntity<MemberDto> postMemberDto3(@RequestBody MemberDto memberDto) {
        return ResponseEntity
            .status(HttpStatus.ACCEPTED)
            .body(memberDto);
    }

메서드 반환 타입에 ResponseEntity를 적용한 예시입니다. status에 넣을 수 있는 값은 다양하며, HttpStatus.ACCEPTED는 응답 코드 202를 지닙니다. 따라서 이 메서드를 호출하면 응답 코드가 202로 들어옵니다.
 
 
 

5.5 DELETE API 만들기

DELETE API저장소에 있는 값을 삭제할 때 사용하는 API입니다. 클라이언트로부터 리소스 식별 값을 받아 데이터베이스에 있는 리소스를 조회하여 삭제하는 역할을 수행합니다. 컨트롤러를 통하여 값을 받는 단계에서 식별 가능한 간단한 값을 받기 때문에, GET 메서드와 동일하게 URI에 값을 넣어 요청받는 형식으로 구현합니다.
 
DELETE  API를 작성하기 위해, api/src/main/java/com.springboot.api/controller 패키지 아래에 DeleteController 클래스를 생성합니다. 이전의 컨트롤러 클래스를 작성했을 때와 같이, DeleteController 클래스에 @RequestMapping을 이용하여 내부에 선언되는 메서드 URL 리소스 앞에 추가할 공통 URL을 설정합니다. 

@RestController
@RequestMapping("/api/v1/delete-api")
public class DeleteController {}

 
 
 

5.5.1 @PathVariable와 @RequestParam을 활용한 DELETE 메서드 구현

@PathVariable을 이용하여 URI에 포함된 값을 받아 로직을 처리할 수 있습니다. 또한, @DeleteMapping 어노테이션에 정의한 value의 이름과 메서드의 매개변수 이름을 동일하게 설정해야 삭제할 값이 주입됩니다. @RequestParam 어노테이션을 통하여 쿼리스트링 값을 받을 수 있습니다.

    @DeleteMapping(value = "/{variable}")
    public String DeleteVariable(@PathVariable String variable) {
        return variable;
    }

    @DeleteMapping(value = "/request1")
    public String getRequestParam1(@RequestParam String email) {
        return "e-mail : " + email;
    }




 

5.6 REST API 명세 문서화 - Swagger

API 개발 시 명세를 필수적으로 관리해야 합니다. 명세는 해당 API가 어떤 로직을 수행하는지, 이 로직을 수행하기 위하여 어떤 값을 요청하는지, 이에 따른 응답값이 무엇인지를 정리한 자료를 의미합니다.
API는 개발 과정에서 계속 변경되기 때문에 명세 또한 주기적인 업데이트가 요구됩니다. 그러나 개발에 따른 명세 작업은 번거롭고 시간이 오래 걸리기 때문에 오픈소스 프로젝트를 사용하여 간단히 명세를 작성하고 관리할 수 있습니다. 오픈소스 프로젝트 Swagger를 사용하여 API 명세를 문서화해 봅시다.
 

5.6.1 Swagger 설정

Swagger를 사용하기 위하여 api/pom.xml에 의존성을 추가합시다.

  <dependencies>
    ...생략...
    <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-swagger2</artifactId>
      <version>2.9.2</version>
    </dependency>

    <dependency>
      <groupId>io.springfox</groupId>
      <artifactId>springfox-swagger-ui</artifactId>
      <version>2.9.2</version>
    </dependency>
  </dependencies>

 
Swagger 관련 설정 코드를 작성합니다. api/src/main/java/com.springboot.api/config 패키지를 생성하고 SwaggerConfiguration 클래스를 작성합니다.

@Configuration
@EnableSwagger2
public class SwaggerConfiguration {

    @Bean
    public Docket api() {
        return new Docket(DocumentationType.SWAGGER_2)
            .apiInfo(apiInfo())
            .select()
            // 스캔 범위 설정(com.springboot.api 하위 클래스 모두 스캔하여 문서 생성)
            .apis(RequestHandlerSelectors.basePackage("com.springboot.api"))
            .paths(PathSelectors.any())
            .build();
    }

    private ApiInfo apiInfo() {
        return new ApiInfoBuilder()
            .title("Spring Boot Open API Test with Swagger")
            .description("설명 부분")
            .version("1.0.0")
            .build();
    }
}

 
이러한 기본적인 설정 이후에 실행 시 아래의 오류가 발생할 수 있습니다.

org.springframework.context.ApplicationContextException: Failed to start bean 'documentationPluginsBootstrapper'; nested exception is java.lang.NullPointerException

해당 오류는 Spring boot 2.6 버전 이후 발생하는 오류입니다. 해당 오류가 발생한다면, api/src/main/resources/application.properties에 아래 코드를 삽입하여 해결할 수 있습니다.

spring.mvc.pathmatch.matching-strategy=ant_path_matcher

 
위 과정을 모두 마쳤다면, 애플리케이션 실행 후 웹 브라우저를 통하여 'http://localhost:8082/swagger-ui.html'에 접속하면 Swagger 페이지가 출력됩니다.

http://localhost:8082/swagger-ui.html

 
 

5.6.2 Swagger 적용하기

Swagger를 활용하는 코드가 존재합니다. 이번 장에서 작성한 GET API의 getRequestParam1 메서드를 수정해 봅시다.

    @ApiOperation(value = "GET 메소드 예제", notes = "@RequestParam을 활용한 GET Method")
    @GetMapping(value = "/request1")
    public String getRequestParam1(
        @ApiParam(value = "이름", required = true) @RequestParam String name,
        @ApiParam(value = "이메일", required = true) @RequestParam String email,
        @ApiParam(value = "회사", required = true) @RequestParam String organization) {
        return name + " " + email + " " + organization;
    }

@ApiOperation@ApiParamSwagger 대표 어노테이션입니다. @ApiOperation는 해당 API의 설명을 작성하기 위한 어노테이션입니다. @ApiParam는 매개변수에 대한 설명 및 설정을 위한 어노테이션입니다. DTO 객체를 매개변수로 사용한다면, DTO 클래스 내에 정의할 수 있습니다.
위와 같이 수정한 후 Swagger 페이지를 통하여 해당 메서드를 보면, 다음과 같이 반영된 것을 확인할 수 있습니다.

http://localhost:8082/swagger-ui.html의 request1

@ApiOperation에 작성한 내용은 Parameters 위에, @ApiParam에 작성한 애용은 Parameters 영역의 Description 항목에 추가되었습니다. 이를 통하여 해당 API가 무엇인지 설명할 수 있으며, 어떤 값이 필요한지 보기 쉽게 안내할 수 있습니다.
 
Swagger를 사용하여 직접 통신 또한 테스트할 수 있습니다. Try it out 버튼을 클릭하면 이메일, 이름, 회사 항목에 값을 입력할 수 있습니다. 적절한 값을 입력하고 Execute 버튼을 누르면, 완성된 요청 URL을 확인할 수 있고, 결괏값 또한 확인 가능합니다.

http://localhost:8082/swagger-ui.html의 request1 Execute

 
 
 

5.7 로깅 라이브러리 - Logback

로깅(logging)이란 애플리케이션 동작 동안 시스템의 상태나 동작 정보를 시간 순으로 기록하는 것입니다. 로깅은 개발 영역 중 비기능 요구사항에 속합니다. 사용자에게 필요한 기능은 아니지만, 개발 중 발생한 문제를 해결할 때 원인을 분석하기 수월하도록 돕는 꼭 필요한 요소입니다.
가장 많이 사용되는 자바 로깅 프레임워크는 Logback입니다. Logback이란 slf4j를 기반으로 구현된 로깅 프레임워크로, 스프링 부트 spring-boot-starter-web 라이브러리 내부에 내장되어 있어 사용 시 의존성을 별도로 추가할 필요가 없습니다. Logback은 다음과 같은 특징을 갖습니다.

  • 5가지 로그 레벨 설정 (ERROR, WARN, INFO, TRACE, )
  • 실제 운영 환경과 개발 환경 각각 다른 출력 레벨 설정 가능
  • Logback 설정 파일 일정 시간 스캔 > 애플리케이션 재가동하지 않아도 설정 변경 가능
  • 로그 보관 기간 설정하여 관리

 
 
 

5.7.1 Logback 설정

Logback을 사용하기 위해 api/src/main/resources/logback-spring.xml을 생성하고, 내용을 추가합니다.

<?xml version="1.0" encoding="UTF-8"?>
<configuration>
    <property name="LOG_PATH" value="./logs"/>

    <!-- Appenders -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>INFO</level>
        </filter>
        <encoder>
            <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%-5level] [%thread] %logger %msg%n</pattern>
        </encoder>
    </appender>

    <!-- TRACE > DEBUG > INFO > WARN > ERROR > OFF -->
    <!-- Root Logger -->
    <root level="INFO">
        <appender-ref ref="console"/>
    </root>
</configuration>

태그에 따라 Property 영역, Appender 영역, Pattern 영역, Root 영역으로 구분할 수 있습니다. Logback 설정에서 가장 중요한 Appender 영역, Root 영역에 대하여 알아봅시다.
 
 
 
Appender 영역
Appender 영역에서 로그의 형태를 설정하고 어떤 방법으로 출력할지 설정합니다. Appender는 하나의 인터페이스이며 하위에 여러 구현체가 존재합니다. Logback의 설정 파일을 이용하면 Appender의 각 구현체를 등록하여 원하는 형식대로 로그를 출력할 수 있습니다. 다음과 같은 대표적인 구현체가 있습니다.

  • ConsileAppender: 콘솔에 로그 출력
  • FileAppender: 파일에 로그 저장
  • RollingFileAppender: 여러 개 파일 순회하며 로그 저장
  • SMTPAppender: 메일로 로그 전송
  • DBAppender: 데이터베이스에 로그 저장

위에서 작성한 logback-spring.xml 파일을 참고하여 로그 저장 방식을 지정하는 구체적인 예를 알아봅시다. appender 요소의 class 속성에 값을 넣어 각 구현체를 정의합니다. filter 요소로 각 Appender가 어떤 레벨로 로그를 기록하는지 지정합니다. 그 뒤, encoder 요소를 통하여 로그의 표현 형식을 패턴으로 정의합니다. 사용 가능한 패턴은 다양하며, 다음과 같은 대표적인 패턴이 있습니다.

패턴의미
%Logger{length}로거 이름
%-5level로그 레벨
%msg or %message로그 메세지
%d로그 기록 시간
%p로깅 레벨
%F로깅 발생 애플리케이션 파일명
%M로깅 발생 메서드 이름
%I로깅 발생 호출지 정보
%thread현재 스레드명
%t로깅 발생 스레드명
%c로깅 발생 카테고리
%C로깅 발생 클래스명
%m로그 메세지
%n줄바꿈
%r애플리케이션 실행부터 로깅 발생 시점까지 시간
%L로깅 발생 호출 지점 라인 수

위 패턴을 활용하여 사용자가 원하는 패턴을 만들 수 있습니다.
 
 
 
Root 영역
Root 영역은 Appender를 참조하여 로깅 레벨을 설정합니다. 만약 특정 패키지에 대해 로깅 레벨을 다르게 설정하고 싶다면, root 대신 logger를 사용하여 지정할 수 있습니다.

    <logger name="com.springboot.api.controller" level="DEBUG" additivity="false">
        <appender-ref ref="console"/>
        <appender-ref ref="INFO_LOG"/>
    </logger>

logger 요소의 name 속성에는 로깅이 적용될 패키지 범위, level 속성에는 로그 레벨, additivity 속성은 name에서 지정한 패키지 범위에 하위 패키지를 포함하는가에 대한 여부를 지정합니다. 위 코드는 com.springboot.api.controller 패키지에 DEBUG 레벨로, 하위 패키지를 포함하지 않고 로깅을 적용시키겠다는 의미를 지닙니다.
 
 

5.7.2 Logback 적용하기

Logback은 출력할 메시지를 Appender에게 전달할 Logger 객체를 각 클래스에 정의하여 사용합니다. GetController.java 파일에 Logger를 적용해 봅시다. 먼저, GetControllerLOGGER 전역 변수로 Logger 객체를 정의합니다.

    private final Logger LOGGER = LoggerFactory.getLogger(GetController.class);

Logger는 LoggerFactory를 통하여 객체를 생성합니다. 이 과정에서 클래스의 이름을 함께 지정하여 클래스의 정보를 Logger에서 가져갑니다. 이제 이전 예제에서 작성했던 메서드에 로그를 출력하는 코드를 삽입합니다.

    @RequestMapping(value = "/hello", method = RequestMethod.GET)
    public String getHello() {
        // 로그 출력
        LOGGER.info("getHello 메소드가 호출되었습니다.");
        return "Hello World";
    }

    @GetMapping(value = "/name")
    public String getName() {
        // 로그 출력
        LOGGER.info("getName 메소드가 호출되었습니다.");
        return "Flature";
    }

    @GetMapping(value = "/variable1/{variable}")
    public String getVariable1(@PathVariable String variable) {
        // 컨트롤러에 들어오는 값을 포함하여 로그 출력
        LOGGER.info("@PathVariable을 통해 들어온 값 : {}", variable);
        return variable;
    }

info 레벨에서 로그가 출력되도록 합니다. 또한, 변수를 지정하여 변수로 들어오는 값을 로깅하면 컨트롤러에 들어오는 값을 포함하여 로그를 출력할 수 있습니다. 변수의 값이 들어가는 부분을 중괄호({})로 지정하면 포매팅을 통하여 로그 메시지가 구성됩니다.
실제로 로그가 출력되는지 확인하려면, Talend API Tester, Postman, Swagger 등을 사용하여 해당 메서드를 호출하면 됩니다. 각각 호출 시 아래 사진과 같이 로그가 잘 출력되는 것을 확인할 수 있습니다.

순서대로 getHello(), getName(), getVariable1(corner) 호출

 
 
 

5.8 정리

컨트롤러를 통하여 외부에 인터페이스를 노출하는 방법(API 작성)에 대하여 다루었습니다. 또한, API를 보다 잘 작성할 수 있도록 돕는 Swagger와 Logback 라이브러리를 알아보았습니다. 이어서 6장에서 데이터베이스를 연동하는 방법을 공부하고 이번 5장의 내용과 연계하여 컨트롤러의 동작 원리를 이해합시다.
 
 
 

6장. 데이터베이스 연동

애플리케이션은 데이터를 주고받은 것이 주목적입니다. 따라서 데이터베이스가 필수적입니다. 가장 널리 사용되는 마리아DB(MariaDB)를 애플리케이션에 적용해 봅시다.
 
 
 

6.1 마리아DB 설치

마리아DB 다운로드 페이지(https://mariadb.org/download)에 접속합니다. 각자의 개발 환경에 맞추어 설치 파일을 내려받읍시다. 마리아DB 서버 버전은 10.6.5를 사용하였습니다.

마리아DB 다운로드 페이지( https://mariadb.org/download )

[Next]를 클릭하여 넘어가다 보면, 패스워드 입력창이 나옵니다. root 패스워드를 입력하고 대중적으로 사용되는 문자 인코딩 방식(UTF-8)을 기본값으로 설정하는 항목에 체크합니다. 설치가 완료되면 서드파티 도구로 HeidiSQL이 함께 설치됩니다. HeidiSQL은 데이터베이스에 접속하여 관리하는 GUI 도구입니다. 이 프로그램을 실행합시다.
[신규] 버튼을 눌러 우측에 접속 정보를 설정합시다. 세션 이름을 springboot로 입력하고 설치 단계에서 입력한 root 패스워드를 입력하여 저장합니다.

HeidiSQL 신규 세션 저장

세션이 잘 저장되었다면, [열기] 버튼을 클릭하여 데이터베이스에 접속합시다. 기본적으로 데이터베이스 4개가 존재하는데, 마리아DB의 환경 설정 정보를 담고 있는 것이기 때문에 실습 과정에서 다루지는 않을 것입니다. 쿼리 탭을 선택하여 데이터베이스 생성 쿼리를 입력하고 실행(▶)합니다. 정상적으로 실행되었다면 좌측 탭에서 springboot 데이터베이스가 생성된 것을 확인할 수 있습니다.

데이터베이스 접속 및 생성

 

6.2 ORM

ORM(Object Relational Mapping)객체 관계 매핑으로, 객체(클래스)와 RDB(Relational Database)(관계형 데이터베이스)의 테이블을 자동으로 매핑하는 방법입니다. 클래스는 데이터베이스의 테이블과 매핑하기 위해 만들어진 것이 아니기 때문에, RDB 테이블과의 불일치가 존재합니다. ORM은 이 둘의 불일치와 제약사항을 해결하고, 쿼리문이 아닌 코드로 데이터를 조작할 수 있게 합니다. ORM은 장점과 단점이 존재합니다.

장점단점
데이터베이스 쿼리 객체지향적 조작 가능온전한 서비스 구현에 한계 존재
재사용 및 유지보수 편리객체 관점과 데이터베이스 관계 관점 간 불일치 발생
데이터베이스 종속성 감소 

 
 
 

6.3 JPA

JPA(Java Persistence API)ORM 기술 표준으로 채택된 인터페이스 모음입니다. ORM의 구체화한 스펙을 포함합니다. 어떻게 동작해야 하는지 메커니즘을 정리한 표준 명세입니다. 자바에서 데이터베이스에 접속할 수 있도록 하는 자바 API인 JDBC를 내부적으로 사용하여 적절한 SQL을 생성하고 데이터베이스와 객체를 자동 매핑하는 역할을 수행합니다. 개발자가 직접 JDBC를 구현하면 개발의 효율성이 떨어지는 문제를 해소하였습니다.  다음과 같은 대표적인 JPA가 있습니다.

  • 하이버네이트
  • 이클립스 링크
  • 데이터 뉴클리어스

 
 

6.4 하이버네이트

하이버네이트JAP 구현체 중 하나로, 가장 많이 사용되는 구현체입니다. 이후로 하이버네이트를 편하게 사용하도록 모듈화한 Spring Data JPA를 사용할 것입니다.
 
 
 

6.4.1 Spring Data JPA

Spring Data JPA는 JPA를 편리하게 사용할 수 있도록 지원하는 스프링 하위 프로젝트 중 하나입니다. CRUD(Create(생성), Read(읽기), Update(갱신), Delete(삭제)) 처리에 필요한 인터페이스를 제공하며, 하이버네이트의 엔티티 매니저를 직접 다루지 않고 리포지토리를 정의하여 사용합니다. 즉, 스프링이 적합한 쿼리를 동적으로 생성하는 방식으로 데이터베이스를 조작하여 하이버네이트 기능을 더 쉽게 사용할 수 있도록 구현하였습니다.
 
 
 

6.5 영속성 컨텍스트

영속성 컨텍스트(Persistence Context)는 엔티티와 레코드의 괴리를 해소하며 객체를 보관하는 기능을 수행합니다. 엔티티 객체가 영속성 컨텍스트에 들어오면, JPA는 엔티티 객체의 매핑 정보를 데이터베이스에 반영하는 작업을 수행합니다. 이처럼 엔티티 객체가 영속성 컨텍스트에 들어와 JPA의 관리 대상이 되는 객체영속 객체라고 부릅니다.
영속성 컨텍스트는 세션 단위의 생명 주기를 가집니다. 데이터베이스에 접근하기 위한 세션이 생성되면 영속성 컨텍스트가 만들어지고, 세션이 종료되면 영속성 컨텍스트도 사라집니다.
 
 

6.5.1 엔티티 매니저

엔티티 매니저(Entity Manager)는 위와 같은 과정에서 영속성 컨텍스트에 접근하기 위한 수단입니다. 데이터베이스에 접근하여 CRUD 작업을 수행합니다. 앞서 언급한 Spring Data JPA의 실제 내부 구현체인 SimpleJpaRepository가 리포지토리에서 엔티티 매니저를 사용합니다.
엔티티 매니저는 엔티티 매니저 팩토리가 만듭니다. 이는 데이터베이스에 대응하는 객체입니다. 스프링 부트에서는 application.properties에 작성한 최소한의 설정만으로 동작합니다. 하이버네이트에서는 persistence.xml 설정 파일을 구성하고 사용해야 합니다. 엔티티 매니저 팩토리는 애플리케이션에서 단 하나만 생성되며, 모든 엔티티가 공유하여 사용합니다. 엔티티 매니저 팩토리로 생성된 엔티티 매니저는 엔티티를 영속성 컨텍스트에 추가해서 영속 객체로 만드는 작업을 수행하고, 영속성 컨텍스트와 데이터베이스를 비교하며 실제 데이터베이스를 대상으로 작업을 수행합니다.
 
 
 

6.5.2 엔티티의 생명주기

엔티티 객체는 영속성 컨텍스트에서 다음과 같은 4가지 상태로 구분됩니다.

  • 비영속
    • 영속성 컨텍스트에 추가되지 않은 엔티티 객체 상태
  • 영속
    • 영속성 컨텍스트에 추가되어 엔티티 객체가 관리되는 상태
  • 준영속
    • 영속 엔티티 객체가 컨텍스트와 분리된 상태
  • 삭제
    • 영속성 컨텍스트에 삭제 요청한 상태

 
 
 


QUIZ

1. 서버에서 값을 가져올 때 사용하는 API는 (GET API)이며, 저장소에 리소스를 저장할 때 사용하는 API는 (POST API), 저장소에 저장한 리소스를 업데이트할 때 사용하는 API는 (PUT API),  저장소에 있는 값을 삭제할 때 사용하는 API는 (DELET API)이다.
2. (@RequestMapping)을 사용하여 내부에 선언되는 메서드 URL 리소스 앞에 추가할 공통 URL을 설정할 수 있다.
3. 스프링 4.3 버전 이후로 (@GetMapping), (@PostMapping),  (@PutMapping),  (@DeleteMapping) 어노테이션이 추가되었다.
4. 중괄호를 사용하여 URL에 값을 담아 전달하는 메서드는 ( @PathVariable ) 어노테이션을 사용하여 구현한다.
5. 쿼리 형식으로 , URI의 '?'를 기준으로 우측에 '{키}={값}' 형태로 구성된 요청을 전송하는 형식은 (@RequestParam) 어노테이션을 사용하여 구현한다.
6. (JPA)은  객체 관계 매핑인 (ORM)의 구체화한 스펙을 포함하여 어떻게 동작해야 하는지 메커니즘을 정리한 표준 명세이다.
7. ( 엔티티 매니저 팩토리)로 생성된 (엔티티 매니저)는 엔티티를 (영속성 컨텍스트)에 추가해서 (영속 객체)로 만드는 작업을 수행한다.
8. 
9. 
 
 
 


[출처] 장정우, 『스프링 부트 핵심 가이드』, 위키북스(2022), p.55-103.
[출처] 윤복로그, 5. 18., [오류해결-swagger], 티스토리리, https://goyunji.tistory.com/137.

Corner Spring 2

Editor : 동동

728x90

관련글 더보기