K-Digital Training/내일배움캠프

입문 1주차/Spring

hoonssss 2023. 11. 21. 10:26
반응형
SMALL

Web Server 와 Web Application Server(WAS)

브라우저에서 URL을 입력하여 어떠한 페이지를 요청했을 때 HTTP의 요청을 받아들여 HTML 문서와 같은 정적인 콘텐츠를 사용자에게 전달해주는 역할을 하는 것이 Web Server

웹 서버의 역할을 크게 2가지로 구분해보면

  1. 정적인 콘텐츠 즉, 이미 완성이 되어있는 HTML과 같은 문서를 브라우저로 전달
  2. 브라우저로부터 ‘로그인하여 MyPage를 요청’과 같은 동적인 요청이 들어왔을 때 웹 서버 자체적으로 처리하기 어렵기 때문에 해당 요청을 WAS에 전달

- 종류로는 Apache, Nginx

 

WAS는 웹 서버와 똑같이 HTTP 기반으로 동작

웹 서버에서 할 수 있는 기능 대부분을 WAS에서도 처리가능

WAS를 사용하면 로그인,회원가입을 처리하거나 게시물을 조회하거나 정렬하는 등의 다양한 로직들을 수행하는 프로그램을 동작가능

종류로는 Tomcat, JBoss 등

 

Apache Tomcap

Tomcat은 동적인 처리를 할 수 있는 웹 서버를 만들기 위한 웹 컨테이너

대표사진 삭제

사진 설명을 입력하세요.

Apache Tomcat이란 Apache와 Tomcat이 합쳐진 형태로 정적인 데이터 처리와 동적인 데이터 처리를 효율적으로 처리함(정적+동적)

 

SpringFramework

 
사진 삭제

사진 설명을 입력하세요.

핵심 기능을 사용하기 위해서는 XML같은 많은 설정이 필요했음

이러한 불편점을 개선하기 위해 SpringBoot가 나옴

SpringBoot

XML->Annotation 설정으로 대체함

외부 라이브러리나 하위 프레임워크들의 의존성 관리가 매우 쉬워짐

내장 Apache Tomcat을 가지고 있음

즉 직접 다운하고 설정하고 프로젝트에 삽입하던 작업들을 SpringBoot에서는 기본적으로

starter-web dependency를 설정하면 자동으로 내장형 Apache Tomcat을 제공

 

HelloWorld API

package com.sparta.springprepare.controller;

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

@RestController
public class Hellocontroller {

    @GetMapping("/api/hello/")
    public String hello(){
        return "Hello World";
    }
}
 

HTTP

데이터를 주고 받는 양식을 정의한 통신 규약 중 하나

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

 
사진 삭제

사진 설명을 입력하세요.

개발자도구->Network

웹페이지를 보여주기 위해 데이터를 받아온 과정

서버무응답, 데이터요청오류 같은 것을 확인할 수 있음

  • 1XX: Informational(정보 제공)
  • 임시 응답으로 현재 클라이언트의 요청까지는 처리되었으니 계속 진행하라는 의미입니다. HTTP 1.1 버전부터 추가되었습니다.
  • 2XX: Success(성공)
  • 클라이언트의 요청이 서버에서 성공적으로 처리되었다는 의미입니다.
  • 3XX: Redirection(리다이렉션)
  • 완전한 처리를 위해서 추가 동작이 필요한 경우입니다. 주로 서버의 주소 또는 요청한 URI의 웹 문서가 이동되었으니 그 주소로 다시 시도하라는 의미입니다.
  • 4XX: Client Error(클라이언트 에러)
  • 없는 페이지를 요청하는 등 클라이언트의 요청 메시지 내용이 잘못된 경우를 의미합니다.
  • 5XX: Server Error(서버 에러)
  • 서버 사정으로 메시지 처리에 문제가 발생한 경우입니다. 서버의 부하, DB 처리 과정 오류, 서버에서 익셉션이 발생하는 경우를 의미합니다.

