상세 컨텐츠

본문 제목

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

24-25/Spring 2

by dalpaeng4 2024. 11. 22. 10:00

본문

728x90

 

 

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

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

5.1 프로젝트 설정

 Group을 'com.springboot'로, Artifact와 Name을 'api'로 설정

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

 

5.2 GET API 만들기

GET API는 서버에서 값을 가져올 때 사용하는 API다. GET API를 작성하는 방법은 매우 다양하다.

원활한 실습을 위하여 메서드별로 클래스를 생성한다.


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

GetController 클래스를 수정하여 GET API를 작성

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

@RequestMapping을 추가하여 내부에 선언되는 메서드 URL 리소스 앞에 추가할 공통 URL을 설정한다.

이후 GetController 클래스에 선언하는 메서드 주소는 모두 localhost:8080/api/v1/get-api로 시작한다.
 
 
 

 @RequestMapping으로 구현하기

@RequestMapping을 별도 설정 없이 선언하면 HTTP의 모든 요청을 받음

GET 형식의 요청만 받기 위하여 별도 설정이 요구된다.

-> @RequestMapping의 method 요소의 값을 RequestMethod.GET으로 설정

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

 

Talend API Tester, Postman 등의 API 플랫폼을 사용하여 호출할 수 있다. 

 
 

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

스프링 4.3 버전 이후로 @GetMapping, @PostMapping,  @PutMapping,  @DeleteMapping 어노테이션이 추가되어 더 이상 @RequestMapping 어노테이션은 사용되지 않는다. 

@GetMapping을 사용하여 별도의 매개변수가 없는 GET API를 구현

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

 

 

 
 
 

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

웹 통신의 기본 목적은 데이터를 주고받고 그에 따른 처리를 하는 것이기 때문에, 대부분 매개변수가 존재하는 메서드를 작성한다.

자주 쓰이는 방법은 URL에 값을 담아 요청하는 것. 

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

중괄호({})로 표시된 위치의 값을 받아 요청한다. 주로 값을 간단히 전달할 때 사용. 

  1. @GetMapping의 값으로 URL을 입력할 때 중괄호를 사용하여 값을 어디서 받을지 지정한다.
  2. 메서드의 매개변수와 그 값을 연결하기 위해 @PathVariable을 명시한다.
  3. @GetMapping과 @PathVariable의 변수의 이름이 동일해야 한다.
  4. @GetMapping과 @PathVariable의 변수의 이름을 동일하게 맞추기 어렵다면, @PathVariable 뒤에 괄호(())를 사용하여 @GetMapping의 변수명을 지정한다.


 
 

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

GET 요청을 구현할 때 쿼리 형식으로 값을 전달할 수도 있다. 

URI의 '?'를 기준으로 우측에 '{키}={값}' 형태로 구성된 요청을 전송하는 방법.

URI는 특정 리소스를 식별할 수 있는 식별자를 의미한다. URL을 통해 리소스가 어느 서버에 위치해 있는지 알고, 그 서버에 접근하여 URI를 통해 리소스에 접근하는 것이다.  -> @RequestParam을 명시하여 쿼리 값과 매핑하여 구현

@GetMapping(value = "/request1")
    public String getRequestParam1(
        @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}

 '?' 우측에 쿼리스트링(Query String)을 명시

 

쿼리스트링에 어떤 값이 들어올지 모른다면, 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 객체를 활용하면 값에 상관없이 요청을 받을 수 있다. 매개변수의 항목이 일정하지 않을 때 사용한다.
 
 
 

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

DTO(Data Transfer Object)는 각 클래스 및 인터페이스를 호출하면서 전달하는 매개변수로 사용되는 데이터 객체다. 

계층 간 데이터 전송을 위해 도메인 모델 대신 사용되는 객체, 다른 레이어 간의 데이터 교환에 활용된다.

데이터를 교환하는 용도로만 사용하기 때문에, 별도의 로직이 포함되지 않는다.
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 메서드를 구현.

 

컨트롤러의 메서드에서 매핑.

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

 
 
 

5.3 POST API 만들기

POST API는 저장소에 리소스를 저장할 때 사용하는 API다.

GET API는 URL의 경로나 파라미터에 변수를 넣어 요청을 보냈으나, POST API는 저장하고자 하는 리소스나 값을 HTTP 바디에 담아 서버에 전달한다. ->  URI가 비교적 간단하다.
api/src/main/java/com.springboot.api/controller 패키지, PostController 클래스를 생성

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

 
 
 
@RequestMapping으로 구현하기

@RequestMapping의 method 요소의 값을 RequestMethod.POST으로 설정

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

 
 
 

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

