Skip to content

๐Ÿคธโ€โ™€๏ธ SpringBoot, JPA, OAuth, SpringSecurity, AWS, Travis

Notifications You must be signed in to change notification settings

devAon/AoneeMall

Folders and files

NameName
Last commit message
Last commit date

Latest commit

40c325a ยท Aug 4, 2020

History

77 Commits
Jul 25, 2020
Aug 4, 2020
Aug 3, 2020
Aug 4, 2020
Aug 4, 2020
Jul 25, 2020
Jul 25, 2020
Jul 25, 2020

Repository files navigation

๐Ÿคธโ€โ™€๏ธ WELCOME ! AoneeMall ! ๐Ÿคธโ€โ™€๏ธ


โš™ ํ”„๋กœ์ ํŠธ ๊ฐœ๋ฐœํ™˜๊ฒฝ

ํ”„๋กœ์ ํŠธ ๊ฐœ๋ฐœ ํ™˜๊ฒฝ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

IDE : IntelliJ IDEA Ultimate
Git Tools : Git Bash
OS : Window
SpringBoot 2.1.9
Java8
Gradle 4.10.2

๐Ÿ‘‰ springBootVersion 2.1.7 / 2.1.8 / 2.1.9 ๋ชจ๋‘ ๊ดœ์ฐฎ์œผ๋‚˜, 2.2 ์ด์ƒ ๋ฒ„์ „์—์„œ๋Š” ์ •์ƒ์ž‘๋™ ํ•˜์ง€ ์•Š๋Š”๋‹ค.

๐Ÿ‘‰ Gradle 5.x์—์„œ๋Š” ์ •์ƒ์ž‘๋™ ํ•˜์ง€ ์•Š๋Š”๋‹ค.
๐Ÿ™‹โ€โ™€๏ธ Gradle 4.10.2 ๋กœ ๋ณ€๊ฒฝํ•˜๋Š” ๋ฐฉ๋ฒ• ?

โ€‹ ์ธํ…”๋ฆฌ์ œ์ด์—์„œ alt+F12 ๋ˆŒ๋Ÿฌ ๐Ÿ‘‰ ํ„ฐ๋ฏธ๋„์—์„œ gradlew wrapper --gradle-version 4.10.2 ๋ช…๋ น์–ด ์‹คํ–‰


๐Ÿงถ build.gradle

buildscript {
    ext {
        springBootVersion = '2.1.9.RELEASE'
    }
    repositories {
        mavenCentral()
        jcenter()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'com.devAon'
version '1.0.4-SNAPSHOT-'+new Date().format("yyyyMMddHHmmss")

sourceCompatibility = 1.8

repositories {
    mavenCentral()
    jcenter()
}

dependencies {
    compile('org.springframework.boot:spring-boot-starter-web')
    compile('org.projectlombok:lombok')
    compile('org.springframework.boot:spring-boot-starter-data-jpa')
    compile('com.h2database:h2')
    compile('org.springframework.boot:spring-boot-starter-mustache')

    compile('org.springframework.boot:spring-boot-starter-oauth2-client')
    compile('org.springframework.session:spring-session-jdbc')

    testCompile('org.springframework.boot:spring-boot-starter-test')
    testCompile("org.springframework.security:spring-security-test")
}

  • ext

    : build.gradle์—์„œ ์‚ฌ์šฉํ•˜๋Š” ์ „์—ญ๋ณ€์ˆ˜๋ฅผ ์„ค์ •ํ•œ๋‹ค๋Š” ์˜๋ฏธ

  • spring-boot-gradle-plugin:${springBootVersion}

    : springBootVersion = '2.1.9.RELEASE' ๋ฅผ ์˜์กด์„ฑ์œผ๋กœ ๋ฐ›๊ฒ ๋‹ค๋Š” ์˜๋ฏธ

  • repositories

    : ๊ฐ์ข… ์˜์กด์„ฑ(๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ)๋“ค์„ ์–ด๋–ค ์›๊ฒฉ ์ €์žฅ์†Œ์— ๋ฐ›์„์ง€ ์ •ํ•œ๋‹ค

    • mavenCentral()

      : ๊ธฐ๋ณธ์ ์œผ๋กœ ๋งŽ์ด ์‚ฌ์šฉ.

      ํ•˜์ง€๋งŒ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์—…๋กœ๋“œ๋ฅผ ์œ„ํ•ด ์ •๋ง ๋งŽ์€ ๊ณผ์ •๊ณผ ์„ค์ •์ด ํ•„์š”ํ•ด์„œ ํž˜๋“ฆ

    • jcenter()

      : ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์—…๋กœ๋“œ ๋ฌธ์ œ์  ๊ฐœ์„ ํ•ด์„œ ๊ฐ„๋‹จํ•˜๊ฒŒ ํ•ด์คŒ

      jcenter์— ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์—…๋กœ๋“œํ•˜๋ฉด mavenCentral์—๋„ ์—…๋กœ๋“œ๋  ์ˆ˜ ์žˆ๋„๋ก ์ž๋™ํ™”ํ•  ์ˆ˜ ์žˆ๋‹ค.

    ๐Ÿ‘‰ ์š”์ฆ˜์€ jcenter์„ ๋” ๋งŽ์ด ์‚ฌ์šฉํ•˜์ง€๋งŒ, ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ๋‘˜ ๋‹ค ๋“ฑ๋กํ•ด์„œ ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋‹ค.

  • dependencies

    • lombok ๐Ÿ™‹โ€โ™€๏ธ ์ถ”๊ฐ€ํ•˜๋Š” ๋ฐฉ๋ฒ• ?
      step 1 ) build.gradle์˜ dependencies์— compile('org.projectlombok:lombok') ์ถ”๊ฐ€
      step 2 ) Setting > Build > Compiler > Annotation Processros ์—์„œ Enable annotation processing ์ฒดํฌ๋ฅผ ํ†ตํ•ด
      ํ•ด๋‹น ํ”„๋กœ์ ํŠธ์—์„œ ๋กฌ๋ณต์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ํ•ด์ค˜์•ผ ํ•œ๋‹ค. (๋กฌ๋ณต์€ ํ”„๋กœ์ ํŠธ๋งˆ๋‹ค ์„ค์ •ํ•ด์•ผ ํ•œ๋‹ค ! ! !)

    • spring-boot-starter-jpa ์Šคํ”„๋ง ๋ถ€ํŠธ์šฉ Spring Data JPA ์ถ”์ƒํ™” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ

      ์Šคํ”„๋ง ๋ถ€ํŠธ ๋ฒ„์ „์— ๋งž์ถฐ ์ž๋™์œผ๋กœ JPA ๊ด€๋ จ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋“ค์˜ ๋ฒ„์ „์„ ๊ด€๋ฆฌํ•ด์ค€๋‹ค.

    • h2

      ์ธ๋ฉ”๋ชจ๋ฆฌ RDBMS

      ๋ณ„๋„์˜ ์„ค์น˜ ์—†์ด ํ”„๋กœ์ ํŠธ ์˜์กด์„ฑ๋งŒ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค

      ๋ฉ”๋ชจ๋ฆฌ์—์„œ ์‹คํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์— ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์žฌ์‹œ์ž‘ํ•  ๋•Œ๋งˆ๋‹ค ์ดˆ๊ธฐํ™”๋œ๋‹ค๋Š” ์ ์„ ์ด์šฉํ•˜์—ฌ ํ…Œ์ŠคํŠธ ์šฉ๋„๋กœ ๋งŽ์ด ์‚ฌ์šฉ๋œ๋‹ค

      AoneeMall ํ”„๋กœ์ ํŠธ์—์„œ๋Š” JPA์˜ ํ…Œ์ŠคํŠธ, ๋กœ์ปฌ ํ™˜๊ฒฝ์—์„œ์˜ ๊ตฌ๋™์—์„œ ์‚ฌ์šฉํ•  ์˜ˆ์ • !


๐Ÿ”— ํŒจํ‚ค์ง€ ๊ตฌ์กฐ

src-main-java

  • web : ์ปจํŠธ๋กค๋Ÿฌ์™€ ๊ด€๋ จ๋œ ํด๋ž˜์Šค

  • domain : ๋„๋ฉ”์ธ (์†Œํ”„ํŠธ์›จ์–ด์— ๋Œ€ํ•œ ์š”๊ตฌ์‚ฌํ•ญ or ๋ฌธ์ œ์˜์—ญ)

    • Posts

      : ๋„๋ฉ”์ธ ํด๋ž˜์Šค

    • PostsRepository

      : Posts ํด๋ž˜์Šค๋กœ DB๋ฅผ ์ ‘๊ทผํ•˜๊ฒŒ ํ•ด์ค„ JpaRepository

      MyBatis ๋“ฑ์—์„œ Dao๋ผ๊ณ  ๋ถˆ๋ฆฌ๋Š” DB Layer ์ ‘๊ทผ์ž

      JPA์—์„œ๋Š” Repository๋ผ๊ณ  ๋ถ€๋ฅด๋ฉฐ ์ธํ„ฐํŽ˜์ด์Šค๋กœ ์ƒ์„ฑํ•œ๋‹ค.

      JpaRepository<Entity ํด๋ž˜์Šค, PKํƒ€์ž…> ์„ ์ƒ์†กํ•˜๋ฉด ๊ธฐ๋ณธ์ ์ธ CRUD ๋ฉ”์†Œ๋“œ๊ฐ€ ์ž๋™์œผ๋กœ ์ƒ์„ฑ๋œ๋‹ค. ex) JpaRepository<Posts, Long>

      โœ ์ฃผ์˜ โœ

      Entity ํด๋ž˜์Šค์™€ ๊ธฐ๋ณธ Repository๋Š” ํ•จ๊ป˜ ์œ„์น˜ํ•ด์•ผ ์ œ๋Œ€๋กœ๋œ ์—ญํ• ์„ ํ•  ์ˆ˜ ์žˆ๋‹ค.



๐Ÿ“Œ feature-1 : ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ

build.gralde ์˜ ์ฝ”๋“œ์˜ ์—ญํ•  ๋ฐ ์˜์กด์„ฑ ์ถ”๊ฐ€ ๋ฐฉ๋ฒ•์„ ์Šค์Šค๋กœ ํ•ด๋ณด์ž !

๐Ÿ“ ์Šคํ”„๋ง ์ด๋‹ˆ์…œ๋ผ์ด์ € ์—†์ด ํ”„๋กœ์ ํŠธ ์ƒ์„ฑํ•˜๊ธฐ

  1. ํ”„๋กœ์ ํŠธ ์ƒ์„ฑ

    create - Gradle - Java

  2. ๊ทธ๋ ˆ์ด๋“ค ํ”„๋กœ์ ํŠธ๋ฅผ ์Šคํ”„๋ง ๋ถ€ํŠธ ํ”„๋กœ์ ํŠธ๋กœ ๋ณ€๊ฒฝํ•˜๊ธฐ

    build.gradle

buildscript {
    ext {
        springBootVersion = '2.1.9.RELEASE'
    }
    repositories {
        mavenCentral()
        jcenter()
    }
    dependencies {
        classpath("org.springframework.boot:spring-boot-gradle-plugin:${springBootVersion}")
    }
}

apply plugin: 'java'
apply plugin: 'eclipse'
apply plugin: 'org.springframework.boot'
apply plugin: 'io.spring.dependency-management'

group = 'com.devAon'
version '1.0.4-SNAPSHOT-'+new Date().format("yyyyMMddHHmmss")

sourceCompatibility = 1.8

repositories {
    mavenCentral()
    jcenter()
}

dependencies {
    compile('org.springframework.boot:spring-boot-starter-web')
    testCompile('org.springframework.boot:spring-boot-starter-test')
}