https://hongong.hanbit.co.kr/http-%EC%83%81%ED%83%9C-%EC%BD%94%EB%93%9C-%ED%91%9C-1xx-5xx-%EC%A0%84%EC%B2%B4-%EC%9A%94%EC%95%BD-%EC%A0%95%EB%A6%AC/ 출처

HTTP에는 크게 다음과 같은 구성요소가 존재

  • Method (호출/요청 방식)

GET: 이름 그대로 어떤 리소스를 얻을 때 사용 브라우저의 주소창에 URL을 입력하면 GET 메서드를 사용해서 서버에 요청을 보냄

POST: 웹 서버에 데이터를 게시할 때 사용(ex. 회원가입, 게시글 작성, 댓글 작성)

그외 DELETE 등의 여러 요청 방식이 존재

가장 대표적인 요청 방식이 GET 과 POST

  • Header (추가 데이터. 메타 데이터)

브라우저가 어떤 페이지를 원하는지

요청 받은 페이지를 찾았는지

요청 받은 데이터를 성공적으로 찾았는지

어떤 형식으로 데이터를 보낼지

GET **naver.com** HTTP/1.1
 

이러한 사례 외에도 아주 다양한 의사 표현을 위한 데이터를 모두 Header 필드에 넣고 주고 받음 위에서 설명 된 메서드도 사실은 헤더에 포함되어 서버로 보내짐

  • Payload (데이터. 실제 데이터, 데이터를 담는 곳)

서버가 응답을 보낼 때에는 항상 Payload를 보낼 수 있음

클라이언트(브라우저)가 요청을 할 때에도 Payload를 보낼 수 있음. 그리고 "GET method를 제외하곤 모두 Payload를 보낼 수 있다" 는게 HTTP에서의 약속

HTML

<!DOCTYPE html>
<html>
  <head><title>By @ResponseBody</title></head>
   <body>Hello, Spring 정적 웹 페이지!!</body>
</html>
 

JSON

{ 
  "name":"Robbie",
  "age": 20
}
 

HTTP의 Payload를 통해 위와 같은 데이터들을 요청하고 응답 받을 수 있음

 

테스트코드

기존코드 커맨드+시프트+T -> 테스트코드로 감

테스트코드는 Main이 없음 Junit은 테스트 실행 환경을 따로 가지고 있기 때문

package com.sparta.springprepare.calculator;

public class Calculator {
    public Double operate(double num1, String op, double num2) {
        switch (op) {
            case "*":
                return num1 * num2;
            case "/":
                if (num2 != 0) {
                    return num1 / num2;
                } else {
                    return null;
                }
            case "+":
                return num1 + num2;
            case "-":
                return num1 - num2;
            default:
                throw new IllegalArgumentException("잘못된 연산자입니다.");
        }
    }
}
//위 코드를 밑 테스트코드로 테스트
--------------------------------------------------------------------
package com.sparta.springprepare.calculator;

import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

class CalculatorTest {
    @Test
    @DisplayName("더하기 테스트")
    void test1() {
        Calculator calculator = new Calculator();
        Double result = calculator.operate(8, "+", 2);
        System.out.println("result = " + result);

        Assertions.assertEquals(10, result); //동작을 검사(result가 10임을 예상)
    }

    @Test
    @DisplayName("나누기 테스트")
    void test2() {
        Calculator calculator = new Calculator();
        Double result = calculator.operate(8, "/", 2);
        System.out.println("result = " + result);

        Assertions.assertEquals(4, result);
    }
}
 

 

Lombok과 application.properties

Lombok은 자바 프로젝트를 진행하는데 거의 필수적으로 필요한 메서드/생성자 등을 자동 생성해줌으로써 코드를 절약할 수 있도록 도와주는 라이브러리

 
사진 삭제

사진 설명을 입력하세요.

package com.sparta.springprepare;

import lombok.*;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class Memo {
    private String username;
    private String contents;

}
    class Main{
    public static void main(String[] args) {
        Memo memo = new Memo();
        memo.setUsername("JH");
        System.out.println(memo.getUsername());
    }
}
 

Getter와 Setter를 만들지 않아도 어노테이션을 통해 자동으로 생성

