본문 바로가기

항해 99/Spring

Spring - 입문

학습 내용 정리

 

Git SSH 키 생성

private 저장소는 권한이 있는 사람만 접근 가능 - SSH Key 발급 후 GitHub에 Public Key 등록 시 private 저장소 클론 가능

순서

  1. Git Bash에서 ssh-keygen 입력 후 enter 3회
  2. cd ~/.ssh 입력 후 enter
  3. ls -al 입력 후 enter
  4. cat (key name).pub 입력 (key name에는 .pub 앞 문장을 입력) 후 enter
  5. 아래의 공개키 복사(ctrl + Inst , 우클릭 paste)
  6. 깃허브 프로필 사진 클릭 후 Settings 클릭
  7. SSH and GPG keys에서 New SSH Key 클릭
  8. Key에 복사한 SSH 키 붙여넣기
  9. Add SSH key 클릭
  10. private 저장소 SSH 주소 복사
  11. git clone 복사한 주소 입력 후 enter → yes 후 enter

 

 

 

프로젝트 생성 Git 연동

  • 인텔리제이 프로젝트 생성 시 Create Git repository 선택 시 자동으로 프로젝트에 Git 연동
  • Menu → VCS → Create Git Repository → 프로젝트 폴더에 open

Git Commit

  • 좌측 Commit 탭 클릭 후 Changes 선택 Commit 메시지 입력 후 Commit 버튼 클릭

  • 하단 Git 탭에서 버전 별 변경사항 확인 가능

 

 

 

Gradle 사용

  • Build 자동화 시스템(작성한 Java 코드를 설정에 맞게 자동 Build)

build.gradle : Gradle 기반의 빌드 스크립트(소스 코드를 빌드하고 라이브러리들의 의존성을 쉽게 관리)

  • 필요한 외부 라이브러리는 dependencies 부분에 작성하면 Gradle이 해당 라이브러리들을 자동으로 다운로드 해옴
    • 다른 라이브러리들과의 의존성을 자동으로 관리해 주기 때문에 라이브러리들간의 충돌 걱정없이 개발에만 집중할 수 있음
    • 스프링 부트의 버전은 version '3.1.0' 부분에서 수정 가능
  • 변경 사항 수정 후 Gradle 아이콘(코끼리 모양)을 클릭해서 변경 사항 갱신 가능
  • External Libraries에서 Gradle이 다운로드해온 라이브러리들을 확인 가능(project 폴더 목록 하단에 위치)

 

 

 

서버

Client 와 Server

브라우저에서 서버에 정보를 요청하는 과정의 간략화 표현

  • 사용자의 요청이 서버에 도달하기 위해서는 해당 서버의 정보가 필요
  • 사용자의 요청이 해당 서버에 정확하게 도달할 수 있게 제공되는 정보가 IP 주소
    • 네트워크상에서의 데이터 송/수신은 이 주소를 기준으로 이루어지고 있음