POST 형식의 요청은 주로 클라이언트가 서버에 리소스를 저장하는 데 사용한다. 리소스를 담기 위하여 HTTP Body에 값을 넣어 전송한다.
Body 영역에 작성하는 값은 일반적으로 JSON(JavaScript Object Notaion) 형식으로 전송한다. (자바스크립트 객체 문법을 따르는 문자 기반의 데이터 포맷)

대체로 네트워크를 통해 데이터를 전달할 때 사용하며, 문자열 형태로 작성되기 때문에 파싱(구문 분석)하기 쉽다.

    @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();
    }

@RequestBody 어노테이션은 HTTP의 Body 내용을 지정된 객체에 매핑하는 역할을 한다. 예제에서는 Body의 내용이 postData에 매핑된다.
 주로 API 명세를 통하여 올바른 사용법을 안내한다.

 

5.4 PUT API 만들기

PUT API는 저장소에 저장한 리소스를 업데이트할 때 사용하는 API다. 

api/src/main/java/com.springboot.api/controller 패키지 아래에 PutController 클래스를 생성

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

 

 
 

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

HTTP Body의 값을 받기 위해 @RequestBody를 사용. POST API와 거의 동일.

 
 

 ResponseEntity를 활용한 PUT 메서드 구현

스프링 프레임워크에 포함된 HttpEntity 클래스는 헤더와 Body로 구성된 HTTP 요청과 응답을 구성하는 역할을 수행.

ResponseEntity는 HttpEntity를 상속받아 구현한 클래스로, 서버에 들어온 요청에 대해 응답 데이터를 구성하여 전달할 수 있다.

응답 코드를 변경할 수 있고, 헤더와 Body를 쉽게 구성할 수 있다. PUT 메서드 외 다른 메서드에서도 사용할 수 있다.

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

HttpStatus.ACCEPTED는 응답 코드 202를 지닌다. 
 
 
 

5.5 DELETE API 만들기

DELETE API는 저장소에 있는 값을 삭제할 때 사용하는 API다.

 컨트롤러를 통하여 값을 받는 단계에서 식별 가능한 간단한 값을 받기 때문에, GET 메서드와 동일하게 URI에 값을 넣어 요청받는 형식으로 구현.

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

 
 
 

@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

 오픈소스 프로젝트 Swagger를 사용하여 API 명세를 문서화해 보자.
 

5.6.1 Swagger 설정

Swagger를 사용하기 위하여 api/pom.xml에 의존성을 추가. Springfox에서 오류 ->  Springdoc으로 변경

<dependency>
    <groupId>org.springdoc</groupId>
    <artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
    <version>2.2.0</version> <!-- 최신 버전 확인 -->
</dependency>

 
 api/src/main/java/com.springboot.api/config 패키지를 생성하고 SwaggerConfiguration 클래스를 작성

@Configuration
public class SwaggerConfiguration {

    @Bean
    public OpenAPI customOpenAPI() {
        return new OpenAPI()
            .info(new Info()
                .title("Spring Boot Open API Test with Swagger")
                .description("설명 부분")
                .version("1.0.0")
            );
    }
}


'http://localhost:8080/swagger-ui.html'에 접속하면 Swagger 페이지가 출력.

 
 

5.6.2 Swagger 적용하기

 GET API의 getRequestParam1 메서드를 수정

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

 

  • Springdoc 어노테이션:
    • @ApiOperation → @Operation
    • @ApiParam → @Parameter
  • 메타데이터 위치 변경:
    • summary와 description으로 더 세분화된 정보를 제공

 

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

 Try it out 버튼을 클릭하면 이메일, 이름, 회사 항목에 값을 입력할 수 있다.

적절한 값을 입력하고 Execute 버튼을 누르면, 완성된 요청 URL을 확인할 수 있고, 결괏값 또한 확인 가능하다.

 

5.7 로깅 라이브러리 - Logback

로깅(logging)이란 애플리케이션 동작 동안 시스템의 상태나 동작 정보를 시간 순으로 기록하는 것이다.

로깅은 개발 중 발생한 문제를 해결할 때 원인을 분석하기 수월하도록 돕는 꼭 필요한 요소다.


가장 많이 사용되는 자바 로깅 프레임워크는 Logback.

Logback이란 slf4j를 기반으로 구현된 로깅 프레임워크로, 스프링 부트 spring-boot-starter-web 라이브러리 내부에 내장되어 있어 사용 시 의존성을 별도로 추가할 필요가 없다.

  
 

5.7.1 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 영역으로 구분할 수 있다. 
 
 
 
Appender 영역
Appender 영역에서 로그의 형태를 설정하고 어떤 방법으로 출력할지 설정.

 