@AllArgsConstructor -> 모든필드를 가진 오버로딩 생성자를 만들어줌

    public Memo(String username, String contents) {
        this.username = username;
        this.contents = contents;
    }
------------------------------------------------------
    class Main{
    public static void main(String[] args) {
        Memo memo = new Memo("JH","JH");
        memo.setUsername("JH");
        System.out.println(memo.getUsername());
    }
}
 

@NoArgsConstructor -> 기본생성자를 만들어줌(아무것도 가지지않음)

    public Memo() {
    }
------------------------------------------------------
    class Main{
    public static void main(String[] args) {
        Memo memo = new Memo();
        memo.setUsername("JH");
        System.out.println(memo.getUsername());
    }
}
 

@RequiredArgsConstructor

요구되는 필드의 생성자를 만듬

앞에 final을 달아줘야함

package com.sparta.springprepare;

import lombok.*;
@Getter
@Setter
@RequiredArgsConstructor
public class Memo {
    private final String username;
    private String contents;

}
    class Main{
    public static void main(String[] args) {
        Memo memo = new Memo("JH");
        memo.setUsername("JH");
        System.out.println(memo.getUsername());
    }
}
 

username을 가진 생성자를 만들었음

 

application.properties

Spring과 관련된 설정을 할때 사용되는 파일

자동으로 설정된 값들을 쉽게 수정 가능함

server.port=8081
 

8080 -> 8081로 수정

 
사진 삭제

사진 설명을 입력하세요.

 

Spring MVC

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

 
사진 삭제

사진 설명을 입력하세요.

Model

데이터와 비즈니스 로직을 담당, 데이터베이스와 연동을 하거나 저장, 불러오는 등 작업

View

인터페이스를 담당

Controller

Model과 View 사이의 상호 작용을 조정하고 제어

사용자의 입력을 받아 Model에 전달하고, Model의 결과를 바탕으로 View를 업데이트

 

MVC 패턴은 소프트웨어를 구성하는 요소들을 분리함으로써 코드의 재사용성과 유지보수성을 높이고, 개발자들 간의 협업을 용이하게 함. 따라서 소프트웨어를 개발할 때, MVC 패턴을 적용하여 구조를 잘 설계하는 것이 중요

 

Spring MVC란?

Spring Web MVC는 Servlet API를 기반으로 구축된 독창적인 웹 프레임워크로, 처음부터 Spring Framework에 포함되어 왔으며, 정식 명칭인 "Spring Web MVC"는 소스 모듈(spring-webmvc)의 이름에서 따왔으나, "Spring MVC"로 더 일반적으로 알려져 있습니다. …

Spring MVC는 중앙에 있는 DispatcherServlet이 요청을 처리하기 위한 공유 알고리즘을 제공하는 Front Controller 패턴을 중심으로 설계되어 있으며 이 모델은 유연하고 다양한 워크 플로우를 지원합니다.

Spring 공식 문서에서 Spring MVC에 대한 설명으로 ‘DispatcherServlet이 중앙에서 HTTP 요청을 처리해주는데 이는 Front Controller 패턴으로 설계되어있다’라고 설명하고 있음

쉽게 표현해보자면 ‘Spring에서 MVC 디자인 패턴을 적용하여 HTTP 요청을 효율적으로 처리하고 있다’ 라고 이해

 

‘DispatcherServlet에 대한 이해를 하기 위해 먼저 Servlet이 무엇인지 학습해야 함

Servlet(서블릿)은 자바를 사용하여 웹 페이지를 동적으로 생성하는 서버 측 프로그램 혹은 그 사양을 말함

Tomcat안에서 Servlet이 동작하고 있음

사용자가 (HTTP) API 요청했을 때 서버의 서블릿은 밑 그림으로 동작함

 
사진 삭제