웹 서버

  • 인터넷을 통해 HTTP를 이용하여 웹상의 클라이언트의 요청을 응답해주는 통신을 하는 일종의 컴퓨터

  1. 브라우저를 통해 HTTP Request로 웹사이트를 웹서버에 요청
  2. 웹서버는 요청을 승인하고 HTTP Response를 통해 웹사이트 데이터를 브라우저에 전송
  3. 브라우저는 서버에서 받아온 데이터를 이용해 웹사이트를 브라우저에 그려냄
    • 기본적으로 브라우저가 웹서버에 요청을 할때는 항상 GET method로 요청
  • API / Interface
    • API(application programming interface)는 다른 소프트웨어 시스템과 통신하기 위해 따라야 하는 규칙을 정의
      • 개발자는 다른 애플리케이션이 프로그래밍 방식으로 애플리케이션과 통신할 수 있도록 API를 표시하거나 생성
    • 인터페이스(interface)는 서로 다른 두 개의 시스템, 장치 사이에서 정보나 신호를 주고받는 경우의 접점이나 경계면을 의미
      • 사용자가 기기를 쉽게 동작시키는데 도움을 주는 시스템
  • RESTful API
    • Representational State Transfer(REST)는 API 작동 방식에 대한 조건을 부과하는 소프트웨어 아키텍처로 REST 아키텍처 스타일을 따르는 API를 REST API라고함
      • 서버의 api가 적절하게 http를 준수하며 잘 설계되어있으면 RESTful 하게 설계되어 있다고 생각하면 좋음
  • Web Server / Web Application Server(WAS)
    • 브라우저에서 URL을 입력하여 어떠한 페이지를 요청했을 때 HTTP의 요청을 받아들여 HTML 문서와 같은 정적인 콘텐츠를 사용자에게 전달해주는 역할을 하는 것이 Web Server
    • WAS를 사용하면 로그인,회원가입을 처리하거나 게시물을 조회하거나 정렬하는 등의 다양한 로직들을 수행하는 프로그램을 동작시킬 수 있음

 

 

 

HTTP

HTTP(HyperText Transfer Protocol) : 데이터를 주고 받는 양식을 정의한 "통신 규약"중 하나

  • 통신 규약이란, 컴퓨터끼리 데이터를 주고 받을 때 정해둔 약속

Request / Response

  • 브라우저는 서버에게 자신이 원하는 페이지(URL 등의 정보)를 요구(Request)
  • 서버는 브라우저가 원하는 페이지가 있는지 확인하고, 있다면 해당 페이지에 대한 데이터를 실어 응답(Response)
    • 없다면 없는 페이지에 대한 데이터를 반환

웹 개발자 도구의 네트워크 탭

  • 웹 개발을 진행하면서 문제가 생겼을 때 문제에 대한 분석을 위해 개발자 도구와 네트워크 탭을 사용
  • Headers 탭(Name의 목록 중 하나 클릭 시 나옴)
    • General : 브라우저에서 서버로 보낸 Request 데이터
    • HTTP 상태 코드
      • 1xx (Informational) : 요청이 수신 됨, 처리가 계속되고 있음(브라우저와 클라이언트 서버의 연결 상태 확인)
      • 2xx (Successful) : 클라이언트의 요청이 성공적으로 처리됨
      • 3xx (Redirection) : 클라언트가 추가적인 조치를 취해야 함을 나타냄(페이지 이동, 리다이렉션 등에 사용)
      • 4xx (Client Error) : 클라이언트에 오류가 있음(클라이언트의 잘못된 요청, 인증 오류)
        • 404 : 요청한 페이지나 리소스를 서버에서 찾을 수 없음
      • 5xx (Server Error) : 서버에 오류 발생(서버의 오류, 서버 과부하 등)
        • 500 : 서버 내부에 오류 발생

HTTP 구성 요소

  • Method (호출 요청 방식)
    • GET, POST, DELETE 등이 존재
  • Header (추가 데이터, 메타 데이터)
    • 아주 다양한 의사 표현을 위한 데이터를 모두 Header 필드에 넣고 주고 받음

 

 

 

테스트 코드

  • 블랙박스 테스팅
    • 소프트웨어 내부 구조나 동작원리를 모르는 상태, 웹 서비스의 사용자 입장에서 동작을 검사하는 방법
      • 장점 : 누구나 테스트 가능
      • 단점 : 기능이 증가될수록 테스의 범위가 증가, 테스트 하는 사람에 따라 테스트 퀄리티가 다를 수 있음
  • 개발자 테스트
    • 개발자가 직접 "본인이 작성한 코드"를 검증하기 위해 "테스트  코드"를 작성
      • 장점 : 빠르고 정확한 테스트가 가능(예상 동작 vs 실제 동작), 테스트 자동화 가능, 리팩토링이나 기능 추가가 편함
      • 단점 : 개발 시간이 오래 걸림, 테스트 코드를 유지보수하는 비용
  • 테스트 파일 생성
    • 클래스 파일 내에서 마우스 우클릭 → Generate... 클릭 → Test... 클릭 → 기본 세팅으로 생성(단축키 ctrl + shift + t)