대표적인 구현체

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

위 Logback 설정 파일 코드에서 appender 요소의 class 속성에 각 구현체를 정의한다. 그리고 하단에 filter 요소로 각 Appender가 어떤 레벨로 로그를 기록하는지 지정한다.
다음으로 encoder 요소를 통해 로그의 표현 형식을 패턴으로 정의한다.

대표적인 패턴

  • %Logger{length}: 로거의 이름
  • %-5level: 로그 레벨, -5는 출력 고정폭의 값
  • %msg{%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>


 
 

5.7.2 Logback 적용하기

Logback은 출력할 메시지를 Appender에게 전달할 Logger 객체를 각 클래스에 정의하여 사용

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

Logger는 LoggerFactory를 통하여 객체를 생성

    @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;
    }

 

 
 

컨트롤러를 통하여 외부에 인터페이스를 노출하는 방법(API 작성)

 

 

6장. 데이터베이스 연동

애플리케이션은 데이터를 주고받은 것이 주목적 -> 데이터베이스가 필수적

가장 널리 사용되는 마리아DB(MariaDB)
 

6.1 마리아DB 설치

 

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

 

6.2 ORM

ORM(Object Relational Mapping)은 객체 관계 매핑으로,

객체(클래스)와 RDB(Relational Database)(관계형 데이터베이스)의 테이블을 자동으로 매핑하는 방법이다.

ORM은 이 둘의 불일치와 제약사항을 해결하고, 쿼리문이 아닌 코드로 데이터를 조작할 수 있게 한다. 
 

6.3 JPA

JPA(Java Persistence API)는 ORM 기술 표준으로 채택된 인터페이스 모음, ORM의 구체화한 스펙을 포함

어떻게 동작해야 하는지 메커니즘을 정리한 표준 명세다.

자바 API인 JDBC를 내부적으로 사용하여 적절한 SQL을 생성하고 데이터베이스와 객체를 자동 매핑하는 역할을 수행

 

대표적인 JPA

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

 
 

6.4 하이버네이트

하이버네이트는 JPA 중 하나로, 가장 많이 사용되는 구현체다.
 

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 설정 파일을 구성하고 사용. 
 
 

엔티티의 생명주기

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

 


Q1. 문제 (드래그하여 답 확인해주세요.)

1. Logback 에서 Appender 영역에서의 대표적인 구현체 중 콘솔에 로그를 출력하는 것은?

 답: ConsoleAppender

 

2. ( DTO(Data Transfer Object))는 각 클래스 및 인터페이스를 호출하면서 전달하는 매개변수로 사용되는 데이터 객체로, 계층 간 데이터 전송을 위해 도메인 모델 대신 사용되는 객체, 다른 레이어 간의 데이터 교환에 활용된다.

 

3. POST 형식의 요청은 주로 클라이언트가 서버에 리소스를 저장하는 데 사용한다. 리소스를 담기 위하여 ( HTTP Body )에 값을 넣어 전송한다.

 

4. ( @RequestMapping )을 추가하여내부에 선언되는 메서드 URL 리소스 앞에 추가할 공통 URL을 설정

 

5. ( 엔티티 매니저(Entity Manager)) 영속성 컨텍스트에 접근하기 위한 수단으로, 데이터베이스에 접근하여 CRUD 작업을 수행한다.

 

6.(  Logback )이란 slf4j를 기반으로 구현된 로깅 프레임워크로, 스프링 부트 spring-boot-starter-web 라이브러리 내부에 내장되어 있어 사용 시 의존성을 별도로 추가할 필요가 없다.

 

7. ( 하이버네이트 )는 JAP 구현체 중 하나로, 가장 많이 사용되는 구현체다.


Q2. 코드 문제

 

* 아래는 GET 메소드 구현의 여러 방법이다.

    @GetMapping(value = "/variable2/{variable}")
    public String getVariable2(****(1번)****("variable") String var) {
        return var;
    }
@GetMapping(value = "/request1")
    public String getRequestParam1(
        ****(2번)**** String name,
        ****(2번)**** String email,
        ****(2번)**** String organization) {
        return name + " " + email + " " + organization;
}
    @GetMapping(value = "/request2")
    public String getRequestParam2(@RequestParam Map<String, String> param) {
        StringBuilder sb = new StringBuilder();
        param.entrySet().forEach(****(3번)**** -> {
            sb.append(map.getKey() + " : " + map.getValue() + "\n");
        });
        return sb.toString();
    }

 

1.

답: @PathVariable

2.

답: @RequestParam

3.

답: map


 

출처: 스프링부트 핵심 가이드

SPRING #2

Editor : dalpaeng4

728x90

관련글 더보기