24.05.14 화 TIL(Today I Learned)

신민금·2024년 5월 14일
0
post-thumbnail

TIL(Today I Learned)

: 매일 저녁, 하루를 마무리하며 작성 !
: ⭕ 지식 위주, 학습한 것을 노트 정리한다고 생각하고 작성하면서 머리 속 흩어져있는 지식들을 정리 !


알고리즘 코드카타

  • 문제 설명
    1937년 Collatz란 사람에 의해 제기된 이 추측은, 주어진 수가 1이 될 때까지 다음 작업을 반복하면, 모든 수를 1로 만들 수 있다는 추측입니다. 작업은 다음과 같습니다.
1-1. 입력된 수가 짝수라면 2로 나눕니다. 
1-2. 입력된 수가 홀수라면 3을 곱하고 1을 더합니다. 
2. 결과로 나온 수에 같은 작업을 1이 될 때까지 반복합니다. 

예를 들어, 주어진 수가 6이라면 6 → 3 → 10 → 5 → 16 → 8 → 4 → 2 → 1 이 되어 총 8번 만에 1이 됩니다. 위 작업을 몇 번이나 반복해야 하는지 반환하는 함수, solution을 완성해 주세요. 단, 주어진 수가 1인 경우에는 0을, 작업을 500번 반복할 때까지 1이 되지 않는다면 –1을 반환해 주세요.

  • 제한사항
    입력된 수, num은 1 이상 8,000,000 미만인 정수입니다.
class Solution {
    public int solution(long num) {
        int answer = 0;
        
        while(num != 1) {
            if(num % 2 == 0)
                num /= 2;
            else
                num = num * 3 + 1;
            answer++;
            
            if(answer >= 500) {
                answer = -1;
                break;
            }
        }
        
        return answer;
    }
}

Spring 입문주차 1주차

1-7 Lombok과 application.properties

  1. Lombok이란?

  2. application.properties

  • application.properties는 Spring과 관련된 설정을 할 때 사용되는 파일
  • Spring과 SpringBoot의 차이에 대해 학습할 때 SpringBoot를 사용하면 개발에 필요한 설정 정보들이 자동으로 설정, 이 파일을 사용하면 자동으로 설정되고 있는 설정 값을 쉽게 수정 가능 !
  • DB 연결 시 DB의 정보를 제공해야하는데 이러한 경우에도 이 파일을 이용하여 쉽게 값을 전달
  • 예) server.port=8081이렇게 설정을 하면 서버의 port 번호를 ‘8080’에서 ‘8081’로 바꿔서 실행

1-8 MySQL 설치하기

  1. MySQL 설치
  • 위치 이동 : cd /usr/local/mysql/bin
  • MySQL 접속 : ./mysql -u root -p
  • academy 데이터베이스 생성 : CREATE DATABASE academy;
  • 데이터베이스 확인 : show databases;
  • 데이터베이스 이동 (사용) : use academy;

1-9 Spring MVC란 무엇일까?

  1. MVC 디자인 패턴이란?
  • MVC란 Model-View-Controller의 약자로, 소프트웨어 디자인 패턴 중 하나
  • MVC 패턴은 소프트웨어를 구성하는 요소들을 Model, View, Controller로 구분하여 각각의 역할을 분리
  1. Spring MVC란?
  • Servlet (서블릿) : 자바를 사용하여 웹 페이지를 동적으로 생성하는 서버 측 프로그램 혹은 그 사양

    1) 사용자가 Client(브라우저)를 통해 서버에 HTTP Request 즉, API 요청
    2) 요청을 받은 Servlet 컨테이너는 HttpServletRequest, HttpServletResponse 객체를 생성( 약속된 HTTP의 규격을 맞추면서 쉽게 HTTP에 담긴 데이터를 사용하기 위한 객체 )
    3) 설정된 정보를 통해 어떠한 Servlet에 대한 요청인지 찾는다.
    4) 해당 Servlet에서 service 메서드를 호출한 뒤 브라우저의 요청 Method에 따라 doGet 혹은 doPost 등의 메서드를 호출
    5) 호출한 메서드들의 결과를 그대로 반환하거나 동적 페이지를 생성한 뒤 HttpServletResponse 객체에 응답을 담아 Client(브라우저)에 반환
    6) 응답이 완료되면 생성한 HttpServletRequest, HttpServletResponse 객체를 소멸
  1. Front Controller
  • 모든 API 요청을 앞서 살펴본 서블릿의 동작 방식에 맞춰 코드를 구현한다면 무수히 많은 Servlet 클래스를 구현해야함.
  • 따라서 Spring은 DispatcherServlet을 사용하여 Front Controller 패턴 방식으로 API 요청을 효율적으로 처리

1) Client(브라우저)에서 HTTP 요청이 들어오면 DispatcherServlet 객체가 요청을 분석
2) DispatcherServlet 객체는 분석한 데이터를 토대로 Handler mapping을 통해 Controller를 찾아 요청을 전달 -> Handler mapping 에는 API path 와 Controller 메서드가 매칭 -> API path 즉, URL을 Controller에 작성하는 방법은 @Controller 애너테이션이 달려있는 클래스를 생성한 뒤 @GetMapping 처럼 요청한 HTTP Method 와 일치하는 애너테이션을 추가한 메서드를 구현
3) Controller → DispathcerServlet : 해당 Controller는 요청에 대한 처리를 완료 후 처리에 대한 결과 즉, 데이터('Model')와 'View' 정보를 전달
4) DispatcherServlet → Client : ViewResolver 통해 View에 Model을 적용하여 View를 Client에게 응답으로 전달