테스트 코드 사용 예제

package com.sparta.springmvc;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sparta.springmvc.response.Star;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class JacksonTest {

    @Test
    @DisplayName("Object To JSON : get Method 필요")
    void test1() throws JsonProcessingException {
        Star star = new Star("Robbie", 95);

        ObjectMapper objectMapper = new ObjectMapper(); // Jackson 라이브러리의 ObjectMapper
        String json = objectMapper.writeValueAsString(star);

        System.out.println("json = " + json);
    }

    @Test
    @DisplayName("JSON To Object : 기본 생성자 & (get OR set) Method 필요")
    void test2() throws JsonProcessingException {
        String json = "{\"name\":\"Robbie\",\"age\":95}"; // JSON 타입의 String

        ObjectMapper objectMapper = new ObjectMapper(); // Jackson 라이브러리의 ObjectMapper

        Star star = objectMapper.readValue(json, Star.class);
        System.out.println("star.getName() = " + star.getName());
        System.out.println("star.getAge() = " + star.getAge());
    }
}

 

 

 

Lombok

  • 자바 프로젝트를 진행하는데 거의 필수적으로 필요한 메서드/ 생성자 등을 자동 생성해줌으로써 코드를 절약할 수 있도록 도와주는 라이브러리
    • 환경 설정(crtl + alt + s) → Annotation Processors 입력 → Enable 체크 후 ok → IDE plugins에서 lombok install 후 IDE 재시작
    • 사용 @(Annotation) 입력 후 필요한 lombok 입력
    • java.lang.NoSuchFieldError 에러 해결방법
      • IDE JDK 17로 변경(JDK 17 이후 버전에서는 lombok 사용 불가)

 

 

 

application.properties

  • application.properties는 관련된 설정을 할 때 사용되는 파일
    • 이 파일 사용 시 자동으로 설정되고 있는 설정 값을 쉽게 수정할 수 있음
    • DB 연결 시 DB의 정보를 제공하는 것도 쉽게 가능

 

 

 

Spring MVC

  • Spring Web MVC는 Servlet API를 기반으로 구축된 독창적인 웹 프레임워크
  • 중앙에 있는 DispatcherServlet이 요청을 처리하기 위한 공유 알고리즘을 제공하는 Front Controller 패턴을 중심으로 설계되어 있으며 이 모델은 유연하고 다양한 워크 플로우를 지원

MVC 디자인 패턴 : Model-View-Controller 의 약자, 소프트웨어 디자인 패턴 중 하나

  • MVC 패턴은 소프트웨어 구성 요소들을 Model, View, Contorller로 구분하여 각각의 역할을 분리

Model

  • 데이터와 비즈니스 로직을 담당
  • DB와 연동하여 데이터를 저장하고 불러오는 등의 작업 수행

View

  • 사용자 인터페이스를 담당
  • 사용자가 보는 화면과 버튼, 폼 등을 디자인하고 구현

Controller

  • Model과 View 사이의 상호작용을 조정하고 제어
  • 사용자의 입력을 받아 Model에 전달, Model의 결과를 바탕으로 View를 업데이트

Servlet(서블릿)

  • 자바를 사용하여 웹 페이지를 동적으로 생성하는 서버 측 프로그램 혹은 그 사양

사용자 (HTTP) API 요청 시 서버의 서블릿 동작

 

Front Controller

  1. Client(브라우저)에서 HTTP 요청이 들어오면 DispatcherServlet 객체가 요청을 분석
  2. DispatcherServlet 객체는 분석한 데이터를 토대로 Handler mapping을 통해 Controller를 찾아 요청을 전달
  3. ControllerDispathcerServlet : Controller는 요청에 대한 처리를 완료 후 처리에 대한 결과 즉, 데이터('Model')와 'View' 정보를 전달
  4. DispatcherServletClient : ViewResolver 통해 View에 Model을 적용하여 View를 Client에게 응답으로 전달

 

 

 