๐Ÿ‘‰ ํ”„๋กœ์ ํŠธ์˜ ํ”Œ๋Ÿฌ๊ทธ์ธ ์˜์กด์„ฑ ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ ์„ค์ •




๐Ÿ“Œ feature-4 : TDD

๐Ÿ“ SpringBootApplication ์ถ”๊ฐ€

package com.devAon.aoneemall;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application {
    public static void main(String[] args){
        SpringApplication.run(Application.class, args);
    }
}

  • ํ•ด๋‹น ํด๋ž˜์Šค๋Š” ํ”„๋กœ์ ํŠธ์˜ ๋ฉ”์ธ ํด๋ž˜์Šค

  • @SpringBootApplication

    : ์Šคํ”„๋ง ๋ถ€ํŠธ์˜ ์ž๋™ ์„ค์ •, ์Šคํ”„๋ง Bean ์ฝ๊ธฐ์™€ ์ƒ์„ฑ ๋ชจ๋‘ ์ž๋™์œผ๋กœ ์„ค์ •๋œ๋‹ค.

    โœ ์ฃผ์˜ โœ

    @SpringBootApplication ์ด ์žˆ๋Š” ์œ„์น˜๋ถ€ํ„ฐ ์„ค์ •์„ ์ฝ์–ด๊ฐ€๊ธฐ ๋•Œ๋ฌธ์— ํ•ญ! ์ƒ! ํ”„๋กœ์ ํŠธ์˜ ์ตœ์ƒ๋‹จ์— ์œ„์น˜ํ•ด์•ผ ํ•œ๋‹ค.

  • SpringApplication.run

    : ๋‚ด์žฅ WAS( Web Application Server)๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.

    ๐Ÿฅ ๋‚ด์žฅ WAS ๋ž€ ?

    • ์™ธ๋ถ€์— WAS๋ฅผ ๋‘์ง€ ์•Š๊ณ  ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ์‹คํ–‰ํ•  ๋•Œ ๋‚ด๋ถ€์—์„œ WAS๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์„ ์˜๋ฏธ
    • ๋‚ด์žฅ WAS ์‚ฌ์šฉ ์ด์œ  ? ํ•ญ์ƒ ์„œ๋ฒ„์— ํ†ฐ์บฃ์„ ์„ค์น˜ํ•  ํ•„์š”๊ฐ€ ์—†๋‹ค. ์Šคํ”„๋ง ๋ถ€ํŠธ๋กœ ๋งŒ๋“ค์–ด์ง„ Jar ํŒŒ์ผ (์‹คํ–‰ ๊ฐ€๋Šฅํ•œ Java ํŒจํ‚ค์ง• ํŒŒ์ผ)๋กœ ์‹คํ–‰ํ•˜๋ฉด ๋œ๋‹ค.
    • ์–ธ์ œ ์–ด๋””์„œ๋‚˜ ๊ฐ™์€ ํ™˜๊ฒฝ์—์„œ ์Šคํ”„๋ง ๋ถ€ํŠธ๋ฅผ ๋ฐฐํฌ ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋‚ด์žฅ WAS ์‚ฌ์šฉ ๊ถŒ์žฅ
    • ์™ธ์žฅ WAS ์ง€์–‘ํ•˜๋Š” ์ด์œ  ? ๋ชจ๋“  ์„œ๋ฒ„๋Š” WAS์˜ ์ข…๋ฅ˜, ๋ฒ„์ „, ์„ค์ •์„ ์ผ์น˜์‹œ์ผœ์•ผ๋งŒ ํ•œ๋‹ค. Oh my God

๐Ÿ“ API ์ž‘์„ฑ

  • @RestController

    : JSON์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ์ปจํŠธ๋กค๋Ÿฌ๋กœ ๋งŒ๋“ค์–ด์ค€๋‹ค

  • @GetMapping

    : HTTP Method์ธ Get์˜ ์š”์ฒญ์„ ๋ฐ›์„ ์ˆ˜ ์žˆ๋Š” API๋ฅผ ๋งŒ๋“ค์–ด์ค€๋‹ค

  • @PostMapping, @PutMapping, @DeleteMapping

  • @RequestParam

    : ์™ธ๋ถ€์—์„œ API๋กœ ๋„˜๊ธด ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋Š” ์–ด๋…ธํ…Œ์ด์…˜

    ํŒŒ๋ผ๋ฏธํ„ฐ์—์„œ name @RequestParam("name")์ด๋ž€ ์ด๋ฆ„์œผ๋กœ ๋„˜๊ธด ๊ฐ’์„ ๋ฉ”์†Œ๋“œ ํŒŒ๋ผ๋ฏธํ„ฐ name(String name)์— ์ €์žฅ


๐Ÿ“ TDD๋ž€ ?

Test Driven Development, ํ…Œ์ŠคํŠธ ์ฃผ๋„ ๊ฐœ๋ฐœ

  • Red

    : ํ•ญ์ƒ ์‹คํŒจํ•˜๋Š” ํ…Œ์ŠคํŠธ๋ฅผ ๋จผ์ € ์ž‘์„ฑ

  • Green

    : ํ…Œ์ŠคํŠธ๊ฐ€ ํ†ต๊ณผํ•˜๋Š” ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑ

  • Refactor

    : ํ…Œ์ŠคํŠธ๊ฐ€ ํ†ต๊ณผํ•˜๋ฉด ํ”„๋กœ๋•์…˜ ์ฝ”๋“œ๋ฅผ ๋ฆฌํŒฉํ† ๋ง


๐Ÿ“ TDD ์žฅ์ 

  1. ๋น ๋ฅธ ํ”ผ๋“œ๋ฐฑ ๊ฐ€๋Šฅ

    โŒ TDD ์‚ฌ์šฉ ์•ˆํ•œ๋‹ค๋ฉด ? ๋ฒˆ๊ฑฐ๋กญ๋‹ค

    โ€‹ ํ”„๋กœ๊ทธ๋žจ(Tomcat)์„ ์‹คํ–‰ํ•˜๊ณ 

    โ€‹ Postman๊ณผ ๊ฐ™์€ API ๋„๊ตฌ๋กœ HTTP ์š”์ฒญํ•˜๊ณ 

    โ€‹ System.out.println()์œผ๋กœ ๋ˆˆ์œผ๋กœ ๊ฒ€์ฆํ•˜๊ณ  ๊ฒฐ๊ณผ๊ฐ€ ๋‹ค๋ฅด๋ฉด

    โ€‹ ๊ฒฐ๊ณผ๊ฐ€ ๋‹ค๋ฅด๋ฉด ๋‹ค์‹œ ํ”„๋กœ๊ทธ๋žจ(Tomcat)์„ ์ค‘์ง€ํ•˜๊ณ  ์ฝ”๋“œ ์ˆ˜์ •

  2. ์ž๋™๊ฒ€์ฆ ๊ฐ€๋Šฅ

  3. ๊ฐœ๋ฐœ์ž๊ฐ€ ๋งŒ๋“  ๊ธฐ๋Šฅ์„ ์•ˆ์ „ํ•˜๊ฒŒ ๋ณดํ˜ธ

    EX)

    ๊ธฐ์กด์— ์ž˜๋˜๋˜ A๊ธฐ๋Šฅ์ด B๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๊ณ  ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค.

    ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ์ด ์ถ”๊ฐ€๋  ๋•Œ, ๊ธฐ์กด ๊ธฐ๋Šฅ์ด ์ž˜ ์ž‘๋™๋˜๋Š” ๊ฒƒ์„ ๋ณด์žฅํ•ด์ฃผ๋Š” ๊ฒƒ์ด ํ…Œ์ŠคํŠธ ์ฝ”๋“œ์ด๋‹ค.

    A๋ผ๋Š” ๊ธฐ์กด ๊ธฐ๋Šฅ์— ๊ธฐ๋ณธ ๊ธฐ๋Šฅ์„ ๋น„๋กฏํ•ด ์—ฌ๋Ÿฌ ๊ฒฝ์šฐ๋ฅผ ๋ชจ๋‘ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋กœ ๊ตฌํ˜„ํ•ด ๋†“์•˜๋‹ค๋ฉด ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ˆ˜ํ–‰๋งŒ ํ•˜๋ฉด ์กฐ๊ธฐ์— ๋ฌธ์ œ๋ฅผ ์ฐพ์„ ์ˆ˜ ์žˆ๋‹ค.

    (์„œ๋น„์Šค์˜ ๋ชจ๋“  ๊ธฐ๋Šฅ์„ ํ…Œ์ŠคํŠธํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๋„ˆ๋ฌด๋‚˜ ๋งŽ์€ ์ž์›์ด ๋“œ๋Š”๋ฐ ์ด๋ฅผ ๋ฐฉ์ง€ํ•  ์ˆ˜ ์žˆ๋‹ค โ—)


๐Ÿ“ TDD ์ž‘์„ฑ

Controller TDD

  • @RunWith(SpringRunner.class)

    : ์Šคํ”„๋ง ๋ถ€ํŠธ ํ…Œ์ŠคํŠธ์™€ JUnit ์‚ฌ์ด์— ์—ฐ๊ฒฐ์ž ์—ญํ•  ํ…Œ์ŠคํŠธ๋ฅผ ์ง„ํ–‰ํ•  ๋•Œ JUnit์— ๋‚ด์žฅ๋œ ์‹คํ–‰์ž ์™ธ์— ๋‹ค๋ฅธ ์‹คํ–‰์ž๋ฅผ ์‹คํ–‰์‹œํ‚จ๋‹ค. ์‹คํ–‰์ž : SpringRunner

  • @WebMvcTest

    : ์—ฌ๋Ÿฌ ์Šคํ”„๋ง ํ…Œ์ŠคํŠธ ์–ด๋…ธํ…Œ์ด์…˜ ์ค‘, Web (Spring MVC)์— ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ๋Š” ์–ด๋…ธํ…Œ์ด์…˜

    ์‚ฌ์šฉ ๊ฐ€๋Šฅ - @Controller, @ControllerAdvice

    ์‚ฌ์šฉ ๋ถˆ๊ฐ€๋Šฅ - @Service, @Component, @Repository

  • @Autowired

    : ์Šคํ”„๋ง์ด ๊ด€๋ฆฌํ•˜๋Š” ๋นˆ(Bean)์„ ์ฃผ์ž… ๋ฐ›๋Š”๋‹ค.

  • private MockMvc mvc

    : ์›น API ๋ฅผ ํ…Œ์ŠคํŠธํ•  ๋•Œ ์‚ฌ์šฉ

    ์Šคํ”„๋ง MVC ํ…Œ์ŠคํŠธ์˜ ์‹œ์ž‘์ 

    ์ด ํด๋ž˜์Šค๋ฅผ ํ†ตํ•ด HTTP GET, POST ๋“ฑ์— ๋Œ€ํ•œ API ํ…Œ์ŠคํŠธ๋ฅผ ํ•  ์ˆ˜ ์žˆ๋‹ค.

  • mvc.perform(get("/hello"))

    : MockMvc๋ฅผ ํ†ตํ•ด /hello ์ฃผ์†Œ๋กœ HTTP GET ์š”์ฒญ์„ ํ•œ๋‹ค

    ์ฒด์ด๋‹์ด ์ง€์›๋˜์–ด ์—ฌ๋Ÿฌ ๊ฒ€์ฆ ๊ธฐ๋Šฅ์„ ์ด์–ด์„œ ์„ ์–ธํ•  ์ˆ˜ ์žˆ๋‹ค.

  • .andExpect(status().isOk())

    : mvc.perform ์˜ ๊ฒฐ๊ณผ๋ฅผ ๊ฒ€์ฆํ•œ๋‹ค.

    HTTP Header์˜ Status๋ฅผ ๊ฒ€์ฆํ•œ๋‹ค.

    Status์ธ 200,404, 500 ๋“ฑ ์ƒํƒœ ์ค‘ 200์ธ์ง€ ๊ฒ€์ฆํ•œ๋‹ค

  • .andExpect(content().string(hello))

    : mvc.perform ์˜ ๊ฒฐ๊ณผ๋ฅผ ๊ฒ€์ฆํ•œ๋‹ค.

    ์‘๋‹ต ๋ณธ๋ฌธ์˜ ๋‚ด์šฉ์„ ๊ฒ€์ฆํ•œ๋‹ค.

    Controller์—์„œ ๋ฆฌํ„ดํ•˜๋Š” "hello"์™€ ๋‚ด์šฉ์ด ์ผ์น˜ํ•˜๋Š”์ง€ ๊ฒ€์ฆํ•œ๋‹ค.

  • param

    : API ํ…Œ์ŠคํŠธํ•  ๋•Œ ์‚ฌ์šฉ๋  ์š”์ฒญ ํŒŒ๋ผ๋ฏธํ„ฐ๋ฅผ ์„ค์ •

    ๋‹จ, ๊ฐ’์€ String๋งŒ ํ—ˆ์šฉ๋œ๋‹ค.

    ๋”ฐ๋ผ์„œ, ์ˆซ์ž/๋‚ ์งœ/์ˆซ์ž ๋“ฑ ๋ฌธ์ž์—ด๋กœ ๋ณ€๊ฒฝํ•ด์•ผ๋งŒ ๊ฐ€๋Šฅ

  • jsonPath

    : JSON ์‘๋‹ต๊ฐ’์„ ํ•„๋“œ๋ณ„๋กœ ๊ฒ€์ฆํ•  ์ˆ˜ ์žˆ๋Š” ๋ฉ”์†Œ๋“œ

    $๋ฅผ ๊ธฐ์ค€์œผ๋กœ ํ•„๋“œ๋ช… ๋ช…์‹œ

    โ€‹ ex) $.amount