1-10 Controller 이해하기

  1. 프로젝트 생성

  2. Controller의 장점

  • API 마다 파일을 만들 필요 X
  • 메서드 이름도 내 마음대로 설정 가능
  1. @Controller
@Controller
public class HelloController {
    @GetMapping("/api/hello")
    @ResponseBody
    public String hello() {
        return "Hello World!";
    }
}

@Controller는 해당 클래스가 Controller의 역할을 수행할 수 있도록 등록

  1. @GET, @POST, @PUT, @DELETE
// @GET
@GetMapping("/api/get")
@ResponseBody
public String get() {
    return "GET Method 요청";
}

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

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

// @DELETE
@DeleteMapping("/api/delete")
@ResponseBody
public String delete() {
    return "DELETE Method 요청";
}


  1. @RequestMapping
    @RequestMapping은 중복되는 URL를 단축
@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 요청";
    }
}

// HelloController
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 요청";
    }
}

1-11 정적 페이지와 동적 페이지


1. 정적 페이지 처리하기

// 1. static 폴더 : http://localhost:8080/hello.html
// /static/hello.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Hello Spring</title>
</head>
<body>
Hello, Spring 정적 웹 페이지!! (static)
</body>
</html>

// "hello.html" 이렇게 문자열로 반환하면 static 폴더의 해당 html 파일을 찾아 반환

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


// 2. Redirect : http://localhost:8080/html/redirect

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

// 3. Template engine 에 View 전달 : http://localhost:8080/html/templates

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

// /templates/hello.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Hello Spring</title>
</head>
<body>
Hello, Spring 정적 웹 페이지!! (templates)
</body>
</html>
  1. 동적 페이지 처리하기
// /templates/hello-visit.html

private static long visitCount = 0;

...

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

// HtmlController

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

1-12 데이터를 Client에 반환하는 방법

  1. Response 트렌드의 변화

웹 생태계가 고도화 되는 과정중에 상대적으로 프론트엔드와 백엔드가 각각 따로 발전하게 되면서, 느슨하게 결합하는 방식을 더 많이 채택하게 되었고, 최근에는 서버가 직접 뷰(html/css/js)를 반환하기 보다는 요청에 맞는 특정한 정보만 반환하는 것을 조금 더 선호 -> 그래서 요즘에는 주로 서버에서는 데이터 교환 포맷 중 JSON 형태로 데이터를 반환하기도 !

  1. JSON 데이터 반환하는 방법
// 1. 반환값: String 
// http://localhost:8080/response/json/string

@GetMapping("/response/json/string")
@ResponseBody
public String helloStringJson() {
    return "{\"name\":\"Robbie\",\"age\":95}";
}

// helloStringJson 메서드

@GetMapping("/json/string")
@ResponseBody
public String helloStringJson() {
    return "{\"name\":\"Robbie\",\"age\":95}";
}


// 2. 반환값: String 외 자바 클래스
// http://localhost:8080/response/json/class

@GetMapping("/response/json/class")
@ResponseBody
public Star helloClassJson() {
    return new Star("Robbie", 95);
}

// Star.java

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() {}
}

// ResponseController.java

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

  1. @RestController : @Controller + @ResponseBody
    @RestController를 사용하면 해당 클래스의 모든 메서드에 @ResponseBody 애너테이션이 추가되는 효과를 부여
// ResponseRestController.java

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

1-13 Jackson이란 무엇일까?

  1. Jackson 라이브러리
  • Jackson은 JSON 데이터 구조를 처리해주는 라이브러리
    ( Object를 JSON 타입의 String으로 변환, JSON 타입의 String을 Object로 변환 )
  1. Object To JSON
@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);
}
  1. JSON To Object
@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());
}

1-14 Path Variable과 Request Param

// Request 요청 HTML 추가
// hello-request-form.html

<!DOCTYPE html>
<html>
<head>
  <title>Hello Request</title>
</head>
<body>
<h2>GET /star/{name}/age/{age}</h2>
<form id="helloPathForm">
  <div>
    이름: <input name="name" type="text">
  </div>
  <div>
    나이: <input name="age" type="text">
  </div>
</form>
<div>
  <button id="helloPathFormSend">전송</button>
</div>
<br>

<h2>GET /hello/request/form/param</h2>
<form method="GET" action="/hello/request/form/param">
  <div>
    이름: <input name="name" type="text">
  </div>
  <div>
    나이: <input name="age" type="text">
  </div>
  <button>전송</button>
</form>

<br>

<h2>POST /hello/request/form/param</h2>
<form method="POST" action="/hello/request/form/param">
  <div>
    이름: <input name="name" type="text">
  </div>
  <div>
    나이: <input name="age" type="text">
  </div>
  <button>전송</button>
</form>
<br>

<h2>POST /hello/request/form/model</h2>
<form method="POST" action="/hello/request/form/model">
  <div>
    이름: <input name="name" type="text">
  </div>
  <div>
    나이: <input name="age" type="text">
  </div>
  <button>전송</button>
