ํ๋ก์ ํธ ๊ฐ๋ฐ ํ๊ฒฝ์ ๋ค์๊ณผ ๊ฐ๋ค.
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
๋ช
๋ น์ด ์คํ
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์ ํ ์คํธ, ๋ก์ปฌ ํ๊ฒฝ์์์ ๊ตฌ๋์์ ์ฌ์ฉํ ์์ !
-
-
web : ์ปจํธ๋กค๋ฌ์ ๊ด๋ จ๋ ํด๋์ค
-
domain : ๋๋ฉ์ธ (์ํํธ์จ์ด์ ๋ํ ์๊ตฌ์ฌํญ or ๋ฌธ์ ์์ญ)
-
Posts
: ๋๋ฉ์ธ ํด๋์ค
-
PostsRepository
: Posts ํด๋์ค๋ก DB๋ฅผ ์ ๊ทผํ๊ฒ ํด์ค JpaRepository
MyBatis ๋ฑ์์ Dao๋ผ๊ณ ๋ถ๋ฆฌ๋ DB Layer ์ ๊ทผ์
JPA์์๋ Repository๋ผ๊ณ ๋ถ๋ฅด๋ฉฐ ์ธํฐํ์ด์ค๋ก ์์ฑํ๋ค.
JpaRepository<Entity ํด๋์ค, PKํ์ > ์ ์์กํ๋ฉด ๊ธฐ๋ณธ์ ์ธ CRUD ๋ฉ์๋๊ฐ ์๋์ผ๋ก ์์ฑ๋๋ค. ex) JpaRepository<Posts, Long>
โ ์ฃผ์ โ
Entity ํด๋์ค์ ๊ธฐ๋ณธ Repository๋ ํจ๊ป ์์นํด์ผ ์ ๋๋ก๋ ์ญํ ์ ํ ์ ์๋ค.
-
build.gralde ์ ์ฝ๋์ ์ญํ ๋ฐ ์์กด์ฑ ์ถ๊ฐ ๋ฐฉ๋ฒ์ ์ค์ค๋ก ํด๋ณด์ !
-
ํ๋ก์ ํธ ์์ฑ
create - Gradle - Java
-
๊ทธ๋ ์ด๋ค ํ๋ก์ ํธ๋ฅผ ์คํ๋ง ๋ถํธ ํ๋ก์ ํธ๋ก ๋ณ๊ฒฝํ๊ธฐ
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')
}
๐ ํ๋ก์ ํธ์ ํ๋ฌ๊ทธ์ธ ์์กด์ฑ ๊ด๋ฆฌ๋ฅผ ์ํ ์ค์
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
-
@RestController
: JSON์ ๋ฐํํ๋ ์ปจํธ๋กค๋ฌ๋ก ๋ง๋ค์ด์ค๋ค
-
@GetMapping
: HTTP Method์ธ Get์ ์์ฒญ์ ๋ฐ์ ์ ์๋ API๋ฅผ ๋ง๋ค์ด์ค๋ค
-
@PostMapping, @PutMapping, @DeleteMapping
-
@RequestParam
: ์ธ๋ถ์์ API๋ก ๋๊ธด ํ๋ผ๋ฏธํฐ๋ฅผ ๊ฐ์ ธ์ค๋ ์ด๋ ธํ ์ด์
ํ๋ผ๋ฏธํฐ์์ name
@RequestParam("name")
์ด๋ ์ด๋ฆ์ผ๋ก ๋๊ธด ๊ฐ์ ๋ฉ์๋ ํ๋ผ๋ฏธํฐ name(String name)
์ ์ ์ฅ
Test Driven Development, ํ ์คํธ ์ฃผ๋ ๊ฐ๋ฐ
-
Red
: ํญ์ ์คํจํ๋ ํ ์คํธ๋ฅผ ๋จผ์ ์์ฑ
-
Green
: ํ ์คํธ๊ฐ ํต๊ณผํ๋ ํ๋ก๋์ ์ฝ๋๋ฅผ ์์ฑ
-
Refactor
: ํ ์คํธ๊ฐ ํต๊ณผํ๋ฉด ํ๋ก๋์ ์ฝ๋๋ฅผ ๋ฆฌํฉํ ๋ง
-
๋น ๋ฅธ ํผ๋๋ฐฑ ๊ฐ๋ฅ
โ TDD ์ฌ์ฉ ์ํ๋ค๋ฉด ?
๋ฒ๊ฑฐ๋กญ๋ค
โ ํ๋ก๊ทธ๋จ(Tomcat)์ ์คํํ๊ณ
โ Postman๊ณผ ๊ฐ์ API ๋๊ตฌ๋ก HTTP ์์ฒญํ๊ณ
โ System.out.println()์ผ๋ก ๋์ผ๋ก ๊ฒ์ฆํ๊ณ ๊ฒฐ๊ณผ๊ฐ ๋ค๋ฅด๋ฉด
โ ๊ฒฐ๊ณผ๊ฐ ๋ค๋ฅด๋ฉด ๋ค์ ํ๋ก๊ทธ๋จ(Tomcat)์ ์ค์งํ๊ณ ์ฝ๋ ์์
-
์๋๊ฒ์ฆ ๊ฐ๋ฅ
-
๊ฐ๋ฐ์๊ฐ ๋ง๋ ๊ธฐ๋ฅ์ ์์ ํ๊ฒ ๋ณดํธ
EX)
๊ธฐ์กด์ ์๋๋ A๊ธฐ๋ฅ์ด B๊ธฐ๋ฅ์ ์ถ๊ฐํ๊ณ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค.
์๋ก์ด ๊ธฐ๋ฅ์ด ์ถ๊ฐ๋ ๋, ๊ธฐ์กด ๊ธฐ๋ฅ์ด ์ ์๋๋๋ ๊ฒ์ ๋ณด์ฅํด์ฃผ๋ ๊ฒ์ด ํ ์คํธ ์ฝ๋์ด๋ค.
A๋ผ๋ ๊ธฐ์กด ๊ธฐ๋ฅ์ ๊ธฐ๋ณธ ๊ธฐ๋ฅ์ ๋น๋กฏํด ์ฌ๋ฌ ๊ฒฝ์ฐ๋ฅผ ๋ชจ๋ ํ ์คํธ ์ฝ๋๋ก ๊ตฌํํด ๋์๋ค๋ฉด ํ ์คํธ ์ฝ๋๋ฅผ ์ํ๋ง ํ๋ฉด ์กฐ๊ธฐ์ ๋ฌธ์ ๋ฅผ ์ฐพ์ ์ ์๋ค.
(์๋น์ค์ ๋ชจ๋ ๊ธฐ๋ฅ์ ํ ์คํธํ๊ธฐ ์ํด์๋ ๋๋ฌด๋ ๋ง์ ์์์ด ๋๋๋ฐ ์ด๋ฅผ ๋ฐฉ์งํ ์ ์๋ค โ)
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์ ์๋ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ๋ ๋ฉ์๋
-
@Getter
์ ์ธ๋ ๋ชจ๋ ํ๋์ get ๋ฉ์๋๋ฅผ ์์ฑํด์ค๋ค
-
@RequireArgsConstructor
์ ์ธ๋ ๋ชจ๋ final ํ๋๊ฐ ํฌํจ๋ ์์ฑ์๋ฅผ ์์ฑํด์ค
final์ด ์๋ ํ๋๋ ์์ฑ์์ ํฌํจ๋์ง ์๋๋ค.
-
@NoArgsConstructor
: ๊ธฐ๋ณธ ์์ฑ์ ์๋ ์ถ๊ฐ
ex)
public Posts() {} ์ ๊ฐ์ ํจ๊ณผ
-
@Builder
: ํด๋น ํด๋์ค์ ๋น๋ ํจํด ํด๋์ค๋ฅผ ์์ฑ
์์ฑ์ ์๋จ์ ์ ์ธ ์, ์์ฑ์์ ํฌํจ๋ ํ๋๋ง ๋น๋์ ํฌํจ
๐ ์๋น์ค ์ด๊ธฐ ๊ตฌ์ถ ๋จ๊ณ์์ ๋น๋ฒํ๊ฒ ํ ์ด๋ธ ์ค๊ณ๊ฐ ๋ณ๊ฒฝ๋๋๋ฐ,
โ ๋กฌ๋ณต์ ์ด๋ ธํ ์ด์ ๋ค์ ์ฝ๋ ๋ณ๊ฒฝ๋์ ์ต์ํ์์ผ ์ฃผ๊ธฐ ๋๋ฌธ์ ์ ๊ทน์ ์ผ๋ก ์ฌ์ฉํ๋ค !
๊ฐ์ฒด์งํฅ์ ์ผ๋ก ํ๋ก๊ทธ๋๋ฐ์ ํ๊ณ , JPA๊ฐ ์ด๋ฅผ RDBMS์ ๋ง๊ฒ SQL์ ๋์ ์์ฑํด์ ์คํํ๋ค.
์ฆ, JPA๋ ์ค๊ฐ์์ ํจ๋ฌ๋ค์์ ์ผ์น์์ผ์ฃผ๋ ๊ธฐ์ ์ด๋ค.
๐โโ๏ธ ๋ฌด์จ ์๋ฆฌ๋๊ตฌ ?
RDBMS์ ๊ฐ์ฒด์งํฅ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด์ ํจ๋ฌ๋ค์์ ์๋ก ๋ฌ๋ผ ๋ฌธ์ ๊ฐ ๋ฐ์ํ๋ค. (RDBMS์ ์๋ ์์์ ๊ฐ๋ ๋ ํด๊ฒฐํด์ค !)
๊ทธ๋ฐ๋ฐ! ! ! JPA๊ฐ ์ด๋ฅผ ํด๊ฒฐํด์ค๋ค. WOW
๐ฅ ์ฐธ๊ณ
RDBMS ํจ๋ฌ๋ค์
: ์ด๋ป๊ฒ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ ์ง ์ด์ ์ด ๋ง์ถฐ์ง ๊ธฐ์
๊ฐ์ฒด์งํฅ ํ๋ก๊ทธ๋๋ฐ ์ธ์ด ํจ๋ฌ๋ค์
: ๊ฐ์ฒด๋ ๊ธฐ๋ฅ๊ณผ ์์ฑ์ ํ ๊ณณ์ ๊ด๋ฆฌํ๋ ๊ธฐ์
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๋ฅผ ๊ถ์ฅํ๋ค.
-
@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 ๋ฉ์๋๋ฅผ ํธ์ถํ์ฌ ๋ณ๊ฒฝํ๋ ๊ฒ์ ์ ์ ๋ก ํ๋ค.
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 ํ ํ ์ด๋ธ ์กฐํ ๊ฐ๋ฅ
-
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
- @Autowired
- setter
- ์์ฑ์
๐โโ๏ธ ์ด ์ค ๊ฐ์ฅ ๊ถ์ฅํ๋ ๋ฐฉ์์ ?
์์ฑ์๋ก ์ฃผ์ ๋ฐ๋ ๋ฐฉ์ (@Autowired๋ ๊ถ์ฅํ์ง ์๋๋ค)
๋กฌ๋ณต์ @RequiredArgsConstructor์ด final์ด ์ ์ธ๋ ๋ชจ๋ ํ๋๋ฅผ ์ธ์๊ฐ์ผ๋ก ํ๋ ์์์๋ฅผ ์์ฑํด์ค
๐โโ๏ธ ์ ์์ฑ์๋ฅผ ์ง์ ์ ์ฐ๊ณ ๋กฌ๋ณต ์ด๋ ธํ ์ด์ ์ ์ฌ์ฉํ๋ ?
ํด๋น ํด๋์ค์ ์์กด์ฑ ๊ด๊ณ๊ฐ ๋ณ๊ฒฝ๋ ๋๋ง๋ค
์์ฑ์ ์ฝ๋๋ฅผ ๊ณ์ํด์ ์์ ํ๋ ๋ฒ๊ฑฐ๋ก์ ํด๊ฒฐํ๊ธฐ ์ํจ
์ฆ, ํด๋น ์ปจํธ๋กค๋ฌ์ ์๋ก์ด ์๋น์ค๋ฅผ ์ถ๊ฐํ๊ฑฐ๋, ๊ธฐ์กด ์ปดํฌ๋ํธ๋ฅผ ์ ๊ฑฐํ๋ ๋ฑ์ ์ํฉ์ด ๋ฐ์ํด๋ ์์ฑ์ ์ฝ๋๋ ์ ํ ์๋์ง ์์๋ ๋๋ค. ํธ๋ฆฌํ๋ค !
API๋ฅผ ๋ง๋ค๊ธฐ ์ํด ์ด 3๊ฐ์ ํด๋์ค๊ฐ ํ์ํ๋ค
- Request ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ Dto
- API ์์ฒญ์ ๋ฐ์ Controller
- ํธ๋์ญ์ , ๋๋ฉ์ธ ๊ธฐ๋ฅ ๊ฐ์ ์์๋ฅผ ๋ณด์ฅํ๋ Service
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๋ฅผ ํ ์คํธํ ๊ฒ์ด๊ธฐ ๋๋ฌธ์ ์ฌ์ฉ ์ํจ!)
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);
}
}
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();
}
}
์กฐํํ๊ธฐ
-
application.properties
spring.h2.console.enabled=true ์ถ๊ฐ
-
localhost:8080/h2-console
JDBC URL : jdbc:h2:mem:testdb
connect
-
test ๋ฐ์ดํฐ ์ฝ์
INSERT INTO POSTS(author, title, content) VALUES('aonee', 'title', 'content');
-
API ์กฐํ
http://localhost:8080/api/v1/posts/1
{"id":1,"title":"title","content":"content","author":"aonee"}
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);
}
}
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);
}
}
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();
}
}
๋ฉ์ธํ๋ฉด์ ์ ์ฒด ๊ธ ๋ชฉ๋ก ์กฐํ ์ถ๊ฐ
๐ 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 ๊ตฌํํ ํ๋ก์ ํธ ๊ตฌ์กฐ
-
domain
- user
- Role
- User
- UserRepository
- user
-
config
- auth
- dto
- OAuthAttributes
- SessionUser
- CustomOAuth2UserService
- LoginUser
- LoginUserArgumentReslover
- SecurityConfig
- dto
- JpaConfig
- WebConfig
- auth
Elastic Compute Cloud AWS์์ ์ ๊ณตํ๋ ์ฑ๋ฅ, ์ฉ๋ ๋ฑ์ ์ ๋์ ์ผ๋ก ์ฌ์ฉํ ์ ์๋ ์๋ฒ
-
๋ฆฌ์ ์์ธ๋ก ๋ณ๊ฒฝ
-
EC2 ์ธ์คํด์ค ์์ ๋ฐ ์ค์
-
EIP ํ ๋น
๐โโ๏ธ EIP ๋ ?
AWS ์ ๊ณ ์ IP๋ฅผ Elastic IP (EIP, ํ๋ ฅ์ IP)๋ผ๊ณ ํ๋ค.
์ธ์คํด์ค๋ ๊ฒฐ๊ตญ ํ๋์ ์๋ฒ์ด๊ธฐ ๋๋ฌธ์ IP๊ฐ ์กด์ฌํ๋ค.
์ธ์คํด์ค ์์ฑ ์์ ํญ์ ์ IP๋ฅผ ํ ๋นํ๋๋ฐ, ํ ๊ฐ์ง ์กฐ๊ฑด์ด ๋ ์๋ค.
๊ฐ์ ์ธ์คํด์ค๋ฅผ ์ค์งํ๊ณ ๋ค์ ์์ํ ๋๋ ์ IP๊ฐ ํ ๋น๋๋ค.
์ฆ, ์๊ธ์ ์๋ผ๊ธฐ ์ํด ์ ๊น ์ธ์คํด์ค๋ฅผ ์ค์งํ๊ณ ๋ค์ ์์ํ๋ฉด IP๊ฐ ๋ณ๊ฒฝ๋๋ ๊ฒ์ด๋ค.
์ด๋ ๊ฒ ๋๋ฉด ๋งค๋ฒ ์ ์ํด์ผํ๋ IP๊ฐ ๋ณ๊ฒฝ๋ผ์ PC์์ ์ ๊ทผํ ๋๋ง๋ค IP์ฃผ์๋ฅผ ํ์ธํด์ผํ๋ค. ๊ต์ฅํ ๋ฒ๊ฑฐ๋ก์ฐ๋ฏ๋ก ์ธ์คํด์ค์ IP๊ฐ ๋งค๋ฒ ๋ณ๊ฒฝ๋์ง ์๊ณ ๊ณ ์ IP๋ฅผ ๊ฐ์ง๊ฒ ํด์ผํ๋ค.๊ทธ๋์ ๊ณ ์ IP๋ฅผ ํ ๋นํ ๊ฒ์ด๋ค.
๐ฟ ์ฃผ์ ํ๋ ฅ์ IP๋ ์์ฑํ๊ณ EC2 ์๋ฒ์ ์ฐ๊ฒฐํ์ง ์์ผ๋ฉด ๋น์ฉ์ด ๋ฐ์ํ๋ค. ์ฆ, ์์ฑํ ํ๋ ฅ์ IP๋ ๋ฌด์กฐ๊ฑด EC2์ ๋ฐ๋ก ์ฐ๊ฒฐํด์ผ ํ๋ค. ๋ํ, ๋ง์ฝ ๋๋ ์ฌ์ฉํ ์ธ์คํด์ค๊ฐ ์์ ๋๋ ํ๋ ฅ์ IP๋ฅผ ์ญ์ ํด์ผํ๋ค.
-
EC2 ์๋ฒ์ ์ ์ํ๊ธฐ
-
์๋ง์กด ๋ฆฌ๋ ์ค 1 ์๋ฒ ์์ฑ ์ ๊ผญ ํด์ผ ํ ์ค์ ๋ค ์ ์ฉ
- java 8 ์ค์น
- ํ์์กด ๋ณ๊ฒฝ
- ํธ์คํธ๋ค์ ๋ณ๊ฒฝ
Relational Database Service ๊ด๋ฆฌํ ์๋น์ค AWS์์ ์ง์ํ๋ ํด๋ผ์ฐ๋ ๊ธฐ๋ฐ ๊ด๊ณํ DB
ํ๋์จ์ด ํ๋ก๋น์ ๋, ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค์ , ํจ์น ๋ฐ ๋ฐฑ์ ๊ณผ ๊ฐ์ด ์ฆ์ ์ด์ ์์ ์ ์๋ํํ์ฌ ๊ฐ๋ฐ์๊ฐ ๊ฐ๋ฐ์ ์ง์คํ ์ ์๊ฒ ์ง์ํ๋ ์๋น์ค ์ถ๊ฐ๋ก ์กฐ์ ๊ฐ๋ฅํ ์ฉ๋์ ์ง์ํ์ฌ ์์์น ๋ชปํ ์์ ๋ฐ์ดํฐ๊ฐ ์์ฌ๋ ๋น์ฉ๋ง ์ถ๊ฐ๋ก ๋ด๋ฉด ์ ์์ ์ผ๋ก ์๋น์ค๊ฐ ๊ฐ๋ฅํ ์ฅ์ ๋ ์๋ค.
RDS์ ๊ฐ๊ฒฉ์ ๋ผ์ด์ผ์ค ๋น์ฉ ์ํฅ์ ๋ฐ๋๋ค.
MySQL, MariaDB, PostgreSQL ์ค MariaDB๋ก ๊ตฌ์ถํ ๊ฒ์ด๋ค.
๐โโ๏ธ ์ ? ๊ฐ๊ฒฉ : ๋ฌด๋ฃ Amazon AUrora ๊ต์ฒด ์ฉ์ดํ๋ค. (*Amazon AUrora : AWS์์ MySQL๊ณผ PostgreSQL์ ํด๋ผ์ฐ๋ ๊ธฐ๋ฐ์ ๋ง๊ฒ ์ฌ๊ตฌ์ฑํ DB. )
MariaDB์ MySQL ๋๋น ์ฅ์ ?
- ๋์ผ ํ๋์จ์ด ์ฌ์์ผ๋ก MySQL ๋ณด๋ค ํฅ์๋ ์ฑ๋ฅ
- ์ข ๋ ํ์ฑํ๋ ์ปค๋ฎค๋ํฐ
- ๋ค์ํ ๊ธฐ๋ฅ
- ๋ค์ํ ์คํ ๋ฆฌ์ง ์์ง
- MariaDB ๋ก ์์ฑ
- ํ๋ผ๋ฏธํฐ ๊ทธ๋ฃน ์์ฑ
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 &
๐โโ๏ธ ์ ์คํฌ๋ฆฝํธ๋ฅผ ์์ฑํ๋ ์ด์ ๋ ?
๋ฐฐํฌํ ๋๋ง๋ค ๊ฐ๋ฐ์๊ฐ ํ๋ํ๋ ๋ช ๋ น์ด๋ฅผ ์คํํ๋ ๊ฒ์ ๋ง์ ๋ถํธํจ์ด ๋ฐ๋ฅธ๋ค.
๊ทธ๋์ ์ ์คํฌ๋ฆฝํธ๋ฅผ ์์ฑํด ์คํฌ๋ฆฝํธ๋ง ์คํํ๋ฉด ์ฐจ๋ก๋ก ์งํ๋๋๋ก ํ ์ ์๋ค.
์๋ฒ์ gitignore๋ application-oauth.properties ์ถ๊ฐ
H2์์ ์๋ ์์ฑํด์ฃผ๋ ํ ์ด๋ธ์ MarialDB์ ์ง์ ์ฟผ๋ฆฌ๋ฅผ ์ด์ฉํด ์์ฑํด์ค
์๋ฐ ํ๋ก์ ํธ๊ฐ MariaDB์ ์ ๊ทผํ๋ ค๋ฉด DB Driver๊ฐ ํ์.
build.gradle์ ์์กด์ฑ ์ถ๊ฐ
DB ์ ์ ์ ๋ณด๋ ์ค์ํ๊ฒ ๋ณดํธํด์ผ ํ ์ ๋ณด์ด๋ค.
๊ณต๊ฐ๋๋ฉด ์ธ๋ถ์์ ๋ฐ์ดํฐ๋ฅผ ๋ชจ๋ ๊ฐ์ ธ๊ฐ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค.
ํ๋ก์ ํธ ์์ ์ ์ ์ ๋ณด๋ฅผ ๊ฐ๊ณ ์์ผ๋ฉด ๊นํ๋ธ ๊ฐ์ด ์คํ๋ ๊ณต๊ฐ์์ ๋๊ตฌ๋ ํดํนํ ์ํ์ด ์๋ค.
EC2 ์๋ฒ ๋ด๋ถ์์ ์ ์ ์ ๋ณด๋ฅผ ๊ด๋ฆฌํ๋๋ก ์ค์