Controller

장점

  • API 마다 파일을 만들 필요 없음 - 유사한 성격의 API 를 하나의 Controller 로 관리
  • 메서드 이름도 내 마음대로 설정 가능합니다. (단, 클래스 내의 중복메서드명 불가)

컨트롤러 사용 예제

package com.sparta.springmvc.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@Controller
@RequestMapping("/api")
public class HelloController {
    @GetMapping("/hello")
    @ResponseBody
    public String hello() {
        return "Hello World!!";
    }

    @GetMapping("/get")
    @ResponseBody
    public String get() {
        return "GET Method 요청";
    }

    @PostMapping("/post")
    @ResponseBody
    public String post() {
        return "POST Method 요청";
    }

    @PutMapping("/put")
    @ResponseBody
    public String put() {
        return "PUT Method 요청";
    }

    @DeleteMapping("/delete")
    @ResponseBody
    public String delete() {
        return "DELETE Method 요청";
    }
}
  • 각각의 HTTP Method에 매핑되는 애너테이션 사용
  • @RequestMapping은 중복되는 URL를 단축

 

 

정적페이지 처리

  • SpringBoot 서버에 html 파일을 바로 요청하면 해당 html 파일을 static 폴더에서 찾아서 반환
  • 템플릿 엔진을 적용한 상태에서 static 폴더의 html 파일을 Controller를 통해서 처리하고 싶다면 redirect 요청을 문자열로 반환 시 http://localhost:8080/hello.html 요청이 재수행되면서 static 폴더의 파일을 반환할 수 있음
@GetMapping("/html/redirect")
public String htmlStatic() {
    return "redirect:/hello.html";
}
  • Template engine 에 View 전달 : 브라우저에서 바로 접근하지 못하게 하고 싶거나 특정 상황에 Controller를 통해서 제어하고 싶은 경우, templates 폴더에 해당 정적 html 파일을 추가하고 해당 html 파일명인  "hello" 문자열을 반환하여 처리할 수 있음
@GetMapping("/html/templates")
public String htmlTemplates() {
    return "hello";
}

 

 

 

동적페이지 처리

private static long visitCount = 0;

...

@GetMapping("/html/dynamic")
public String htmlDynamic(Model model) {
    visitCount++;
    model.addAttribute("visits", visitCount);
    return "hello-visit";
}

 

  1. Client 의 요청을 Controller에서 Model 로 처리
    • DB 조회가 필요하다면 DB 작업 후 처리한 데이터를 Model에 저장
  2. Template engine(Thymeleaf) 에게 View, Model 전달
    • View: 동적 HTML 파일 / Model: View 에 적용할 정보들
  3. Template engine
    • ViewModel을 적용 → 동적 웹페이지 생성
      • 예) 로그인 성공 시, "로그인된 사용자의 Nickname"을 페이지에 추가
  4. Client(브라우저)에게 View(동적 웹 페이지, HTML)를 전달

동적 페이지 처리 예제

package com.sparta.springmvc.html;

import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;

@Controller
public class HtmlController {

    private static long visitCount = 0;

    @GetMapping("/static-hello")
    public String hello() {
        return "hello.html";
    }

    @GetMapping("/html/redirect")
    public String htmlStatic() {
        return "redirect:/hello.html";
    }

    @GetMapping("/html/templates")
    public String htmlTemplates() {
        return "hello";
    }

    @GetMapping("/html/dynamic")
    public String htmlDynamic(Model model) {
        visitCount++;
        model.addAttribute("visits", visitCount);
        return "hello-visit";
    }
}

 

 

 