</form>
<br>

<h2>GET /hello/request/form/param/model </h2>
<form method="GET" action="/hello/request/form/param/model">
  <div>
    이름: <input name="name" type="text">
  </div>
  <div>
    나이: <input name="age" type="text">
  </div>
  <button>전송</button>
</form>
<br>

<h2>POST /hello/request/form/json</h2>
<form id="helloJsonForm">
  <div>
    이름: <input name="name" type="text">
  </div>
  <div>
    나이: <input name="age" type="text">
  </div>
</form>
<div>
  <button id="helloJsonSend">전송</button>
</div>
<div>
  <div id="helloJsonResult"></div>
</div>
</body>
<script>
  // GET /star/{name}/age/{age}
  const helloPathForm = document.querySelector("#helloPathFormSend")
  helloPathForm.onclick = (e) => {
    const form = document.querySelector("#helloPathForm");
    const name = form.querySelector('input[name="name"]').value;
    const age = form.querySelector('input[name="age"]').value;
    const relativeUrl = `/hello/request/star/${name}/age/${age}`;
    window.location.href = relativeUrl;
  }

  // POST /hello/request/form/json
  const helloJson = document.querySelector("#helloJsonSend")
  helloJson.onclick = async (e) => {
    const form = document.querySelector("#helloJsonForm");

    const data = {
      name: form.querySelector('input[name="name"]').value,
      age: form.querySelector('input[name="age"]').value
    }

    const response = await fetch('/hello/request/form/json', {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
      },
      body: JSON.stringify(data),
    })

    const text = await response.text(); // read response body as text
    document.querySelector("#helloJsonResult").innerHTML = text;
  };
</script>
</html>

  1. Path Variable
// Path Variable 방식
// helloRequestPath

// [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);
}
  1. Request Param
// Request Param 방식
// helloGetRequestParam

// [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);
}

1-15 HTTP 데이터를 객체로 처리하는 방법

  1. @ModelAttribute
// form 태그 POST
// helloRequestBodyForm 

// [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);
}


// Query String 방식
// helloRequestParam

// [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);
}
  1. @RequestBody
// Body JSON 데이터
// helloPostRequestJson

// [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);
}


// RequestController.java

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(required = false) String name, 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);
    }

    // [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);
    }
}

