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

ย 

History

77 Commits
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 
ย 

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