데이터를 Client 반환

  • JSON 데이터 반환하는 방법
    • 템플릿 엔진이 적용된 SpringBoot에서는 Controller에서 문자열을 반환하면 templates 폴더에서 해당 문자열의 .html 파일을 찾아서 반환
    • html 파일이 아닌 JSON 데이터를 브라우저에 반환하고 싶다면 해당 메서드에 @ResponseBody 애너테이션을 추가

JSON 데이터 반환 예제

package com.sparta.springmvc.response;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequestMapping("/response")
public class ResponseController {
    // [Response header]
    //   Content-Type: text/html
    // [Response body]
    //   {"name":"Robbie","age":95}
    @GetMapping("/json/string")
    @ResponseBody
    public String helloStringJson() {
        return "{\"name\":\"Robbie\",\"age\":95}";
    }

    // [Response header]
    //   Content-Type: application/json
    // [Response body]
    //   {"name":"Robbie","age":95}
    @GetMapping("/json/class")
    @ResponseBody
    public Star helloClassJson() {
        return new Star("Robbie", 95);
    }
}

 

Star 클래스

package com.sparta.springmvc.response;

import lombok.Getter;

@Getter
public class Star {
    String name;
    int age;

    public Star(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public Star() {}
}

 

@RestController

  • 해당 클래스의 모든 메서드에 @ResponseBody 애너테이션이 추가되는 효과를 부여할 수 있음
package com.sparta.springmvc.response;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/response/rest")
public class ResponseRestController {
    // [Response header]
    //   Content-Type: text/html
    // [Response body]
    //   {"name":"Robbie","age":95}
    @GetMapping("/json/string")
    public String helloStringJson() {
        return "{\"name\":\"Robbie\",\"age\":95}";
    }

    // [Response header]
    //   Content-Type: application/json
    // [Response body]
    //   {"name":"Robbie","age":95}
    @GetMapping("/json/class")
    public Star helloClassJson() {
        return new Star("Robbie", 95);
    }
}

 

 

 

Jackson

  • Jackson은 JSON 데이터 구조를 처리해주는 라이브러리
    • Object를  JSON 타입의 String으로 변환
    • JSON 타입의 String을 Object로 변환

Jackson 사용 예제

package com.sparta.springmvc;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.sparta.springmvc.response.Star;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

public class JacksonTest {

    @Test
    @DisplayName("Object To JSON : get Method 필요")
    void test1() throws JsonProcessingException {
        Star star = new Star("Robbie", 95);

        ObjectMapper objectMapper = new ObjectMapper(); // Jackson 라이브러리의 ObjectMapper
        String json = objectMapper.writeValueAsString(star);

        System.out.println("json = " + json);
    }

    @Test
    @DisplayName("JSON To Object : 기본 생성자 & (get OR set) Method 필요")
    void test2() throws JsonProcessingException {
        String json = "{\"name\":\"Robbie\",\"age\":95}"; // JSON 타입의 String

        ObjectMapper objectMapper = new ObjectMapper(); // Jackson 라이브러리의 ObjectMapper

        Star star = objectMapper.readValue(json, Star.class);
        System.out.println("star.getName() = " + star.getName());
        System.out.println("star.getAge() = " + star.getAge());
    }
}

 

Path Variable / Request Param

  • Path Variable
    • 서버에 보내려는 데이터를 URL 경로에 추가할 수 있음
  • Request Param
    • 서버에 보내려는 데이터를 URL 경로 마지막에 ? 와 & 를 사용하여 추가할 수 있음

사용 예제

package com.sparta.springmvc.request;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;

@Controller
@RequestMapping("/hello/request")
public class RequestController {
    @GetMapping("/form/html")
    public String helloForm() {
        return "hello-request-form";
    }

    // [Request sample]
    // GET http://localhost:8080/hello/request/star/Robbie/age/95
    @GetMapping("/star/{name}/age/{age}")
    @ResponseBody
    public String helloRequestPath(@PathVariable String name, @PathVariable int age)
    {
        return String.format("Hello, @PathVariable.<br> name = %s, age = %d", name, age);
    }