1-16 메모장 프로젝트 설계

  1. 프로젝트 생성
    시작코드 : src > main > resources > static > index.html
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Memo Service</title>

  <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
  <link href="https://fonts.googleapis.com/css2?family=Noto+Sans+KR:wght@500&display=swap" rel="stylesheet">

  <style>
    @import url(//spoqa.github.io/spoqa-han-sans/css/SpoqaHanSans-kr.css);

    body {
      margin: 0px;
    }

    .area-edit {
      display: none;
    }

    .wrap {
      width: 538px;
      margin: 10px auto;
    }

    #contents {
      width: 538px;
    }

    .area-write {
      position: relative;
      width: 538px;
    }

    .area-write img {
      cursor: pointer;
      position: absolute;
      width: 22.2px;
      height: 18.7px;
      bottom: 15px;
      right: 17px;
    }

    .background-header {
      position: fixed;
      z-index: -1;
      top: 0px;
      width: 100%;
      height: 428px;
      background-color: #339af0;
    }

    .background-body {
      position: fixed;
      z-index: -1;
      top: 428px;
      height: 100%;
      width: 100%;
      background-color: #dee2e6;
    }

    .header {
      margin-top: 50px;
    }

    .header h2 {
      /*font-family: 'Noto Sans KR', sans-serif;*/
      height: 33px;
      font-size: 42px;
      font-weight: 500;
      font-stretch: normal;
      font-style: normal;
      line-height: 0.79;
      letter-spacing: -0.5px;
      text-align: center;
      color: #ffffff;
    }

    .header p {
      margin: 40px auto;
      width: 217px;
      height: 48px;
      font-family: 'Noto Sans KR', sans-serif;
      font-size: 16px;
      font-weight: 500;
      font-stretch: normal;
      font-style: normal;
      line-height: 1.5;
      letter-spacing: -1.12px;
      text-align: center;
      color: #ffffff;
    }

    textarea.field {
      width: 502px !important;
      height: 146px;
      border-radius: 5px;
      background-color: #ffffff;
      border: none;
      padding: 18px;
      resize: none;
    }

    textarea.field::placeholder {
      width: 216px;
      height: 16px;
      font-family: 'Noto Sans KR', sans-serif;
      font-size: 16px;
      font-weight: normal;
      font-stretch: normal;
      font-style: normal;
      line-height: 1;
      letter-spacing: -0.96px;
      text-align: left;
      color: #868e96;
    }

    .card {
      width: 538px;
      border-radius: 5px;
      background-color: #ffffff;
      margin-bottom: 12px;
    }

    .card .metadata {
      position: relative;
      display: flex;
      font-family: 'Spoqa Han Sans';
      font-size: 11px;
      font-weight: normal;
      font-stretch: normal;
      font-style: normal;
      line-height: 1;
      letter-spacing: -0.77px;
      text-align: left;
      color: #adb5bd;
      height: 14px;
      padding: 10px 23px;
    }

    .card .metadata .date {

    }

    .card .metadata .username {
      margin-left: 20px;
    }

    .contents {
      padding: 0px 23px;
      word-wrap: break-word;
      word-break: break-all;
    }

    .contents div.edit {
      display: none;
    }

    .contents textarea.te-edit {
      border-right: none;
      border-top: none;
      border-left: none;
      resize: none;
      border-bottom: 1px solid #212529;
      width: 100%;
      font-family: 'Spoqa Han Sans';
    }

    .footer {
      position: relative;
      height: 40px;
    }

    .footer img.icon-start-edit {
      cursor: pointer;
      position: absolute;
      bottom: 14px;
      right: 55px;
      width: 18px;
      height: 18px;
    }

    .footer img.icon-end-edit {
      cursor: pointer;
      position: absolute;
      display: none;
      bottom: 14px;
      right: 55px;
      width: 20px;
      height: 15px;
    }

    .footer img.icon-delete {
      cursor: pointer;
      position: absolute;
      bottom: 12px;
      right: 19px;
      width: 14px;
      height: 18px;
    }

    #cards-box {
      margin-top: 12px;
    }
  </style>
  <script>
    // 사용자가 내용을 올바르게 입력하였는지 확인합니다.
    function isValidContents(contents) {
      if (contents == '') {
        alert('내용을 입력해주세요');
        return false;
      }
      if (contents.trim().length > 140) {
        alert('공백 포함 140자 이하로 입력해주세요');
        return false;
      }
      return true;
    }

    // 익명의 username을 만듭니다.
    function genRandomName(length) {
      let result = '';
      let characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
      let charactersLength = characters.length;
      for (let i = 0; i < length; i++) {
        let number = Math.random() * charactersLength;
        let index = Math.floor(number);
        result += characters.charAt(index);
      }
      return result;
    }

    // 수정 버튼을 눌렀을 때, 기존 작성 내용을 textarea 에 전달합니다.
    // 숨길 버튼을 숨기고, 나타낼 버튼을 나타냅니다.
    function editPost(id) {
      showEdits(id);
      let contents = $(`#${id}-contents`).text().trim();
      $(`#${id}-textarea`).val(contents);
    }

    function showEdits(id) {
      $(`#${id}-editarea`).show();
      $(`#${id}-submit`).show();
      $(`#${id}-delete`).show();

      $(`#${id}-contents`).hide();
      $(`#${id}-edit`).hide();
    }

    $(document).ready(function () {
      // HTML 문서를 로드할 때마다 실행합니다.
      getMessages();
    })

    // 메모를 불러와서 보여줍니다.
    function getMessages() {
      // 1. 기존 메모 내용을 지웁니다.
      $('#cards-box').empty();
      // 2. 메모 목록을 불러와서 HTML로 붙입니다.
      $.ajax({
        type: 'GET',
        url: '/api/memos',
        success: function (response) {
          for (let i = 0; i < response.length; i++) {
            let message = response[i];
            let id = message['id'];
            let username = message['username'];
            let contents = message['contents'];
            let modifiedAt = message['modifiedAt'];
            addHTML(id, username, contents, modifiedAt);
          }
        }
      })
    }

    // 메모 하나를 HTML로 만들어서 body 태그 내 원하는 곳에 붙입니다.
    function addHTML(id, username, contents, modifiedAt) {
      // 1. HTML 태그를 만듭니다.
      let tempHtml = `<div class="card">
                <!-- date/username 영역 -->
                <div class="metadata">
                    <div class="date">
                        ${modifiedAt}
                    </div>
                    <div id="${id}-username" class="username">
                        ${username}
                    </div>
                </div>
                <!-- contents 조회/수정 영역-->
                <div class="contents">
                    <div id="${id}-contents" class="text">
                        ${contents}
                    </div>
                    <div id="${id}-editarea" class="edit">
                        <textarea id="${id}-textarea" class="te-edit" name="" id="" cols="30" rows="5"></textarea>
                    </div>
                </div>
                <!-- 버튼 영역-->
                <div class="footer">
                    <img id="${id}-edit" class="icon-start-edit" src="images/edit.png" alt=""token interpolation">${id}')">
                    <img id="${id}-delete" class="icon-delete" src="images/delete.png" alt=""token interpolation">${id}')">
                    <img id="${id}-submit" class="icon-end-edit" src="images/done.png" alt=""token interpolation">${id}')">
                </div>
            </div>`;
      // 2. #cards-box 에 HTML을 붙인다.
      $('#cards-box').append(tempHtml);
    }

    // 메모를 생성합니다.
    function writePost() {
      // 1. 작성한 메모를 불러옵니다.
      let contents = $('#contents').val();

      // 2. 작성한 메모가 올바른지 isValidContents 함수를 통해 확인합니다.
      if (isValidContents(contents) == false) {
        return;
      }
      // 3. genRandomName 함수를 통해 익명의 username을 만듭니다.
      let username = genRandomName(10);

      // 4. 전달할 data JSON으로 만듭니다.
      let data = {'username': username, 'contents': contents};

      // 5. POST /api/memos 에 data를 전달합니다.
      $.ajax({
        type: "POST",
        url: "/api/memos",
        contentType: "application/json",
        data: JSON.stringify(data),
        success: function (response) {
          alert('메시지가 성공적으로 작성되었습니다.');
          window.location.reload();
        }
      });
    }

    // 메모를 수정합니다.
    function submitEdit(id) {
      // 1. 작성 대상 메모의 username과 contents 를 확인합니다.
      let username = $(`#${id}-username`).text().trim();
      let contents = $(`#${id}-textarea`).val().trim();

      // 2. 작성한 메모가 올바른지 isValidContents 함수를 통해 확인합니다.
      if (isValidContents(contents) == false) {
        return;
      }

      // 3. 전달할 data JSON으로 만듭니다.
      let data = {'username': username, 'contents': contents};

      // 4. PUT /api/memos/{id} 에 data를 전달합니다.
      $.ajax({
        type: "PUT",
        url: `/api/memos/${id}`,
        contentType: "application/json",
        data: JSON.stringify(data),
        success: function (response) {
          alert('메시지 변경에 성공하였습니다.');
          window.location.reload();
        }
      });
    }

    // 메모를 삭제합니다.
    function deleteOne(id) {
      // 1. DELETE /api/memos/{id} 에 요청해서 메모를 삭제합니다.
      $.ajax({
        type: "DELETE",
        url: `/api/memos/${id}`,
        success: function (response) {
          alert('메시지 삭제에 성공하였습니다.');
          window.location.reload();
        }
      })
    }
  </script>