사진 설명을 입력하세요.

  1. 사용자가 Client(브라우저)를 통해 서버에 HTTP Request 즉, API 요청을 합니다.
  2. 요청을 받은 Servlet 컨테이너는 HttpServletRequest, HttpServletResponse 객체를 생성합니다.
  3. 약속된 HTTP의 규격을 맞추면서 쉽게 HTTP에 담긴 데이터를 사용하기 위한 객체입니다.
  4. 설정된 정보를 통해 어떠한 Servlet에 대한 요청인지 찾습니다.
  5. 해당 Servlet에서 service 메서드를 호출한 뒤 브라우저의 요청 Method에 따라 doGet 혹은 doPost 등의 메서드를 호출합니다.
  6. 호출한 메서드들의 결과를 그대로 반환하거나 동적 페이지를 생성한 뒤 HttpServletResponse 객체에 응답을 담아 Client(브라우저)에 반환합니다.
  7. 응답이 완료되면 생성한 HttpServletRequest, HttpServletResponse 객체를 소멸합니다.

 

Front Controller

모든 API 요청을 앞서 살펴본 서블릿의 동작 방식에 맞춰 코드를 구현한다면 무수히 많은 Servlet 클래스를 구현해야함 Spring은 DispatcherServlet을 사용하여 Front Controller 패턴 방식으로 API 요청을 효율적으로 처리하고 있음

 
사진 삭제

사진 설명을 입력하세요.

 

  1. Client(브라우저)에서 HTTP 요청이 들어오면 DispatcherServlet 객체가 요청을 분석합니다.
  2. DispatcherServlet 객체는 분석한 데이터를 토대로 Handler mapping을 통해 Controller를 찾아 요청을 전달해 줍니다.
Sample]
GET /api/hello → HelloController 의 hello() 함수
GET /user/login → UserController 의 login() 함수
GET /user/signup → UserController 의 signup() 함수
POST /user/signup → UserController 의 registerUser() 함수
 

Handler mapping 에는 API path 와 Controller 메서드가 매칭되어 있습니다.

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

API path 즉, URL을 Controller에 작성하는 방법은 @Controller 애너테이션이 달려있는 클래스를 생성한 뒤 @GetMapping 처럼 요청한 HTTP Method 와 일치하는 애너테이션을 추가한 메서드를 구현합니다.

URL은 @GetMapping("/api/hello") 이처럼 해당 애너테이션의 속성값으로 전달해주면 됩니다.

해당 메서드명은 URL을 매핑하는데 영향을 미치지 않음으로 자유롭게 정해도 상관 없습니다.

이제는 직접 Servlet을 구현하지 않아도 DispatcherServlet에 의해 간편하게 HTTP 요청을 처리할 수 있게 되었습니다.

3. Controller → DispathcerServlet

해당 Controller는 요청에 대한 처리를 완료 후 처리에 대한 결과 즉, 데이터('Model')와 'View' 정보를 전달합니다.

4.DispatcherServlet → Client

ViewResolver 통해 View에 Model을 적용하여 View를 Client에게 응답으로 전달합니다.

 

Controller의 장점

Spring MVC는 효율적인 API 처리를 위해 Front Controller 패턴을 만들었음

유사한 성격의 API 를 하나의 Controller 로 관리

메서드 이름도 내 마음대로 설정 가능(단, 클래스 내의 중복메서드명 불가)

@Controller
@RequestMapping("/user")
public class UserController {
	@GetMapping("/login")
	public String login() {
	    // ...
	}
  @GetMapping("/logout")
  public String logout() {
      // ...
  }
	@GetMapping("/signup")
	public String signup() { 
		// ... 
	}
	@PostMapping("/signup")
  public String registerUser(SignupRequestDto requestDto) {
		// ... 
	}
}
 
@Controller
public class HelloController {

    @GetMapping("/api/hello")
    @ResponseBody
    public String hello(){
        return "Hello World";
    }
    @PostMapping("/api/hello")
    @ResponseBody
    public String postHello(){
        return "Hello World";
    }
//api경로는 중복이 될 수 있으나 메서드는 달라야함
위 코드 실행가능
-------------------------------------------------------
@Controller
@RequestMapping("/api")
public class HelloController {
    @GetMapping("/hello")
    @ResponseBody
    public String hello(){
        return "Hello World";
    }