DTO TDD

  • assertThat

    : org.assertj.core.api.Assertions.assertThat assertj์˜ ํ…Œ์ŠคํŠธ ๊ฒ€์ฆ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ์˜ ๊ฒ€์ฆ ๋ฉ”์†Œ๋“œ

    ๊ฒ€์ฆํ•˜๊ณ  ์‹ถ์€ ๋Œ€์ƒ์„ ๋ฉ”์†Œ๋“œ ์ธ์ž๋กœ ๋ฐ›๋Š”๋‹ค

    ๋ฉ”์†Œ๋“œ ์ฒด์ด๋‹์ด ์ง€์›๋˜์–ด isEqualTo์™€ ๊ฐ™์ด ๋ฉ”์†Œ๋“œ๋ฅผ ์ด์–ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋‹ค.

  • isEqualTo

    : assertj์˜ ๋™๋“ฑ ๋น„๊ต ๋ฉ”์†Œ๋“œ

    assertThat์— ์žˆ๋Š” ๊ฐ’๊ณผ isEqualTo์˜ ๊ฐ’์„ ๋น„๊ตํ•ด์„œ ๊ฐ™์„ ๋•Œ๋งŒ ์„ฑ๊ณต

Repository TDD

  • @RunWith(SpringRunner.class)

  • @SpringBootTest

    : H2 ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๋ฅผ ์ž๋™์œผ๋กœ ์‹คํ–‰ํ•ด์ค€๋‹ค.

  • @After

    : Junit ์—์„œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๊ฐ€ ๋๋‚  ๋•Œ๋งˆ๋‹ค ์ˆ˜ํ–‰๋˜๋Š” ๋ฉ”์†Œ๋„๋ฅผ ์ง€์ •

    ๐Ÿ™‹โ€โ™€๏ธ ์–ธ์ œ ์‚ฌ์šฉํ•ด ?

    ๋ณดํ†ต, ๋ฐฐํฌ ์ „, ์ „์ฒด ํ…Œ์ŠคํŠธ ์ˆ˜ํ–‰ ์‹œ, ํ…Œ์ŠคํŠธ๊ฐ„ ๋ฐ์ดํ„ฐ ์นจ๋ฒ”์„ ๋ง‰๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉํ•œ๋‹ค.

    ์—ฌ๋Ÿฌ ํ…Œ์ŠคํŠธ๊ฐ€ ๋™์‹œ์— ์ˆ˜ํ–‰๋˜๋ฉด ํ…Œ์ŠคํŠธ์šฉ DB์ธ H2์— ๋ฐ์ดํ„ฐ๊ฐ€ ๊ทธ๋Œ€๋กœ ๋‚จ์•„ ์žˆ์–ด ๋‹ค์Œ ํ…Œ์ŠคํŠธ ์‹คํ–‰ ์‹œ, ํ…Œ์ŠคํŠธ๊ฐ€ ์‹คํŒจํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

  • postRepository.save

    : ํ…Œ์ด๋ธ” posts์— insert/update ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•œ๋‹ค.

    id ๊ฐ’์ด ์žˆ๋‹ค๋ฉด update, ์—†๋‹ค๋ฉด insert ์ฟผ๋ฆฌ๊ฐ€ ์‹คํ–‰๋œ๋‹ค.

  • postRepository.findAll

    : ํ…Œ์ด๋ธ” posts์— ์žˆ๋Š” ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ์กฐํšŒํ•˜๋Š” ๋ฉ”์†Œ๋“œ


๐Ÿ“ lombok ์‚ฌ์šฉ

  • @Getter

    ์„ ์–ธ๋œ ๋ชจ๋“  ํ•„๋“œ์˜ get ๋ฉ”์†Œ๋“œ๋ฅผ ์ƒ์„ฑํ•ด์ค€๋‹ค

  • @RequireArgsConstructor

    ์„ ์–ธ๋œ ๋ชจ๋“  final ํ•„๋“œ๊ฐ€ ํฌํ•จ๋œ ์ƒ์„ฑ์ž๋ฅผ ์ƒ์„ฑํ•ด์คŒ

    final์ด ์—†๋Š” ํ•„๋“œ๋Š” ์ƒ์„ฑ์ž์— ํฌํ•จ๋˜์ง€ ์•Š๋Š”๋‹ค.

  • @NoArgsConstructor

    : ๊ธฐ๋ณธ ์ƒ์„ฑ์ž ์ž๋™ ์ถ”๊ฐ€

    ex)

    public Posts() {} ์™€ ๊ฐ™์€ ํšจ๊ณผ

  • @Builder

    : ํ•ด๋‹น ํด๋ž˜์Šค์˜ ๋นŒ๋” ํŒจํ„ด ํด๋ž˜์Šค๋ฅผ ์ƒ์„ฑ

    ์ƒ์„ฑ์ž ์ƒ๋‹จ์— ์„ ์–ธ ์‹œ, ์ƒ์„ฑ์ž์— ํฌํ•จ๋œ ํ•„๋“œ๋งŒ ๋นŒ๋”์— ํฌํ•จ

๐Ÿ‘‰ ์„œ๋น„์Šค ์ดˆ๊ธฐ ๊ตฌ์ถ• ๋‹จ๊ณ„์—์„œ ๋นˆ๋ฒˆํ•˜๊ฒŒ ํ…Œ์ด๋ธ” ์„ค๊ณ„๊ฐ€ ๋ณ€๊ฒฝ๋˜๋Š”๋ฐ,

โ€‹ ๋กฌ๋ณต์˜ ์–ด๋…ธํ…Œ์ด์…˜๋“ค์€ ์ฝ”๋“œ ๋ณ€๊ฒฝ๋Ÿ‰์„ ์ตœ์†Œํ™”์‹œ์ผœ ์ฃผ๊ธฐ ๋•Œ๋ฌธ์— ์ ๊ทน์ ์œผ๋กœ ์‚ฌ์šฉํ•œ๋‹ค !




๐Ÿ“Œ feature-8 : JPA

๐Ÿ“ JPA๋ž€ ?

๊ฐ์ฒด์ง€ํ–ฅ์ ์œผ๋กœ ํ”„๋กœ๊ทธ๋ž˜๋ฐ์„ ํ•˜๊ณ , JPA๊ฐ€ ์ด๋ฅผ RDBMS์— ๋งž๊ฒŒ SQL์„ ๋Œ€์‹  ์ƒ์„ฑํ•ด์„œ ์‹คํ–‰ํ•œ๋‹ค.

์ฆ‰, JPA๋Š” ์ค‘๊ฐ„์—์„œ ํŒจ๋Ÿฌ๋‹ค์ž„์„ ์ผ์น˜์‹œ์ผœ์ฃผ๋Š” ๊ธฐ์ˆ ์ด๋‹ค.

๐Ÿ™‹โ€โ™€๏ธ ๋ฌด์Šจ ์†Œ๋ฆฌ๋ƒ๊ตฌ ?

RDBMS์™€ ๊ฐ์ฒด์ง€ํ–ฅ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด์˜ ํŒจ๋Ÿฌ๋‹ค์ž„์€ ์„œ๋กœ ๋‹ฌ๋ผ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. (RDBMS์— ์—†๋Š” ์ƒ์†์˜ ๊ฐœ๋…๋„ ํ•ด๊ฒฐํ•ด์คŒ !)

๊ทธ๋Ÿฐ๋ฐ! ! ! JPA๊ฐ€ ์ด๋ฅผ ํ•ด๊ฒฐํ•ด์ค€๋‹ค. WOW

๐Ÿฅ ์ฐธ๊ณ 

RDBMS ํŒจ๋Ÿฌ๋‹ค์ž„ : ์–ด๋–ป๊ฒŒ ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ• ์ง€ ์ดˆ์ ์ด ๋งž์ถฐ์ง„ ๊ธฐ์ˆ 

๊ฐ์ฒด์ง€ํ–ฅ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์–ธ์–ด ํŒจ๋Ÿฌ๋‹ค์ž„ : ๊ฐ์ฒด๋Š” ๊ธฐ๋Šฅ๊ณผ ์†์„ฑ์„ ํ•œ ๊ณณ์— ๊ด€๋ฆฌํ•˜๋Š” ๊ธฐ์ˆ 


๐Ÿ“ Spring Data JPA๋ž€ ?

JPA : ์ธํ„ฐํŽ˜์ด์Šค๋กœ์„œ ์ž๋ฐ” ํ‘œ์ค€๋ช…์„ธ์„œ

์ธํ„ฐํŽ˜์ด์Šค์ธ JPA๋ฅผ ์‚ฌ์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ๊ตฌํ˜„์ฒด๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

๋Œ€ํ‘œ์ ์œผ๋กœ Hibernate, Eclipse, Link๋“ฑ์ด ์žˆ๋‹ค.

ํ•˜์ง€๋งŒ Spring์—์„œ JPA๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ์ด ๊ตฌํ˜„์ฒด๋ฅผ ์ง์ ‘ ๋‹ค๋ฃจ์ง€ ์•Š๋Š”๋‹ค.

Spring Data JPA๋ผ๋Š” ๋ชจ๋“ˆ์„ ์ด์šฉํ•˜์—ฌ JPA ๊ธฐ์ˆ ์„ ๋‹ค๋ฃฌ๋‹ค.

JPA ๐Ÿ‘ˆ Hibernate ๐Ÿ‘ˆ Spring Data JPA

Hibernate์™€ Spring Data JPA ์“ฐ๋Š” ๊ฒƒ ์‚ฌ์ด์—๋Š” ํฐ ์ฐจ์ด๊ฐ€ ์—†๋‹ค

๐Ÿ™‹โ€โ™€๏ธ ๊ทธ๋Ÿฌ๋‚˜ Spring Data JPA ๊ฐ€ ๋“ฑ์žฅํ•œ ์ด์œ ๋Š” ?

์Šคํ”„๋ง ์ง„์˜์—์„œ ๊ฐœ๋ฐœํ•จ