</head>

<body>
<div class="background-header">

</div>
<div class="background-body">

</div>
<div class="wrap">
  <div class="header">
    <h2>Memo</h2>
    <p>
      공유하고 싶은 소식을 입력해주세요.
    </p>
  </div>
  <div class="area-write">
        <textarea class="field" placeholder="공유하고 싶은 소식을 입력해주세요" name="contents" id="contents" cols="30"
                  rows="10"></textarea>
    <!--            <button class="btn btn-danger">작성하기</button>-->
    <img src="images/send.png" alt="" onclick="writePost()">
  </div>
  <div id="cards-box" class="area-read">
    <div class="card">
      <!-- date/username 영역 -->
      <div class="metadata">
        <div class="date">
          October 10, 2020
        </div>
        <div class="username">
          anonymous
        </div>
      </div>
      <!-- contents 조회/수정 영역-->
      <div class="contents">

      </div>
      <!-- 버튼 영역-->
      <div class="footer">
        <img id="1-edit" class="icon-start-edit" src="images/edit.png" alt="" onclick="editPost('1')">
        <img id="1-delete" class="icon-delete" src="images/delete.png" alt="" onclick="deleteOne('1')">
        <img id="1-submit" class="icon-end-edit" src="images/done.png" alt="" onclick="submitEdit('1')">
      </div>
    </div>
  </div>
</div>
</body>

</html>
  1. 메모장 기능 설계

  2. API 테이블

1-17 Create, Read 구현하기

  1. DTO란 무엇일까?
  • DTO(Data Transfer Object)는 데이터 전송 및 이동을 위해 생성되는 객체를 의미
  • Client에서 보내오는 데이터를 객체로 처리할 때 사용
  • 서버의 계층간의 이동에도 사용
  • DB와의 소통을 담당하는 Java 클래스를 그대로 Client에 반환하는 것이 아니라 DTO로 한번 변환한 후 반환할 때도 사용
  1. Create 구현
// 1. 메모 데이터를 저장할 Memo 클래스 생성
// entity > Memo

package com.sparta.memo.entity;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

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


// 2. 메모 생성하기 API를 받을 수 있는 Controller와 메서드 생성

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

    @PostMapping("/memos")
    public MemoResponseDto createMemo(@RequestBody MemoRequestDto requestDto) {
        return null;
    }

}


// 3. Client에 데이터를 반환할 때 사용할 MemoResponseDto 클래스 생성
// MemoResponseDto

package com.sparta.memo.dto;

import lombok.Getter;

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


// 4. Client의 요청 데이터를 받아줄 MemoRequestDto 클래스 생성
// MemoRequestDto

package com.sparta.memo.dto;

import lombok.Getter;

@Getter
public class MemoRequestDto {
    private String username;
    private String contents;
}


// 5. DB와 연결을 하지 않았기 때문에 메모 데이터를 저장할 Java 컬렉션 생성

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


// 6. 메모 생성 로직 작성

@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;
}
  1. Read 구현
// DB 역할을 하는 memoList를 조회하여 List<MemoResponseDto>로 변환한 후 반환

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

    return responseList;
}
  1. 구현 완료 코드
// MemoController

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

1-18 Update, Delete 구현하기

// 1. Update 구현

@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("선택한 메모는 존재하지 않습니다.");
    }
}

// - Memo 클래스에 수정하는 기능의 메서드 update 생성

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


// 2. Delete 구현

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


// 3. 구현 완료 코드
// MemoController

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

    @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("선택한 메모는 존재하지 않습니다.");
        }
    }
}


1-19 Database

  1. 개념 : Database를 한 마디로 정의하면 ‘데이터의 집합’
  2. 용어 정리

1-20 SQL

  1. 개념 : SQL은 ‘Structured Query Language’ 의 약자로 RDBMS에서 사용되는 언어, 수 많은 정보를 Database에서 조작하고 관리하기 위해서는 SQL 언어를 사용
  • 국제표준화기구에서 SQL에 대한 표준을 정해서 발표
  • 하지만 DBMS를 만드는 회사가 여러 곳이기 때문에 DBMS 마다 표준 SQL을 준수하되, 각 제품의 특성을 반영하기 위한 약간의 차이가 존재
  1. DDL : ‘Data Definition Language’ 의 약자로 테이블이나 관계의 구조를 생성하는데 사용