    @PostMapping("/hello")
    @ResponseBody
    public String postHello(){
        return "Hello World";
    }
// /api로 시작되는 요청은 HelloCOntroller클래스로 넘어옴 그 뒤 path를 분석해서 매칭함
// localhost:8080/api/hello(login,logout...)
 

(@Controller)

(@ResponseBody)

Spring MVC의 컨트롤러인 @Controller는 주로 View를 반환하기 위해 사용

Spring MVC의 컨트롤러를 사용하면서 Data를 반환해야 하는 경우도 있습니다.

@Controller에서는 데이터를 반환하기 위해 @ResponseBody 어노테이션을 활용

(@RestController)

@RestController는 @Controller에 @ResponseBody가 추가된 것

당연하게도 @RestController의 주용도는 Json 형태로 객체 데이터를 반환

즉 @Controller는 뷰를 반환하는 역할, @RestController는 데이터를 반환하는 역할

 

정적페이지

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

/html/redirect -> static 폴더 내 hello.html 실행

    @GetMapping("/html/templates")
    public String htmlTemplates(){
        return "hello"; //hello.html -> html제외
    }
 

/html/templates -> templates 폴더 내 hello.html 실행

 

동적페이지

@Controller
public class HtmlController {

    private static long visitCount = 0;

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

model.addAttruidute("visits",visitCount);

-> html ${visits} 적용

-> return "hello-visit" -> templates 폴더 내 hello-visit.html 실행

 

JACKSON

JSON데이터 구조를 처리하는 라이브러리

Java의 객체를 JSON타입의 String으로 변환해주거나 반대로 JSON타입의 String값을 Object로 변환 가능

Java -> JSON

JSON -> Java

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

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

    public Star(){
    }
}
OR---------------------------------------------------------
@Getter
@AllArgsConstructor
@NoArgsConstructor
public class Star {
    private int age;
    private String name;
}
-----------------------------------------------------------
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);
        }
    }
//String -> JSON형태로
//json = {"age":95,"name":"Robbie"} 출력
//@Getter필요 직렬화

    @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());
    }
//Jsom -> String
//star.getName() = Robbie
//star.getage() = 95
//기본생성자필요 @NoArgsConstructor & get Or set 필요, 역직렬화
 

@PathVariable

@Controller
@RequestMapping("/hello/request")
public class RequestController {
// [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);
    } //<br>줄바꿈
}
 

@RequestParm

서버에 보내려는 데이터를 ? 와 & 로 구분

@Controller
@RequestMapping("/hello/request")
public class RequestController {
// [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);
    }
}
 

required = false

@PathVariable 에서도 가능 ex)@PathVariable(required = false)

@RequestParam(required = false) String name

name = null 값 들어옴

@RequestParam 생략가능함

    @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);
    }
//required = false 빈값을 받아도 오류 안남 기본값은 true 빈값 입력시 오류 발생 
 

 

 

@ModelAttribute

Body부분 -> 객체형태로 바꿔줌

GET -> form/param/model/?name=~&age~

POST -> /form/model

@ModelAttribute는 생략도 가능함

값이 들어오지 않는다면 생성자나 @Setter 확인

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

Spring에서는 @ModelAttribute뿐만 아니라 @RequestParam도 생략 가능

그렇다면 Spring은 이를 어떻게 구분할까?

Spring은 해당 파라미터(매개변수)가 SimpleValueType이라면 @RequestParam으로 간주

아니라면 @ModelAttribute가 생략되어있다 판단

SimpleValueType은 원시타입(int) Wrapper타입(Integer), Date등의 타입을 의미합니다.

@ModelAttribute -> Star star 같은 클래스 등

 

@RequestBody