1) ๊ตฌํ˜„์ฒด ๊ต์ฒด์˜ ์šฉ์ด์„ฑ

โ€‹ Hibernate ์™ธ์— ๋‹ค๋ฅธ ๊ตฌํ˜„์ฒด๋กœ ์‰ฝ๊ฒŒ ๊ต์ฒดํ•˜๊ธฐ ์œ„ํ•จ

2) ์ €์žฅ์†Œ ๊ต์ฒด์˜ ์šฉ์ด์„ฑ

โ€‹ RDBMS ์™ธ์— ๋‹ค๋ฅธ ์ €์žฅ์†Œ๋กœ ์‰ฝ๊ฒŒ ๊ต์ฒดํ•˜๊ธฐ ์œ„ํ•จ

โ€‹ ๋งŒ์•ฝ, NoSQL์ธ MongoDB๋กœ ๊ต์ฒด๊ฐ€ ํ•„์š”ํ•˜๋‹ค๋ฉด Spring Data MongoDB๋กœ ์˜์กด์„ฑ๋งŒ ๊ต์ฒดํ•˜๋ฉด ๋œ๋‹ค.

๐Ÿ‘‰ Spring Data ์˜ ํ•˜์œ„ ํ”„๋กœ์ ํŠธ๋“ค์€ ๊ธฐ๋ณธ์ ์ธ CRUD์˜ ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ๊ฐ™๊ณ  1)2)์™€ ๊ฐ™์€ ์žฅ์ ๋“ค๋กœ ์ธํ•ด Spring Data JPA๋ฅผ ๊ถŒ์žฅํ•œ๋‹ค.


๐Ÿ“ JPA ์–ด๋…ธํ…Œ์ด์…˜

  • @Entity

    : ํ…Œ์ด๋ธ”๊ณผ ๋งํฌ๋  ํด๋ž˜์Šค์ž„์„ ๋‚˜ํƒ€๋‚ธ๋‹ค.

    ๊ธฐ๋ณธ ๊ฐ’์œผ๋กœ ํด๋ž˜์Šค์˜ ์นด๋ฉœ์ผ€์ด์Šค ์ด๋ฆ„์„ ์–ธ๋”์Šค์ฝ”์–ด ๋„ค์ด๋ฐ (_) ์œผ๋กœ ํ…Œ์ด๋ธ” ์ด๋ฆ„์„ ๋งค์นญํ•œ๋‹ค.

    ex ) SalesManager.java -> sales_manager table

  • @ Id

    ํ•ด๋‹น ํ…Œ์ด๋ธ”์˜ PKํ•„๋“œ๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค

  • @GeneratedValue

    : PK์˜ ์ƒ์„ฑ ๊ทœ์น™์„ ๋‚˜ํƒ€๋‚ธ๋‹ค

    ์Šคํ”„๋ง ๋ถ€ํŠธ 2.0์—์„œ๋Š” GenerationType.IDENTITY ์˜ต์…˜์„ ์ถ”๊ฐ€ํ•ด์•ผ๋งŒ auto_increment๊ฐ€ ๋œ๋‹ค.

  • @Column

    : ํ…Œ์ด๋ธ” ์นผ๋Ÿผ์„ ๋‚˜ํƒ€๋‚ด๋ฉฐ ๊ตณ์ด ์„ ์–ธํ•˜์ง€ ์•Š๋”๋ผ๋„ ํ•ด๋‹น ํด๋ž˜์Šค์˜ ํ•„๋“œ๋Š” ๋ชจ๋‘ ์นผ๋Ÿผ์ด ๋œ๋‹ค.

    ๐Ÿ™‹โ€โ™€๏ธ ๊ทธ๋Ÿฐ๋ฐ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ ๋Š” ?

    ๊ธฐ๋ณธ๊ฐ’ ์™ธ์— ์ถ”๊ฐ€๋กœ ๋ณ€๊ฒฝ์ด ํ•„์š”ํ•œ ์˜ต์…˜์ด ์žˆ์œผ๋ฉด ์‚ฌ์šฉํ•œ๋‹ค.

    ex)

    ๋ฌธ์ž์—ด์˜ ๊ธฐ๋ณธ๊ฐ’์ธ VARCHAR(255) ์—์„œ ์‚ฌ์ด์ฆˆ๋ฅผ 500์œผ๋กœ ๋Š˜๋ฆฌ๊ฑฐ๋‚˜,

    TEXT๋กœ ํƒ€์ž…์„ ๋ณ€๊ฒฝํ•˜๊ณ  ์‹ถ์€ ๊ฒฝ์šฐ์— ์‚ฌ์šฉ๋จ.

๐Ÿฅ ์ฐธ๊ณ 

Entity ํด๋ž˜์Šค์—์„œ๋Š” ์ ˆ๋Œ€ Setter ๋ฉ”์†Œ๋“œ๋ฅผ ๋งŒ๋“ค์ง€ ์•Š๋Š”๋‹ค ! !

โ€‹ ๐Ÿ™‹โ€โ™€๏ธ ์™œ ?

โ€‹ ํ•ด๋‹น ํด๋ž˜์Šค์˜ ์ธ์Šคํ„ด์Šค ๊ฐ’๋“ค์ด ์–ธ์ œ ์–ด๋””์„œ ๋ณ€ํ•ด์•ผํ•˜๋Š”์ง€ ์ฝ”๋“œ์ƒ์œผ๋กœ ๋ช…ํ™•ํ•˜๊ฒŒ ๊ตฌ๋ถ„ํ•  ์ˆ˜ ์—†์–ด, ์ฐจํ›„ ๊ธฐ๋Šฅ ๋ณ€๊ฒฝ ์‹œ ์ •๋ง ๋ณต์žกํ•ด์ง€๊ธฐ ๋•Œ ! ๋ฌธ !

๋Œ€์‹ , ํ•ด๋‹น ํ•„๋“œ์˜ ๊ฐ’ ๋ณ€๊ฒฝ์ด ํ•„์š”ํ•˜๋ฉด ๋ช…ํ™•ํžˆ ๊ทธ ๋ชฉ์ ๊ณผ ์˜๋„๋ฅผ ๋‚˜ํƒ€๋‚ผ ์ˆ˜ ์žˆ๋Š” ๋ฉ”์†Œ๋“œ๋ฅผ ์ถ”๊ฐ€ํ•ด์•ผ๋งŒ ํ•œ๋‹ค.

ex)

โŒ ์ž˜๋ชป๋œ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•

public class Order{
	public void setStatus(boolean status){
		this.status = status;
	}
}
public void ์ฃผ๋ฌธ_์ทจ์†Œevent(){
	order.setStatus(false);
}

โญ• ์˜ฌ๋ฐ”๋ฅธ ์‚ฌ์šฉ ๋ฐฉ๋ฒ•

public class Order{
	public void cancelOrder(){
		this.status = false;
	}
}
public void ์ฃผ๋ฌธ_์ทจ์†Œevent(){
	order.cancelOrder();
}

๐Ÿ™‹โ€โ™€๏ธ Setter๊ฐ€ ์—†๋Š” ์ƒํ™ฉ์—์„œ ๊ฐ’์„ ์ฑ„์›Œ DB์— ๋„ฃ๋Š” ๋ฐฉ๋ฒ•์€ ?

์ƒ์„ฑ์ž๋ฅผ ํ†ตํ•ด ์ตœ์ข…๊ฐ’์„ ์ฑ„์šด ํ›„ DB์— ์‚ฝ์ž… !

๊ฐ’ ๋ณ€๊ฒฝ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ ํ•ด๋‹น ์ด๋ฒคํŠธ์— ๋งž๋Š” public ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์„ ์ „์ œ๋กœ ํ•œ๋‹ค.


๐Ÿ“application.properties ํŒŒ์ผ ์ƒ์„ฑ

src-main-resources-application.properties

  • spring.jpa.show_sql=true

    : ์‹ค์ œ๋กœ ์‹คํ–‰๋œ ์ฟผ๋ฆฌ์˜ ํ˜•ํƒœ๋ฅผ ์ฝ˜์†”์—์„œ ์ฟผ๋ฆฌ๋กœ๊ทธ๋กœ ํ™•์ธ ๊ฐ€๋Šฅํ•˜๊ฒŒ ํ•ด์ค€๋‹ค.

    Hibernate: create table posts (id bigint generated by default as identity, author varchar(255), content TEXT not null, title varchar(500) not null, primary key (id))
    
  • spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect

    : ์ถœ๋ ฅ๋˜๋Š” ์ฟผ๋ฆฌ ๋กœ๊ทธ๋ฅผ MySQL ๋ฒ„์ „์œผ๋กœ ๋ณ€๊ฒฝ

    Hibernate: create table posts (id bigint not null auto_increment, author varchar(255), content TEXT not null, title varchar(500) not null, primary key (id)) engine=InnoDB
    
  • spring.h2.console.enabled=true

    main ์‹คํ–‰ ํ›„,

    http://localhost:8080/h2-console ๋กœ ์ ‘์†

    JDBC URL : jdbc:h2:mem:testdb

    connect ํ›„ ํ…Œ์ด๋ธ” ์กฐํšŒ ๊ฐ€๋Šฅ


๐Ÿ“Spring ์›น ๊ณ„์ธต

  • Web Layer

    : ํ”ํžˆ ์‚ฌ์šฉ๋˜๋Š” ์ปจํŠธ๋กค๋Ÿฌ(@Controller)์™€ JSP/Freemarker ๋“ฑ์˜ ๋ทฐ ํ…œํ”Œ๋ฆฟ ์˜์—ญ

    ์ด์™ธ์—๋„ ํ•„ํ„ฐ(@Filter), ์ธํ„ฐ์…‰ํ„ฐ, ์ปจํŠธ๋กค๋Ÿฌ ์–ด๋“œ๋ฐ”์ด์Šค(@ControllerAdvice)๋“ฑ ์™ธ๋ถ€ ์š”์ฒญ๊ณผ ์‘๋‹ต์— ๋Œ€ํ•œ ์ „๋ฐ˜์ ์ธ ์˜์—ญ

  • Service Layer

    : @Service์— ์‚ฌ์šฉ๋˜๋Š” ์„œ๋น„์Šค ์˜์—ญ

    ์ผ๋ฐ˜์ ์œผ๋กœ Controller์™€ Dao์˜ ์ค‘๊ฐ„ ์˜์—ญ์—์„œ ์‚ฌ์šฉ๋œ๋‹ค

    @Transactional์ด ์‚ฌ์šฉ๋˜์–ด์•ผ ํ•˜๋Š” ์˜์—ญ

    โœ ์ฃผ์˜ โœ

    "Service์—์„œ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ฒ˜๋ฆฌํ•ด์•ผํ•œ๋‹ค." ๋Š” ์˜ค ! ํ•ด ! ์ •๋ง?๐Ÿ˜ต

    ๐Ÿ‘‰ Service๋Š” ํŠธ๋žœ์žญ์…˜, ๋„๋ฉ”์ธ ๊ฐ„ ์ˆœ์„œ ๋ณด์žฅ์˜ ์—ญํ• ๋งŒ ํ•œ๋‹ค !

  • Repository Layer

    : DB์™€ ๊ฐ™์ด ๋ฐ์ดํ„ฐ ์ €์žฅ์†Œ์— ์ ‘๊ทผํ•˜๋Š” ์˜์—ญ

    Dao(Data Access Object) ์˜์—ญ

  • Dtos

    : ๊ณ„์ธต ๊ฐ„์— ๋ฐ์ดํ„ฐ ๊ตํ™˜์„ ์œ„ํ•œ ๊ฐ์ฒด

    ๋ทฐ ํ…œํ”Œ๋ฆฟ ์—”์ง„์—์„œ ์‚ฌ์šฉ๋  ๊ฐ์ฒด๋‚˜ Repository Layer์—์„œ ๊ฒฐ๊ณผ๋กœ ๋„˜๊ฒจ์ค€ ๊ฐ์ฒด ๋“ฑ

  • Domain Model

    : ๋„๋ฉ”์ธ์ด๋ผ ๋ถˆ๋ฆฌ๋Š” ๊ฐœ๋ฐœ ๋Œ€์ƒ์„ ๋ชจ๋“  ์‚ฌ๋žŒ์ด ๋™์ผํ•œ ๊ด€์ ์—์„œ ์ดํ•ดํ•  ์ˆ˜ ์žˆ๊ณ  ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋‹จ์ˆœํ™”์‹œํ‚จ ๊ฒƒ

    ex) ํƒ์‹œ ์•ฑ์˜ ๋ฐฐ์ฐจ, ํƒ‘์Šน, ์š”๊ธˆ ๋“ฑ์ด ๋ชจ๋‘ ๋„๋ฉ”์ธ

    @Entity๊ฐ€ ์‚ฌ์šฉ๋œ ์˜์—ญ ์—ญ์‹œ ๋„๋ฉ”์ธ ๋ชจ๋ธ์ด๋‹ค.

    ๋‹ค๋งŒ, ๋ฌด์กฐ๊ฑด DB ํ…Œ์ด๋ธ”๊ณผ ๊ด€๊ณ„๊นŒ ์žˆ์–ด์•ผ ํ•˜๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค. VO์ฒ˜๋Ÿผ ๊ฐ’ ๊ฐ์ฒด๋“ค๋„ ์ด ์˜์—ญ์— ํ•ด๋‹นํ•˜๊ธฐ ๋•Œ๋ฌธ !