// CREATE : 새로운 데이터베이스 및 테이블을 생성 

CREATE DATABASE 데이터베이스이름;
CREATE TABLE 테이블이름
(
		필드이름1 필드타입1,
    필드이름2 필드타입2,
    ...
);


// ALTER : 데이터베이스와 테이블의 내용을 수정

ALTER TABLE 테이블이름 ADD 필드이름 필드타입;
ALTER TABLE 테이블이름 DROP 필드이름;
ALTER TABLE 테이블이름 MODIFY COLUMN 필드이름 필드타입;


// DROP : 데이터베이스와 테이블을 삭제할 수 있습니다. 데이터 및 테이블 전체를 삭제

DROP DATABASE 데이터베이스이름;
DROP TABLE 테이블이름;


// TRUNCATE : 데이터베이스와 테이블을 삭제할 수 있습니다. 최초 테이블이 만들어졌던 상태 즉, 컬럼값만 남김.

TRUNCATE DATABASE 데이터베이스이름;
TRUNCATE TABLE 테이블이름;
  1. DCL : ‘Data Control Language’ 의 약자로 데이터의 사용 권한을 관리하는데 사용
// GRANT : 사용자 또는 ROLE에 대해 권한을 부여

GRANT [객체권한명] (컬럼)
ON [객체명]
TO { 유저명 | 롤명 | PUBLC} [WITH GRANT OPTION];

예)
GRANT SELECT ,INSERT 
ON mp
TO scott WITH GRANT OPTION;


// REVOKE : 사용자 또는 ROLE에 부여한 권한을 회수

REVOKE { 권한명 [, 권한명...] ALL}
ON 객체명
FROM {유저명 [, 유저명...] | 롤명(ROLE) | PUBLIC} 
[CASCADE CONSTRAINTS];

예)
REVOKE SELECT , INSERT
ON emp
FROM scott
[CASCADE CONSTRAINTS];
  1. DML : ‘Data Manipulation Language’ 의 약자로 테이블에 데이터를 검색, 삽입, 수정, 삭제하는데 사용
// INSERT : 테이블에 새로운 row를 추가

INSERT INTO 테이블이름(필드이름1, 필드이름2, 필드이름3, ...) VALUES(데이터값1, 데이터값2, 데이터값3, ...);
INSERT INTO 테이블이름 VALUES(데이터값1, 데이터값2, 데이터값3, ...);


// SELECT : 테이블의 row를 선택

SELECT 필드이름 FROM 테이블이름 [WHERE 조건];


// UPDATE : 테이블의 row의 내용을 수정

UPDATE 테이블이름 SET 필드이름1=데이터값1, 필드이름2=데이터값2, ... WHERE 필드이름=데이터값;


// DELETE : 테이블의 row를 삭제

DELETE FROM 테이블이름 WHERE 필드이름=데이터값;

1-21 SQL 연습하기

  1. Intellij Database 연동
  • CREATE DATABASE : CREATE DATABASE academy;
  1. CREATE
  • 제약조건
// AUTO_INCREMENT : 컬럼의 값이 중복되지 않게 1씩 자동으로 증가하게 해줘 고유번호를 생성

CREATE TABLE 테이블이름
(
    필드이름 필드타입 AUTO_INCREMENT,
    // id bigint AUTO_INCREMENT,
    ...
);


// NOT NULL : 해당 필드는 NULL 값을 저장할 수 X

CREATE TABLE 테이블이름
(
    필드이름 필드타입 NOT NULL,
    ...
);


// UNIQUE : 해당 필드는 서로 다른 값을 가져야함

CREATE TABLE 테이블이름
(
    필드이름 필드타입 UNIQUE,
    ...
);


// PRIMARY KEY : 해당 필드가 NOT NULL과 UNIQUE 제약 조건의 특징을 모두 가지게 됩니다.

CREATE TABLE 테이블이름
(
    필드이름 필드타입 PRIMARY KEY,
    ...
);


// FOREIGN KEY : 하나의 테이블을 다른 테이블에 의존하게 만들며 데이터의 무결성을 보장

CREATE TABLE 테이블이름
(
    필드이름 필드타입,
    ...
		FOREIGN KEY(필드이름)
    REFERENCES 테이블이름(필드이름)
);


// CASCADE : FOREIGN KEY 로 연관된 데이터를 삭제,변경
CREATE TABLE 테이블이름
(
    필드이름 필드타입,
    ...
		FOREIGN KEY(필드이름)
    REFERENCES 테이블이름(필드이름) ON DELETE CASCADE 
														 //ON UPDATE CASCADE
);


// MAJOR 테이블

CREATE TABLE IF NOT EXISTS MAJOR
(	
	major_code varchar(100) primary key comment '주특기코드', 
	major_name varchar(100) not null comment '주특기명',
	tutor_name varchar(100) not null comment '튜터'
);


// EXAM 테이블

CREATE TABLE IF NOT EXISTS STUDENT
(
	student_code varchar(100) primary key comment '수강생코드', 
	name varchar(100) not null comment '이름',
	birth varchar(8) null comment '생년월일',
	gender varchar(1) not null comment '성별',
	phone varchar(11) null comment '전화번호',
	major_code varchar(100) not null comment '주특기코드',
	foreign key(major_code) references major(major_code)
);