HTTP Body 부분에 Json형식{"name":"Robbie","age":"95} 으로 데이터가 넘어왔을 때 @RequestBody 애너테이션을 사용해 데이터를 객체 형태로 받을 수 있음

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

DTO

DTO(Data Transfer Object)는 데이터 전송 및 이동을 위해 생성되는 객체를 의미

Client에서 보내오는 데이터를 객체로 처리할 때 사용

또한 서버의 계층간의 이동에도 사용

DB와의 소통을 담당하는 Java 클래스를 그대로 Client에 반환하는 것이 아니라 DTO로 한번 변환한 후 반환할 때도 사용

Request의 데이터를 처리할 때 사용되는 객체는 RequestDto, Response를 할 때 사용되는 객체는 ResponseDto라는 이름을 붙여 DTO 클래스를 만들 수 있음

- 절대적인 규칙은 아님

 

Memo 웹(Post,Get)

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();
    }
}
 
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();
    }
}
 
package com.sparta.memo.dto;

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

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

}
 
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> getMemo() {
        //Map -> List
        List<MemoResponseDto> responseList = memoList.values()
                .stream()
                .map(MemoResponseDto::new)
                .toList();
        return responseList;
    }
}
 

Memo(Put(update), Delete)

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

    public void update(MemoRequestDto memoRequestDto){
        this.username = memoRequestDto.getUsername();
        this.contents = memoRequestDto.getContents();
    }
}
 
    @PutMapping("/memos/{id}")
    public Long updateMemo(@PathVariable Long id, @RequestBody MemoRequestDto requestDto){
        //해당 메모가 DB에 존재하는지 확인
        if(memoMap.containsKey(id)){ //boolean type
            Memo memo = memoMap.get(id);
            // memo 수정
            memo.update(requestDto);
            return memo.getId();
        }else{
            throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
        }
    }

    @DeleteMapping("/memos/{id}")
    public Long deleteMemo(@PathVariable Long id) {
        if (memoMap.containsKey(id)) {
            memoMap.remove(id);
            return id;
        } else {
            throw new IllegalArgumentException("선택한 메모는 존재하지 않습니다.");
        }
    }
 

Memo 웹

package com.sparta.memo.controller;

import com.sparta.memo.dto.MemoRequestDto;
import com.sparta.memo.dto.MemoResponseDto;
import com.sparta.memo.entity.Memo;
import lombok.Getter;
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 {

    Map<Long,Memo> memoMap = new HashMap<>();
    @PostMapping("/memos")
    public MemoResponseDto responseDto(@RequestBody MemoRequestDto requestDto){
        Memo memo = new Memo(requestDto);
        Long maxId = memoMap.size() > 0 ? Collections.max(memoMap.keySet()) + 1 : 1 ;
        memo.setId(maxId);

        memoMap.put(memo.getId(),memo);

        MemoResponseDto memoResponseDto = new MemoResponseDto(memo);

        return memoResponseDto;
    }

    @GetMapping("/memos")
    public List<MemoResponseDto> memoResponseDtos(){
        List<MemoResponseDto> memoResponseDtos = memoMap.values()
                .stream()
                .map(MemoResponseDto::new)
                .toList();

        return memoResponseDtos;
    }

    @PutMapping("/memos/{id}")
    public Long updateMemo(@PathVariable Long id, @RequestBody MemoRequestDto memoRequestDto){
        if(memoMap.containsKey(id)){
            Memo memo = memoMap.get(id);
            memo.update(memoRequestDto);
            return memo.getId();
        }else{
            throw new IllegalArgumentException("오류입니다.");
        }
    }

    @DeleteMapping("/memos/{id}")
    public Long deleteMemo(@PathVariable Long id){
        if(memoMap.containsKey(id)){
            memoMap.remove(id);
            return id;
        }else{
            throw new IllegalArgumentException("오류입니다.");
        }
    }
}
----------------------------------------------------------------------------------------------
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();
    }

    public void update(MemoRequestDto memoRequestDto) {
        this.username = memoRequestDto.getUsername();
        this.contents = memoRequestDto.getContents();
    }
}
----------------------------------------------------------------------------------------------
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();
    }
}
----------------------------------------------------------------------------------------------
package com.sparta.memo.dto;

import lombok.Getter;

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

Database

데이터의 집합

DBMS

DB를 관리하고 운영하는 소프트웨어를 의미