๐Ÿ™‹โ€โ™€๏ธ 5๊ฐ€์ง€ ๋ ˆ์ด์–ด ์ค‘ ๋น„์ฆˆ๋‹ˆ์Šค ์ฒ˜๋ฆฌ๋ฅผ ๋‹ด๋‹นํ•ด์•ผ ํ•  ๊ณณ์€ ? Domain


๐Ÿ“์Šคํ”„๋ง์—์„œ Bean์„ ์ฃผ์ž…๋ฐ›๋Š” ๋ฐฉ์‹

  • @Autowired
  • setter
  • ์ƒ์„ฑ์ž

๐Ÿ™‹โ€โ™€๏ธ ์ด ์ค‘ ๊ฐ€์žฅ ๊ถŒ์žฅํ•˜๋Š” ๋ฐฉ์‹์€ ?

์ƒ์„ฑ์ž๋กœ ์ฃผ์ž…๋ฐ›๋Š” ๋ฐฉ์‹ (@Autowired๋Š” ๊ถŒ์žฅํ•˜์ง€ ์•Š๋Š”๋‹ค)

๋กฌ๋ณต์˜ @RequiredArgsConstructor์ด final์ด ์„ ์–ธ๋œ ๋ชจ๋“  ํ•„๋“œ๋ฅผ ์ธ์ž๊ฐ’์œผ๋กœ ํ•˜๋Š” ์ƒ์ƒ์ž๋ฅผ ์ƒ์„ฑํ•ด์คŒ

๐Ÿ™‹โ€โ™€๏ธ ์™œ ์ƒ์„ฑ์ž๋ฅผ ์ง์ ‘ ์•ˆ ์“ฐ๊ณ  ๋กฌ๋ณต ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•˜๋‚˜ ?

ํ•ด๋‹น ํด๋ž˜์Šค์˜ ์˜์กด์„ฑ ๊ด€๊ณ„๊ฐ€ ๋ณ€๊ฒฝ๋  ๋•Œ๋งˆ๋‹ค

์ƒ์„ฑ์ž ์ฝ”๋“œ๋ฅผ ๊ณ„์†ํ•ด์„œ ์ˆ˜์ •ํ•˜๋Š” ๋ฒˆ๊ฑฐ๋กœ์›€ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•จ

์ฆ‰, ํ•ด๋‹น ์ปจํŠธ๋กค๋Ÿฌ์— ์ƒˆ๋กœ์šด ์„œ๋น„์Šค๋ฅผ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜, ๊ธฐ์กด ์ปดํฌ๋„ŒํŠธ๋ฅผ ์ œ๊ฑฐํ•˜๋Š” ๋“ฑ์˜ ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•ด๋„ ์ƒ์„ฑ์ž ์ฝ”๋“œ๋Š” ์ „ํ˜€ ์†๋Œ€์ง€ ์•Š์•„๋„ ๋œ๋‹ค. ํŽธ๋ฆฌํ•˜๋‹ค !


๐Ÿ“API ๋งŒ๋“ค๊ธฐ

API๋ฅผ ๋งŒ๋“ค๊ธฐ ์œ„ํ•ด ์ด 3๊ฐœ์˜ ํด๋ž˜์Šค๊ฐ€ ํ•„์š”ํ•˜๋‹ค

  • Request ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›์„ Dto
  • API ์š”์ฒญ์„ ๋ฐ›์„ Controller
  • ํŠธ๋žœ์žญ์…˜, ๋„๋ฉ”์ธ ๊ธฐ๋Šฅ ๊ฐ„์˜ ์ˆœ์„œ๋ฅผ ๋ณด์žฅํ•˜๋Š” Service


โœ ๋“ฑ๋ก POST

web - PostsApiController

@RequiredArgsConstructor
@RestController
public class PostsApiController {
    private final PostsService postsService;

    @PostMapping("/api/v1/posts")
    public Long save(@RequestBody PostsSaveRequestDto requestDto){
        return postsService.save(requestDto);
    }
}

service - PostsService

@RequiredArgsConstructor
@Service
public class PostsService {
    private final PostsRepository postsRepository;

    @Transactional
    public Long save(PostsSaveRequestDto requestDto) {
        return postsRepository.save(requestDto.toEntity()).getId();
    }
}

web.dto - PostsSaveRequestDto

@Getter
@NoArgsConstructor
public class PostsSaveRequestDto {
    private String title;
    private String content;
    private String author;

    @Builder
    public PostsSaveRequestDto(String title, String content, String author){
        this.title = title;
        this.content = content;
        this.author = author;
    }

    public Posts toEntity(){
        return Posts.builder()
                .title(title)
                .content(content)
                .author(author)
                .build();
    }
}

๐Ÿ™‹โ€โ™€๏ธ Entity ํด๋ž˜์Šค์™€ ๊ฑฐ์˜ ์œ ์‚ฌํ•œ ํ˜•ํƒœ์ž„์—๋„ Dtoํด๋ž˜์Šค๋ฅผ ์ถ”๊ฐ€๋กœ ์ƒ์„ฑํ•˜๋Š” ์ด์œ ๋Š” ?

Request์™€ Response์šฉ Dto๋Š” View๋ฅผ ์œ„ํ•œ ํด๋ž˜์Šค์ด๊ธฐ ๋•Œ๋ฌธ์— ์ž์ฃผ ๋ณ€๊ฒฝ๋œ๋‹ค.

Entity ํด๋ž˜์Šค๋Š” DB์™€ ๋งž๋‹ฟ์€ ํ•ต์‹ฌ ํด๋ž˜์Šค๋กœ Entity ํด๋ž˜์Šค๋ฅผ ๊ธฐ์ค€์œผ๋กœ ํ…Œ์ด๋ธ”์ด ์ƒ์„ฑ๋˜๊ณ , ์Šคํ‚ค๋งˆ๊ฐ€ ๋ณ€๊ฒฝ๋œ๋‹ค.

์ฆ‰, ํ™”๋ฉด๋ณ€๊ฒฝ์„ ์‚ฌ์†Œํ•œ ๊ธฐ๋Šฅ ๋ณ€๊ฒฝ์ธ๋ฐ, ์ด๋ฅผ ์œ„ํ•ด ํ…Œ์ด๋ธ”๊ณผ ์—ฐ๊ฒฐ๋œ Entity ํด๋ž˜์Šค๋ฅผ ๋ณ€๊ฒฝํ•˜๋Š” ๊ฒƒ์€ ๋„ˆ๋ฌด ํฐ ๋ณ€๊ฒฝ์ด๋‹ค.

๋”ฐ๋ผ์„œ, View Layer์™€ DB Layer์˜ ์—ญํ•  ๋ถ„๋ฆฌ๋ฅผ ์ฒ ์ €ํ•˜๊ฒŒ ํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.


test - web - PostsApiControllerTest

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class PostsApiControllerTest {
    @LocalServerPort
    private int port;

    @Autowired
    private TestRestTemplate restTemplate;

    @Autowired
    private PostsRepository postsRepository;

    @After
    public void tearDown() throws Exception{
        postsRepository.deleteAll();
    }

    @Test
    public void Posts_๋“ฑ๋ก๋œ๋‹ค() throws Exception{
        //given
        String title = "title";
        String content = "content";
        PostsSaveRequestDto requestDto = PostsSaveRequestDto.builder()
                .title(title)
                .content(content)
                .author("author")
                .build();

        String url = "http://localhost:" + port + "/api/v1/posts";

        //when
        ResponseEntity<Long> responseEntity = restTemplate.postForEntity(url, requestDto, Long.class);

        //then
        assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(responseEntity.getBody()).isGreaterThan(0L);

        List<Posts> all = postsRepository.findAll();
        assertThat(all.get(0).getTitle()).isEqualTo(title);
        assertThat(all.get(0).getContent()).isEqualTo(content);
    }
}
  • @SpringBootTest, TestRestTemplate

    JPA ๊ธฐ๋Šฅ๊นŒ์ง€ ํ•œ ๋ฒˆ์— ํ…Œ์ŠคํŠธํ•  ๋•Œ ์‚ฌ์šฉ

    (@WebMvcTest์˜ ๊ฒฝ์šฐ, JPA๊ธฐ๋Šฅ์ด ์ž‘๋™ํ•˜์ง€ ์•Š์œผ๋ฉฐ, controller์™€ ControllerAdvice ๋“ฑ ์™ธ๋ถ€ ์—ฐ๋™๊ณผ ๊ด€๋ จ๋œ ๋ถ€๋ถ„๋งŒ ํ™œ์„ฑํ™”๋œ๋‹ค! ๊ทธ๋Ÿฌ๋‹ˆ ์—ฌ๊ธฐ์„œ๋Š” JPA๋ฅผ ํ…Œ์ŠคํŠธํ•  ๊ฒƒ์ด๊ธฐ ๋•Œ๋ฌธ์— ์‚ฌ์šฉ ์•ˆํ•จ!)



โœ ์ˆ˜์ • UPDATE

web - PostsApiController

@RequiredArgsConstructor
@RestController
public class PostsApiController {
    private final PostsService postsService;

    ...
    
    @PutMapping("/api/v1/posts/{id}")
    public Long update(@PathVariable Long id, @RequestBody PostsUpdateRequestDto requestDto){
        return postsService.update(id, requestDto);
    }
}

service - PostsService

@RequiredArgsConstructor
@Service
public class PostsService {
    private final PostsRepository postsRepository;
	
	...
    
    @Transactional
    public Long update(Long id, PostsUpdateRequestDto requestDto) {
        Posts posts = postsRepository.findById(id)
                .orElseThrow(()->
                        new IllegalArgumentException("ํ•ด๋‹น ๊ฒŒ์‹œ๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค. id = "+ id));
        posts.update(requestDto.getTitle(), requestDto.getContent());
        return id;
    }
}

web.dto - PostsUpdateRequestDto

@Getter
@NoArgsConstructor
public class PostsUpdateRequestDto {
    private String title;
    private String content;

    @Builder
    public PostsUpdateRequestDto(String title, String content){
        this.title = title;
        this.content = content;
    }
}

domain - posts - Posts

@Getter
@NoArgsConstructor
@Entity
public class Posts {