    // [Request sample]
    // GET http://localhost:8080/hello/request/form/param?name=Robbie&age=95
    @GetMapping("/form/param")
    @ResponseBody
    public String helloGetRequestParam(@RequestParam String name, @RequestParam int age) {
        return String.format("Hello, @RequestParam.<br> name = %s, age = %d", name, age);
    }

    // [Request sample]
    // POST http://localhost:8080/hello/request/form/param
    // Header
    //  Content type: application/x-www-form-urlencoded
    // Body
    //  name=Robbie&age=95
    @PostMapping("/form/param")
    @ResponseBody
    public String helloPostRequestParam(@RequestParam String name, @RequestParam int age) {
        return String.format("Hello, @RequestParam.<br> name = %s, age = %d", name, age);
    }
}

 

 

 

HTTP 데이터 객체로 처리

  • HTML의 form 태그를 사용하여 POST 방식으로 HTTP 요청을 보낼 수 있음
  • 이때 해당 데이터는 HTTP Body에 name=Robbie&age=95 형태로 담겨져서 서버로 전달
  • 해당 데이터를 Java의 객체 형태로 받는 방법은 @ModelAttribute 애너테이션을 사용한 후 Body 데이터를 Star star 받아올 객체를 선언
package com.sparta.springmvc.request;

public class Star {
    String name;
    int age;