RDBMS

관계형 데이터베이스

테이블이라는 최소단위로 구성되며 테이블은 열과 행으로 구성

테이블간 FK를 통해 다른 데이터를 조합해서 함께 볼 수 있음

 

SQL

RDBMS에서 사용되는 언어

DDL

테이블이나 관계의 구조를 생성하는데 사용

- CREATE 새로운 데이터베이스 및 테이블을 생성

CREATE DATABASE 데이터베이스이름;
------------------------------
CREATE DATABASE academy;
------------------------------
CREATE TABLE 테이블이름
(
		필드이름1 필드타입1,
    필드이름2 필드타입2,
    ...
);
------------------------------
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 '튜터'
);
 

- ALTER

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

ALTER TABLE 테이블이름 ADD 필드이름 필드타입;
ALTER TABLE 테이블이름 DROP 필드이름;
ALTER TABLE 테이블이름 MODIFY COLUMN 필드이름 필드타입;
-----------------------------------------------
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);
//exma_fk_student_code 임의이름 
 

- DROP

데이터베이스와 테이블을 삭제, 데이터 및 테이블 전체를 삭제

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

- TRUNCATE

데이터베이스와 테이블을 삭제, 최초 테이블이 만들어졌던 상태 즉 컬럼값만 남김

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

DCL

데이터의 사용 권한을 관리하는데 사용

- GRANT

사용자 또는 ROLE에 대해 권한을 부여

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

//ex
GRANT SELECT ,INSERT 
ON mp
TO scott WITH GRANT OPTION;
 

- REVOKE

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

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

//ex
REVOKE SELECT , INSERT
ON emp
FROM scott
[CASCADE CONSTRAINTS];
 

DML

데이터를 검색, 삽입, 수정, 삭제

- INSERT

테이블에 새로운 row를 추가

INSERT INTO 테이블이름(필드이름1, 필드이름2, 필드이름3, ...) 
       VALUES(데이터값1, 데이터값2, 데이터값3, ...);
INSERT INTO 테이블이름 
       VALUES(데이터값1, 데이터값2, 데이터값3, ...);
------------------------------------------------
INSERT INTO STUDENT(student_code, name, gender, major_code) VALUES('s17', '박가현', 'F', 'm7');
//student_code,name,gender,major_code 외 null
INSERT INTO STUDENT(student_code, name, birth, gender, phone, major_code) VALUES('s16', '정영호', '20221105', 'M', '01000000050', 'm5');
INSERT INTO STUDENT VALUES('s15', '박은진', '20221101', 'F', '01000000040', 'm1');
//모든 필드 값 입력
 

- SELECT

테이블의 row를 선택

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

- UPDATE

테이블의 row의 내용을 수정

UPDATE 테이블이름 
SET 필드이름1=데이터값1, 필드이름2=데이터값2, ... 
WHERE 필드이름=데이터값;
---------------------------------------------------------
UPDATE STUDENT SET major_code= 'm2' where student_code= 's0'; //s0 -> m2
 

- DELETE

테이블의 row를 삭제

DELETE 
FROM 테이블이름 
WHERE 필드이름=데이터값;
------------------------------
DELETE FROM STUDENT WHERE student_code = 's0';
 

JDBC

 
사진 삭제

사진 설명을 입력하세요.

MySQL -> 다른 DB로 바꿀 시 로직을 전부 바꿔야함

 
사진 삭제

사진 설명을 입력하세요.

JDBC로 로직을 변경안해도 됌

Driver만 교체하여 서버 코드(로직) 변경없이 손쉽게 DB 교체 가능

 
사진 삭제

사진 설명을 입력하세요.

JDBC사용방법

application.properties

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
 

build.grable

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

추가 후

memo database생성 후 create memo id, username, contents 추가

    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 (?, ?)";
        //?는 setString을 통해 값이 들어감 동적으로 관리하여야 함으로 ?로 입력
        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;
    }
 

insert, update, delete 세가지 전부도 update 메서드를 통해서 처리

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