    ...
    
    public void update(String title, String content){
        this.title = title;
        this.content = content;
    }

}
๐Ÿฅ ๋”ํ‹ฐ์ฒดํ‚น

update ๊ธฐ๋Šฅ์—์„œ๋Š” ์ฟผ๋ฆฌ๋ฅผ ๋‚ ๋ฆฌ๋Š” ๋ถ€๋ถ„์ด ์—†๋‹ค ?! !? ?!

์—”ํ‹ฐํ‹ฐ๋ฅผ ์˜๊ตฌ ์ €์žฅํ•˜๋Š” ํ™˜๊ฒฝ์ธ JPA์˜ ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ ๋•Œ๋ฌธ์— ๊ฐ€๋Šฅ ! WOW

JPA์˜ ์—”ํ‹ฐํ‹ฐ ๋งค๋‹ˆ์ €๊ฐ€ ํ™œ์„ฑํ™”๋œ ์ƒํƒœ๋กœ ํŠธ๋žœ์žญ์…˜ ์•ˆ์—์„œ DB์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋ฉด ์ด ๋ฐ์ดํ„ฐ๋Š” ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ๊ฐ€ ์œ ์ง€๋œ ์ƒํƒœ.

์ด ์ƒํƒœ์—์„œ ํ•ด๋‹น ๋ฐ์ดํ„ฐ์˜ ๊ฐ’์„ ๋ณ€๊ฒฝํ•˜๋ฉด ํŠธ๋žœ์žญ์…˜์ด ๋๋‚˜๋Š” ์‹œ์ ์— ํ•ด๋‹น ํ…Œ์ด๋ธ”์— ๋ณ€๊ฒฝ๋ถ„์„ ๋ฐ˜์˜ํ•œ๋‹ค. ์ฆ‰, Entity ๊ฐ์ฒด์˜ ๊ฐ’๋งŒ ๋ณ€๊ฒฝํ•˜๋ฉด ๋ณ„๋„๋กœ Update ์ฟผ๋ฆฌ๋ฅผ ๋‚ ๋ฆด ํ•„์š”๊ฐ€ ์—†๋‹ค. ์ด ๊ฐœ๋…์„ ๋”ํ‹ฐ ์ฒดํ‚น์ด๋ผ๊ณ  ํ•œ๋‹ค.


test - web - PostsApiControllerTest

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class PostsApiControllerTest {
    @LocalServerPort
    private int port;

    @Autowired
    private TestRestTemplate restTemplate;

    @Autowired
    private PostsRepository postsRepository;

    @After
    public void tearDown() throws Exception{
        postsRepository.deleteAll();
    }

    
    ...
    

    @Test
    public void Posts_์ˆ˜์ •๋œ๋‹ค() throws Exception{
        //given
        Posts savedPosts = postsRepository.save(Posts.builder()
                .title("title")
                .content("content")
                .author("author")
                .build());

        Long updateId = savedPosts.getId();
        String title = "title2";
        String content = "content2";

        PostsUpdateRequestDto requestDto = PostsUpdateRequestDto.builder()
                .title(title)
                .content(content)
                .build();

        String url = "http://localhost:" + port + "/api/v1/posts/" + updateId;

        HttpEntity<PostsUpdateRequestDto> requestEntity  = new HttpEntity<>(requestDto);

        //when
        ResponseEntity<Long> responseEntity
                = restTemplate.exchange(url, HttpMethod.PUT, requestEntity, Long.class);

        //then
        assertThat(responseEntity.getStatusCode()).isEqualTo(HttpStatus.OK);
        assertThat(responseEntity.getBody()).isGreaterThan(0L);

        List<Posts> all = postsRepository.findAll();
        assertThat(all.get(0).getTitle()).isEqualTo(title);
        assertThat(all.get(0).getContent()).isEqualTo(content);
    }
}



โœ ์กฐํšŒ GET

web - PostsApiController

@RequiredArgsConstructor
@RestController
public class PostsApiController {
    private final PostsService postsService;

    ...
    
    @GetMapping("/api/v1/posts/{id}")
    public PostsResponseDto findById (@PathVariable Long id){
        return postsService.findById(id);
    }
}

service - PostsService

@RequiredArgsConstructor
@Service
public class PostsService {
    private final PostsRepository postsRepository;

    ...

    @Transactional(readOnly = true)
    public PostsResponseDto findById(Long id) {
        Posts entity = postsRepository.findById(id)
                .orElseThrow(()->
                        new IllegalArgumentException("ํ•ด๋‹น ๊ฒŒ์‹œ๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค. id = " + id));
        return new PostsResponseDto(entity);
    }
}

web.dto - PostsResponseDto

@Getter
public class PostsResponseDto {
    private Long id;
    private String title;
    private String content;
    private String author;

    public PostsResponseDto (Posts entity){
        this.id = entity.getId();
        this.title = entity.getTitle();
        this.content = entity.getContent();
        this.author = entity.getAuthor();
    }
}

์กฐํšŒํ•˜๊ธฐ

  1. application.properties

    spring.h2.console.enabled=true ์ถ”๊ฐ€

  2. localhost:8080/h2-console

    JDBC URL : jdbc:h2:mem:testdb

    connect

  3. test ๋ฐ์ดํ„ฐ ์‚ฝ์ž…

    INSERT INTO POSTS(author, title, content) VALUES('aonee', 'title', 'content');
    
  4. API ์กฐํšŒ

    http://localhost:8080/api/v1/posts/1

    {"id":1,"title":"title","content":"content","author":"aonee"}
    



โœ ์‚ญ์ œ DELETE

web - PostsApiController

@RequiredArgsConstructor
@RestController
@RequestMapping("/api/v1/posts")
public class PostsApiController {
    private final PostsService postsService;

    ...

    @DeleteMapping("/{id}")
    public Long delete(@PathVariable Long id){
        postsService.delete(id);
        return id;
    }
}

service - PostsService

@RequiredArgsConstructor
@Service
public class PostsService {
    private final PostsRepository postsRepository;

    ...
    
    public void delete(Long id) {
        Posts posts = postsRepository.findById(id)
                .orElseThrow(()->
                        new IllegalArgumentException("ํ•ด๋‹น ๊ฒŒ์‹œ๊ธ€์ด ์—†์Šต๋‹ˆ๋‹ค. id = "+ id));

        postsRepository.delete(posts);
    }
}

๐Ÿ“JPA Auditing์œผ๋กœ ์ƒ์„ฑ์‹œ๊ฐ„/์ˆ˜์ •์‹œ๊ฐ„ ์ž๋™ํ™”

Entity์— ์ƒ์„ฑ์‹œ๊ฐ„๊ณผ ์ˆ˜์ •์‹œ๊ฐ„์€ ์ฐจํ›„ ์œ ์ง€๋ณด์ˆ˜์— ์žˆ์–ด ๊ต‰์žฅํžˆ ์ค‘์š”ํ•œ ์ •๋ณด์ด๊ธฐ ๋•Œ๋ฌธ์— ํ•ด๋‹น ๋‚ด์šฉ์„ ํฌํ•จํ•œ๋‹ค.

๋งค๋ฒˆ DB์— ์‚ฝ์ž…/๊ฐฑ์‹  ์ „ ๋‚ ์งœ ๋ฐ์ดํ„ฐ๋ฅผ ๋“ฑ๋ก/์ˆ˜์ •ํ•˜๋Š” ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š๋„๋ก JPA Auditing ์‚ฌ์šฉํ•  ๊ฒƒ์ด๋‹ค.

domain - BaseTimeEntity

@Getter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseTimeEntity {
    @CreatedDate
    private LocalDateTime createdDate;

    @LastModifiedDate
    private LocalDateTime modifiedDate;
}
  • @MappedSuperclass

    : JPA Entity ํด๋ž˜์Šค๋“ค์ด BaseTimeEntity๋ฅผ ์ƒ์†ํ•  ๊ฒฝ์šฐ, ํ•„๋“œ๋“ค(createdDate, modifiedDate)๋„ ์นผ๋Ÿผ์œผ๋กœ ์ธ์‹ํ•˜๋„๋ก ํ•œ๋‹ค

  • @EntityListeners(AuditingEntityListener.class)

    : BaseTimeEntity ํด๋ž˜์Šค์— Auditing ๊ธฐ๋Šฅ์„ ํฌํ•จ์‹œํ‚จ๋‹ค.

  • @CreatedDate

    : Entity๊ฐ€ ์ƒ์„ฑ๋˜์–ด ์ €์žฅ๋  ๋•Œ ์‹œ๊ฐ„์ด ์ž๋™ ์ €์žฅ๋œ๋‹ค.

  • @LastModifiedDate

    : ์กฐํšŒํ•œ Entity์˜ ๊ฐ’์„ ๋ณ€๊ฒฝํ•  ๋•Œ ์‹œ๊ฐ„์ด ์ž๋™ ์ €์žฅ๋œ๋‹ค.

domain - posts - Posts

public class Posts extends BaseTimeEntity 

BaseTimeEntity ์ƒ์†๋ฐ›๊ธฐ

Application

@EnableJpaAuditing //JPA Auditing ํ™œ์„ฑํ™”

@EnableJpaAuditing ๋กฌ๋ณต ์–ด๋…ธํ…Œ์ด์…˜์„ ๋ถ™์—ฌ์ฃผ์–ด JPA Auditing์„ ํ™œ์„ฑํ™” ์‹œํ‚จ๋‹ค.

test - domain - posts - PostRepositoryTest

@RunWith(SpringRunner.class)
@SpringBootTest
public class PostRepositoryTest {
    @Autowired
    PostsRepository postsRepository;

    @After
    public void cleanup(){
        postsRepository.deleteAll();
    }
    
...

    @Test
    public void BaseTimeEntity_๋“ฑ๋ก(){
       //given
        LocalDateTime now = LocalDateTime.of(2020,7,28,0,0,0);
        postsRepository.save(Posts.builder()
                .title("title")
                .content("content")
                .author("author")
                .build());

        //when
        List<Posts> postsList = postsRepository.findAll();

        //then
        Posts posts = postsList.get(0);

        System.out.println(">>>>>>>>>> createDate = " + posts.getCreatedDate()
        + ", modifiedDate = " + posts.getModifiedDate());

        assertThat(posts.getCreatedDate()).isAfter(now);
        assertThat(posts.getModifiedDate()).isAfter(now);

    }
}




๐Ÿ“Œ feature-11 : ๋จธ์Šคํ…Œ์น˜

๐Ÿ“๋จธ์Šคํ…Œ์น˜๋ž€ ?

JSP์™€ ๊ฐ™์ด HTML์„ ๋งŒ๋“ค์–ด์ฃผ๋Š” ํ…œํ”Œ๋ฆฟ ์—”์ง„