    public Star(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
// [Request sample]
// POST http://localhost:8080/hello/request/form/model
// Header
//  Content type: application/x-www-form-urlencoded
// Body
//  name=Robbie&age=95
@PostMapping("/form/model")
@ResponseBody
public String helloRequestBodyForm(@ModelAttribute Star star) {
    return String.format("Hello, @ModelAttribute.<br> (name = %s, age = %d) ", star.name, star.age);
}

// [Request sample]
// GET http://localhost:8080/hello/request/form/param/model?name=Robbie&age=95
@GetMapping("/form/param/model")
@ResponseBody
public String helloRequestParam(@ModelAttribute Star star) {
    return String.format("Hello, @ModelAttribute.<br> (name = %s, age = %d) ", star.name, star.age);
}

// [Request sample]
// POST http://localhost:8080/hello/request/form/json
// Header
//  Content type: application/json
// Body
//  {"name":"Robbie","age":"95"}
@PostMapping("/form/json")
@ResponseBody
public String helloPostRequestJson(@RequestBody Star star) {
    return String.format("Hello, @RequestBody.<br> (name = %s, age = %d) ", star.name, star.age);
}

 

 

 

Create, Read

  • Create 구현
    1. 메모 데이터를 저장할 Memo 클래스 생성
    2. 메모 생성하기 API를 받을 수 있는 Controller와 메서드 생성
    3. Client에 데이터를 반환할 때 사용할 MemoResponseDto 클래스 생성
    4. Client의 요청 데이터를 받아줄 MemoRequestDto 클래스 생성
    5. DB와 연결을 하지 않았기 때문에 메모 데이터를 저장할 Java 컬렉션 생성
    6. 메모 생성 로직 작성
  • Read 구현
    • DB 역할을 하는 memoList를 조회하여 List<MemoResponseDto>로 변환한 후 반환

구현 예제

Memo 클래스

package com.sparta.memo.entity;

import com.sparta.memo.dto.MemoRequestDto;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Getter
@Setter
@NoArgsConstructor
public class Memo {
    private Long id;
    private String username;
    private String contents;

    public Memo(MemoRequestDto requestDto) {
        this.username = requestDto.getUsername();
        this.contents = requestDto.getContents();
    }
}

 

Memo Controller

package com.sparta.memo.controller;

import com.sparta.memo.dto.MemoRequestDto;
import com.sparta.memo.dto.MemoResponseDto;
import com.sparta.memo.entity.Memo;
import org.springframework.web.bind.annotation.*;

import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@RequestMapping("/api")
public class MemoController {

    private final Map<Long, Memo> memoList = new HashMap<>();

    @PostMapping("/memos")
    public MemoResponseDto createMemo(@RequestBody MemoRequestDto requestDto) {
        // RequestDto -> Entity
        Memo memo = new Memo(requestDto);

        // Memo Max ID Check
        Long maxId = memoList.size() > 0 ? Collections.max(memoList.keySet()) + 1 : 1;
        memo.setId(maxId);

        // DB 저장
        memoList.put(memo.getId(), memo);

        // Entity -> ResponseDto
        MemoResponseDto memoResponseDto = new MemoResponseDto(memo);

        return memoResponseDto;
    }

    @GetMapping("/memos")
    public List<MemoResponseDto> getMemos() {
        // Map To List
        List<MemoResponseDto> responseList = memoList.values().stream()
                .map(MemoResponseDto::new).toList();

        return responseList;
    }
}

 

DTO

package com.sparta.memo.dto;

import lombok.Getter;

@Getter
public class MemoRequestDto {
    private String username;
    private String contents;
}
package com.sparta.memo.dto;

import com.sparta.memo.entity.Memo;
import lombok.Getter;

@Getter
public class MemoResponseDto {
    private Long id;
    private String username;
    private String contents;

    public MemoResponseDto(Memo memo) {
        this.id = memo.getId();
        this.username = memo.getUsername();
        this.contents = memo.getContents();
    }
}

 

 

 

Update, Delete

구현 예제

Memo 클래스 - update 추가

public void update(MemoRequestDto requestDto) {
    this.username = requestDto.getUsername();
    this.contents = requestDto.getContents();
}

 

MemoController - update, delete 추가

@PutMapping("/memos/{id}")
public Long updateMemo(@PathVariable Long id, @RequestBody MemoRequestDto requestDto) {
    // 해당 메모가 DB에 존재하는지 확인
    if (memoList.containsKey(id)) {
        Memo memo = memoList.get(id);

        // memo 수정
        memo.update(requestDto);
        return memo.getId();
    } else {
        throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
    }
}

@DeleteMapping("/memos/{id}")
public Long deleteMemo(@PathVariable Long id) {
    // 해당 메모가 DB에 존재하는지 확인
    if (memoList.containsKey(id)) {
        // 해당 메모를 삭제하기
        memoList.remove(id);
        return id;
    } else {
        throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
    }
}

 

 

 

JDBC

  • JDBC는 Java Database Connectivity로 DB에 접근할 수 있도록 Java에서 제공하는 API
  • JDBC에 연결해야하는 DB의 JDBC 드라이버를 제공하면 DB 연결 로직을 변경할 필요없이 DB 변경이 가능
  • MySQL 드라이버를 사용해 DB에 연결을 하다 PostgreSQL 서버로 변경이 필요할 때 드라이버만 교체하면 손쉽게 DB 변경이 가능

JDBC Template

  • JDBC의 등장으로 손쉽게 DB교체가 가능해졌지만 아직도 DB에 연결하기 위해 여러가지 작업 로직들을 직접 작성해야한다는 불편함이 남음
  • 이러한 불편함을 해결하기 위해 커넥션 연결, statement 준비 및 실행, 커넥션 종료 등의 반복적이고 중복되는 작업들을 대신 처리해주는 JdbcTemplate이 등장

사용방법

  1. application.properties에 DB에 접근하기 위한 정보를 작성
  2. build.gradle에 JDBC 라이브러리와 MySQL을 등록
  3. DB연결이 필요한 곳에서 JdbcTemplate을 주입받아와 사용

application.properties

spring.datasource.url=jdbc:mysql://localhost:3306/memo
spring.datasource.username=root
spring.datasource.password=zaq123
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

 

build.gradle dependencies

// MySQL
implementation 'mysql:mysql-connector-java:8.0.28'
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'

 

MemoController

import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;
import org.springframework.web.bind.annotation.*;

import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.List;

@RestController
@RequestMapping("/api")
public class MemoController {

    private final JdbcTemplate jdbcTemplate;

    public MemoController(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @PostMapping("/memos")
    public MemoResponseDto createMemo(@RequestBody MemoRequestDto requestDto) {
        // RequestDto -> Entity
        Memo memo = new Memo(requestDto);

        // DB 저장
        KeyHolder keyHolder = new GeneratedKeyHolder(); // 기본 키를 반환받기 위한 객체

        String sql = "INSERT INTO memo (username, contents) VALUES (?, ?)";
        jdbcTemplate.update( con -> {
                    PreparedStatement preparedStatement = con.prepareStatement(sql,
                            Statement.RETURN_GENERATED_KEYS);

                    preparedStatement.setString(1, memo.getUsername());
                    preparedStatement.setString(2, memo.getContents());
                    return preparedStatement;
                },
                keyHolder);

        // DB Insert 후 받아온 기본키 확인
        Long id = keyHolder.getKey().longValue();
        memo.setId(id);

        // Entity -> ResponseDto
        MemoResponseDto memoResponseDto = new MemoResponseDto(memo);

        return memoResponseDto;
    }

    @GetMapping("/memos")
    public List<MemoResponseDto> getMemos() {
        // DB 조회
        String sql = "SELECT * FROM memo";

        return jdbcTemplate.query(sql, new RowMapper<MemoResponseDto>() {
            @Override
            public MemoResponseDto mapRow(ResultSet rs, int rowNum) throws SQLException {
                // SQL 의 결과로 받아온 Memo 데이터들을 MemoResponseDto 타입으로 변환해줄 메서드
                Long id = rs.getLong("id");
                String username = rs.getString("username");
                String contents = rs.getString("contents");
                return new MemoResponseDto(id, username, contents);
            }
        });
    }

    @PutMapping("/memos/{id}")
    public Long updateMemo(@PathVariable Long id, @RequestBody MemoRequestDto requestDto) {
        // 해당 메모가 DB에 존재하는지 확인
        Memo memo = findById(id);
        if(memo != null) {
            // memo 내용 수정
            String sql = "UPDATE memo SET username = ?, contents = ? WHERE id = ?";
            jdbcTemplate.update(sql, requestDto.getUsername(), requestDto.getContents(), id);

            return id;
        } else {
            throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
        }
    }

    @DeleteMapping("/memos/{id}")
    public Long deleteMemo(@PathVariable Long id) {
        // 해당 메모가 DB에 존재하는지 확인
        Memo memo = findById(id);
        if(memo != null) {
            // memo 삭제
            String sql = "DELETE FROM memo WHERE id = ?";
            jdbcTemplate.update(sql, id);

            return id;
        } else {
            throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
        }
    }

    private Memo findById(Long id) {
        // DB 조회
        String sql = "SELECT * FROM memo WHERE id = ?";

        return jdbcTemplate.query(sql, resultSet -> {
            if(resultSet.next()) {
                Memo memo = new Memo();
                memo.setUsername(resultSet.getString("username"));
                memo.setContents(resultSet.getString("contents"));
                return memo;
            } else {
                return null;
            }
        }, id);
    }
}

 

MemoResponseDto

package com.sparta.memo.dto;

import com.sparta.memo.entity.Memo;
import lombok.Getter;

@Getter
public class MemoResponseDto {
    private Long id;
    private String username;
    private String contents;

    public MemoResponseDto(Memo memo) {
        this.id = memo.getId();
        this.username = memo.getUsername();
        this.contents = memo.getContents();
    }

    public MemoResponseDto(Long id, String username, String contents) {
        this.id = id;
        this.username = username;
        this.contents = contents;
    }
}

 

예제 파일

memo.zip
0.28MB
spring-mvc.zip
0.28MB