column 하나가 java 객체의 field 하나가 매칭 되는거고 데이터베이스에서 한 row가 java 객체하나가 됨

    @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);
    }
 
 
사진 삭제

사진 설명을 입력하세요.

1주차 SQL과제

수강생을 관리하는 MANAGER 테이블을 만들어보세요.

- 컬럼은 총 id, name, student_code 입니다.
- id는 bigint 타입이며 PK입니다.
- name은 최소 2자 이상, varchar 타입, not null 입니다.
- student_code는 STUDENT 테이블을 참조하는 FK이며 not null 입니다.
- FK는 CONSTRAINT 이름을 ‘manager_fk_student_code’ 로 지정해야합니다.
- comment를 사용해서 컬럼과 테이블에 설명을 추가합니다.

CREATE TABLE IF NOT EXISTS MANAGER(
    id bigint primary key comment 'id',
    name varchar(100) not null comment 'name',
    student_code varchar(100) not null comment 'code',
    constraint manager_fk_student_code foreign key(student_code) references STUDENT(student_code)
);
--------------------------------------------------------------------------------------------------
ALTER, MODIFY를 이용하여 MANAGER 테이블의 id 컬럼에 AUTO_INCREMENT 기능을 부여하세요

ALTER TABLE MANAGER MODIFY id bigint auto_increment;
--------------------------------------------------------------------------------------------------

INSERT를 이용하여 수강생 s1, s2, s3, s4, s5를 관리하는 managerA와 s6, s7, s8, s9를 관리하는 managerB를 추가하세요.
- AUTO_INCREMENT 기능을 활용하세요

insert into MANAGER(name,student_code) values ('MANAGERA','s1');
insert into MANAGER(name,student_code) values ('MANAGERA','s2');
insert into MANAGER(name,student_code) values ('MANAGERA','s3');
insert into MANAGER(name,student_code) values ('MANAGERA','s4');
insert into MANAGER(name,student_code) values ('MANAGERA','s5');
insert into MANAGER(name,student_code) values ( 'MANAGERB','s6' );
insert into MANAGER(name,student_code) values ( 'MANAGERB','s7' );
insert into MANAGER(name,student_code) values ( 'MANAGERB','s8' );
insert into MANAGER(name,student_code) values ( 'MANAGERB','s9' );
--------------------------------------------------------------------------------------------------
JOIN을 사용하여 managerA가 관리하는 수강생들의 이름과 시험 주차 별 성적을 가져오세요.

select s.NAME, e.EXAM_SEQ, e.SCORE
from MANAGER m
join EXAM e on m.student_code = e.STUDENT_CODE
join STUDENT s on e.STUDENT_CODE = s.STUDENT_CODE;
--------------------------------------------------------------------------------------------------
STUDENT 테이블에서 s1 수강생을 삭제했을 때 EXAM에 있는 s1수강생의 시험성적과 MANAGER의 managerA가 관리하는 수강생 목록에 자동으로 삭제될 수 있도록 하세요.
- ALTER, DROP, MODIFY, CASCADE 를 사용하여 EXAM, MANAGER 테이블을 수정합니다.

ALTER TABLE EXAM DROP CONSTRAINT exam_fk_student_code;
ALTER TABLE EXAM ADD CONSTRAINT exam_fk_student_code 
      FOREIGN KEY(student_code) REFERENCES STUDENT(student_code) ON DELETE CASCADE;

ALTER TABLE MANAGER DROP CONSTRAINT manager_fk_student_code;
ALTER TABLE MANAGER ADD CONSTRAINT manager_fk_student_code 
      FOREIGN KEY(student_code) REFERENCES STUDENT(student_code) ON DELETE CASCADE;
DELETE FROM STUDENT WHERE student_code = 's1';
 

 

반응형
LIST

'K-Digital Training > 내일배움캠프' 카테고리의 다른 글

숙련 1주차/Spring  (2) 2023.11.21
입문 2주차/Spring  (2) 2023.11.21
자바 5주차  (1) 2023.11.01
자바 4주차  (1) 2023.11.01
자바 3주차  (0) 2023.10.20