๐Ÿฅ ์ฐธ๊ณ 

  • ํ…œํ”Œ๋ฆฟ ์—”์ง„?

    ์ง€์ •๋œ ํ…œํ”Œ๋ฆฟ ๋ฐ์ดํ„ฐ๋ฅผ ์ด์šฉํ•ด HTML์„ ์ƒ์„ฑํ•˜๋Š” ํ…œํ”Œ๋ฆฟ ์—”์ง„

  • ์„œ๋ฒ„ ํ…œํ”Œ๋ฆฟ ์—”์ง„

    JSP, Freemarker

    ํ™”๋ฉด ์ƒ์„ฑ : ์„œ๋ฒ„์—์„œ Java ์ฝ”๋“œ๋กœ ๋ฌธ์ž์—ด์„ ๋งŒ๋“  ๋’ค ์ด ๋ฌธ์ž์—ด์„ HTML ๋กœ ๋ณ€ํ™˜ํ•˜์—ฌ ๋ธŒ๋ผ์šฐ์ €์— ์ „๋‹ฌ

  • ํด๋ผ์ด์–ธํŠธ ํ…œํ”Œ๋ฆฟ ์—”์ง„

    React, Vue

    SPA(Single Page Application) ๋ธŒ๋ผ์šฐ์ €์—์„œ ํ™”๋ฉด์„ ์ƒ์„ฑํ•œ๋‹ค. ์ฆ‰, ์„œ๋ฒ„์—์„œ ์ด๋ฏธ ์ฝ”๋“œ๊ฐ€ ๋ฒ—์–ด๋‚œ ๊ฒฝ์šฐ

    ์„œ๋ฒ„์—์„œ๋Š” Json or Xml ํ˜•์‹์˜ ๋ฐ์ดํ„ฐ๋งŒ ์ „๋‹ฌํ•˜๊ณ  ํด๋ผ์ด์–ธํŠธ์—์„œ ์กฐ๋ฆฝํ•œ๋‹ค.

    ์ตœ๊ทผ์—๋Š” React, Vue ์™€ ๊ฐ™์€ ์ž๋ฐ”์Šคํฌ๋ฆผํŠธ ํ”„๋ ˆ์ž„์›Œํฌ์—์„œ ์„œ๋ฒ„ ์‚ฌ์ด๋“œ ๋ Œ๋”๋ง์„ ์ง€์›ํ•˜๊ธด ํ•œ๋‹ค.

๐Ÿ“๋จธ์Šคํ…Œ์น˜ ์„ค์น˜

ctrl+shift+A -> 'plugins' -> mustache ๊ฒ€์ƒ‰ ํ›„ ์„ค์น˜

build.gradle์— ์ถ”๊ฐ€

compile('org.springframework.boot:spring-boot-starter-mustache')

๐Ÿ“๋จธ์Šคํ…Œ์น˜๋กœ ํ™”๋ฉด ๊ตฌ์„ฑ

IndexController

@RequiredArgsConstructor
@Controller
public class IndexController {

    private final PostsService postsService;

    @GetMapping("/") //๊ฒฝ๋กœ: ๋จธ์Šคํ…Œ์น˜ ์Šคํƒ€ํ„ฐ๊ฐ€ ์ž๋™ ์ง€์ •ํ•ด์คŒ
    public String index(Model model){
        model.addAttribute("posts", postsService.findAllDesc());

        return "index";
    }

    @GetMapping("/posts/save")
    public String postsSave() {
        return "posts-save";
    }

    @GetMapping("/posts/update/{id}")
    public String postsUpdate(@PathVariable Long id, Model model) {
        PostsResponseDto dto = postsService.findById(id);
        model.addAttribute("post", dto);

        return "posts-update";
    }

๊ฒฝ๋กœ: ๋จธ์Šคํ…Œ์น˜ ์Šคํƒ€ํ„ฐ๊ฐ€ ์ž๋™ ์ง€์ •ํ•ด์คŒ

src/main/resources/templates/index.mustache


PostsRepository

public interface PostsRepository extends JpaRepository<Posts, Long> {
    @Query("SELECT p FROM Posts p ORDER BY p.id DESC")
    List<Posts> findAllDesc();
}

๋ฉ”์ธํ™”๋ฉด์— ์ „์ฒด ๊ธ€ ๋ชฉ๋ก ์กฐํšŒ ์ถ”๊ฐ€


PostsService

@RequiredArgsConstructor
@Service
public class PostsService {
    private final PostsRepository postsRepository;

    
    ...
    
    @Transactional
    public List<PostsListResponseDto> findAllDesc(){
        return postsRepository.findAllDesc().stream()
                .map(PostsListResponseDto::new)
                .collect(Collectors.toList());
    }
}

๋ฉ”์ธํ™”๋ฉด์— ์ „์ฒด ๊ธ€ ๋ชฉ๋ก ์กฐํšŒ ์ถ”๊ฐ€


PostsListResponseDto

@Getter
public class PostsListResponseDto {
    private Long id;
    private String title;
    private String author;
    private LocalDateTime modifiedDate;

    public PostsListResponseDto(Posts entity){
        this.id = entity.getId();
        this.title = entity.getTitle();
        this.author = entity.getAuthor();
        this.modifiedDate = entity.getModifiedDate();
    }
}

๋ฉ”์ธํ™”๋ฉด์— ์ „์ฒด ๊ธ€ ๋ชฉ๋ก ์กฐํšŒ ์ถ”๊ฐ€




๐Ÿ“Œ feature-11 : Oauth

๐Ÿ“Google

RubberDuck


RubberDuck


RubberDuck


๐Ÿ“Naver

RubberDuck


RubberDuck


RubberDuck




๐Ÿ“ ISSUE

RubberDuck

๐Ÿ‘‰ Naver๋Š” ๊ฐœ๋ฐœ์ค‘์ธ ์ƒํƒœ์—์„œ๋Š” ๋“ฑ๋ก๋œ ์•„์ด๋””๋กœ๋งŒ ๋กœ๊ทธ์ธ ๊ฐ€๋Šฅํ•˜๋‹ค. ์ฆ‰, ์™ธ๋ถ€ ์‚ฌ์šฉ์ž๋Š” ๊ฐ€์ž…ํ•˜์ง€ ๋ชปํ•œ๋‹ค. โŒ ์ถœ์‹œ๋ฅผ ํ•ด์•ผ๊ฒ ๋„ค๐Ÿค” ๐Ÿ‘‰ Google์€ ๊ฐœ๋ฐœ์ž ์ด๋ฉ”์ผ ์™ธ์—๋„ ๋กœ๊ทธ์ธ ๊ฐ€๋Šฅํ•˜๋‹ค.โญ•




๐Ÿ“ ๊ตฌํ˜„

application-oauth.properties

# Google
spring.security.oauth2.client.registration.google.client-id=
spring.security.oauth2.client.registration.google.client-secret=
spring.security.oauth2.client.registration.google.scope=profile,email

# Naver
# registration
spring.security.oauth2.client.registration.naver.client-id=
spring.security.oauth2.client.registration.naver.client-secret=
spring.security.oauth2.client.registration.naver.redirect-uri={baseUrl}/{action}/oauth2/code/{registrationId}
spring.security.oauth2.client.registration.naver.authorization-grant-type=authorization_code
spring.security.oauth2.client.registration.naver.scope=name,email,profile_image
spring.security.oauth2.client.registration.naver.client-name=Naver

# provider
spring.security.oauth2.client.provider.naver.authorization-uri=https://nid.naver.com/oauth2.0/authorize
spring.security.oauth2.client.provider.naver.token-uri=https://nid.naver.com/oauth2.0/token
spring.security.oauth2.client.provider.naver.user-info-uri=https://openapi.naver.com/v1/nid/me
spring.security.oauth2.client.provider.naver.user-name-attribute=response

OAuth ๊ตฌํ˜„ํ•œ ํ”„๋กœ์ ํŠธ ๊ตฌ์กฐ

โœ Main

  • domain

    • user
      • Role
      • User
      • UserRepository
  • config

    • auth
      • dto
        • OAuthAttributes
        • SessionUser
      • CustomOAuth2UserService
      • LoginUser
      • LoginUserArgumentReslover
      • SecurityConfig
    • JpaConfig
    • WebConfig




๐Ÿ“Œ feature-19 : AWS

๐Ÿ“EC2

Elastic Compute Cloud AWS์—์„œ ์ œ๊ณตํ•˜๋Š” ์„ฑ๋Šฅ, ์šฉ๋Ÿ‰ ๋“ฑ์„ ์œ ๋™์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ์„œ๋ฒ„

โœ EC2 ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ

  1. ๋ฆฌ์ „ ์„œ์šธ๋กœ ๋ณ€๊ฒฝ

  2. EC2 ์ธ์Šคํ„ด์Šค ์‹œ์ž‘ ๋ฐ ์„ค์ •

  3. EIP ํ• ๋‹น

    ๐Ÿ™‹โ€โ™€๏ธ EIP ๋ž€ ?

    AWS ์˜ ๊ณ ์ • IP๋ฅผ Elastic IP (EIP, ํƒ„๋ ฅ์  IP)๋ผ๊ณ  ํ•œ๋‹ค.

    ์ธ์Šคํ„ด์Šค๋„ ๊ฒฐ๊ตญ ํ•˜๋‚˜์˜ ์„œ๋ฒ„์ด๊ธฐ ๋•Œ๋ฌธ์— IP๊ฐ€ ์กด์žฌํ•œ๋‹ค.
    ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ ์‹œ์— ํ•ญ์ƒ ์ƒˆ IP๋ฅผ ํ• ๋‹นํ•˜๋Š”๋ฐ, ํ•œ ๊ฐ€์ง€ ์กฐ๊ฑด์ด ๋” ์žˆ๋‹ค.
    ๊ฐ™์€ ์ธ์Šคํ„ด์Šค๋ฅผ ์ค‘์ง€ํ•˜๊ณ  ๋‹ค์‹œ ์‹œ์ž‘ํ•  ๋•Œ๋„ ์ƒˆ IP๊ฐ€ ํ• ๋‹น๋œ๋‹ค.
    ์ฆ‰, ์š”๊ธˆ์„ ์•„๋ผ๊ธฐ ์œ„ํ•ด ์ž ๊น ์ธ์Šคํ„ด์Šค๋ฅผ ์ค‘์ง€ํ•˜๊ณ  ๋‹ค์‹œ ์‹œ์ž‘ํ•˜๋ฉด IP๊ฐ€ ๋ณ€๊ฒฝ๋˜๋Š” ๊ฒƒ์ด๋‹ค.
    ์ด๋ ‡๊ฒŒ ๋˜๋ฉด ๋งค๋ฒˆ ์ ‘์†ํ•ด์•ผํ•˜๋Š” IP๊ฐ€ ๋ณ€๊ฒฝ๋ผ์„œ PC์—์„œ ์ ‘๊ทผํ•  ๋•Œ๋งˆ๋‹ค IP์ฃผ์†Œ๋ฅผ ํ™•์ธํ•ด์•ผํ•œ๋‹ค. ๊ต‰์žฅํžˆ ๋ฒˆ๊ฑฐ๋กœ์šฐ๋ฏ€๋กœ ์ธ์Šคํ„ด์Šค์˜ IP๊ฐ€ ๋งค๋ฒˆ ๋ณ€๊ฒฝ๋˜์ง€ ์•Š๊ณ  ๊ณ ์ • IP๋ฅผ ๊ฐ€์ง€๊ฒŒ ํ•ด์•ผํ•œ๋‹ค.

    ๊ทธ๋ž˜์„œ ๊ณ ์ •IP๋ฅผ ํ• ๋‹นํ•  ๊ฒƒ์ด๋‹ค.

    ๐Ÿ‘ฟ ์ฃผ์˜ ํƒ„๋ ฅ์  IP๋Š” ์ƒ์„ฑํ•˜๊ณ  EC2 ์„œ๋ฒ„์— ์—ฐ๊ฒฐํ•˜์ง€ ์•Š์œผ๋ฉด ๋น„์šฉ์ด ๋ฐœ์ƒํ•œ๋‹ค. ์ฆ‰, ์ƒ์„ฑํ•œ ํƒ„๋ ฅ์  IP๋Š” ๋ฌด์กฐ๊ฑด EC2์— ๋ฐ”๋กœ ์—ฐ๊ฒฐํ•ด์•ผ ํ•œ๋‹ค. ๋˜ํ•œ, ๋งŒ์•ฝ ๋”๋Š” ์‚ฌ์šฉํ•  ์ธ์Šคํ„ด์Šค๊ฐ€ ์—†์„ ๋•Œ๋„ ํƒ„๋ ฅ์  IP๋ฅผ ์‚ญ์ œํ•ด์•ผํ•œ๋‹ค.

  4. EC2 ์„œ๋ฒ„์— ์ ‘์†ํ•˜๊ธฐ