// EXAM 테이블

CREATE TABLE IF NOT EXISTS EXAM
(
	student_code varchar(100) not null comment '수강생코드', 
	exam_seq int not null comment '시험주차', 
	score decimal(10,2) not null comment '시험점수',
	result varchar(1) not null comment '합불'
);
  • ERD
  1. ALTER
// ALTER

ALTER TABLE EXAM ADD PRIMARY KEY(student_code, exam_seq);
ALTER TABLE EXAM ADD CONSTRAINT exam_fk_student_code FOREIGN KEY(student_code) REFERENCES STUDENT(student_code);

  1. INSERT
// MAJOR INSERT

INSERT INTO MAJOR VALUES('m1', '스프링', '남병관');
INSERT INTO MAJOR VALUES('m2', '노드', '강승현');
INSERT INTO MAJOR VALUES('m3', '플라스크', '이범규');
INSERT INTO MAJOR VALUES('m4', '루비온레일즈', '차은서');
INSERT INTO MAJOR VALUES('m5', '라라벨', '구름');
INSERT INTO MAJOR VALUES('m6', '리엑트', '임민영');
INSERT INTO MAJOR VALUES('m7', '뷰', '김서영');
INSERT INTO MAJOR VALUES('m8', '엥귤러', '한현아');



// STUDENT INSERT

INSERT INTO STUDENT VALUES('s1', '최원빈', '20220331', 'M', '01000000001', 'm1');
INSERT INTO STUDENT VALUES('s2', '강준규', '20220501', 'M', '01000000002', 'm1');
INSERT INTO STUDENT VALUES('s3', '김영철', '20220711', 'M', '01000000003', 'm1');
INSERT INTO STUDENT VALUES('s4', '예상기', '20220408', 'M', '01000000004', 'm6');
INSERT INTO STUDENT VALUES('s5', '안지현', '20220921', 'F', '01000000005', 'm6');
INSERT INTO STUDENT VALUES('s6', '이대호', '20221111', 'M', '01000000006', 'm7');
INSERT INTO STUDENT VALUES('s7', '정주혜', '20221117', 'F', '01000000007', 'm8');
INSERT INTO STUDENT VALUES('s8', '고미송', '20220623', 'F', '01000000008', 'm6');
INSERT INTO STUDENT VALUES('s9', '이용우', '20220511', 'M', '01000000009', 'm2');
INSERT INTO STUDENT VALUES('s10', '심선아', '20220504', 'F', '01000000010', 'm8');
INSERT INTO STUDENT VALUES('s11', '변정섭', '20220222', 'M', '01000000020', 'm2');
INSERT INTO STUDENT(student_code, name, gender, major_code) VALUES('s12', '권오빈', 'M', 'm3');
INSERT INTO STUDENT VALUES('s13', '김가은', '20220121', 'F', '01000000030', 'm1');
INSERT INTO STUDENT(student_code, name, gender, major_code) VALUES('s14', '김동현', 'M', 'm4');
INSERT INTO STUDENT VALUES('s15', '박은진', '20221101', 'F', '01000000040', 'm1');
INSERT INTO STUDENT(student_code, name, birth, gender, phone, major_code) VALUES('s16', '정영호', '20221105', 'M', '01000000050', 'm5');
INSERT INTO STUDENT(student_code, name, gender, major_code) VALUES('s17', '박가현', 'F', 'm7');
INSERT INTO STUDENT(student_code, name, birth, gender, phone, major_code) VALUES('s18', '박용태', '20220508', 'M', '01000000060', 'm6');
INSERT INTO STUDENT VALUES('s19', '김예지', '20220505', 'F', '01000000070', 'm2');
INSERT INTO STUDENT VALUES('s20', '윤지용', '20220909', 'M', '01000000080', 'm3');
INSERT INTO STUDENT VALUES('s21', '손윤주', '20220303', 'F', '01000000090', 'm6');



// EXAM INSERT

INSERT INTO EXAM VALUES('s1', 1, 8.5, 'P');
INSERT INTO EXAM VALUES('s1', 2, 9.5, 'P');
INSERT INTO EXAM VALUES('s1', 3, 3.5, 'F');
INSERT INTO EXAM VALUES('s2', 1, 8.2, 'P');
INSERT INTO EXAM VALUES('s2', 2, 9.5, 'P');
INSERT INTO EXAM VALUES('s2', 3, 7.5, 'P');
INSERT INTO EXAM VALUES('s3', 1, 9.3, 'P');
INSERT INTO EXAM VALUES('s3', 2, 5.3, 'F');
INSERT INTO EXAM VALUES('s3', 3, 9.9, 'P');
INSERT INTO EXAM VALUES('s4', 1, 8.4, 'P');
INSERT INTO EXAM VALUES('s5', 1, 9.5, 'P');
INSERT INTO EXAM VALUES('s5', 2, 3.5, 'F');
INSERT INTO EXAM VALUES('s6', 1, 8.3, 'P');
INSERT INTO EXAM VALUES('s7', 1, 9.2, 'P');
INSERT INTO EXAM VALUES('s7', 2, 9.9, 'P');
INSERT INTO EXAM VALUES('s7', 3, 3.6, 'F');
INSERT INTO EXAM VALUES('s8', 1, 8.4, 'P');
INSERT INTO EXAM VALUES('s9', 1, 9.7, 'P');
INSERT INTO EXAM VALUES('s10', 1, 8.4, 'P');
INSERT INTO EXAM VALUES('s10', 2, 9.8, 'P');
INSERT INTO EXAM VALUES('s10', 3, 8.4, 'P');
INSERT INTO EXAM VALUES('s11', 1, 8.6, 'P');
INSERT INTO EXAM VALUES('s12', 1, 9.2, 'P');
INSERT INTO EXAM VALUES('s13', 1, 8.1, 'P');
INSERT INTO EXAM VALUES('s13', 2, 9.5, 'P');
INSERT INTO EXAM VALUES('s13', 3, 2.1, 'F');
INSERT INTO EXAM VALUES('s14', 1, 9.2, 'P');
INSERT INTO EXAM VALUES('s15', 1, 9.7, 'P');
INSERT INTO EXAM VALUES('s15', 2, 1.7, 'F');
INSERT INTO EXAM VALUES('s16', 1, 8.4, 'P');
INSERT INTO EXAM VALUES('s17', 1, 9.3, 'P');
INSERT INTO EXAM VALUES('s17', 2, 9.9, 'P');
INSERT INTO EXAM VALUES('s17', 3, 1.3, 'F');
INSERT INTO EXAM VALUES('s18', 1, 9.9, 'P');
INSERT INTO EXAM VALUES('s19', 1, 9.4, 'P');
INSERT INTO EXAM VALUES('s19', 2, 8.9, 'P');
INSERT INTO EXAM VALUES('s19', 3, 7.4, 'F');
INSERT INTO EXAM VALUES('s20', 1, 8.1, 'P');
INSERT INTO EXAM VALUES('s20', 2, 6.4, 'F');
INSERT INTO EXAM VALUES('s21', 1, 9.5, 'P');
INSERT INTO EXAM VALUES('s21', 2, 8.8, 'P');
INSERT INTO EXAM VALUES('s21', 3, 8.2, 'P');


  1. UPDATE
INSERT INTO STUDENT VALUES('s0', '수강생', '20220331', 'M', '01000000005', 'm1');
UPDATE STUDENT SET major_code= 'm2' where student_code= 's0';
  1. DELETE
DELETE FROM STUDENT WHERE student_code = 's0';
  1. SELECT
SELECT * FROM STUDENT;
SELECT * FROM STUDENT WHERE STUDENT_CODE = 's1';
SELECT name, major_code FROM STUDENT WHERE student_code = 's1';
  1. JOIN
SELECT s.name, s.major_code, m.major_name FROM STUDENT s JOIN MAJOR m ON s.major_code = m.major_code;
SELECT s.name, s.major_code, m.major_name FROM STUDENT s, MAJOR m WHERE s.major_code = m.major_code;

1-22 JDBC란 무엇일까?

  1. JDBC의 등장배경
  • JDBC는 Java Database Connectivity로 DB에 접근할 수 있도록 Java에서 제공하는 API
  • JDBC에 연결해야하는 DB의 JDBC 드라이버를 제공하면 DB 연결 로직을 변경할 필요없이 DB 변경이 가능
  • MySQL 드라이버를 사용해 DB에 연결을 하다 PostgreSQL 서버로 변경이 필요할 때 드라이버만 교체하면 손쉽게 DB 변경이 가능
  1. JdbcTemplate이란?
  • JDBC의 등장으로 손쉽게 DB교체가 가능해졌지만 아직도 DB에 연결하기 위해 여러가지 작업 로직들을 직접 작성해야한다는 불편함 여전히 존재
  • 이러한 불편함을 해결하기 위해 커넥션 연결, statement 준비 및 실행, 커넥션 종료 등의 반복적이고 중복되는 작업들을 대신 처리해주는 JdbcTemplate이 등장
  1. JdbcTemplate 사용방법
// 1. application.properties에 DB에 접근하기 위한 정보를 작성

spring.datasource.url=jdbc:mysql://localhost:3306/memo
spring.datasource.username=root
spring.datasource.password={비밀번호}
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

// 2. build.gradle에 JDBC 라이브러리와 MySQL을 등록
// MySQL
implementation 'mysql:mysql-connector-java:8.0.28'
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc'

// 3. DB연결이 필요한 곳에서 JdbcTemplate을 주입받아와 사용
private final JdbcTemplate jdbctemplate;

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

---------------------------------------------------

// INSERT
String sql = "INSERT INTO memo (username, contents) VALUES (?, ?)";
jdbcTemplate.update(sql, "Robbie", "오늘 하루도 화이팅!");

// UPDATE
String sql = "UPDATE memo SET username = ? WHERE id = ?";
jdbcTemplate.update(sql, "Robbert", 1);

// DELETE
String sql = "DELETE FROM memo WHERE id = ?";
jdbcTemplate.update(sql, 1);

// SELECT
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);
    }
});

---------------------------------------------------
// MemoController
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.jdbc.core.JdbcTemplate;
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);
    }
}





개인 프로젝트 진행

Goal: "나만의 일정 관리 앱 서버 만들기"

profile
야옹

0개의 댓글