  5. ์•„๋งˆ์กด ๋ฆฌ๋ˆ…์Šค 1 ์„œ๋ฒ„ ์ƒ์„ฑ ์‹œ ๊ผญ ํ•ด์•ผ ํ•  ์„ค์ •๋“ค ์ ์šฉ

    • java 8 ์„ค์น˜
    • ํƒ€์ž„์กด ๋ณ€๊ฒฝ
    • ํ˜ธ์ŠคํŠธ๋„ค์ž„ ๋ณ€๊ฒฝ

๐Ÿ“RDS

Relational Database Service ๊ด€๋ฆฌํ˜• ์„œ๋น„์Šค AWS์—์„œ ์ง€์›ํ•˜๋Š” ํด๋ผ์šฐ๋“œ ๊ธฐ๋ฐ˜ ๊ด€๊ณ„ํ˜• DB

ํ•˜๋“œ์›จ์–ด ํ”„๋กœ๋น„์ €๋‹, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„ค์ •, ํŒจ์น˜ ๋ฐ ๋ฐฑ์—…๊ณผ ๊ฐ™์ด ์žฆ์€ ์šด์˜ ์ž‘์—…์„ ์ž๋™ํ™”ํ•˜์—ฌ ๊ฐœ๋ฐœ์ž๊ฐ€ ๊ฐœ๋ฐœ์— ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ๊ฒŒ ์ง€์›ํ•˜๋Š” ์„œ๋น„์Šค ์ถ”๊ฐ€๋กœ ์กฐ์ • ๊ฐ€๋Šฅํ•œ ์šฉ๋Ÿ‰์„ ์ง€์›ํ•˜์—ฌ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ์–‘์˜ ๋ฐ์ดํ„ฐ๊ฐ€ ์Œ“์—ฌ๋„ ๋น„์šฉ๋งŒ ์ถ”๊ฐ€๋กœ ๋‚ด๋ฉด ์ •์ƒ์ ์œผ๋กœ ์„œ๋น„์Šค๊ฐ€ ๊ฐ€๋Šฅํ•œ ์žฅ์ ๋„ ์žˆ๋‹ค.



โœ RDS ์„ ํƒ ์ด์œ 

RDS์˜ ๊ฐ€๊ฒฉ์€ ๋ผ์ด์„ผ์Šค ๋น„์šฉ ์˜ํ–ฅ์„ ๋ฐ›๋Š”๋‹ค.

MySQL, MariaDB, PostgreSQL ์ค‘ MariaDB๋กœ ๊ตฌ์ถ•ํ•  ๊ฒƒ์ด๋‹ค.

๐Ÿ™‹โ€โ™€๏ธ ์™œ ? ๊ฐ€๊ฒฉ : ๋ฌด๋ฃŒ Amazon AUrora ๊ต์ฒด ์šฉ์ดํ•˜๋‹ค. (*Amazon AUrora : AWS์—์„œ MySQL๊ณผ PostgreSQL์„ ํด๋ผ์šฐ๋“œ ๊ธฐ๋ฐ˜์— ๋งž๊ฒŒ ์žฌ๊ตฌ์„ฑํ•œ DB. )

MariaDB์˜ MySQL ๋Œ€๋น„ ์žฅ์ ?

  • ๋™์ผ ํ•˜๋“œ์›จ์–ด ์‚ฌ์–‘์œผ๋กœ MySQL ๋ณด๋‹ค ํ–ฅ์ƒ๋œ ์„ฑ๋Šฅ
  • ์ข€ ๋” ํ™œ์„ฑํ™”๋œ ์ปค๋ฎค๋‹ˆํ‹ฐ
  • ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ
  • ๋‹ค์–‘ํ•œ ์Šคํ† ๋ฆฌ์ง€ ์—”์ง„


โœ RDS ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ

  1. MariaDB ๋กœ ์ƒ์„ฑ
  2. ํŒŒ๋ผ๋ฏธํ„ฐ ๊ทธ๋ฃน ์ƒ์„ฑ




๐Ÿ“Œ feature-21 : ๋ฐฐํฌ

๐Ÿ“ EC2์— ํ”„๋กœ์ ํŠธ clone

sudo yum install git

git --version

mkdir ~/app && mkdir ~/app/step1

cd ~/app/step1

git clone ๊นƒํ—ˆ๋ธŒ http ์ฃผ์†Œ

cd aoneemall

ll

./gradlew test # ์ฝ”๋“œ ์ž˜ ์ˆ˜ํ–‰๋˜๋Š”์ง€ ๊ฒ€์ฆ

git pull #๊ถŒํ•œ์ด ์—†๋‹ค๋ฉด ? chmod +x ./gradlew


๐Ÿ“ ๋ฐฐํฌ ์Šคํฌ๋ฆฝํŠธ ์ž‘์„ฑ

โœ ๋ฐฐํฌ๋ž€ ?

์ž‘์„ฑํ•œ ์ฝ”๋“œ๋ฅผ ์‹ค์ œ ์„œ๋ฒ„์— ๋ฐ˜์˜ํ•˜๋Š” ๊ฒƒ

ํ•ด๋‹น ํ”„๋กœ์ ํŠธ์—์„œ ๋ฐฐํฌ๋Š” ์•„๋ž˜์˜ ๊ณผ์ •์„ ๋ชจ๋‘ ํฌ๊ด„ํ•˜๋Š” ์˜๋ฏธ์ด๋‹ค.

  • git clone ํ˜น์€ git pull์„ ํ†ตํ•ด ์ƒˆ ๋ฒ„์ „์˜ ํ”„๋กœ์ ํŠธ๋ฅผ ๋ฐ›์Œ
  • Gradle ์ด๋‚˜ Maven์„ ํ†ตํ•ด ํ”„๋กœ์ ํŠธ ํ…Œ์ŠคํŠธ์™€ ๋นŒ๋“œ
  • EC2 ์„œ๋ฒ„์—์„œ ํ•ด๋‹น ํ”„๋กœ์ ํŠธ ์‹คํ–‰ ๋ฐ ์žฌ์‹คํ–‰

โœ ์‰˜ ์Šคํฌ๋ฆฝํŠธ ์ž‘์„ฑ

deploy.sh ํŒŒ์ผ ์ƒ์„ฑ


#! /bin/bash

REPOSITORY=/home/ec2-user/app/step1
PROJECT_NAME=aoneemall

cd $REPOSITORY/$PROJECT_NAME/

echo "> Git Pull"

git pull

echo "> Start Building Project"

./gradlew build

echo "> Change Directory to step1"

cd $REPOSITORY

echo "> Copy Build Files"

cp $REPOSITORY/$PROJECT_NAME/build/libs/*.jar $REPOSITORY/

echo "> Check Current Application Pids"

# pgrep : process id๋งŒ ์ถ”์ถœ
# -f : process ์ด๋ฆ„์œผ๋กœ ๊ฒ€์ƒ‰
CURRENT_PID=$(pgrep -f ${PROJECT_NAME}*.jar)

echo "> Current Application Pids : $CURRENT_PID"

if [ -z "$CURRENT_PID" ] ; then
	echo "> ํ˜„์žฌ ๊ตฌ๋™์ค‘์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด ์—†์œผ๋ฏ€๋กœ ์ข…๋ฃŒํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค."

else
	echo "> kill -15 $CURRENT_PID"
	kill -15 $CURRENT_PID
	sleep 5
fi

echo "> Deploy New Application"

# ์ƒˆ๋กœ ์‹คํ–‰ํ•  jarํŒŒ์ผ ์ฐพ์•„์„œ
# ์—ฌ๋Ÿฌ jarํŒŒ์ผ ์ค‘ ๊ฐ€์žฅ ๋งˆ์ง€๋ง‰(์ตœ์‹ ) ํŒŒ์ผ์„ ๋ณ€์ˆ˜์— ์ €์žฅ
JAR_NAME=$(ls -tr $REPOSITORY/ | grep *.jar | tail -n 1)

echo "> JAR NAME : JAR_NAME"

# nohup์œผ๋กœ ํŒŒ์ผ ์‹คํ–‰
# application-oauth.properties ํŒŒ์ผ ์œ„์น˜ ์„ค์ •
nohup java -jar \
        -Dspring.config.location=classpath:/application.properties,/home/ec2-user/app/application-oauth.properties \
        $REPOSITORY/$JAR_NAME 2>&1 &

๐Ÿ™‹โ€โ™€๏ธ ์‰˜ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ์ด์œ ๋Š” ?

๋ฐฐํฌํ•  ๋•Œ๋งˆ๋‹ค ๊ฐœ๋ฐœ์ž๊ฐ€ ํ•˜๋‚˜ํ•˜๋‚˜ ๋ช…๋ น์–ด๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๊ฒƒ์€ ๋งŽ์€ ๋ถˆํŽธํ•จ์ด ๋”ฐ๋ฅธ๋‹ค.

๊ทธ๋ž˜์„œ ์‰˜ ์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ž‘์„ฑํ•ด ์Šคํฌ๋ฆฝํŠธ๋งŒ ์‹คํ–‰ํ•˜๋ฉด ์ฐจ๋ก€๋กœ ์ง„ํ–‰๋˜๋„๋ก ํ•  ์ˆ˜ ์žˆ๋‹ค.


๐Ÿ“ ์™ธ๋ถ€ Securiy ํŒŒ์ผ ๋“ฑ๋ก

์„œ๋ฒ„์— gitignore๋œ application-oauth.properties ์ถ”๊ฐ€


๐Ÿ“ ์Šคํ”„๋ง ๋ถ€ํŠธ ํ”„๋กœ์ ํŠธ๋กœ RDS ์ ‘๊ทผ

โœ ํ…Œ์ด๋ธ” ์ƒ์„ฑ

H2์—์„œ ์ž๋™ ์ƒ์„ฑํ•ด์ฃผ๋˜ ํ…Œ์ด๋ธ”์„ MarialDB์— ์ง์ ‘ ์ฟผ๋ฆฌ๋ฅผ ์ด์šฉํ•ด ์ƒ์„ฑํ•ด์คŒ


โœ ํ”„๋กœ์ ํŠธ ์„ค์ •

์ž๋ฐ” ํ”„๋กœ์ ํŠธ๊ฐ€ MariaDB์— ์ ‘๊ทผํ•˜๋ ค๋ฉด DB Driver๊ฐ€ ํ•„์š”.

build.gradle์— ์˜์กด์„ฑ ์ถ”๊ฐ€


โœ EC2 ์„ค์ •

DB ์ ‘์† ์ •๋ณด๋Š” ์ค‘์š”ํ•˜๊ฒŒ ๋ณดํ˜ธํ•ด์•ผ ํ•  ์ •๋ณด์ด๋‹ค.

๊ณต๊ฐœ๋˜๋ฉด ์™ธ๋ถ€์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ๋ชจ๋‘ ๊ฐ€์ ธ๊ฐˆ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

ํ”„๋กœ์ ํŠธ ์•ˆ์— ์ ‘์† ์ •๋ณด๋ฅผ ๊ฐ–๊ณ  ์žˆ์œผ๋ฉด ๊นƒํ—ˆ๋ธŒ ๊ฐ™์ด ์˜คํ”ˆ๋œ ๊ณต๊ฐ„์—์„  ๋ˆ„๊ตฌ๋‚˜ ํ•ดํ‚นํ•  ์œ„ํ—˜์ด ์žˆ๋‹ค.

EC2 ์„œ๋ฒ„ ๋‚ด๋ถ€์—์„œ ์ ‘์† ์ •๋ณด๋ฅผ ๊ด€๋ฆฌํ•˜๋„๋ก ์„ค์ •

About

๐Ÿคธโ€โ™€๏ธ SpringBoot, JPA, OAuth, SpringSecurity, AWS, Travis

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published