# Initial page

![](/files/-Li77JobMdORiXxW0yA7)


# ShouldKnow

[12 Factors App](https://12factor.net/ko/)


# IntelliJ

## 자주 사용하는 단축키 (Mac 기준)

| 설명              | 단축키    |
| --------------- | ------ |
| Code formatting | ⌘L     |
| Optimize import | ⌃⌥O    |
| Run             | ⌃R     |
| Run again       | ⌃⇧R    |
| Debug           | ⌃D     |
| Delete line     | ⌘⌫     |
|                 | ⌃Space |
|                 | ⌥⏎     |
| Comment         | ⌘/     |


# Spring Framework


# SpringBoot

Repository 만으로 REST Api 만들기 -> [Spring Data Rest](https://blog.woniper.net/345)


# exception handling

#### 작업에 참고했던 사이트

[Guide to Spring Boot REST API Error Handling](https://www.toptal.com/java/spring-boot-rest-api-error-handling)

[Exception Handling in Spring](https://stackabuse.com/exception-handling-in-spring/)


# SpringCloud


# Zuul


# Eureka

#### 참고 사이트

[티스토리 블로그](https://miin29na.tistory.com/11)

[공식 홈페이지](https://spring.io/guides/gs/service-registration-and-discovery/)


# Ribbon


# README.md

[예제1](https://gist.github.com/fvcproductions/1bfc2d4aecb01a834b46) [예제2](https://gist.github.com/PurpleBooth/109311bb0361f32d87a2)


# Web


# RESTApi

[멱등성 이야기](https://greatkim91.tistory.com/14)

[HTTP Status](https://developer.mozilla.org/en-US/docs/Web/HTTP/Status)


# Material Design

[디자인 및 아이콘](https://material.io)


# React

[리액트가 만들어진 배경](http://webframeworks.kr/tutorials/react/getting-started/)


# ESLint

## Linter를 사용 시 좋은 점

초보자도 고수처럼 코딩할 수 있다.

## airbnb linter 설정방법

create-react-app 으로 만든 project 라고 가정.

### &#x20;1. eslint를 위한 airbnb 설정을 설치한다.

`yarn add eslint-config-airbnb`

### 2. .eslintrc 파일 생성&#x20;

{% code title=".eslintrc" %}

```javascript
{
  "env": {
    "browser": true,
    "es6": true,
    "jest": true
  },
  "parser": "babel-eslint",
  "extends": [
    "airbnb"
  ],
  "plugins": [
    "react"
  ],
  "rules": {
    "strict": "error"
  }
}

```

{% endcode %}

제가 사용중인 설정입니다.

### 3. 빨간 줄을 맞이하고 해결한다.

:D

## 추가 정보

[eslint rules](https://eslint.org/docs/rules/)

[eslint-plugin-rules](https://github.com/yannickcr/eslint-plugin-react/tree/master/docs/rules)

[airbnb javascript code style](https://github.com/airbnb/javascript)


# Scala

http\://scala-lang.org

### 1. 스칼라 언어는 누가 만들었을까?

[Martin Ordersky](https://en.wikipedia.org/wiki/Martin_Odersky) 라는 스위스 [어느 대학](https://en.wikipedia.org/wiki/École_Polytechnique_Fédérale_de_Lausanne)의 교수님이 만들었음.

### 2. 스칼라의 특징은?

* JVM 위에서 작동하는 언어 (Java와 호환)
* 사용하다보니 Java에 비해서 작성해야 하는 코드의 양이 많이 줄어든다.

{% code title="HelloJava.java" %}

```java
public class HelloJava {
  public static void main(String[] args) {
    System.out.println("Hello, Java!");
  }
}
```

{% endcode %}

{% code title="HelloScala.scala" %}

```scala
object HelloScala {
  def main(args: Array[String]): Unit = {
    print("Hello, Scala!")
  }
}
```

{% endcode %}

여기까지만 적었다면 분명 Java와 Scala 별로 차이 없네\~ 하고 끝날꺼 같아서 진짜 줄어든 형태를 적어본다.

{% code title="HelloScala2.scala" %}

```scala
object HelloScala2 extends App {
  print("Hello, Scala2")
}
```

{% endcode %}

* Java보다 규칙이 단순하다.

예를들면, 세미콜론 생략, 인자가 없는 함수 호출 시 () 생략 가능.

* 컴파일러의 Type 추론 능력이 뛰어나다.
* 패턴 매칭으로 사용하면 편한점이 있다.

```scala
val test = "Hello, Scala2"

test match {
  case "Hello, Scala" => print("wrong")
  case "Hello, Scala2" => print("yes!")
  case _ => print("????")
}
```

* 변수의 선언이 간편하다.

```scala
val test1: Int = 1              <- Java의 final 변수와 같다. 나중에 수정이 불가능
var test2: Int = 2              <- 수정 가능

test1 = 3                       <- 오류 발생
test2 = 4
```

*
* Java 의 Pojo 객체처럼 데이터만 담을 수 있는 객체를 정의해서 자료형으로 쓰기 편하다.\
  (이런 객체를 이용하여 위에서 적었던 패턴 매칭에 활용할 수도 있다.)

{% code title="ArticleTest.scala" %}

```scala
case class Article(id: Long, title: String, content: String)

object ArticleTest extends App {
  val article = Article(1, "title", "content")

  print(article)
}
```

{% endcode %}

다양한 내용들이 더 있다. 많이 사용되는곳이라던지...\
하지만!\
글이 길어지면 읽지 않는 사람이 있다고 들었으니, 여기까지만 알아보도록 한다.

### 3. 스칼라에 대해서 더 알아보고 싶다면?

[스칼라 공식 홈페이지 문서](https://docs.scala-lang.org)

[스칼라 시작하기](https://www.scala-lang.org/documentation/your-first-lines-of-scala.html)

[자바 개발자를 위한 스칼라 튜토리얼](https://docs.scala-lang.org/ko/tutorials/scala-for-java-programmers.html)


# Akka

### Akka를 이야기할땐 Actor가 먼저!

![http://renien.com/blog/akka-blog-series/akka-queue.png](/files/-LirADefxRf9pimASXAf)

Actor는 어떤 동작의 주체라고 보면 된다.\
메시지 기반 통신을 하여 Actor간 직접적인 호출이 없다. (Class 모델과 다른점)

Dispatcher 가 Mailbox Queue에서 Message를 하나 가져와서 Actor를 동작시킬 때는 Single thread로 동작한다. 그 덕분에 Lock이나 Atomic 연산에 대해서 생각할 필요가 없다.

###

### Actor들을 관리하는 ActorSystem을 알아보자

![https://i1.wp.com/www.datio.com/wp-content/uploads/2017/02/Akka3.png?resize=691%2C516](/files/-LirDSlu3WLjT4tV1MDc)

그림이 매우 잘 나와있다!

### ActorReference

![https://miro.medium.com/max/680/1\*Z9mbLItuNvx2lq8O0Dymvg.png](/files/-LirFCyDpavZ0h2XBygf)

Actor Instance에 직접 접근 하지 못하고 Reference를 통해서만 접근이 가능하다는 것만 알면 된다.\
그 덕분에 결합도도 낮아지고, 위치 투명성 등이 보장된다!

###

### ActorPath

![https://doc.akka.io/docs/akka/2.5.11/guide/diagrams/actor\_top\_tree.png](/files/-LirFlQmqEvQSkWyImMw)

Actor 들은 ActorSystem 아래로 Tree 구조처럼 자신의 위치 정보를 가지고 있다. 그 중 개발자의 Actor 는 /user 아래에 위치해있고, /system 아래에 있는 Actor들은 ActorSystem을 위한 기본 Actor들이다.

###

### 그 외 Akka Library들

[다양합니다.](https://akka.io/docs/)

### Akka를 더 알고싶다면...

[아카 공식 홈페이지 문서](https://doc.akka.io/docs/akka/current/index.html)

[스칼라를 이용하여 아카 시작하기](https://developer.lightbend.com/guides/akka-quickstart-scala/index.html)

[아카 라이프 사이클](http://wiki.webnori.com/display/AKKA/00.Lifecycle)

[아카 홈 설명](http://wiki.webnori.com/display/AKKA/Akka-Home)

[아카 공부용 번역 자료](https://blog.rajephon.dev/2018/11/25/akka-00/)

[길벗 아카 코딩공작소 책](http://www.yes24.com/Product/Goods/44015335), [예제 소스코드](https://github.com/gilbutITbook/006877)

[액터 테스트](https://reniowood.github.io/akka/2019/05/27/Akka-Akka-코딩공작소-Chapter03-액터를-사용한-테스트-주도-개발.html)


# Kotlin

http\://kotlinlang.org

### 1. Kotlin 은 누가 만들었을까?

2011년 7월 JetBrains 에서 JVM에서 돌아가는 새로운 언어로 kotlin을 공개\
JetBrains 는 IntelliJ 를 개발한 유명한 회사\
Kotlin이라는 이름은 JetBrains회사가 위치한 상트페테르부르크 근처의 섬이름에서 따온 것<br>

### 2. Kotlin 을 왜 만든거지?

### 3. Kotlin 의 특징

1. 간결한 코드
2. 빠른 컴파일 속도
3. 여러 플랫폼 지원
4. Java와 호환
5. 스칼라랑 비슷비슷
6. 2017 Google I/O 에서 Android 앱 개발용 공식 언어로 채택

##

### 4. 그만 알아보자.. 다 필요없고 공식 홈페이지 FAQ 문서만 보면 되는거였다. ㅠㅠ

[FAQ에 뭐가 있길래...](https://kotlinlang.org/docs/reference/faq.html)

### 더 알아보기

[코틀린 공식 홈페이지 문서](https://kotlinlang.org/docs/reference/)

[코틀린 위키](https://en.wikipedia.org/wiki/Kotlin_\(programming_language\))(영문 주의)

[코틀린 위키](https://namu.wiki/w/Kotlin)

[스칼라 vs 코틀린](https://hamait.tistory.com/895)

[Effective Java 와 Kotlin](http://ohjeonggyu.github.io/2017/03/31/Effective-Java와-Kotlin/)


# DevTools


# Redmine

[공식홈페이지](https://www.redmine.org)

[redmine-github 연동](https://yookeun.github.io/tools/2015/01/26/redmine-github/)

[redmine-github hook](https://github.com/koppen/redmine_github_hook)


# Gerrit

[공식 홈페이지](https://www.gerritcodereview.com)

[gerrit 팀이 사용하고 있는 모습](https://gerrit-review.googlesource.com/q/status:open+project:gerrit)


# Jenkins


# VisualParadime

[기본 교육자료](https://www.visual-paradigm.com/training/visual-paradigm-essential/)

[데이터베이스 디자인](https://www.visual-paradigm.com/training/database-design-and-management/)


# styled-components 라이브러리 활용 예

Getting the Most Out of Styled Components (7 Must Know Features)

<https://blog.cloudboost.io/getting-the-most-out-of-styled-components-7-must-know-features-acba3cc15b5>


# Postfix Notation

## infix notation&#x20;

일반적으로 사용하는 표기법 \
위키피디아 : <https://en.wikipedia.org/wiki/Infix_notation>

### postfix notation&#x20;

infix notation에서 오른쪽 피연산자와 연산자의 위치를 바꾼 표기법 \
위키피디아 : <https://en.wikipedia.org/wiki/Reverse_Polish_notation>&#x20;


# Stack을 이용하여 infix notation을 postfix notation으로 변경하기

## 변경 방법&#x20;

### 사용할 데이터와 자료구조&#x20;

* 원본 입력 스트림 -> "입력스트림"
* 스택 1개 -> "스택"
* 최종 결과를 담을 리스트 -> "결과리스트"

### 알고리즘&#x20;

1. 사용자가 입력하는 수식을 의미 단위로 처리한다고 가정한다. (수, 연산자, 괄호 단위로 처리)
2. 공백 문자는 무시한다.
3. 사용자가 수를 입력한 경우 결과리스트에 넣는다.
4. 사용자가 연산자 ( $$+, -, \*, /$$ )를 입력한 경우
   1. 스택이 비어있는 경우 스택에 push한다.
   2. 스택이 비어있지 않은 경우 pop하여 맨 뒤 토큰을 가져온다.
      1. 스택에서 뽑은 토큰이 사용자가 입력한 연산자와 같거나 높 계산 우선순위를 갖는 연산자이면 토큰을 결과리스트에 넣고 4번 과정을 다시 반복한다.
      2. 스택에서 뽑은 토큰이 사용자가 입력한 연산자보다 낮은 계산 우선순위를 갖는 연산자이면 토큰을 스택에 다시 push하고 사용자가 입력한 연산자를 스택에 push한다.
      3. 스택에서 뽑은 토큰이 여는 괄호이면 토큰을 스텍에 다시 push하고 사용자가 입력한 연산자를 스택에 push한다.
5. 사용자가 괄호를 입력한 경우
   1. 여는 괄호일 경우 스택에 push한다.
   2. 닫는 괄호일 경우 스택에서 닫는 괄호와 쌍을 이루는 여는 괄호가 나올때까지 토큰을 pop하여 결과리스트에 넣는다. 이때 사용자가 입력한 닫는 괄호와 스택에서 pop한 쌍을 이루는 여는 괄호는 결과리스트에 넣지 않고 버린다.
6. 입력스트림이 비워졌을 때 스택이 비어있지 않은 경우 스택의 모든 토큰을 하나씩 pop하여 결과리스트에 넣는다.

## 예시&#x20;

### 연산자가 하나인 경우&#x20;

```
# 문제

infix   : 2 + 3
postfix : 2 3 +

# 풀이 (순서대로 원본스트림, 스택, 결과리스트)

[2, +, 3]
[]
[]

[+, 3]
[]
[2]

[3]
[+]
[2]

[]
[2, 3]
[+]

[]
[2, 3, +]
[]
```

### 같은 계산 우선순위를 가진 연산자가 2개 이상 있는 경우&#x20;

```
# 문제

infix   : 2 + 3 - 4
postfix : 2 3 + 4 -

# 풀이 (순서대로 원본스트림, 스택, 결과리스트)

[2, +, 3, -, 4]
[]
[]

[+, 3, -, 4]
[]
[2]

[3, -, 4]
[+]
[2]

[-, 4]
[+]
[2, 3]

[4]
[-]
[2, 3, +]

[]
[-]
[2, 3, +, 4]

[]
[]
[2, 3, +, 4, -]
```

```
# 문제

infix   : 3 * 4 / 5
postfix : 3 4 * 5 /

# 풀이 (순서대로 원본스트림, 스택, 결과리스트)

[3, *, 4, /, 5]
[]
[]

[*, 4, /, 5]
[]
[3]

[4, /, 5]
[*]
[3]

[/, 5]
[*]
[3, 4]

[5]
[/]
[3, 4, *]

[]
[/]
[3, 4, *, 5]

[]
[]
[3, 4, *, 5, /]
```

### 높은 계산 우선순위를 가진 연산자가 먼저 오고 낮은 계산 우선순위를 가진 연산자가 나중에 오는 경우&#x20;

```
# 문제

infix   : 2 * 3 + 4
postfix : 2 3 * 4 +

# 풀이 (순서대로 원본스트림, 스택, 결과리스트)

[2, *, 3, +, 4]
[]
[]

[*, 3, +, 4]
[]
[2]

[3, +, 4]
[*]
[2]

[+, 4]
[*]
[2, 3]

[4]
[+]
[2, 3, *]

[]
[+]
[2, 3, *, 4]

[]
[]
[2, 3, *, 4, +]
```

### 낮은 계산 우선순위를 가진 연산자가 먼저 오고 높은 계산 우선순위를 가진 연산자가 나중에 오는 경우&#x20;

```
# 문제

infix   : 2 + 3 * 4
postfix : 2 3 4 * +

# 풀이 (순서대로 원본스트림, 스택, 결과리스트)

[2, +, 3, *, 4]
[]
[]

[+, 3, *, 4]
[]
[2]

[3, *, 4]
[+]
[2]

[*, 4]
[+]
[2, 3]

[4]
[+, *]
[2, 3]

[]
[+, *]
[2, 3, 4]

[]
[]
[2, 3, 4, *, +]
```

### 다른 계산 우선순위를 가진 연산자가 2개 이상 있는 경우&#x20;

```
# 문제

infix   : 2 + 3 * 4 - 5
postfix : 2 3 4 * + 5 -

# 풀이 (순서대로 원본스트림, 스택, 결과리스트)

[2, +, 3, *, 4, -, 5]
[]
[]
    
[+, 3, *, 4, -, 5]
[]
[2]
    
[3, *, 4, -, 5]
[+]
[2]

[*, 4, -, 5]
[+]
[2, 3]

[4, -, 5]
[+, *]
[2, 3]

[-, 5]
[+, *]
[2, 3, 4]

[5]
[-]
[2, 3, 4, *, +]

[]
[-]
[2, 3, 4, *, +, 5]

[]
[]
[2, 3, 4, *, +, 5, -]
```

```
# 문제

infix   : 2 + 3 * 4 / 5 - 6
postfix : 2 3 4 * 5 / + 6 -

# 풀이 (순서대로 원본스트림, 스택, 결과리스트)

[2, +, 3, *, 4, /, 5, -, 6]
[]
[]

[+, 3, *, 4, /, 5, -, 6]
[]
[2]

[3, *, 4, /, 5, -, 6]
[+]
[2]

[*, 4, /, 5, -, 6]
[+]
[2, 3]

[4, /, 5, -, 6]
[+, *]
[2, 3]

[/, 5, -, 6]
[+, *]
[2, 3, 4]

[5, -, 6]
[+, /]
[2, 3, 4, *]

[-, 6]
[+, /]
[2, 3, 4, *, 5]

[6]
[-]
[2, 3, 4, *, 5, /, +]

[]
[-]
[2, 3, 4, *, 5, /, +, 6]

[]
[]
[2, 3, 4, *, 5, /, +, 6, -]
```

### 괄호가 포함된 복잡한 식의 경우&#x20;

```
# 문제

infix   : (7 + 3 - 6) + 5 * (4 / 2 + 1 - 9) + 13 / 5 * 21
postfix : 7 3 + 6 - 5 4 2 / 1 + 9 - * + 13 5 / 21 * +

# 풀이 (순서대로 입력스트림, 스택, 결과리스트)

[(, 7, +, 3, -, 6, ), +, 5, *, (, 4, /, 2, +, 1, -, 9, ), +, 13, /, 5, *, 21]
[]
[]

[7, +, 3, -, 6, ), +, 5, *, (, 4, /, 2, +, 1, -, 9, ), +, 13, /, 5, *, 21]
[(]
[]

[+, 3, -, 6, ), +, 5, *, (, 4, /, 2, +, 1, -, 9, ), +, 13, /, 5, *, 21]
[(]
[7]

[3, -, 6, ), +, 5, *, (, 4, /, 2, +, 1, -, 9, ), +, 13, /, 5, *, 21]
[(, +]
[7]

[-, 6, ), +, 5, *, (, 4, /, 2, +, 1, -, 9, ), +, 13, /, 5, *, 21]
[(, +]
[7, 3]

[6, ), +, 5, *, (, 4, /, 2, +, 1, -, 9, ), +, 13, /, 5, *, 21]
[(, -]
[7, 3, +]

[), +, 5, *, (, 4, /, 2, +, 1, -, 9, ), +, 13, /, 5, *, 21]
[(, -]
[7, 3, +, 6]

[+, 5, *, (, 4, /, 2, +, 1, -, 9, ), +, 13, /, 5, *, 21]
[]
[7, 3, +, 6, -]

[5, *, (, 4, /, 2, +, 1, -, 9, ), +, 13, /, 5, *, 21]
[+]
[7, 3, +, 6, -]

[*, (, 4, /, 2, +, 1, -, 9, ), +, 13, /, 5, *, 21]
[+]
[7, 3, +, 6, -, 5]

[(, 4, /, 2, +, 1, -, 9, ), +, 13, /, 5, *, 21]
[+, *]
[7, 3, +, 6, -, 5]

[4, /, 2, +, 1, -, 9, ), +, 13, /, 5, *, 21]
[+, *, (]
[7, 3, +, 6, -, 5]

[/, 2, +, 1, -, 9, ), +, 13, /, 5, *, 21]
[+, *, (]
[7, 3, +, 6, -, 5, 4]

[2, +, 1, -, 9, ), +, 13, /, 5, *, 21]
[+, *, (, /]
[7, 3, +, 6, -, 5, 4]

[+, 1, -, 9, ), +, 13, /, 5, *, 21]
[+, *, (, /]
[7, 3, +, 6, -, 5, 4, 2]

[1, -, 9, ), +, 13, /, 5, *, 21]
[+, *, (, +]
[7, 3, +, 6, -, 5, 4, 2, /]

[-, 9, ), +, 13, /, 5, *, 21]
[+, *, (, +]
[7, 3, +, 6, -, 5, 4, 2, /, 1]

[9, ), +, 13, /, 5, *, 21]
[+, *, (, -]
[7, 3, +, 6, -, 5, 4, 2, /, 1, +]

[), +, 13, /, 5, *, 21]
[+, *, (, -]
[7, 3, +, 6, -, 5, 4, 2, /, 1, +, 9]

[+, 13, /, 5, *, 21]
[+, *]
[7, 3, +, 6, -, 5, 4, 2, /, 1, +, 9, -]

[13, /, 5, *, 21]
[+]
[7, 3, +, 6, -, 5, 4, 2, /, 1, +, 9, -, *, +]

[/, 5, *, 21]
[+]
[7, 3, +, 6, -, 5, 4, 2, /, 1, +, 9, -, *, +, 13]

[5, *, 21]
[+, /]
[7, 3, +, 6, -, 5, 4, 2, /, 1, +, 9, -, *, +, 13]

[*, 21]
[+, /]
[7, 3, +, 6, -, 5, 4, 2, /, 1, +, 9, -, *, +, 13, 5]

[21]
[+, *]
[7, 3, +, 6, -, 5, 4, 2, /, 1, +, 9, -, *, +, 13, 5, /]
 
[]
[+, *]
[7, 3, +, 6, -, 5, 4, 2, /, 1, +, 9, -, *, +, 13, 5, /, 21]

[]
[]
[7, 3, +, 6, -, 5, 4, 2, /, 1, +, 9, -, *, +, 13, 5, /, 21, *, +]
```

```
# 문제

infix   : 1 * 2 - 3 / [{(4 + 5) * 6} + 7 / 8] - (9 + (12 - 15) / 5)
postfix : 1 2 * 3 4 5 + 6 * 7 8 / + / - 9 12 15 - 5 / + -

# 풀이 (순서대로 입력스트림, 스택, 결과리스트)

[1, *, 2, -, 3, /, [, {, (, 4, +, 5, ), *, 6, }, +, 7, /, 8, ], -, (, 9, +, (, 12, -, 15, ), /, 5, )]
[]
[]

[*, 2, -, 3, /, [, {, (, 4, +, 5, ), *, 6, }, +, 7, /, 8, ], -, (, 9, +, (, 12, -, 15, ), /, 5, )]
[]
[1]

[2, -, 3, /, [, {, (, 4, +, 5, ), *, 6, }, +, 7, /, 8, ], -, (, 9, +, (, 12, -, 15, ), /, 5, )]
[*]
[1]

[-, 3, /, [, {, (, 4, +, 5, ), *, 6, }, +, 7, /, 8, ], -, (, 9, +, (, 12, -, 15, ), /, 5, )]
[*]
[1, 2]

[3, /, [, {, (, 4, +, 5, ), *, 6, }, +, 7, /, 8, ], -, (, 9, +, (, 12, -, 15, ), /, 5, )]
[-]
[1, 2, *]

[/, [, {, (, 4, +, 5, ), *, 6, }, +, 7, /, 8, ], -, (, 9, +, (, 12, -, 15, ), /, 5, )]
[-]
[1, 2, *, 3]

[[, {, (, 4, +, 5, ), *, 6, }, +, 7, /, 8, ], -, (, 9, +, (, 12, -, 15, ), /, 5, )]
[-, /]
[1, 2, *, 3]

[{, (, 4, +, 5, ), *, 6, }, +, 7, /, 8, ], -, (, 9, +, (, 12, -, 15, ), /, 5, )]
[-, /, []
[1, 2, *, 3]

[(, 4, +, 5, ), *, 6, }, +, 7, /, 8, ], -, (, 9, +, (, 12, -, 15, ), /, 5, )]
[-, /, [, {]
[1, 2, *, 3]

[4, +, 5, ), *, 6, }, +, 7, /, 8, ], -, (, 9, +, (, 12, -, 15, ), /, 5, )]
[-, /, [, {, (]
[1, 2, *, 3]

[+, 5, ), *, 6, }, +, 7, /, 8, ], -, (, 9, +, (, 12, -, 15, ), /, 5, )]
[-, /, [, {, (]
[1, 2, *, 3, 4]

[5, ), *, 6, }, +, 7, /, 8, ], -, (, 9, +, (, 12, -, 15, ), /, 5, )]
[-, /, [, {, (, +]
[1, 2, *, 3, 4]

[), *, 6, }, +, 7, /, 8, ], -, (, 9, +, (, 12, -, 15, ), /, 5, )]
[-, /, [, {, (, +]
[1, 2, *, 3, 4, 5]

[*, 6, }, +, 7, /, 8, ], -, (, 9, +, (, 12, -, 15, ), /, 5, )]
[-, /, [, {]
[1, 2, *, 3, 4, 5, +]

[6, }, +, 7, /, 8, ], -, (, 9, +, (, 12, -, 15, ), /, 5, )]
[-, /, [, {, *]
[1, 2, *, 3, 4, 5, +]

[}, +, 7, /, 8, ], -, (, 9, +, (, 12, -, 15, ), /, 5, )]
[-, /, [, {, *]
[1, 2, *, 3, 4, 5, +, 6]

[+, 7, /, 8, ], -, (, 9, +, (, 12, -, 15, ), /, 5, )]
[-, /, []
[1, 2, *, 3, 4, 5, +, 6, *]

[7, /, 8, ], -, (, 9, +, (, 12, -, 15, ), /, 5, )]
[-, /, [, +]
[1, 2, *, 3, 4, 5, +, 6, *]

[/, 8, ], -, (, 9, +, (, 12, -, 15, ), /, 5, )]
[-, /, [, +]
[1, 2, *, 3, 4, 5, +, 6, *, 7]

[8, ], -, (, 9, +, (, 12, -, 15, ), /, 5, )]
[-, /, [, +, /]
[1, 2, *, 3, 4, 5, +, 6, *, 7]

[], -, (, 9, +, (, 12, -, 15, ), /, 5, )]
[-, /, [, +, /]
[1, 2, *, 3, 4, 5, +, 6, *, 7, 8]

[-, (, 9, +, (, 12, -, 15, ), /, 5, )]
[-, /]
[1, 2, *, 3, 4, 5, +, 6, *, 7, 8, /, +]

[(, 9, +, (, 12, -, 15, ), /, 5, )]
[-]
[1, 2, *, 3, 4, 5, +, 6, *, 7, 8, /, +, /, -]

[9, +, (, 12, -, 15, ), /, 5, )]
[-, (]
[1, 2, *, 3, 4, 5, +, 6, *, 7, 8, /, +, /, -]
 
[+, (, 12, -, 15, ), /, 5, )]
[-, (]
[1, 2, *, 3, 4, 5, +, 6, *, 7, 8, /, +, /, -, 9]

[(, 12, -, 15, ), /, 5, )]
[-, (, +]
[1, 2, *, 3, 4, 5, +, 6, *, 7, 8, /, +, /, -, 9]

[12, -, 15, ), /, 5, )]
[-, (, +, (]
[1, 2, *, 3, 4, 5, +, 6, *, 7, 8, /, +, /, -, 9]

[-, 15, ), /, 5, )]
[-, (, +, (]
[1, 2, *, 3, 4, 5, +, 6, *, 7, 8, /, +, /, -, 9, 12]

[15, ), /, 5, )]
[-, (, +, (, -]
[1, 2, *, 3, 4, 5, +, 6, *, 7, 8, /, +, /, -, 9, 12]

[), /, 5, )]
[-, (, +, (, -]
[1, 2, *, 3, 4, 5, +, 6, *, 7, 8, /, +, /, -, 9, 12, 15]

[/, 5, )]
[-, (, +]
[1, 2, *, 3, 4, 5, +, 6, *, 7, 8, /, +, /, -, 9, 12, 15, -]

[5, )]
[-, (, +, /]
[1, 2, *, 3, 4, 5, +, 6, *, 7, 8, /, +, /, -, 9, 12, 15, -]

[)]
[-, (, +, /]
[1, 2, *, 3, 4, 5, +, 6, *, 7, 8, /, +, /, -, 9, 12, 15, -, 5]

[]
[-]
[1, 2, *, 3, 4, 5, +, 6, *, 7, 8, /, +, /, -, 9, 12, 15, -, 5, /, +]

[]
[]
[1, 2, *, 3, 4, 5, +, 6, *, 7, 8, /, +, /, -, 9, 12, 15, -, 5, /, +, -]
```


# Postfix Notation 계산하기

## 계산 방법&#x20;

### 사용할 데이터와 자료구조&#x20;

* postfix 방식으로 표기된 계산식 스트림&#x20;
  * 의미 단위로 처리한다고 가정
  * 수와 연사자만 존재한다고 가정 (괄호는 postfix 표기법에서 필요 없음)&#x20;
* 계산 결과를 담을 스택&#x20;

### 알고리즘&#x20;

1. 스트림에서 꺼낸 토큰이 수이면 스택에 push한다.
2. 스트림에서 꺼낸 토큰이 연산자이면 스택에서 수를 2개 pop하여 연산자에 해당하는 연산을 수행한다. 수행한 결과로 나온 수를 다시 스택에 push한다.
   1. 처음 pop한 수가 오른쪽 피연산자이고 나중에 pop한 수가 왼쪽 피연산자이다.
3. 스트림을 모두 처리했을때 스택에 남아있는 수가 계산 결과이다.&#x20;

## 예시&#x20;

### 연산자가 하나인 경우&#x20;

```
# 문제

infix   : 2 + 3
postfix : 2 3 +
result  : 5

# 풀이 (순서대로 스트림, 스택)

[2, 3, +]
[]

[3, +]
[2]

[+]
[2, 3]

[]
[5]
```

### 같은 계산 우선순위를 가진 연산자가 2개 이상 있는 경우&#x20;

```
# 문제

infix   : 2 + 3 - 4
postfix : 2 3 + 4 -
result  : 1

# 풀이 (순서대로 스트림, 스택)

[2, 3, +, 4, -]
[]

[3, +, 4, -]
[2]

[+, 4, -]
[2, 3]

[4, -]
[5]

[-]
[5, 4]

[]
[1]
```

```
# 문제

infix   : 3 * 4 / 5
postfix : 3 4 * 5 /
result  : 2.4

# 풀이 (순서대로 스트림, 스택)

[3, 4, *, 5, /]
[]

[4, *, 5, /]
[3]

[*, 5, /]
[3, 4]

[5, /]
[12]

[/]
[12, 5]

[]
[2.4]
```

### 높은 계산 우선순위를 가진 연산자가 먼저 오고 낮은 계산 우선순위를 가진 연산자가 나중에 오는 경우&#x20;

```
# 문제

infix   : 2 * 3 + 4
postfix : 2 3 * 4 +
result  : 10

# 풀이 (순서대로 스트림, 스택)

[2, 3, *, 4, +]
[]

[3, *, 4, +]
[2]

[*, 4, +]
[2, 3]

[4, +]
[6]

[+]
[6, 4]

[]
[10]
```

### 낮은 계산 우선순위를 가진 연산자가 먼저 오고 높은 계산 우선순위를 가진 연산자가 나중에 오는 경우&#x20;

```
# 문제

infix   : 2 + 3 * 4
postfix : 2 3 4 * +
result  : 14

# 풀이 (순서대로 스트림, 스택)

[2, 3, 4, *, +]
[]

[3, 4, *, +]
[2]

[4, *, +]
[2, 3]

[*, +]
[2, 3, 4]

[+]
[2, 12]

[]
[14]
```

### 다른 계산 우선순위를 가진 연산자가 2개 이상 있는 경우&#x20;

```
# 문제

infix   : 2 + 3 * 4 - 5
postfix : 2 3 4 * + 5 -
result  : 9

# 풀이 (순서대로 스트림, 스택)

[2, 3, 4, *, +, 5, -]
[]

[3, 4, *, +, 5, -]
[2]

[4, *, +, 5, -]
[2, 3]

[*, +, 5, -]
[2, 3, 4]

[+, 5, -]
[2, 12]

[5, -]
[14]

[-]
[14, 5]

[]
[9]
```

```
# 문제

infix   : 2 + 3 * 4 / 5 - 6
postfix : 2 3 4 * 5 / + 6 -
result  : -1.6

# 풀이 (순서대로 스트림, 스택)

[2, 3, 4, *, 5, /, +, 6, -]
[]

[3, 4, *, 5, /, +, 6, -]
[2]

[4, *, 5, /, +, 6, -]
[2, 3]

[*, 5, /, +, 6, -]
[2, 3, 4]

[5, /, +, 6, -]
[2, 12]

[/, +, 6, -]
[2, 12, 5]

[+, 6, -]
[2, 2.4]

[6, -]
[4.4]

[-]
[4.4, 6]

[]
[-1.6]
```

### 괄호가 포함된 복잡한 식의 경우&#x20;

```
# 문제

infix   : (7 + 3 - 6) + 5 * (4 / 2 + 1 - 9) + 13 / 5 * 21
postfix : 7 3 + 6 - 5 4 2 / 1 + 9 - * + 13 5 / 21 * +
result  : 28.6

# 풀이 (순서대로 스트림, 스택)

[7, 3, +, 6, -, 5, 4, 2, /, 1, +, 9, -, *, +, 13, 5, /, 21, *, +]
[]

[3, +, 6, -, 5, 4, 2, /, 1, +, 9, -, *, +, 13, 5, /, 21, *, +]
[7]

[+, 6, -, 5, 4, 2, /, 1, +, 9, -, *, +, 13, 5, /, 21, *, +]
[7, 3]

[6, -, 5, 4, 2, /, 1, +, 9, -, *, +, 13, 5, /, 21, *, +]
[10]

[-, 5, 4, 2, /, 1, +, 9, -, *, +, 13, 5, /, 21, *, +]
[10, 6]

[5, 4, 2, /, 1, +, 9, -, *, +, 13, 5, /, 21, *, +]
[4]

[4, 2, /, 1, +, 9, -, *, +, 13, 5, /, 21, *, +]
[4, 5]

[2, /, 1, +, 9, -, *, +, 13, 5, /, 21, *, +]
[4, 5, 4]

[/, 1, +, 9, -, *, +, 13, 5, /, 21, *, +]
[4, 5, 4, 2]

[1, +, 9, -, *, +, 13, 5, /, 21, *, +]
[4, 5, 2]

[+, 9, -, *, +, 13, 5, /, 21, *, +]
[4, 5, 2, 1]

[9, -, *, +, 13, 5, /, 21, *, +]
[4, 5, 3]

[-, *, +, 13, 5, /, 21, *, +]
[4, 5, 3, 9]

[*, +, 13, 5, /, 21, *, +]
[4, 5, -6]

[+, 13, 5, /, 21, *, +]
[4, -30]

[13, 5, /, 21, *, +]
[-26]

[5, /, 21, *, +]
[-26, 13]

[/, 21, *, +]
[-26, 13, 5]

[21, *, +]
[-26, 2.6]

[*, +]
[-26, 2.6, 21]

[+]
[-26, 54.6]

[]
[28.6]
```

```
# 문제

infix   : 1 * 2 - 3 * [{(4 + 5) * 6} + 7 * 8] - (9 + (12 - 15) * 5)
postfix : 1 2 * 3 4 5 + 6 * 7 8 * + * - 9 12 15 - 5 * + -
result  : -322

# 풀이 (순서대로 스트림, 스택)

[1, 2, *, 3, 4, 5, +, 6, *, 7, 8, *, +, *, -, 9, 12, 15, -, 5, *, +, -]
[]

[2, *, 3, 4, 5, +, 6, *, 7, 8, *, +, *, -, 9, 12, 15, -, 5, *, +, -]
[1]

[*, 3, 4, 5, +, 6, *, 7, 8, *, +, *, -, 9, 12, 15, -, 5, *, +, -]
[1, 2]

[3, 4, 5, +, 6, *, 7, 8, *, +, *, -, 9, 12, 15, -, 5, *, +, -]
[2]

[4, 5, +, 6, *, 7, 8, *, +, *, -, 9, 12, 15, -, 5, *, +, -]
[2, 3]

[5, +, 6, *, 7, 8, *, +, *, -, 9, 12, 15, -, 5, *, +, -]
[2, 3, 4]

[+, 6, *, 7, 8, *, +, *, -, 9, 12, 15, -, 5, *, +, -]
[2, 3, 4, 5]

[6, *, 7, 8, *, +, *, -, 9, 12, 15, -, 5, *, +, -]
[2, 3, 9]

[*, 7, 8, *, +, *, -, 9, 12, 15, -, 5, *, +, -]
[2, 3, 9, 6]

[7, 8, *, +, *, -, 9, 12, 15, -, 5, *, +, -]
[2, 3, 54]

[8, *, +, *, -, 9, 12, 15, -, 5, *, +, -]
[2, 3, 54, 7]

[*, +, *, -, 9, 12, 15, -, 5, *, +, -]
[2, 3, 54, 7, 8]

[+, *, -, 9, 12, 15, -, 5, *, +, -]
[2, 3, 54, 56]

[*, -, 9, 12, 15, -, 5, *, +, -]
[2, 3, 110]

[-, 9, 12, 15, -, 5, *, +, -]
[2, 330]

[9, 12, 15, -, 5, *, +, -]
[-328]

[12, 15, -, 5, *, +, -]
[-328, 9]

[15, -, 5, *, +, -]
[-328, 9, 12]

[-, 5, *, +, -]
[-328, 9, 12, 15]

[5, *, +, -]
[-328, 9, -3]

[*, +, -]
[-328, 9, -3, 5]

[+, -]
[-328, 9, -15]

[-]
[-328, -6]

[]
[-322]
```


# Slack App

Slack App 만들기


# Slack App 만들기 기초

Slack 홈페이지에서 Slack App 을 만드는 방법

1. <https://api.slack.com/apps> 으로 접속한다.
2. "Create New App" 버튼을 클릭한다.
3. 만들 App 이름을 입력한다. ( 이름은 나중에 변경 가능 )
4. 개발은 진행할 Workspace를 선택한다. ( javabom )
   1. 해당 Workspace에서는 특별한 인증 없이 앱을 설치할 수 있다.
   2. 나중에 변경 불가능
   3. 인증 기능을 추가하여 public으로 배포하면 다른 Workspace에서 만든 App을 사용할 수 있다.
5. "Create App" 버튼을 클릭한다.
6. App이 생성되고 생성된 App의 기본 관리 페이지로 이동한다.


# Javascript


# Promise

비동기:동시성 프로그래밍

졸리니까 내일써야지 ...


# Iterable Iterator 정의

### 이터러블/이터레이터 프로토콜에 대한 정의 & 간단 예제

#### Iterable 이란?

> 이터레이터를 리턴하는 \[Symbol.iterator]\() 를 가진 값

#### Iterator 란?

> { value, done } 객체를 리턴하는 next() 를 가진 값

{% hint style="info" %}
이터러블/이터레이터 프로토콜:

이터러블을 for ... of, 전개 연산자 등과 함께 동작하도록한 규약
{% endhint %}

1. 배열을 이용한 심플 예제

```javascript
const arr = [1,2,3];
for (const a of arr) console.log(a);
```

arr 은 \[Symbol.iterator] 라는 메서드를 가지고 있고, 평가하면 함수가 반환된다.

```javascript
arr[Symbol.iterator] // 입력시
f values() { [native code] } // 출력
```

```javascript
arr[Symbol.iterator]() // 함수 실행시,
Array Iterator() // 출력값

let iterator = arr[Symbol.iterator]();
iterator.next(); // 반환된 이터레이터 실행 시,
> {value: 1, done: false} // 안에있는 값이 하나씩 출력됨
...
> {value: undefined, done: true}
```

* **for ... of 문**은 `value`에 들어오는 값을 `a`에 담아서 출력하다가 `done` 값이 `true`가 되면 for문을 빠져나오게 되어 있다.
* `new Set([1,2,3])`, `new Map()` 역시 동일하다.

### 참고 링크

### mdn :  [The iterable protocol mdn](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Iteration_protocols) <a href="#iterable" id="iterable"></a>

### &#x20;            [한글 버전](https://developer.mozilla.org/ko/docs/Web/JavaScript/Guide/Iterators_and_Generators) <a href="#iterable" id="iterable"></a>


# Hooks

test page

## Getting Super Powers

Becoming a super hero is a fairly straight forward process:

```
$ give me super-powers
```

{% hint style="info" %}
&#x20;Super-powers are granted randomly so please submit an issue if you're not happy with yours.
{% endhint %}

Once you're strong enough, save the world:

```
// Ain't no code for that yet, sorry
echo 'You got to trust me on this, I saved the world'
```


# Java spring


# Lombok

### Lombok features

{% embed url="<https://projectlombok.org/features/all>" %}

{% embed url="<https://advenoh.tistory.com/24>" %}


# Untitled


# Mysql


# Spring Boot


# 직렬화


# 직렬화(Serializable)

&#x20;[woowabros.github.io](http://woowabros.github.io/experience/2017/10/17/java-serialize2.html)

## 자바 직렬화, 그것이 알고싶다. 실무편 - 우아한형제들 기술 블로그

9-12 minutes

> 자바의 직렬화 기술에 대한 대한 두 번째 이야기입니다. 실제 자바 직렬화를 실무에 적용해보면서 주의해야 할 부분에 대해 이야기해보려고합니다.

### 자바 직렬화 실제 업무에서 사용해보기 <a href="#undefined" id="undefined"></a>

자바 직렬화는 자바 개발자 입장에서는 상당히 쉽고 빠르게 사용할 수 있도록 만든 기술입니다.

JSON 또는 CSV 등 형태의 포맷을 이용하면 직렬화 또는 역직렬화시에 특정 라이브러리를 도입해야 쉽게 개발이 가능하며, 구조가 복잡하면 직접 매핑시켜줘야 하는 작업도 포함하게 됩니다.\
&#x20;그것에 비해 자바 직렬화는 비교적 복잡한 객체도 큰 작업 없이 (`java.io.Serializable` 인터페이스만 구현해주면) 기본 자바 라이브러리만 사용해도 직렬화와 역직렬화를 할 수 있습니다.

하지만 등가교환이라는 말이 있듯이 쉽게 이용할 수 있는 만큼 실제 업무에서 사용할 때에는 신경 써야 하는 부분이 있습니다.\
&#x20;제 경험에 빗대어서 신경 써야 할 부분에 대해 몇 가지 이야기해보겠습니다.

#### 역직렬화시 클래스 구조 변경 문제 <a href="#undefined" id="undefined"></a>

앞서서 예시를 들은 `woowahan.blog.exam1.Member` (이하 `Member`) 클래스를 기준으로 이야기해보겠습니다.

```
    public class Member implements Serializable {
        private String name;
        private String email;
        private int age;
      // 생략
    }
```

예제에서 `Member` 클래스가 있습니다. 이 클래스의 객체를 직렬화 시켜보겠습니다.\
&#x20;아래에의 문자열은 직렬화된 `Member` 클래스의 객체 문자열입니다. 테스트에 용의 하도록 Base64로 인코딩하였습니다.<br>

```
Base64.getEncoder().encodeToString(serializedMember);
```

```
rO0ABXNyABp3b293YWhhbi5ibG9nLmV4YW0xLk1lbWJlcgAAAAAAAAABAgAESQADYWdlSQAEYWdlMkwABWVtYWlsdAASTGphdmEvbGFuZy9TdHJpbmc7TAAEbmFtZXEAfgABeHAAAAAZAAAAAHQAFmRlbGl2ZXJ5a2ltQGJhZW1pbi5jb210AAnquYDrsLDrr7w=
```

이 문자열을 바로 역직렬화 시키면 바로 `Member` 객체로 변환합니다. (테스트할 때에는 반드시 패키지도 동일해야 합니다.)<br>

`Member` 클래스의 구조 변경에 대한 문제를 확인해보겠습니다.

```
    public class Member implements Serializable {
        private String name;
        private String email;
        private int age;
        // phone 속성을 추가
        private String phone;
      // 생략
    }
```

우리가 보통 원하는 것은 `phone` 멤버 변수가 추가되어도 기존 멤버 변수의 기존 멤버 변수는 채워지길 원합니다. `phone`은 `null` 되어 있더라도 말이죠.\
&#x20;이전에 자바 직렬화된 데이터를 역직렬화 시켜 보겠습니다.

```
    java.io.InvalidClassException: woowahan.blog.exam1.Member; local class incompatible: stream classdesc serialVersionUID = -8896802588094338594, local class serialVersionUID = 7127171927132876450
```

이렇게 클래스의 멤버 변수 하나만 추가되어도 `java.io.InvalidClassException` 예외가 발생합니다.\
&#x20;예외 메시지를 읽어보면 `serialVersionUID`의 정보가 일치하지 않기 때문에 발생한 것을 알 수 있습니다.\
&#x20;우리는 `Member` 클래스에서는 `serialVersionUID` 의 값을 **-8896802588094338594** 정보로 설정해준 적도 없으며, **7127171927132876450**으로 변경한 적도 없습니다. 어떻게 된 일일까요?<br>

그래서 자바 직렬화 스펙을 확인해보았습니다. ([링크](http://docs.oracle.com/javase/6/docs/platform/serialization/spec/class.html#4100))

```
    It may be declared in the original class but is not required. 
    The value is fixed for all compatible classes. 
    If the SUID is not declared for a class, the value defaults to the hash for that class. 
```

간단히 정리해보겠습니다.

* SUID(`serialVersionUID`) 필수 값은 아니다.
* 호환 가능한 클래스는 SUID값이 고정되어 있다.
* SUID가 선언되어 있지 않으면 클래스의 기본 해쉬값을 사용한다. (해쉬값 알고리즘은 링크에서 확인이 가능합니다.)

`serialVersionUID` 를 직접 기술하지 않아도 내부 적으로 `serialVersionUID` 정보가 추가되며,\
&#x20;내부 값도 자바 직렬화 스펙 그대로 자동으로 생성된 클래스의 해쉬 값을 이라는 것을 확인할 수 있었습니다.\
&#x20;([링크](http://docs.oracle.com/javase/6/docs/platform/serialization/spec/class.html#4100) 를 보면 알 수 있지만 해쉬값은 클래스 구조 정보를 이용해서 생성하는 것을 알 수 있습니다.)\
&#x20;즉 `serialVersionUID` 정보를 기술하지 않는다고 해서 사용하지 않는 것이 아니 다라는 것이 확인되었습니다.

그럼 어떤 형태가 좋을까요?

```
    public class Member implements Serializable {
        private static final long serialVersionUID = 1L;
      
        private String name;
        private String email;
        private String phone;
      // 생략
    }
```

*“조금이라도 역직렬화 대상 클래스 구조가 바뀌면 에러 발생해야 된다.”* 정도의 민감한 시스템이 아닌 이상은 클래스를 변경할 때에\
&#x20;**직접 `serialVersionUID` 값을 관리해주어야 클래스 변경 시 혼란을 줄일 수 있습니다.**<br>

물론 그렇게 해도 모든 것이 해결되는 것은 아닙니다. `serialVersionUID` 값이 동일할 때에도 신경 써야 할 부분이 생깁니다.\
&#x20;`serialVersionUID` 값이 동일할 때에도 어떠한 문제가 생길 수 있는지 몇 가지 살펴보겠습니다.

1. 멤버 변수명은 같은데 멤버 변수 타입이 바뀔 때

   ```
        public class Member implements Serializable {
            private static final long serialVersionUID = 1L;
             
            private String name;
            private StringBuilder email;
            private int age;
          // 생략
        }
   ```

   기존 자바 직렬화된 데이터는 `String` 타입이었지만 `StringBuilder`클래스 타입으로 바꿔 봤습니다.

   ```
    java.lang.ClassCastException: cannot assign instance of java.lang.String to field woowahan.blog.exam1.Member.email of type java.lang.StringBuilder in instance of woowahan.blog.exam1.Member
   ```

   혹시 primitive 타입인 `int` 을 `long`으로 바꾸는 것은 괜찮지 않을까요?

   ```
        public class Member implements Serializable {
            private String name;
            private String email;
           // int -> long 변경 
            private long age;
          // 생략
        }
   ```

   ```
       java.io.InvalidClassException: woowahan.blog.exam1.Member; incompatible types for field age
   ```

   역시나 타입 예외가 발생했습니다. **자바 직렬화는 상당히 타입의 엄격하다는 것을 알 수 있습니다.**
2. 직렬화 자바 데이터에 존재하는 멤버 변수가 없애거나 추가했을 때

   ```
        public class Member implements Serializable {
            private static final long serialVersionUID = 1L;
            private String name;
            private String email;
            // age 멤버 변수 제거
          // 생략
        }
   ```

   ```
       Member{name='김배민', email='deliverykim@baemin.com'}
   ```

   에러는 발생하지 않습니다. 값 자체만 없어졌습니다. 그럼 멤버 변수를 추가해보겠습니다.

   ```
        public class Member implements Serializable {
            private static final long serialVersionUID = 1L;
            private String name;
            private String email;
            private int age;
            // 추가된 멤버 변수
            private String nick;
          // 생략
        }
   ```

   ```
        Member{name='김배민', email='deliverykim@baemin.com', age=25, nick=null}
   ```

   이번에도 에러가 발생하지 않습니다. 원하는 형태로 값이 채워졌네요.

자바 직렬화를 사용할 때 클래스 구조 변경 시 어떤 부분을 확인해야 할지 **정리**해보겠습니다.

* 특별한 문제없으면 자바 직렬화 버전 `serialVersionUID`의 값은 개발 시 직접 관리해야 합니다.
* `serialVersionUID`의 값이 동일하면 멤버 변수 및 메서드 추가는 크게 문제가 없습니다.\
  &#x20;그리고 멤버 변수 제거 및 이름 변경은 오류는 발생하지 않지만 데이터는 누락됩니다.
* 역직렬화 대상의 클래스의 멤버 변수 타입 변경을 지양해야 합니다. 자바 역직렬화시에 타입에 엄격합니다.\
  &#x20;나중에라도 타입 변경이 되면 직렬화된 데이터가 존재하는 상태라면 발생할 예외를 경우의 수를 다 신경 써야 합니다.
* 외부(DB, 캐시 서버, NoSQL 서버 등)에 장기간 저장될 정보는 자바 직렬화 사용을 지양해야 합니다. 역직렬화 대상의 클래스가 언제 변경이 일어날지 모르는 환경에서 긴 시간 동안 외부에 존재했던 직렬화된 데이터는 쓰레기(Garbage)가 될 가능성이 높습니다.\
  &#x20;**언제 예외가 발생할지 모르는 지뢰 시스템이 될 수도 있습니다.**
* 개발자가 직접 컨트롤이 가능한 클래스의 객체가 아닌 클래스의 객체에 대해서는 직렬화를 지양해야 합니다. 개발자가 직접 컨트롤이 힘든 객체 란 보통 프레임워크 또는 라이브러리에서 제공하는 클래스의 객체를 이야기합니다. (사실 직접 변경 가능한 방법은 있지만 추천하진 않습니다.)\
  &#x20;그런 객체가 직접 `serialVersionUID`를 가지고 있기도 합니다. 그래서 개발 시에 편의상 직렬화 시켜 DB 또는 캐시 서버에 바로 저장하기도 하는데 이 부분에서 많은 문제가 발생합니다.

  * 예시)
    1. 프레임워크 또는 라이브러리가 버전업을 하면서 `serialVersionUID` 을 변경
    2. 테스트시에는 발생 안 하다가 운영에 반영

  생각 지도 못한 오류가 거품처럼 나기 시작할 것입니다. 이 부분은 사실 알아채기가 힘듭니다. 발생하기 위한 사전 조건도 많기 때문입니다. 차라리 이 글을 읽으신 분은 위와 같은 문제 사전에 차단하실 것을 추천합니다. 위와 관련된 예시는 [스프링 시큐리티](https://github.com/spring-projects/spring-security)의 `SecurityContextImpl`클래스가 있습니다.\
  &#x20;`SecurityContext` 를 구현한 클래스 클래스로 [링크](https://github.com/spring-projects/spring-security/tree/master/core/src/main/java/org/springframework/security/core/context/SecurityContextImpl.java) 를 보면 확인할 수 있습니다. `serialVersionUID` 값이 스프링 시큐리티의 버전 값이기 때문에 버전이 변경될 때마다 신경 쓰입니다.
* 결론
  * 자바 직렬화를 사용할 때에는 될 수 있으면 자주 변경되는 클래스의 객체는 사용 안 하는 것이 좋습니다. 변경에 취약하기 때문에 생각지도 못한 예외사항들이 발생할 가능성이 높습니다. **특히 역직렬 화가 되지 않을 때와 같은 예외처리는 기본적으로 해두는 것을 추천합니다.**\
    &#x20;물론 직렬화 대상 클래스 변경과 같은 문제는 자바 직렬 화만 일어나는 문제는 아닙니다만 자바 직렬화 기술은 중간에 끼어들 여지가 없는 블랙박스에 가까워서 변경 부분에 취약한 문제가 존재합니다.

#### 용량 문제 <a href="#undefined" id="undefined"></a>

자바 직렬화시에 기본적으로 타입에 대한 정보 등 클래스의 메타 정보도 가지고 있기 때문에 상대적으로 다른 포맷에 비해서 용량이 큰 문제가 있습니다.\
&#x20;특히 클래스의 구조가 거대해지게 되면 용량 차이가 커지게 됩니다. 예를 들면 클래스 안에 클래스 또 리스트 등 이런 형태의 객체를 직렬화 하게 되면 내부에 참조하고 있는 모든 클래스에 대한 메타정보를 가지고 있기 때문에 용량이 비대해지게 됩니다.\
&#x20;그래서 JSON 같은 최소의 메타정보만 가지고 있으면 테스트로 된 포맷보다 같은 데이터에서 최소 2배 최대 10배 이상의 크기를 가질 수 있습니다.<br>

```
    String base64Member = "rO0ABXNyABp3b293YWhhbi5ibG9nLmV4YW0xLk1lbWJlcgAAAAAAAAABAgAESQADYWdlSQAEYWdlMkwABWVtYWlsdAASTGphdmEvbGFuZy9TdHJpbmc7TAAEbmFtZXEAfgABeHAAAAAZAAAAAHQAFmRlbGl2ZXJ5a2ltQGJhZW1pbi5jb210AAnquYDrsLDrr7w=";
    byte[] serializedMember = Base64.getDecoder().decode(base64Member);
    System.out.printf("serializedMember (byte size = %s) \n", serializedMember.length);
    try (ByteArrayInputStream bais = new ByteArrayInputStream(serializedMember)) {
        try (ObjectInputStream ois = new ObjectInputStream(bais)) {
            // 역직렬화된 Member 객체를 읽어온다.
            Object objectMember = ois.readObject();
            Member member = (Member) objectMember;
            // JSON으로 직렬화 했을 때 용량 체크
            String memberJson = objectMapper.writeValueAsString(member);
            System.out.println(memberJson);
            System.out.printf("json (byte size = %s)\n", memberJson.getBytes("utf-8").length);
        }
    }
```

결과

```
    serializedMember (byte size = 146)         
    {"name":"김배민","email":"deliverykim@baemin.com","age":25}
    json (byte size = 62)    
```

간단한 데이터이지만 위와 같이 용량 크기 두배 이상 차이가 납니다.<br>

용량 문제는 생각보다 많은 곳에서 나타나는 문제입니다. 특히 직렬화된 데이터를 메모리 서버([Redis](https://redis.io/), Memcached)에 저장하는 형태를 가진 시스템에서 두드러집니다.\
&#x20;메모리 서버 특성상 메모리 용량이 크지 않기 때문에 핵심만 요약해서 기록하는 형태가 효율적입니다.\
&#x20;적은 데이터만 입력하는 시스템 구조라면 큰 문제는 발생하지 않습니다. 하지만 트래픽에 따라 데이터 기록이 급증하는 시스템은 유의해야 합니다.<br>

그리고 이 부분을 강조하는 이유는 자바 웹 시스템에서 가장 많이 사용되는 스프링 프레임워크에서 기본적으로 지원하는 캐시 모듈 중 외부 시스템에 저장하는 형태에서 기본적으로 자바 직렬화 형태로 제공되기 때문입니다. (Spring Data Redis, Spring Session …)\
&#x20;기본적으로 프레임워크에서 자바 직렬화로 제공하는 이유는 앞서 말한 자바 직렬화 장점과 일맥상통합니다. 개발자가 신경 안 쓰고 빠르게 개발할 수 있기 때문입니다.\
&#x20;자바 직렬화 사용하는 시스템은 규모가 커지는 시점에서 반드시 다시 점검하여 보시길 바랍니다.

**용량 문제 결론**

일반 사용자를 대상으로 하는 B2C와 같은 시스템에서 자바 직렬화 정보를 외부 캐시 서버에 저장할 때에는 비효율적인 문제를 가지고 있습니다. (용량 크기에 따른 네트워크 비용과 캐시 서버 비용)\
&#x20;새롭게 스타트하는 서비스 같은 경우에는 생산성을 위해서 자바 직렬화를 그대로 이용한다고 해도 트랙픽이 지속적으로 증가할 때에는 JSON 형태 또는 다른 형태의 직렬화로 바꿔주는 것 고려해보시길 바랍니다.<br>

#### 호환성 이야기 <a href="#undefined" id="undefined"></a>

이 부분은 기술적 오류 문제는 아닙니다. 단지 자바 직렬화를 이용해서 개발하면서 불편했던 부분을 이야기하려고 합니다.\
&#x20;자바 직렬화를 이용해서 외부 데이터를 저장하게 되면 제일 큰 아쉬움이 바로 자바에서만 사용할 수 있으면 읽을 수 있는 문제였습니다.\
&#x20;다른 언어를 이용해서 스크립트를 이용해서 여러 가지 처리를 하고 싶어도 불가능에 가깝습니다.\
&#x20;(파이썬에 자바 직렬화 분석하는 라이브러리가 있는 것은 확인해봤지만 사용은 못해봤습니다.)\
&#x20;만약 JSON으로 저장되어 있다면 MYSQL이나 REDIS 등 추가 라이브러리를 통해 조회도 가능하면 다른 언어를 통해서도 탐색 및 조작이 가능합니다<br>

그리고 제가 이야기하고 싶은 것은 “**긴 시간 동안 외부에 저장하는 의미 있는 데이터들은 자바 직렬화를 사용하지 말자.**“입니다.

### 결론 <a href="#undefined" id="undefined"></a>

자바 직렬화는 장점이 많은 기술입니다만 단점도 많습니다. 문제는 이 기술의 단점은 보완하기 힘든 형태로 되어 있기 때문에 사용 시 제약이 많습니다. 그래서 이 글을 적는 저는 직렬화를 사용할 때에는 아래와 같은 규칙을 지키려고 합니다.

1. 외부 저장소로 저장되는 데이터는 짧은 만료시간의 데이터를 제외하고 자바 직렬화를 사용을 지양합니다.
2. 역직렬화시 반드시 예외가 생긴다는 것을 생각하고 개발합니다.
3. 자주 변경되는 비즈니스적인 데이터를 자바 직렬화을 사용하지 않습니다.
4. 긴 만료 시간을 가지는 데이터는 JSON 등 다른 포맷을 사용하여 저장합니다.

### 참고 <a href="#undefined" id="undefined"></a>

* Java Object Serialization Specification version 6.0
  * <https://docs.oracle.com/javase/8/docs/platform/serialization/spec/serial-arch.html>

[이전 포스팅](http://woowabros.github.io/experience/2017/10/17/java-serialize.html)으로 이동하기


# Java의 직렬화(Serialize)란?

## &#x20;Java의 직렬화(Serialize)란?

## 들어가며 <a href="#undefined" id="undefined"></a>

> 예전에 네트워크 통신을 할때 Serialize에 대해 공부를 한 경험이 있지만 지식의 부족함을 느끼고 조사하여 결과를 정리합니다.

## 직렬화(Serialize)란? <a href="#serialize" id="serialize"></a>

### 직렬화(Serialize) <a href="#serialize" id="serialize"></a>

* 자바 시스템 내부에서 사용되는 Object 또는 Data를 외부의 자바 시스템에서도 사용할 수 있도록 byte 형태로 데이터를 변환하는 기술.
* JVM(Java Virtual Machine 이하 JVM)의 메모리에 상주(힙 또는 스택)되어 있는 객체 데이터를 바이트 형태로 변환하는 기술

### 역직렬화(Deserialize) <a href="#deserialize" id="deserialize"></a>

* byte로 변환된 Data를 원래대로 Object나 Data로 변환하는 기술을 역직렬화(Deserialize)라고 부릅니다.
* 직렬화된 바이트 형태의 데이터를 객체로 변환해서 JVM으로 상주시키는 형태.

### 직렬화(Serialize) 시작하기 <a href="#serialize" id="serialize"></a>

#### 직렬화(Serialize) 조건 <a href="#serialize" id="serialize"></a>

* `java.io.Serializable` 인터페이스를 상속받은 객체는 직렬화 할 수 있는 기본 조건입니다.

```
public class Member implements Serializable {
    private String name;
    private String email;
    private int age;

    public Member(String name, String email, int age) {
        this.name = name;
        this.email = email;
        this.age = age;
    }
    @Override
    public String toString() {
        return String.format("Member{name='%s', email='%s', age='%s'}", name, email, age);
    }
}
```

#### 직렬화(Serialize) 방법 <a href="#serialize" id="serialize"></a>

* `java.io.ObjectOutputStream`를 사용하여 직렬화를 진행합니다.

```
public static void main(String[] args){
    Member member = new Member("김배민", "deliverykim@baemin.com", 25);
    byte[] serializedMember;
    try (ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
        try (ObjectOutputStream oos = new ObjectOutputStream(baos)) {
            oos.writeObject(member);
            // serializedMember -> 직렬화된 member 객체
            serializedMember = baos.toByteArray();
        }
    }
    // 바이트 배열로 생성된 직렬화 데이터를 base64로 변환
    System.out.println(Base64.getEncoder().encodeToString(serializedMember));
}
```

### 역직렬화(Deserialize) 시작하기 <a href="#deserialize" id="deserialize"></a>

#### 역직렬화(Deserialize) 조건 <a href="#deserialize" id="deserialize"></a>

* 직렬화 대상이 된 객체의 클래스가 클래스 패스에 존재해야 하며 import 되어 있어야 합니다.
* 중요한 점은 직렬화와 역직렬화를 진행하는 시스템이 서로 다를 수 있다는 것을 반드시 고려해야 합니다.
* 자바 직렬화 대상 객체는 동일한 `serialVersionUID` 를 가지고 있어야 합니다.
  * `private static final long serialVersionUID = 1L;`
* `serialVersionUID`이 왜 필요한지 자세한 내용은 아래에 추가하였습니다 :)

#### 역직렬화(Deserialize) 방법 <a href="#deserialize" id="deserialize"></a>

* `java.io.ObjectInputStream`를 사용하여 역직렬화를 진행합니다.

```
public static void main(String[] args){
    // 직렬화 예제에서 생성된 base64 데이터
    String base64Member = "...생략";
    byte[] serializedMember = Base64.getDecoder().decode(base64Member);
    try (ByteArrayInputStream bais = new ByteArrayInputStream(serializedMember)) {
        try (ObjectInputStream ois = new ObjectInputStream(bais)) {
            // 역직렬화된 Member 객체를 읽어온다.
            Object objectMember = ois.readObject();
            Member member = (Member) objectMember;
            System.out.println(member);
        }
    }
}
```

### 다른 Format의 직렬화 <a href="#format" id="format"></a>

![No Image](/files/-LjAuybQgUzyKNSbHwnV)

* 직렬화방법에는 여러 Format이 존재합니다.
  * 표형태의 다량의 데이터를 직렬화할때는 CSV형태
  * 구조적인 데이터는 XML, JSON형태.

**CSV**

* 데이터를 표현하는 가장 많이 사용되는 방법 중 하나로 콤마(,) 기준으로 데이터를 구분하는 방법입니다.

  > nesoy,youngjaeKwon,Seoul,Korea -> \[nesoy, youngjaeKwon, Seoul, Korea]

```
Member member = new Member("김배민", "deliverykim@baemin.com", 25);
// member객체를 csv로 변환
String csv = String.format("%s,%s,%d",member.getName(), member.getEmail(), member.getAge());
System.out.println(csv);
```

* [Apache Commons CSV](https://commons.apache.org/proper/commons-csv/)
* [opencsv](http://opencsv.sourceforge.net/)

**JSON**

* [JSON이란 무엇인가?](https://nesoy.github.io/articles/2017-02/JSON)

```
Member member = new Member("김배민", "deliverykim@baemin.com", 25);
// member객체를 json으로 변환
String json = String.format(
        "{\"name\":\"%s\",\"email\":\"%s\",\"age\":%d}",
        member.getName(), member.getEmail(), member.getAge());
System.out.println(json);
```

* [Jackson](https://github.com/FasterXML/jackson)
* [Gson](https://github.com/google/gson)

### 자바의 직렬화 왜 사용하는가? <a href="#undefined" id="undefined"></a>

* 복잡한 데이터 구조의 클래스의 객체라도 직렬화 기본 조건만 지키면 큰 작업 없이 바로 직렬화, 역직렬화가 가능합니다.
* 데이터 타입이 자동으로 맞춰지기 때문에 관련 부분을 큰 신경을 쓰지 않아도 됩니다.

### 어디에 사용되는가? <a href="#undefined" id="undefined"></a>

#### 서블릿 세션 (Servlet Session) <a href="#servlet-session" id="servlet-session"></a>

* 세션을 서블릿 메모리 위에서 운용한다면 직렬화를 필요로 하지 않지만, 파일로 저장하거나 세션 클러스터링, DB를 저장하는 옵션 등을 선택하게 되면 세션 자체가 직렬화가 되어 저장되어 전달됩니다.

#### 캐시 (Cache) <a href="#cache" id="cache"></a>

* Ehcache, Redis, Memcached 라이브러리 시스템을 많이 사용됩니다.

#### 자바 RMI(Remote Method Invocation) <a href="#rmiremote-method-invocation" id="rmiremote-method-invocation"></a>

* 원격 시스템 간의 메시지 교환을 위해서 사용하는 자바에서 지원하는 기술.

### 자바의 직렬화 단점? <a href="#undefined" id="undefined"></a>

#### 역직렬화시 클래스 구조 변경 문제 <a href="#undefined" id="undefined"></a>

* 기존 멤버 클래스를 직렬화합니다.

  ```
   public class Member implements Serializable {
        private String name;
        private String email;
        private int age;
      // 생략
    }
  ```
* 직렬화한 Data

  ```
  rO0ABXNyABp3b293YWhhbi5ibG9nLmV4YW0xLk1lbWJlcgAAAAAAAAABAgAESQADYWdlSQAEYWdlMkwABWVtYWlsdAASTGphdmEvbGFuZy9TdHJpbmc7TAAEbmFtZXEAfgABeHAAAAAZAAAAAHQAFmRlbGl2ZXJ5a2ltQGJhZW1pbi5jb210AAnquYDrsLDrr7w=
  ```
* 멤버 클래스에서 속성을 추가합니다.

  ```
  public class Member implements Serializable {
        private String name;
        private String email;
        private int age;
        // phone 속성을 추가
        private String phone;
    }
  ```
* 직렬화한 Data를 역직렬화하면 어떤 결과가 나올까요? > 결과는 `java.io.InvalidClassException`이 발생합니다.
* 위에서 언급했던 것처럼 직렬화하는 시스템과 역직렬화하는 시스템이 다른 경우에 발생하는 문제입니다.
* 각 시스템에서 사용하고 있는 모델의 버젼 차이가 발생했을 경우에 생기는 문제입니다.

#### 해결하기 위해서는 <a href="#undefined" id="undefined"></a>

* 모델의 버젼간의 호환성을 유지하기 위해서는 `SUID(serialVersionUID)`를 정의해야 합니다.
* Default는 클래스의 기본 해쉬값을 사용합니다.

#### 또 다른 문제 <a href="#undefined" id="undefined"></a>

* `String` -> `StringBuilder`, `int` -> `long`으로 변경해도 역직렬화에서 `Exception`이 발생합니다.
* 자바 직렬화는 상당히 타입의 엄격하다는 것을 알 수 있습니다.
* 멤버 변수가 빠지게 된다면 Exception 대신 null값이 들어가는 것을 확인할 수 있습니다.

#### 직렬화 Data Size 문제 <a href="#data-size" id="data-size"></a>

```
{"name":"김배민","email":"deliverykim@baemin.com","age":25}
serializedMember (byte size = 146)
json (byte size = 62)
```

* 아주 간단한 객체의 내용도 2배이상의 차이를 확인할 수 있습니다.
* 일반적인 메모리기반의 Cache에서는 Data를 저장할 수 있는 용량의 한계가 있기 때문에 Json 형태와 같은 경량화된 형태로 직렬화하는 것도 좋은 방법입니다.

## 정리하며 <a href="#undefined" id="undefined"></a>

* 외부 저장소로 저장되는 데이터는 짧은 만료시간의 데이터를 제외하고 자바 직렬화를 사용을 지양합니다.
* 역직렬화시 반드시 예외가 생긴다는 것을 생각하고 개발합니다.
* 자주 변경되는 비즈니스적인 데이터를 자바 직렬화을 사용하지 않습니다.
* 긴 만료 시간을 가지는 데이터는 JSON 등 다른 포맷을 사용하여 저장합니다.

### Reference <a href="#reference" id="reference"></a>

* <http://woowabros.github.io/experience/2017/10/17/java-serialize.html>
* <http://woowabros.github.io/experience/2017/10/17/java-serialize2.html>


# Optional \[Java 8]

자바8을 처음 접했을때 느꼈던 점은 자바8의 꽃은 스트림이라는 것이었다. 그러나 내 맘을 가장 매혹시키고, 언젠가 자바8을 쓸날이 오면 이것만큼은 꼭 잘 쓰고싶다고 생각했던것은 오늘 포스팅할 Optional이었다.

**1. null**

자바는 좀 더 쉽게 프로그래밍하자는 취지에서 개발됐다. OSMU(One Source Multi Use)를 위해 하나의 코드로 OS에 상관없이 돌아갈수 있게 개발됐고, C계열을 처음 공부할때 지옥이라 불리는 포인터를 모두 감췄다. 그러나 단 하나 감추지 못한 포인터가 있는데 그것이 null포인터다.

자바 프로그래밍에서 NullPointerException은 언제 터질지 모르는 에러의 근원이었고 이를 방지하기위해 대부분의 메서드 앞줄에선 중복적인 방어코드가 들어가는 문제가 있었다.

```
public void method01(Object obj){
   if(obj == null){
      return;
   }      // 로직
}public String method02(String str){
   if(str == null){
      // null을 그대로 리턴하는건 클라이언트에서 다시금 null체크를 해줘야하기떄문에 기본값을 리턴하는걸 권장한다.
      return null;
      return "";
   }      // 로직
}
```

매우 익숙한 코드일것이다. 하지만 저런 null체크는 반복적이고 코드가 지저분해지는 문제가 있고, 더욱이 쉽게 까먹고 넣지 못할경우 어김없이 NPE를 만나게된다. 때문에 저런 반복적인 코드를 안빼먹고 넣어주는게 오히려 꼼꼼한 개발자라는 말을 듣게 만들었다. 지금 저 예제는 그나마 인자로 넘어오는 객체의 레퍼런스만 체크하고있지만 이런 코드는 어떨까?

```
public void method01(Object obj) {
   // getter를 이용해 내부 필드를 깊게들어가서 값을 가져옴
   Object obj1 = obj.getObject().getObject.getObject();

   // 로직
}
```

이런코드는 obj의 레퍼런스만 검사한다고해서 NPE를 벗어날수는 없다. 이런 코드가 나오게 된다.

```
public void method01(Object obj) {
   Object obj1 = null;
   if(obj != null){
      if(obj.getObject() != null){
         if(obj.getObject().getObject() != null){
            obj1 = obj.getObject().getObject().getObject();
         }
      }
   }      // 로직
}
```

깊은 의심(Deep Doubt) 패턴

보기만해도 뭔가 아닌거같다. 리팩토링을 해야할것같다. 그나마 해본게 이거다.

```
public void method01(Object obj) {
   Object obj1 = null;
   if (obj != null && obj.getObject() != null && obj.getObject().getObject() != null) {
      obj1 = obj.getObject().getObject().getObject();
   }   // 로직
}
```

if문은 한줄로 줄었지만 보기엔 역시 좋지 않다. 헬퍼메서드를 사용하는게 최선인것 같다.

```
public void method01(Object obj) {
   Object obj1 = null;
   if (isValidObject(obj)) {
      obj1 = obj.getObject().getObject().getObject();
   }   // 로직
}private boolean isValidObject(Object obj){
   return obj != null && obj.getObject() != null && obj.getObject().getObject() != null;
}
```

**2. Optional**

이러한 반복적인 null 체크를 없애기위해 Java8에 Optional\<T>이라는 클래스가 추가됐다. 기술적으로 크게 어려운점은 없다. 실제 레퍼런스를 한번 감싸는 래퍼 객체를 만들어 null 체크를 내부로 숨겼기에 외부코드에선 null 체크가 보이지않게 감춘것에 불과하다.

```
class Optional<T>{
   private T t;
}
```

Stream과 마찬가지로 생성자가 아니라 Static Factory 메서드를 이용해 객체를 생성한다.

```
public static void main(String args[]) {
   String str = "hello";
   Optional<String> o1 = Optional.of(str); // str이 null이면 NPE 발생
   Optional<String> o2 = Optional.ofNullable(str); // str이 null이면 빈 Optional 객체 반환
   Optional<String> o3 = Optional.empty(); // 빈 Optional 객체 반환
}
```

of() 같은 경우는 인자가 null이면 NPE를 발생시키기때문에 Optional을 쓸때 크게 쓸일이 없는것 같고.. of()를 쓰는 경우는 null을 예방하기위한다기보다는 Optional이 제공하는 API를 이용하고자 할때 쓸듯 하다. 슬슬 실무에 Optional을 활용하고있는데 ofNullable()만 사용하고 있는듯...

한번 더 감싸고있는 형태이기때문에 API는 내부 객체에대한 연산이 주를 이룬다.

**2-1. Optional API**

**-boolean isPresent()**

내부객체가 null이 아닌지 확인한다. null이면 false를 반환한다.

**-void ifPresent(Consumer\<T>)**

Consumer\<T>는 함수형 인터페이스 포스팅에서 봤듯 void 추상메서드를 갖고있다. null이 아닐때만 실행된다.

**-Optional\<T> filter(Predicate\<T>)**

스트림은 여러 데이터를 들고있는 객체다보니 filter로 걸러지는 데이터들이 반환됐지만, Optional은 내부객체가 단일객체인만큼 해당 조건을 만족하는지만 확인하는 정도로 사용할 수 있을 것 같다.

**-Optional\<U> map(Function\<T, U>)**

스트림과 같다. 내부 객체를 변환하는 용도로 사용한다.

**-T get()**

내부 객체를 반환한다. 다만 내부 객체가 null이면 NPE가 발생한다. null이 아니라는 확실한 경우에만 사용을 권장한다.

**-T orElse(T)**

내부 객체를 반환한다. 내부 객체가 null이면 인자로 들어간 기본값을 반환한다.

**-T orElseGet(Supplier\<T>)**

orElse()와 동일한데 orElse()가 기본값 레퍼런스를 인자로 받는다면 orElseGet()은 내부 객체가 null일때 기본값을 반환할 객체를 인자로 받는다.

**-T orElseThrow(Supplier\<U>)**

내부 객체가 null이면 인자로 전달받은 예외를 발생시킨다.

**2-2. Anti Pattern**

Optional을 처음 접하면 익숙치 않은맘에 이런 코드를 작성하게 된다.

```
Optional<Integer> integer = Optional.empty();

if(integer.isPresent()){
   // ...
}// ...
```

기존의 == null 체크와 다를게 없는 코드다. 이런코드가 나온다면 Optional을 잘못쓰고있을 확률이 높다. 가장 주의하고, 절대 지양해야할 패턴이다.

**2-3. orElse() / orElseGet()**

둘다 내부객체가 null일때 뭘 줄건지를 지정하는 메서드이다. 둘이 차이가 뭘까? null 이면 Object 객체를 반환한다고 가정해보자.

```
Optional<Object> objectOptional = Optional.empty();

Object object1 = objectOptional.orElse(new Object());
Object object2 = objectOptional.orElseGet(() -> new Object());
Object object3 = objectOptional.orElseGet(Object::new);
```

3개가 모두 같은 표현이다. 2번째와 3번째는 람다를 메서드 레퍼런스로 바꾼것이다. 이건 지극이 개인적인 생각이라 잘못 이해했을수도있는데 내가 이해하기로 orElse()는 값을 지정할때 사용하고 orElseGet()은 레퍼런스를 지정할때 사용하는것이 좋은것같다. 즉, 좀 더 Lazy하게 사용할 수 있는게 orElseGet()이라는 것이다.

orElse(new Object()); 같은 구문은 실제 내부 객체가 null이든 아니든 일단 Object 의 객체는 생성하게된다. 객체를 생성해서 해당 레퍼런스를 전달하고 orElse() 내부에서 null인지 아닌지 판별 후 이미 생성된 Object 객체를 사용하거나 사용하지않는것을 결정한다는 것이다. 그에 반해 orElseGet()은 객체를 생성하는 행위를 하는 메서드를 전달한다. orElseGet() 내부에서는 내부객체가 null일때만 해당 메서드를 실행하기때문에 null이 아니면 객체를 생성되지 않는다. 차이가 이해되는가? 또는 단순히 빈객체만 생성해서 반환하는게 아니라 객체가 생성될때 실행되어야할 행위들이 있다면? 그럴때도 orElseGet()이 유용할 것이다. 간단히 setter를 실행한다고 생각해보자.

```
Object object2 = objectOptional.orElseGet(() -> {
   Object obj = new Object();
   obj.setItem();
   obj.setName();
   return obj;
});
```

orElse()로 이런것들을 하려면 미리 setter를 다 호출해서 값을 넣어두고 그 객체를 인자로 보내야한다. null일때만 사용할, 실제 사용할지말지 알지도못하는 객체를 위해 그런 행위들이 무조건적으로 수행되고 바깥 스코프에 변수가 생기는건 좋은 코드라고 보기 힘들것같다. 그에 반해 값들은 orElse()를 쓰는것이 좀 더 좋을것같다.

```
Optional<Integer> integerOptional = Optional.empty();

Integer number1 = integerOptional.orElse(5);
Integer number2 = integerOptional.orElseGet(() -> 5);
```

이 경우는 orElse()가 좀 더 바람직한 방법이라고 보인다.

**2-4. 활용**

이제 위에서 봤던 깊은 의심 패턴 예제를 Optional로 바꿔보자.

```
Optional<Object> objectOptional = Optional.empty();

Object obj = objectOptional.map(Object::getObject)
                     .map(Object::getObject)
                     .orElse(null); // orElse(new Object), orElseGet(Object::new)
```

Optional 내부에있는 Object에 getter를 연속적으로 호출한다. 메서드가 진행되면서 중간에 null이 발견되면 NPE없이 orElse()가 호출되게된다. 어차피 값이 1개뿐인 filter보다 Optional에서는 map이 거의 주로 사용될 듯하다.

**3. 기본형 Optional**

Stream은 Auto Boxing, Auto Unboxing비용을 줄이기위해, 그리고 좀 더 기본형에 특화된 API 제공을 위해 기본형 스트림을 제공한다. IntStream, DoubleStream이 대표적인데 Optional도 기본형 Optional을 제공한다.

OptionalInt, OptionalDouble, OptionalLong이 있으며 사용법은 크게 다르지 않다.

```
OptionalInt optionalInt1 = OptionalInt.empty();
OptionalDouble optionalDouble1 = OptionalDouble.empty();
OptionalLong optionalLong1 = OptionalLong.empty();
```

다만 스트림같은경우 자료구조를 다루는 API이기때문에 대량의 형변환 비용을 줄이는 이점이 있는 반면 Optional은 어차피 내부에 1개의 레퍼런스만을 갖고있기때문에 기본형 사용의 이점이 크게 반감된다. 거기다 기본형 Optional과 Optional은 관계도 없어 형변환이 되는것도 아니기때문에 혼용한다면 불편함만 생길수있어 기본형스트림에 비해서는 그다지 환영받지 못하는 것 같다.

<https://multifrontgarden.tistory.com/131>


# JPA


# java bean mapper와 DTO

## Overview

spring, jpa 기반으로 개발할때 도움이 될 수 있는 java bean mapper 라이브러리를 소개합니다.

아래와 같은 간단한 JPA Entity 객체를 가지고 설명합니다.

![](https://t1.daumcdn.net/cfile/tistory/9986C3365AAFA07B2A)

![](https://t1.daumcdn.net/cfile/tistory/991412365AAFA07C0B)

Http 기반의 api 를 개발할때 필요에 따라 JPA Entity 를 바로 api 의 응답으로 내보내는 경우가 있습니다. 아래는 샘플 코드입니다. ![](https://t1.daumcdn.net/cfile/tistory/996149345AAFA0EB25)

그러면 다음 그림과 비슷한 결과를 응답하게 됩니다.

![](https://t1.daumcdn.net/cfile/tistory/99BA10375AAFA10528)

이럴 경우에 발생하는 순환 참조 문제같은 몇가지는 jackson json 라이브러리가 해결해주기도하지만 일반적으로 아주 작은 프로젝트가 아니라면 추천할만한 방식이 아닙니다. JPA Entity 도메인 데이터와 api 응답 데이터간에 생명주기가 틀리기 때문입니다.

## 데이터 생명주기

프로젝트의 초기에는 요구사항에 잘 맞춰서 데이터베이스가 설게되기 때문에 이렇게 Entity 를 바로 응답해도 큰 문제가 안되지만 프로젝트가 실제 운영을 시작한후에 발생하는 요구사항을 위한 기능추가가 반복되다보면 처음 설계된 데이터베이스 구조로는 수용이 불가능한 경우가 발생하기 시작합니다. 이때부터 다양한 코딩 기법을 동원하여 문제를 해결하게 되는데, 데이터베이스를 통한 물리적 관계(relation)를 이용하지 않고 논리적인 관계 즉 프로그램 코드 상으로만 연관관계를 형성하게 되는 데이터들이 발생하게 됩니다.

이렇게 처리할 수 밖에 없는 이유는 관계형 데이터베이스는 일반적으로 한번 만들어져서 데이터가 쌓이기 시작하면 큰 수준의 변경이 거의 불가능하기 때문입니다. 데이터베이스는 한번 서비스를 시작한 이후에는 거의 바뀌지 않지만 서비스의 요구사항은 계속해서 추가, 변경을 반복하면서 이애 대응하는 api 응답 데이터 포맷은 더 짧은 주기에 변할 수 있습니다. 그래서 JPA Entity 객체를 바로 api 응답으로 사용하는 것은 적절하지 않습니다.

이렇게 생명주기가 다른 데이터베이스 모델과 api 응답 데이터 간에 사이를 매꿔주는 역할을 하는 것이 바로 DTO(data transfer object) 입니다. JEE(java enterprise edition) 에서 소개되었으며 VO(value object) 라고도하는 DTO 는 model -> service -> controller 로 이어지는 MVC 어플리케이션에서 계층간에 데이터를 전달할 목적으로 사용합니다. 간단하게 말하면 DTO는 로직을 포하하지 않고 데이터의 전달만을 목적으로 사용하는 객체입니다.

도메인 모델 데이터를 DTO를 통해서 전달하게 되면 원래의 데이터에 넣고 빼거나 조합거나, 혹은 또 다른 데이터와 병합하거나 하는 등의 조작을 통해 데이터 포맷을 효과적으로 조정할 수 있습니다.

서버 api 의 응답 데이터를 DTO를 이용해서 만들게 되면 도메인 모델과 비지니스 로직간의 생명주기 간극을 훌륭하게 매꿀 수 있습니다.

## DTO 사용하기

위의 Entity 에 대해서 아래와 같은 DTO 를 만들고 어떻게 사용하는지 확인해보겠습니다.

![](https://t1.daumcdn.net/cfile/tistory/99E0C9335AAFA12733)

![](https://t1.daumcdn.net/cfile/tistory/99E5BA335AAFA1271D)

1.

### 1. Brute-force 방식 DTO 객체 사용

1.

먼저DTO  객체를 가장 단순게 사용하는 코드를 한번 보겠습니다.

![](https://t1.daumcdn.net/cfile/tistory/99EC34415AAFA15527)

위와 같이 작성후 실행해보면 아래와 같은 응답을 받게 됩니다.&#x20;

![](https://t1.daumcdn.net/cfile/tistory/99021F345AAFA16D2F)

jpa entity 에는 있지만 DTO 에서는 정의 하지 않은 필드가 응답에 포함되지 않았습니다. 이와 같이 일차적으로 DTO는 entity 객체에서 불필요한 정보를 제외하고 값을 전달하는 마스킹의 역할을 하게 됩니다.

위 코드는 잘 작동하지만 이런 방식은 지루한 반복작업이 필요한 코드가 만들어질 뿐 아니라 어플리케이션의 유지보수 관점에서도 문제가 있습니다. 만약 새로운 기능 개발로 인해 Entity 에 변경이 생기고 DTO 도 같이 수정 되야 한다면 위 코드도 추가적인 필드 매핑 작업이 필요 합니다. 또 만약 이런 매핑코드가 여러곳에 존재한다면 각 코드들을 모두 추적하여 수정을 해야 합니다. 이런 방식은 유지보수과정에서 오류를 유발할 가능성이 높고 개발자를 피곤하게 만듭니다.

1.

### 2. Bean utils 를 사용

1.

다음으로 생각해볼 수 있는 방법은  Bean util 객체를 사용하는 것입니다. Apache commons 나 spring 에   BeanUtils 객체가 있습니다. Source 객체에 있는 필드들의 값을 target객체의 필드에 똑같이 set 해주는 역할입니다. 이를 이용한 코드는 아래와 같습니다.

![](https://t1.daumcdn.net/cfile/tistory/998F413E5AAFA18502)

&#x20;BeanUtils가 객체를 분석해서 동적으로 값 복사를 해주기 때문에 코드가 단순해졌습니다. 다만 이런 BeanUtil 들은 기본적인 기능만을 가지고 있어서 문자, 숫자같은 단순한 값복사는 잘되지만 그이상의 복잡한 상황에서는 제대로된 값 복사가 되지 않습니다. 위 코드를 실행한 결과는 아래와 같습니다.

![](https://t1.daumcdn.net/cfile/tistory/99135E365AAFA19B39)

위와 같이 String, Long 필드는 복사가 되었지만 MenuGroup Entity 값이 MenuGroupDto 로는 복사되지 않았습니다. 이름이 같아도 객체 타입이 다른 상황등은 해결해주지 못합니다.

1.

### 3. Java bean mapper 사용

1.

Java bean mapper 라이브러리는 java 객체간에 값을 전달하는 기능을 체계적으로 관리해주는 솔루션입니다. 찾아보면 여러 종류의 mapper 라이브러리가 있는데, 이글에서는 modelmapper ([http://modelmapper.org](http://modelmapper.org/)) 라는 라이브러리를 사용합니다.

사용하는 코드를 한번 보겠습니다.&#x20;

![](https://t1.daumcdn.net/cfile/tistory/997BAB395AAFA1AB14)

한줄로 간단하게 처리가 가능하고 BeanUtils 가 처리해주지 못하는 여러가지 상황에 대해서 값 복사를 해줍니다.

![](https://t1.daumcdn.net/cfile/tistory/991410365AAFA1BA11)

위와 같이 MenuGroup 객체의 값이 다른 타입인 MenuGroupDto 객체로 복사되었습니다.

이렇게 bean mapper가 지능적으로 값복사를 해줍니다.

대략적인 기능을 정리해보겠습니다.

*
* 필드의 이름이 같다면 다른 타입의 객체로도 매핑 가능
* 객체안에 중첩되어 있는 다른 객체나 List 같은 collection객체의 값을 계속 따라들어가면서 매핑 가능
* 설정을 통해 다양한  매핑 조건 설정가능
  * 값이 null 인 필드 복사 여부 설정
  * 특정 필드 매핑여부 결정
  * 지정한 다른 이름의 필드로 매핑
  * Access levle(public, protected, private..) 에 따라 매핑 여부 결정
* Custom 변환기를 통해 원하는 형태로 매핑 가능
* *

## 결론

프로젝트 진행 초기에 DTO를 만들면 도메인 Entity 객체와 거의 비슷한 클래스를 만들게 되어 지루하게 객체 생성을 반복하고 도메인 객체와 같은 값을 그대로 내보내게되는 경우가 대부분입니다. 때문에 DTO 객체의 효용성에 대해서 의문을 가지는 경우를 종종 보았습니다.

그러나 DTO객체 사용의 진가는 서비스 운영중에 확인이 됩니다. 앞서 말한데로 운영중인 서비스의 데이터 베이스구조를 크게 바꾸기가 어려운데, 비즈니스 요구사항은 보통개발 설계상 애로사항이나 적정 일정등이 고려되지 않고 결정됩니다. DTO가 이런 문제들을 해결하는데 도움을 줄것입니다. 물론 이런 데이터 조작이 많아 질 수록 프로그램의 최초 설계가 점점 깨져갈것입니다. 저는 이것도  프로그램의 생명주기상 자연스런 과정이고 개발자가 밤을 세는것 보다는 훨씬 낫다고 생각합니다.

### 참조

<http://blog.naver.com/kbh3983/220988245343>


# DAO - DTO - Entity의 차이

### DAO, DTO, Entity Class의 차이를 이해하다.

### Goal <a href="#goal" id="goal"></a>

> * DAO(Data Access Object)란 무엇인지 이해한다.
> * DTO(Data Transfer Object)란 무엇인지 이해한다.
> * Entity Class란 무엇인지 이해한다.
> * package 구조에 따른 흐름, 해당 package의 역할 및 기능을 이해한다.

### DAO(Data Access Object) 란? <a href="#daodata-access-object" id="daodata-access-object"></a>

**repository package**

* 실제로 DB에 접근하는 객체이다.
  * Persistence Layer(DB에 data를 CRUD하는 계층)이다.
* Service와 DB를 연결하는 고리의 역할을 한다.
* SQL를 사용(개발자가 직접 코딩)하여 DB에 접근한 후 적절한 CRUD API를 제공한다.
  * JPA 대부분의 기본적인 CRUD method를 제공하고 있다.
  * `extends JpaRepository<User, Long>`
* 예시(JPA 사용 시)

  ```
  public interface QuestionRepository extends CrudRepository<Question, Long> {
  }
  ```

### DTO(Data Transfer Object) 란? <a href="#dtodata-transfer-object" id="dtodata-transfer-object"></a>

**dto package**

* 계층간 데이터 교환을 위한 객체(Java Beans)이다.
  * DB에서 데이터를 얻어 Service나 Controller 등으터 보낼 때 사용하는 객체를 말한다.
  * 즉, DB의 데이터가 Presentation Logic Tier로 넘어오게 될 때는 DTO의 모습으로 바껴서 오고가는 것이다.
  * *로직을 갖고 있지 않는* 순수한 데이터 객체이며, getter/setter 메서드만을 갖는다.
  * 하지만 DB에서 꺼낸 값을 임의로 변경할 필요가 없기 때문에 DTO클래스에는 setter가 없다. (대신 생성자에서 값을 할당한다.)
* Request와 Response용 DTO는 View를 위한 클래스
  * 자주 변경이 필요한 클래스
  * Presentation Model
  * `toEntity()` 메서드를 통해서 DTO에서 필요한 부분을 이용하여 Entity로 만든다.
  * 또한 Controller Layer에서 Response DTO 형태로 Client에 전달한다.
* 참고 VO(Value Object) vs DTO
  * VO는 DTO와 동일한 개념이지만 read only 속성을 갖는다.
  * VO는 특정한 비즈니스 값을 담는 객체이고, DTO는 Layer간의 통신 용도로 오고가는 객체를 말한다.
* 예시

  ```
  @Getter
  @NoArgsConstructor
  @AllArgsConstructor
  public class UserDto {
    @NotBlank
    @Pattern(regexp = "^([\\w-]+(?:\\.[\\w-]+)*)@((?:[\\w-]+\\.)*\\w[\\w-]{0,66})\\.([a-z]{2,6}(?:\\.[a-z]{2})?)$")
    private String email;

    @JsonIgnore
    @NotBlank
    @Size(min = 4, max = 15)
    private String password;

    @NotBlank
    @Size(min = 6, max = 10)
    private String name;

    public User toEntity() {
        return new User(email, password, name);
    }

    public User toEntityWithPasswordEncode(PasswordEncoder bCryptPasswordEncoder) {
        return new User(email, bCryptPasswordEncoder.encode(password), name);
    }
  }
  ```

### Entity Class란 <a href="#entity-class" id="entity-class"></a>

**domain package**

* 실제 DB의 테이블과 매칭될 클래스
  * 즉, 테이블과 링크될 클래스임을 나타낸다.
  * Entity 클래스 또는 가장 Core한 클래스라고 부른다.
  * `@Entity`, `@Column`, `@Id` 등을 이용
* 최대한 외부에서 Entity 클래스의 getter method를 사용하지 않도록 해당 클래스 안에서 필요한 *로직 method을 구현한다.*
  * 단, *Domain Logic*만 가지고 있어야 하고 Presentation Logic을 가지고 있어서는 안된다.
  * 여기서 구현한 method는 주로 Service Layer에서 사용한다.
* 참고 Entity 클래스와 DTO 클래스를 분리하는 이유
  * View Layer와 DB Layer의 역할을 철저하게 분리하기 위해서
  * 테이블과 매핑되는 Entity 클래스가 변경되면 여러 클래스에 영향을 끼치게 되는 반면 View와 통신하는 DTO 클래스(Request / Response 클래스)는 자주 변경되므로 분리해야 한다.
  * Domain Model을 아무리 잘 설계했다고 해도 각 View 내에서 Domain Model의 getter만을 이용해서 원하는 정보를 표시하기가 어려운 경우가 종종 있다. 이런 경우 Domain Model 내에 Presentation을 위한 필드나 로직을 추가하게 되는데, 이러한 방식이 모델링의 순수성을 깨고 Domain Model 객체를 망가뜨리게 된다.
  * 또한 Domain Model을 복잡하게 조합한 형태의 Presentation 요구사항들이 있기 때문에 Domain Model을 직접 사용하는 것은 어렵다.
  * 즉 DTO는 Domain Model을 복사한 형태로, 다양한 Presentation Logic을 추가한 정도로 사용하며 Domain Model 객체는 Persistent만을 위해서 사용한다.
* 예시

  ```
  @Entity
  @Getter
  @AllArgsConstructor
  @NoArgsConstructor
  @EqualsAndHashCode
  @ToString
  public class User implements Serializable {
    private static final long serialVersionUID = 7342736640368461848L;

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @JsonProperty
    private Long id;

    @Column(nullable = false)
    @JsonProperty
    private String email;

    @Column(nullable = false)
    @JsonIgnore
    private String password;

    // @Override 
    // public boolean equals(Object o) { ... }
    // @Override
    // public int hashCode() { ... }
    // @Override
    // public String toString() { ... }
  ```

### 전체 구조 (package 기준) <a href="#package" id="package"></a>

![](/files/-LjApxyRHNKkYqodLPvA)

#### controller(web) <a href="#controllerweb" id="controllerweb"></a>

* 기능
  * 해당 요청 url에 따라 적절한 view와 mapping 처리
  * `@Autowired Service`를 통해 service의 method를 이용
  * 적절한 ResponseEntity(DTO)를 body에 담아 Client에 반환
* 예시 1)

  ```
  @Controller
  @RequestMapping("/")
  public class HomeController {
    @GetMapping
    public String home(HttpSession session) {
        if (!SessionUtil.getUser(session).isPresent()) {
            return "login";
        }
        return "index";
    }
  }
  ```

  * @Controller
    * API와 view를 동시에 사용하는 경우에 사용
    * 대신 API 서비스로 사용하는 경우는 @ResponseBody를 사용하여 객체를 반환한다.
    * view(화면) return이 주목적
* 예시 2)

  ```
  @RestController
  @RequestMapping("/api/users")
  public class ApiUserController {
    @Autowired
    private UserService userService;

    @PostMapping("/login")
    public ResponseEntity login(@RequestBody @Valid LoginDto loginDto, HttpSession session) {
        SessionUtil.setUser(session, userService.login(loginDto));
        return new ResponseEntity(HttpStatus.OK);
    }
  }
  ```

  * @RestController
    * view가 필요없는 API만 지원하는 서비스에서 사용 (Spring 4.0.1부터 제공)
    * @RequestMapping 메서드가 기본적으로 @ResponseBody 의미를 가정한다.
    * data(json, xml 등) return이 주목적: **return ResponseEntity**
    * 즉, @RestController = @Controller + @ResponseBody

#### service <a href="#service" id="service"></a>

* 기능
  * `@Autowired Repository`를 통해 repository의 method를 이용
  * 적절한 Business Logic을 처리한다.
  * DAO로 DB에 접근하고 DTO로 데이터를 전달받은 다음, 비지니스 로직을 처리해 적절한 데이터를 반환한다.
* 예시

  ```
  @Service
  public class UserService {
    @Autowired
    private UserRepository userRepository;
    @Resource(name = "bCryptPasswordEncoder")
    private PasswordEncoder bCryptPasswordEncoder;
    @Autowired
    private MessageSourceAccessor msa;

    public User save(UserDto userDto) {
        if (isExistUser(userDto.getEmail())) {
            throw new UserDuplicatedException(msa.getMessage("email.duplicate.message"));
        }
        return userRepository.save(userDto.toEntityWithPasswordEncode(bCryptPasswordEncoder);
    }
  }
  ```

#### repository(dao) <a href="#repositorydao" id="repositorydao"></a>

* 기능
  * 실제로 DB에 접근하는 객체이다.
  * Service와 DB를 연결하는 고리의 역할을 한다.
  * SQL를 사용(개발자가 직접 코딩)하여 DB에 접근한 후 적절한 CRUD API를 제공한다.
    * JPA 대부분의 기본적인 CRUD method를 제공하고 있다.
    * `extends JpaRepository<User, Long>`
* 예시 (JPA의 경우)

  ```
  public interface UserRepository extends JpaRepository<User, Long> {
  }
  ```

  ```
  public interface QuestionRepository extends CrudRepository<Question, Long> {
  }
  ```

#### dto(DTO Class)와 domain(Entity Class) <a href="#dtodto-class-domainentity-class" id="dtodto-class-domainentity-class"></a>

* 구체적인 내용과 예시는 위의 설명 참고

## References <a href="#references" id="references"></a>

> * <http://toby.epril.com/?p=99>
> * <http://lazymankook.tistory.com/30>


# Map Struct 참고

Entity - DTO를 연결하는 여러가지 방법

@Mapper interface를 생성하는 방식\
<https://hellokoding.com/mapping-jpa-hibernate-entity-and-dto-with-mapstruct/>

BeansUtils를 이\
<http://appsdeveloperblog.com/dto-to-entity-and-entity-to-dto-conversion/>

ModelMapper 라이브러리를 이용\
<https://yonguri.tistory.com/entry/Entity-DTO-%EA%B7%B8-%EC%82%AC%EC%9D%B4%EC%9D%98-ModelMapper-%EC%9D%B4%EC%95%BC%EA%B8%B0>


# Date의 흐름

Model을 하는 부분에 대해서는 한가지만은 확실히 나올 수 있습니다. \
\
"각자의 영역으로 분리"\
\
이것은 DB를 다루는 Model 뿐 아니라, \
모든 객체와 개발에서의 Layer가 지켜야지 되는 원칙이라고 할 수 있습니다. \
일반적으로 우리가 사용한 Model은 다음과 같은 package로 나뉠 수 있습니다. \
\
com.......entities\
com........vo\
com........dto\
com........dao\
com........repositories\
com........services\
\
구성될 수 있는 package에 대해서 다시 정리하고,\
최종적인 web application의 데이터의 흐름에 대해서 알아보도록 하겠습니다. \
\
**Packages**\
\
**entities package**\
Hibernate와 같은 ORM framework를 사용하는 경우, object와 persistence model간의 관계를 구성한 ORM 객체가 위치하는 영역입니다. 또는 Table에 1:1 mapping을 해서 구성하는 경우도 있습니다. 그렇지만, entities를 사용한다는 것은 기본적으로 DDD를 사용하는 것과 동일합니다. 객체과 그 객체의 관계에 대한 구성을 코드에 녹여내는 방식을 주로 사용하게 됩니다. \
\
**vo package / dto package**\
vo(value object), dto(data transfer object)의 경우에는 일반적으로 persistence model에서 넘어오는 값들을 단순 parsing할 때 사용됩니다. 여기에 가장 대표적인 기술로 mybatis를 소개하였고, 이는 db에서 얻어오는 값을 view에서 단순 표시하기 위한 방법으로도 자주 사용하게 됩니다. 이 두 package는 myBatis를 Domain Layer의 Framework로 사용하는 경우에는 거의 필수로 사용됩니다. 또는 개발시에 Model 영역에서 Controller/View 로 데이터를 넘길때, DTO 객체를 만들어서 넘길때 사용되기도 합니다. 이때는 vo가 View Object의 의미로 사용되기도 합니다.\
\
**dao**\
직접 DB에 query를 보내고, entity, vo, dto를 얻어오는 영역입니다. DB를 사용하는 기술에 따라 다르게 구성이 되는 것이 일반적이며, CRUD에 대한 모음으로도 구성될 수 있습니다. 단순 CRUD가 이루어진다고 해도, DB에 대한 기술적 영역이 바뀌게 될 가능성은 항상 열어두고 작업을 하는 것이 좋을 것 같습니다. 그렇기 위해서는 interface로 정의된 dao를 사용하는 것이 보다더 효과적으로 구성이 가능하게 됩니다. dao로 지정하게 되는 것은 controller, domain 모두에서 dao를 사용하겠다는 의미입니다. Hibernate와 같은 ORM을 사용하는 경우에는 repository pattern으로 접근하는 것이 더 좋습니다.\
\
**repositories**\
Repository는 Dao와 매우 유사한 개념입니다. 이 두 용어의 차이점은 기능이 아닌, 사용되는 코드의 위치입니다. Domain Layer에서만 사용되는 경우에는 repository, Domain뿐 아니라 모든 영역에서 사용된다면 dao로 개발하게 됩니다. 이 둘의 차이는 Model을 풀어가는 pattern의 차이입니다. 보다더 pattern 상위적인 개념이 Repository이고, Dao는 database 적 개념이 강한 Object라고 생각하면 좀 더 이해가 쉬울 것 같습니다. \
\
**services**\
business logic 영역입니다. 여러개의 dao object들과 entity 또는 vo/dto object들을 얻어내고, 그 객체들간의 로직이 구성되는 영역입니다. 일반적으로 transaction의 단위 영역이 된다는 것을 명심해주세요. 위의 구성에서 결국은 model은 3개 이상의 package로 구성이 됨을 알 수 있습니다. 남의 코드를 보더라도 대부분 위와 비슷한 구성으로 package가 구성된 경우가 많으니 참고바랍니다. \
**Domain Framework의 비교**\
\
다음은 지금까지 알아본 Model을 접근하는 Framework의 조합에 대해서 한번 정리해보도록 하겠습니다.&#x20;

| Framework                                    | 특징                                                                              | 장점                                                                                                                                                                                                                                                                                                                                                                                                                                                        | 단점                                                                                                                                                                                      |
| -------------------------------------------- | ------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| No Framework - PreparedStatement 를 이용하는 방법   | 1. Native Query 구성                                                              | 1. native query를 이용한 학습 시간의 단축                                                                                                                                                                                                                                                                                                                                                                                                                            | <p>1. 오타가 발생하는 경우, query를 실행하기 전까지 에러를 확인할 수 없다.<br>2. 중복 코드가 많이 발생된다.<br>3. connection의 관리를 비롯한 Transaction의 처리를 모두 수동으로 해줘야지 된다.<br>4. java 코드에 sql 코드가 들어가기 때문에 관리 및 debug가 힘들다.</p> |
| JdbcTemplate                                 | <p>1. Spring JdbcTemplate 이용<br>2. DataSource 이용<br>3. Native Query<br><br></p> | <p>1. native query를 이용한 학습 시간의 단축<br>2. connection 관리<br>3. Transaction 관리</p>                                                                                                                                                                                                                                                                                                                                                                            | <p>1. 오타가 발생한 경우, query를 실행하기 전까지 에러를 확인할 수 없다.<br>2. 중복 코드가 많이 발생된다.<br>3. java 코드에 sql 코드가 들어가기 때문에 관리 및 debug가 힘들다.</p>                                                              |
| Hibernate                                    | <p>1. HQL, Criteria<br>2. DataSource<br>3. Spring Transaction</p>               | <p>1. 다양한 reference<br>2. 객체를 이용한 query의 처리로 인하여, 코드양을 줄일 수 있다.<br>3. 프로그램 설계 및 구성 부분에 대해서 장점을 갖는다. (DDD와 같은 객체 지향적 구성 가능)<br>4. Table의 변경 또는 DB의 변경에 유연한 장점을 갖고 있다.<br>5. 동적 query가 자유스럽다.</p>                                                                                                                                                                                                                                                           | <p>1. 학습 시간의 소요<br>2. criteria, HQL 모두 오타에 취약한 구조<br>3. 단순 CRUD에 대한 코드양 증가 - 1, 2번 항목 보다는 코드 양이 적다.</p>                                                                                 |
| Hibernate + queryDSL                         | <p>1. JQL, Criteria<br>2. DataSource<br>3. type-safe<br>4. code generate</p>    | <p>1. 다양한 reference<br>2. 객체를 이용한 query의 처리로 인하여, 코드양을 줄일 수 있다.<br>3. 프로그램 설계 및 구성 부분에 대해서 장점을 갖는다. (DDD와 같은 객체 지향적 구성 가능)<br>4. Table의 변경 또는 DB의 변경에 유연한 장점을 갖고 있다.5. 오타가 발생할 수 없는 type-safe 한 query를 작성할 수 있다.6. sql query와 비슷한 문법으로, 학습에 도움을 줄 수 있다.</p>                                                                                                                                                                                               | <p>1. 학습시간의 소요<br>2. 단순 CRUD에 대한 코드양 증가<br>2. 설정이 까다롭다.</p>                                                                                                                             |
| Hibernate + JPA + queryDSL + Spring Data JPA | <p>1. JQL, Criteria<br>2. DataSource<br>3. type-safe4. code generate</p>        | <p>1. JPA - java 표준<br>2. 객체를 이용한 query의 처리로 인하여, 코드양을 줄일 수 있다.<br>3. 프로그램 설계 및 구성 부분에 대해서 장점을 갖는다. (DDD와 같은 객체 지향적 구성 가능)<br>4. Table의 변경 또는 DB의 변경에 유연한 장점을 갖고 있다.5. 오타가 발생할 수 없는 type-safe 한 query를 작성할 수 있다.6. sql query와 비슷한 문법으로, 학습에 도움을 줄 수 있다.7. CUD, select 문의 코딩양이 현격하게 줄어든다.8. Hibernate 코드 역시 사용 가능하고 유연한 방법으로 대처가 가능하다.9. Repository Interface code에 의한 가독성 향상10. Spring @MVC의 @InitBinder와의 연동이 가능하기 때문에, Converter를 작성하는 시간을 줄일 수 있다.</p> | <p>1. 학습 시간의 소요<br>2. 설정이 까다롭다.<br>3. repository 객체를 두개를 만드는 것이 필요하다. (repository, repositorysupport)<br><br></p>                                                                       |
| myBatis (iBatis)                             | <p>1. Native Query<br>2. DataSource<br>3. ibatis-spring 이용</p>                  | <p>1. 국내의 많은 사용자<br>2. sql query의 관리를 하는 것이 가능하다.<br></p>                                                                                                                                                                                                                                                                                                                                                                                                 | <p>1. 객체가 아닌 VO type의 이용으로 인한, 프로그램 architecture의 발전 가능성이 낮아짐<br>2. java 언어 뿐 아니라, sql 로의 확장으로 인하여 관리 코드가 늘어남</p>                                                                       |

\
지금 전 이정도로 정리를 해봤는데, 다른 분들은 어떻게 정리를 할 수 있을까요? 각 방법에 대한 장/단점을 파악하는 것이 중요합니다. 적어도 이곳에 있는 분들만이라도요.그리고, 만약에 외부 Project에 참여하게 되는 경우에는, 그 Project에 맞는 개발 방법을 이용해서 개발을 하는 것이 중요합니다.\
\
\
**Web Application에서의 데이터의 흐름** \
Domain Model을 이용해서 Web Application을 구성하는 방법은 두가지가 있습니다. 원칙을 지키고자 하는 Pure Domain Model과 Domain Model의 자유로운 사용이 특징인 Domain Model Everywhere입니다. 먼저 Domain Model Everywhere에 대해서 알아보도록 하겠습니다. \
**Domain Model Everywhere**<br>

![](https://t1.daumcdn.net/cfile/tistory/217CF23C523001CF31)

\
\
\
Repository, Service, Controller, View가 모두 Entity Model을 가지고 동작하는 방식입니다. 이 방식은 Domain의 Entity 객체가 Repository 뿐 아니라, Controller, View에 표현되는 방법까지 가지게 되게 됩니다. 이렇게 되면 Entity Model이 매우 커지게 됩니다. 그리고 객체의 "단일책임원칙"을 위반하게 되는 단점을 가지고 있습니다. 이 방법의 장단은 다음과 같습니다. \
장점 :# 개발하기에 빠르고 편리함\
단점 : # 복잡한 View의 표현이 매우 힘듭니다.# Domain Model에서 View Logic이 포함되기 때문에 Domain Model의 순수성이 떨어지고 객체의 단일 책임원칙을 위반하게 됩니다.# REST 서버와 같이 외부와의 통신 API에 사용하게 되는 경우에는 Domain Model이 변경되면 API가 바뀌게 되기 때문에 큰 문제를 야기할 수 있습니다.\
장점보다는 단점이 좀더 많아보입니다. 보시면 전에 제가 .NET으로 만든 mobile framework가 바로 이 모습입니다. 원칙상으로는 다음 방법을 더 권장하는 편입니다. \
\
**Pure Domain Model**\
![](file:///C:/Users/XYZLA_~1/AppData/Local/Temp/enhtmlclip/Image\(12\).png)![](https://t1.daumcdn.net/cfile/tistory/210EBB3C523001D029)\
Domain Model은 서비스와 Repository에서만 사용하고, Controller와 View에서는 DTO를 새로 만들어서 사용하는 방법입니다. 이는 Domain의 순수성을 지키게 되는 큰 장점을 가지고 있습니다. View에서만 DTO를 사용하는 것이 BL의 변화에 따른 View의 변경을 격리할 수 있는 좋은 방법이 됩니다. \
장점 : # 순수한 Domain Model - 객체지향적인 OOP 모델\
단점# DTO를 따로 만드는 것이 귀찮고, 성가시고, 괴롭다.# DTO는 또 다른 중복 코드를 만들어낸다. # DTO와 Domain Model간의 Convert를 따로 만들어줘야지 된다. # anti-pattern 중 하나입니다. 사용하지 않아야지 된다고들 이야기합니다.\
Domain Model Everywhere pattern의 경우에는 anti-pattern이라고도 말하는 사람이 있을정도로 호불호가 매우 강하게 갈리는 pattern중 하나입니다. 개인적으로도 최대한 Pure Domain Model을 이용해서 개발하는 것이 좋다고 생각합니다. \
\
\
지금까지 개발을 해본 결과. 다음과 같은 상황에서는 반드시 DTO를 사용해야지 됩니다. \
1\. REST 서버와 같은 API의 response.: Domain Model은 자주 바뀔 수 있습니다. 그렇지만, client와 통신을 하게 되는 API의 response는 바뀌면 문제가 생기게 됩니다. 따라서 이 둘은 반드시 분리를 해야지 됩니다.\
2\. 여러 Domain Model이 결합되어서 만들어지는 새로운 View에 대한 DTO: View가 너무너무나 복잡해서 서로간에 연관성이 없는 Domain Model을 response로 보내줘야지 되는 경우에는 DTO를 만드는 것이 좋습니다. \
이 두가지 경우를 제외하고..... DTO를 모두 만드는 것은 다음과 같은 문제가 발생합니다. \
1\. 객체가 너무 많이 만들어집니다. 지금 DataWindow의 package와 같이 각 SP의 숫자만큼의 package같이 객체들이 구성되게 됩니다. 이는 객체의 naming rule 및 관리가 매우 힘들어지는 결과를 가지고 옵니다.2. 많이 만들어진 객체 숫자 만큼의 Converter가 필요합니다. Domain Model을 DTO로 바꿔주고 DTO를 Domain Model로 바꿔주는 Convert 숫자가 필요하게 됩니다.3. 위 이유로 관리 포인트가 3개로 늘어나게 됩니다. - Domain Model, DTO, Converter\
\>> 배보다 배꼽이 더 큰 사태가 발생할 수 있습니다. Domain Model Everywhere가 절대로 좋은 것은 아닙니다. 그렇지만 개발 시간과 관리 포인트를 생각해서 Domain Model Everywhere로 만들고, 그 객체들을 차츰차츰 DTO로 변환시켜가는 과정이 Application을 더욱더 깔끔하게 만드는 과정이 되게 된다고 생각합니다.\
\
결론입니다. \
\# 처음에는 Domain Model Everywhere로 Project를 시작합니다.# Domain Model로 처리하기 힘든 경우에는 DTO를 사용해서 객체의 변환을 합니다.# 최대한 Pure Domain Model을 유지하기 위해서 계속 노력합니다.\
\* Domain Model Everywhere를 지원하기 위해서 Spring은 JPA Model Object를 위한 OpenEntityManagerInViewFilter와 Hibernate를  위한 OpenSessionInViewFilter를 지원하고 있습니다. \
\
출처: [https://netframework.tistory.com/entry/16-Model-기술-정리-및-비교](https://netframework.tistory.com/entry/16-Model-%EA%B8%B0%EC%88%A0-%EC%A0%95%EB%A6%AC-%EB%B0%8F-%EB%B9%84%EA%B5%90) \[Programming is Fun]<br>


# Session 관리


# Storage


# localStorage/sessionStorage 클라이언트에 정보 저장

웹에서 데이터를 저장하는 방법 중 **서버가 아닌 클라이언트에서 가능한 방법**을 알아보려고 합니다.

웹사이트의 정보를 저장하기 위해서 반드시 서버가 필요한 것은 아닙니다. 사용 목적에 따라 클라이언트 저장소 역시 좋은 역할을 할 수 있습니다. 일반적으로 클라이언트 저장소를 생각하면 가장 먼저 쿠기 저장소를 떠올리겠지만 html5에는 좀 더 효과적인 클라이언트 저장 공간을 제공하는데 어떤 것이 있는지 알아보면 다음과 같죠.

! localStorage, sessionStorage API 알아보기\
html5에서는 좀 더 쉽고 간단한 저장소 제공을 위해 새로운 localStorage와 sessionStorage API를 제공합니다. 둘 다 저장 공간으로 사용할 수 있는데 이 둘의 가장 큰 차이점이라면 저장소로서의 기능은 대부분 동일하며 단지  sessionStorage의 경우 세션이 종료되면 저장된 데이터도 함께 사라진 다는 점이 다른 점입니다. 그렇다면 이 메소드는 어떤 경우에 사용할 수 있을까요? 쿠키를 포함해 서로의 장단점에 대하여 비교하며 알아보도록 하겠습니다.

## **# 쿠키, 서버 저장소, localStorage, sessionStorage 차이점 및 장단점**

쿠키의 경우 저장 가능한 공간의 크기가 가장 작고 하나의 텍스트 형태로 저장된다. 그렇기 때문에 비교적 간단한 텍스트 타입의 데이터를 저장하는데 용이하다. 이런 값으로는 팝업의 이름을 예로 들 수 있다. 팝업의 경우 24시간 동안 동작하지 않도록... 즉 열지 않도록 설정하는 경우가 많은데 이때 쿠키의 변수를 저장하고 24시간의 만료시간을 설정하는 것이 좋다.

서버저장소는 데이터의 동기화가 필요하기 때문에 꼭 필요한 경우가 아니라면 전송을 최소하 하는 것이 좋을 것이다. 이런 이유로 서버 저장소는 데이터의 크기가 매우 크거나 반드시 저장해야 하는 경우에만 사용된다.

데이터가 크기나 만료시간을 고려했을때 위 저장소의 중간 정도의 애매한 경우가 있을 수 있다. 저장소가 필요하지만 다수의 컬럼이 필요한 경우가 아니만 단순한 형태라면 localStorage, sessionStorage를 사용하는게 효과적인 방법이다.

#### **! localStorage, sessionStorage를 어떤 경우에 사용할까**

대부분의 데이터의 경우 데이터베이스에 저장하므로 로컬 및 세션 스토리지가 사용되는 곳은 그리 많지 않다. 대부분 임시적인 용도 또는 캐시, history 기능을 위하여 사용된다. 아래의 경우가 그 예이다.

* 글 작성 중간 중간에 잃어버리지 않기 위한 임시 저장용도
* 장바구니나 좋아하는 콘텐츠 등 수시로 변경되는 경우
* 방문자의 이동 경로를 저장하였다가 이동할 경우
* 그 외 서버에 반드시 저장할 필요가 없는 경우

그럼 아래에서는 예제를 통하여 더욱 자세하게 알아보자.

## # localStorage 예제 및 코드보기

앞에도 언급했지만 위 두가지 객체의 차이점은 **세션에 따라서 데이터가 유지되는가 안되는가**로 구분할 수 있다. 그렇기 때문에 사용하는 방법도 거의 동일하다 하겠다. 아래는 localStorage객체의 사용로 자주 사용되는 메소드, 프로퍼티를 함께 알아보자.

### # 데이터의 저장 setItem()

특정값의 데이터를 저장하기 위한 방법으로 아래와 같이 메소드를 사용하거나 리터럴하게 사용하는 두 가지 방법이 가능하다. 아래 예제는 test란 이름의 key값을 정하고 이에 123이란 값을 저장하는 예제이다.

```
localStorage.test = '123';
localStorage.setItem('test', '123');
```

여기서 사용된 setItem() 메소드는 값을 저장하는 간단한 방식으로 key값에 매칭된 value를 인자로 넘겨주면 된다. 매우 간단하게 저장할 수 있다. 아래는 등록된 key값의 value를 얻기 위한 방법이다.

### # 데이터 불러오기, getItem()

```
localStorage.test;
localStorage.getItem('test');
```

역시 직접 키값을 입력하거나 getItem() 메소드를 사용하는 방법 두 가지가 있다. 아래는 특정 key 값을 지우는 방법에 대하여 알아보자.

참고로 전체 localStorage에 담겨있는 모든 값을 확인하려면 getItem()에 아무런 값도 넘기지 않으면 모든 정보를 받아올 수 있다.

```
localStorage.getItem();  //  전체 값 받아오기
```

그럼 아래는 데이터를 삭제하는 방법이다.

### # 데이터 삭제, removeItem()

아래는 test라는 키값에 저장된 값을 삭제하는 예제이다.

```
localStorage.removeItem('test');
// localStorage 객체에서 원하는 값을 지우는 방법
localStorage.clear();
// 한번에 저장된 모든 값을 삭제하는 방법
```

데이터를 삭제할 경우 removeItem()을 사용하여 하나만 제거할 수 있고 아니면 모든 값을 지울 수 있눈 clear() 메소드도 존재한다.

### # sessionStorage 예제보기

세션을 기준으로 데이터가 저장 및 유지되기 때문에 이 객체에 저장된 값은 **일시적인 수명**을 가지게된다. 만약 세션이 종료되는 경우... 브라우저를 닫거나 일정시간 아무런 동작도 하지 않는다면 당연히 저장된 값은 삭제될 것이다.

만약 데이터가 삭제되지 않길 바란다면 위에 나와있는 localStorage를 사용하기 바란다. 아래의 사용방법은 localStorage와 거의 동일한 메소드와 프로퍼티를 갖는다. 즉, 동일한 방법으로 사용이 가능하다 하겠다. 그럼 아래 예제를 참고하자.\
(참고로 세션을 사용한 저장방법은 보안 및 중요한 데이터의 경우 무방비로 노출되는 경우를 피하기 위해서 사용되는 경우가 많다.)

```
sessionStorage.setItem("domain", "webisfree.com");
//  domain이란 키(key) 값을 사용하여 해당 텍스트를 저장함
sessionStorage.getItem("domain");
// 키에 저장된 값을 반환. 여기서는 webisfree.com 출력됨
sessionStorage.removeItem("domain");
// domain 키와 데이터 모두 삭제
sessionStorage.clear();
// 저장된 모든 값 삭제
```

#### ! 참고사항

인터넷 익스플로러 IE(Internet Explorer)는 10MB의 공간을 사용할 수 있고, 그 외 브라우저는 **대부분 5MB의 공간을 사용할 수 있다**. 사실 로컬 또는 세션스토리지 모두 사용되는 부분이 한정적이고 대부분 텍스트 공간을 차지하므로 용량이 부족하다던가의 이슈는 대부분은 없을 것이다.


# React 블로그 - 로그인 구현

[**https://electricburglar.tistory.com/155**](https://electricburglar.tistory.com/155)

**React로 블로그 만들기 #8**

&#x20;**- 로그인 구현(5) 새로고침 후 로그인 유지 -**

**1. 로그인 유지**

\- 로그인을 한다고 해도 자동으로 로그인 상태가 유지되지 않는다.\
\- 브라우저를 닫거나 새로고침할 시 스토어 값이 초기화되므로 페이지에 다시 들어왔을 때 로그인을 유지하는 로직이 필요하다.

\- TodoApp에서는 쿠키를 이용하여 로그인을 유지하였지만, 블로그 앱에서는 HTML5에서 제공하는Web Storage API를 사용한다.

\- 또한 로그아웃 시 소셜로그인으로 발급받은 토큰을 제거하도록 로직을 추가해야함.

**2. localStorage vs sessionStorage**<br>

\- Key와 Value 값으로 저장.

\- 문자열이 아닌 객체도 저장이 가능하며 5MB까지 저장 가능함.

\- 쿠키와 다르게 만료기간을 지정할 수 없으며, 서버로 전송되지 않음.

\- 자동로그인을 원하면 localStorage를 사용하고 브라우저를 닫으면 로그인이 풀리는 것을 원하면 sessionStorage를 사용

1\) localStorage

\- 사용자가 지우지 않는 이상 영구적으로 계속 브라우저에 남아있음.

\- 따라서 지속적으로 필요한 정보를 저장한다. (자동로그인 등)

2\) sessionStorage

\- 윈도우나 브라우저를 닫는 경우 사라지며 뿐만 아니라 브라우저 내에서 탭을 생성하는 경우에도 별도의 영역으로 할당됨.

\- 잠시동안 필요한 정보를 저장한다. (일회성 로그인 등)

**3. 구현하기**

1\) src/Components/Login.js

\- 소셜로그인 성공 시 받은 정보를 세션스토리지에 저장함.

```
import React, { Component } from 'react';
import { GoogleLogin } from 'react-google-login';
import KakaoLogin from 'react-kakao-login';
import styled from 'styled-components';
import { withRouter } from "react-router-dom";
class Login extends Component {
constructor(props) {
super(props);
this.state = {
id: '',
name: '',
provider: '',
 }
 }
// Google Login
responseGoogle = (res) => {
this.setState({
id: res.googleId,
name: res.profileObj.name,
provider: 'google'
 });
this.doSignUp();
 }
// Kakao Login
responseKakao = (res) => {
this.setState({
id: res.profile.id,
name: res.profile.properties.nickname,
provider: 'kakao'
 });
this.doSignUp();
 }
// Login Fail
responseFail = (err) => {
console.error(err);
 }
doSignUp = () => {
const { id, name, provider } = this.state;
window.sessionStorage.setItem('id', id);
window.sessionStorage.setItem('name', name);
window.sessionStorage.setItem('provider', provider);
this.props.onLogin();
this.props.history.push('/');
 }
render() {
return (
<Container>
<GoogleLogin
clientId={process.env.REACT_APP_Google}
buttonText="Google"
onSuccess={this.responseGoogle}
onFailure={this.responseFail}
/>
<KakaoButton
jsKey={process.env.REACT_APP_Kakao}
buttonText="Kakao"
onSuccess={this.responseKakao}
onFailure={this.responseFail}
getProfile="true"
/>
</Container>
 );
 }
}
const Container = styled.div`
 display: flex;
 flex-flow: column wrap;
`
const KakaoButton = styled(KakaoLogin)`
 padding: 0;
 width: 190px;
 height: 44px;
 line-height: 44px;
 color: #783c00;
 background-color: #FFEB00;
 border: 1px solid transparent;
 border-radius: 3px;
 font-size: 16px;
 font-weight: bold;
 text-align: center;
`
export default withRouter(Login);

```

2\) src/App.js

\- componentDidMount함수에서 세션스토리지에 id가 있는 경우 onLogin함수를 실행하고 없을 경우에는 onLogout함수를 실행하여 로그인을 유지함.

\- 또한 로그아웃 시, 소셜로그인으로 부터 전달받은 토큰을 제거하고 세션스토리지에 저장된 정보를 모두 삭제한다.

```
import React, { Component } from 'react';
import styled from 'styled-components';
import Header from './Layout/Header';
import Navigation from './Layout/Navigation';
import Router from './Routes/Router';
import Store from './Store/store';
class App extends Component {
constructor(props) {
super(props)
this.state = {
logged: false,
onLogin: this.onLogin,
onLogout: this.onLogout
 }
 }
// Login Func
onLogin = () => {
this.setState({
logged: true
 });
 }
// Logout Func
onLogout = () => {
this.setState({
logged: false
 });
const provider = window.sessionStorage.getItem('provider');
//Google AccessToken Remove
if(provider === 'google') {
const auth2 = window.gapi.auth2.getAuthInstance();
auth2.signOut().then(function() {
console.log('Goolge Logout.');
 });
 }
// Kakao AccessToken Remove
else if(provider === 'kakao'){
window.Kakao.Auth.logout(function() {
console.log("Kakao logout");
 });
 }
//SessionStorage Clear
window.sessionStorage.clear();
 }
componentDidMount() {
const id = window.sessionStorage.getItem('id');
if(id) {
this.onLogin();
 }
else {
this.onLogout();
 }
 }
render() {
const { logged, onLogout } = this.state;
return (
<Store.Provider value={this.state}>
<Layout>
<Header logged={logged} onLogout={onLogout}/>
<Navigation />
<Content>
<Router />
</Content>
</Layout>
</Store.Provider>
 );
 }
}
const Layout = styled.div`
 margin: 0 auto;
 display: flex;
 width: 100%;
 flex-flow: row wrap;
`
const Content = styled.div`
 margin: 0 auto;
`
export default App;

```


# Spring Security


# Spring Security 회원가입 / 로그인 구현

![](/files/-LjLxI4d6IDD6h-RzDCu)

스프링 부트 환경에서 BCryptPasswordEncoder를 이용한 회원가입 암호화 / Spring Security를 이용한 로그인 구현

## 시큐리티의 구조 <a href="#undefined" id="undefined"></a>

웹에서 스프링 시큐리티는 기본적으로 아래와 같이 필터 기반으로 동작한다.

많은 필터들이 존재하며, DispatcherServlet 을 호출하기 전에 거치게 된다.

setOrder로 필터간의 순서나 우선순위를 정해서 커스터마이징 할 수도 있다.

![](/files/-LjLxI4hs3lQ3InB0cRn)

## 프로젝트에 Spring Security 적용하기 <a href="#spring-security" id="spring-security"></a>

Maven

```
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-security</artifactId>
</dependency>
```

Gradle

```
compile("org.springframework.boot:spring-boot-starter-security")
```

위와 같이 디펜던시를 추가한 다음에 적절한 패키지를 생성하고, SecurityConfig 클래스를 추가한다. 패키지는 스프링 프로젝트의 패키지 하부에 있어야 한다.

```
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
}
```

위와같이 설정해주면, 이제 사이트 전체가 잠겨서, 비밀번호를 쳐야 접근할 수 있게 된다. 원활한 프로젝트 진행을 위해 일단 페이지의 인증을 해제하자.

```
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	
	@Override
	public void configure(WebSecurity web) throws Exception
	{
		web.ignoring().antMatchers("/css/**", "/script/**", "image/**", "/fonts/**", "lib/**");
	}
	@Override
	protected void configure(HttpSecurity http) throws Exception
	{
		http.authorizeRequests()
			.antMatchers("/admin/**").hasRole("ADMIN")
			.antMatchers("/**").permitAll();
	}
}
```

위처럼 자원에 대한 접근을 풀고, 밑에서는 세부적인 설정을 따로 넣어준다. 일단은 원활한 진행을 위해 모든 경로에 대해 permitAll을 줬다.

## 적용된 Security 기반 회원가입 로직 추가하기 <a href="#security" id="security"></a>

회원가입 이전에, 먼저 User정보를 구성할 테이블을 생성해야하는데 Security의 내부적으로 사용하는 클래스의 이름이 User이다. 겹치기 때문에 Account나 Member라는 이름을 사용해서 구현하자.

Member.java

```
@Getter
@Setter
@Entity
@EqualsAndHashCode(of = "uid")
@ToString
public class Member {
	
	@Id
	@GeneratedValue
	private Long id;
	
	@Column(nullable = false, unique = true, length=50)
	private String uid;
	
	@Column(nullable = false, length=200)
	private String upw;
	
	@Column(nullable = false, unique = true, length=50)
	private String uemail;
	
	@CreationTimestamp
	private Date regdate;
	
	@UpdateTimestamp
	private Date updatedate;
	
	@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)
	@JoinColumn(name="uid")
	private List<MemberRole> roles;
	
}
```

`@OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER)` 설정은, cascade의 경우에는 엔티티들의 영속관계를 한번에 처리하지 못하기 때문에 이에 대한 cascade 설정을 추가하는것이고, member와 member\_role을 둘다 동시에 조회하기 위해서 fetch 설정을 즉시 로딩으로 EAGER 설정을 주어야 에러가 발생하지 않습니다.

MemberRole.java

```
@Getter
@Setter
@Entity
@EqualsAndHashCode(of = "rno")
@ToString
public class MemberRole {
	@Id
	@GeneratedValue(strategy = GenerationType.IDENTITY)
	private Long rno;
	
	private String roleName;
}
```

MemberRepository.java

```
public interface MemberRepository extends CrudRepository<Member, Long> {
}
```

Member가 제대로 동작하는지 확인을 위한 테스트 코드 작성

```
@RunWith(SpringRunner.class)
@SpringBootTest
@Log
public class MemberRepositoryTest {
	@Autowired
	MemberRepository memberRepository;
	
	@Test
	public void insertTest() {
		for(int i=0; i<100; i++) {
			Member member = new Member();
			member.setUid("user" + i);
			member.setUpw("pw" + i);
			member.setUemail("hihi@" + i);
			MemberRole role = new MemberRole();
			if(i <= 80) {
				role.setRoleName("BASIC");
			}else if(i <= 90) {
				role.setRoleName("MANAGER");
			}else {
				role.setRoleName("ADMIN");
			}
			member.setRoles(Arrays.asList(role));
			memberRepository.save(member);
		}
	}
	
	@Test
	public void testMember() {
		Optional<Member> result = Optional.ofNullable(memberRepository.findOne(85L));
		result.ifPresent(member -> log.info("member " + member));
	}
}
```

## 회원가입 로직 구현하기 <a href="#undefined" id="undefined"></a>

위에서 제작한 Member와 MemberRole을 기반으로 회원가입 로직을 구현해보자.

MemberController.java

```
@Controller
@RequestMapping("/member")
public class MemberController {
	
	@Autowired
	MemberRepository memberRepository;
	
	@PostMapping("")
	public String create(Member member) {
		MemberRole role = new MemberRole();
		BCryptPasswordEncoder passwordEncoder = new BCryptPasswordEncoder();
		member.setUpw(passwordEncoder.encode(member.getUpw()));
		role.setRoleName("BASIC");
		member.setRoles(Arrays.asList(role));
		memberRepository.save(member);
		return "redirect:/";
	}
}
```

스프링 시큐리티에서 지원해주는 BCryptPasswordEncoder를 이용해 회원 비밀번호를 암호화하고, MemberRole을 정의해 Member에 넣어주고 save를 했다.

signUp.html

```
<form class="signup-form" action="/member" method="POST">
  <input type="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}" />
  <div class="row">
    <div class="input-field col s12">
      <input id="user_name" name="uid" type="text" class="validate"/>
      <label for="user_name">Username</label>
    </div>
  </div>
  <div class="row">
    <div class="input-field col s12">
      <input id="email" name="uemail" type="email" class="validate"/>
      <label for="email">Email</label>
    </div>
  </div>
  <div class="row">
    <div class="input-field col s12">
      <input id="password" name="upw" type="password" class="validate"/>
      <label for="password">Password</label>
    </div>
  </div>
  <input class="signup-btn waves-effect waves-light btn" type="submit" value="가입하기" />
</form>
```

각각 Member의 파라미터에 해당하는 값을 매칭하고, thymeleaf를 사용한 csrf 토큰 관련 설정을 추가해주면 된다. 이후 Get으로 signUp페이지에 연결하고 회원가입을 진행하면 정상적으로 회원가입이 될 거다.

![](/files/-LjLxI4uffUG9-P4oIW_)

정상적으로 구현했다면, 위와같이 암호화된 비밀번호가 저장된것을 확인할 수 있다.

## 스프링 시큐리티 로그인 기능 구현하기 <a href="#undefined" id="undefined"></a>

스프링 시큐리티에서 로그인 처리를 구현하려면 SecurityConfig에서 AuthenticationManagerBuilder를 주입해서 인증에 대한 처리를 해야 한다.

```
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
}
```

위와같은 설정을 SecurityConfig에 넣고, 저기서 UserDetailsService를 이용해 로그인 인증 처리를 해주어야 한다.

이 과정에서 우리의 데이터베이스를 바탕으로 인증을 처리하기 위해서는 UserDetailsService를 구현하고 이를 HttpSecurity객체가 사용하도록 지정하면 우리가 만든 인증 로직을 바탕으로 동작하게 된다.

CustomUserDetailsService를 구현하면서 MemberRepository와 연동해 우리의 데이터베이스에 저장된 회원정보를 바탕으로 인증을 구현해야하는데 스프링 시큐리티의 User와 우리의 Member, GrantedAuthority와 우리의 MemberRole의 타입이 일치하지 않는 문제가 생긴다.

```
public class CustomUserDetailsService implements UserDetailsService{
	
	@Autowired
	MemberRepository memberRepository;
	
	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		return null;//반환할 타입이 Member와 맞지 않는다.
	}
}
```

위처럼 loadUserByUsername 메소드는 UserDetails를 반환하는데, MemberRepository에서 얻어온 Member와는 타입이 맞지 않는다. 이 타입을 맞춰주기 위해 Spring Security의 User를 상속하는 커스텀 유저 클래스를 추가해주자.

* **SecurirtyMember 구현(SpringSecurity의 User 상속)**

```
@Getter
@Setter
public class SecurityMember extends User {
	private static final String ROLE_PREFIX = "ROLE_";
	private static final long serialVersionUID = 1L;
	
	public SecurityMember(Member member) {
		super(member.getUid(), member.getUpw(), makeGrantedAuthority(member.getRoles()));
	}
	
	private static List<GrantedAuthority> makeGrantedAuthority(List<MemberRole> roles){
		List<GrantedAuthority> list = new ArrayList<>();
		roles.forEach(role -> list.add(new SimpleGrantedAuthority(ROLE_PREFIX + role.getRoleName())));
		return list;
	}
}
```

위에서 SecurityUser의 생성자로 Member를 받아서 Spring Security의 User타입과 우리가 구현한 Member의 값들을 매칭시켜 주자.

* **CustomUserDetailsService 구현(UserDetailsService 구현)**

위에서 구현한 SecurityMember를 바탕으로 아까 타입이 맞지않아 구현하지 못한 CustomUserDetailsService를 구현하자.

```
@Service
public class CustomUserDetailsService implements UserDetailsService{
	
	@Autowired
	MemberRepository memberRepository;
	
	@Override
	public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
		return
				Optional.ofNullable(memberRepository.findByUemail(email))
				.filter(m -> m!= null)
				.map(m -> new SecurityMember(m)).get();
	}
}
```

`@Service` 어노테이션으로 빈으로 등록해주어야 정상적으로 작동하게 된다.

* **Config 파일에 인증 관련 설정 추가하기**

```
@Bean
public PasswordEncoder passwordEncoder() {
  return new BCryptPasswordEncoder();
}
@Autowired
public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
  auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());
}
```

auth에 userDetailsService에 우리가 만든 customUserDetailsService를 등록하고, passwordEncoder에 우리가 회원가입 로직을 만들때 사용한 Encoder를 등록해주자.

* **Login Url 등록하기**

```
@Override
protected void configure(HttpSecurity http) throws Exception
{
	http.authorizeRequests()
		.antMatchers("/admin/**").hasRole("ADMIN")
		.antMatchers("/**").permitAll()
		.and().formLogin()
		.loginPage("/login")
		.loginProcessingUrl("/login")
		.defaultSuccessUrl("/")
    	.failureUrl("/login")
    	.and()
    	.logout();
}
```

`loginPage`는 로그인 뷰 페이지를 연결하고, `loginProcessingUrl`은 Post로 로그인을 처리할 Url

`SucessUrl`은 로그인 성공 후 이동할 페이지, `failureUrl`은 실패 후 이동할 페이지를 지정한다.

```
<form class="login-form" method="POST">
  <input type ="hidden" th:name="${_csrf.parameterName}" th:value="${_csrf.token}"/> 
  <div class="row">
    <div class="input-field col s12">
      <input id="email" type="email" name="username" class="validate"/>
      <label for="email">Email</label>
    </div>
  </div>
  <div class="row">
    <div class="input-field col s12">
      <input id="password" type="password" name="password" class="validate"/>
      <label for="password">Password</label>
    </div>
  </div>
  <input class="login-btn waves-effect waves-light btn" type="submit" value="로그인" />
</form>
```

이제 로그인 뷰 페이지에 username으로 name을 지정해주어야 정상적으로 받을 수 있다.

그리고 csrf 관련 설정을 위해 thymeleaf를 이용해 csrf토큰을 추가해주어야 요청을 정상적으로 받을 수 있다.

## 로그인 성공시의 부가적인 작업 추가하기 <a href="#undefined" id="undefined"></a>

로그인 성공 시, 리다이렉트되기 전의 페이지로 이동시켜준다거나 하는 부가작업을 추가하기 위해서는 SecurityConfig의 successHandler에 핸들러를 등록해주어야 한다, 등록할 핸들러의 타입은 `AuthenticationSuccessHandler` 타입이기 때문에 `AuthenticationSuccessHandler`를 구현하면서 로그인 성공시 사용되는 핸들러인 `SavedRequestAwareAuthenticationSuccessHandler`를 상속받아 구현하고 SecurityConfig에 등록시켜 주면 된다.

그 전에, prevPage를 세션에 등록해서, 이전 페이지 정보를 저장해두자.

* **로그인 페이지 컨트롤러에서 세션에 이전 페이지 정보 넣기**

Referer 요청 헤더는 현재 요청된 페이지의 이전 페이지의 주소 정보를 포함한다. [Referer 관련 mozilla문서](https://developer.mozilla.org/ko/docs/Web/HTTP/Headers/Referer)

```
@GetMapping("/login")
	public String loginForm(HttpServletRequest req) {
		String referer = req.getHeader("Referer");
		req.getSession().setAttribute("prevPage", referer);
		return "login";
	}
```

* **이전 페이지 정보를 받아 Redirect하는 `SavedRequestAwareAuthenticationSuccessHandler` 구현하기**

`AuthenticationSuccessHandler`인터페이스를 구현하는 `SimpleUrlAuthenticationSuccessHandler`를 상속하는 `SavedRequestAwareAuthenticationSuccessHandler`를 상속해서 구현하면 된다.

```
public class CustomLoginSuccessHandler extends SavedRequestAwareAuthenticationSuccessHandler {
	public CustomLoginSuccessHandler(String defaultTargetUrl) {
        setDefaultTargetUrl(defaultTargetUrl);
    }
    @Override
    public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, 
    	Authentication authentication) throws ServletException, IOException {
        HttpSession session = request.getSession();
        if (session != null) {
            String redirectUrl = (String) session.getAttribute("prevPage");
            if (redirectUrl != null) {
                session.removeAttribute("prevPage");
                getRedirectStrategy().sendRedirect(request, response, redirectUrl);
            } else {
                super.onAuthenticationSuccess(request, response, authentication);
            }
        } else {
            super.onAuthenticationSuccess(request, response, authentication);
        }
    }
}
```

* **SecurityConfig에 해당 핸들러 등록하기**

SecurityConfig.java에 successHandler를 등록하고, 빈 형태로 등록해두면 된다.

```
http.successHandler(successHandler())
  
@Bean
public AuthenticationSuccessHandler successHandler() {
  return new CustomLoginSuccessHandler("/");//default로 이동할 url
}
```

이후 로그인 시에만 해당 페이지로 접근 가능하도록 막아두려면 아래와 같은 설정을 추가하면 된다.

```
.antMatchers("/board/**").authenticated()
```

이제 /board의 경로에는 로그인 시에만 접근이 가능하도록 막히게 된다.

다른 페이지에서 board에 접근해보고, 로그인 시에 원하는 페이지로 정상적으로 이동되는지 테스트해보자.

### 전체 SecurityConfig 파일 <a href="#securityconfig" id="securityconfig"></a>

```
@EnableWebSecurity
@EnableOAuth2Client
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	@Autowired
	CustomUserDetailsService customUserDetailsService;
	
	@Autowired
	OAuth2ClientContext oauth2ClientContext;
	
	@Override
	public void configure(WebSecurity web) throws Exception
	{
		web.ignoring().antMatchers("/css/**", "/script/**", "image/**", "/fonts/**", "lib/**");
	}
	@Override
	protected void configure(HttpSecurity http) throws Exception
	{
		http.authorizeRequests()
			.antMatchers("/admin/**").hasRole("ADMIN")
			.antMatchers("/board/**").authenticated()
			.antMatchers("/**").permitAll()
			.and().formLogin()
			.loginPage("/login")
			.loginProcessingUrl("/login")
			.defaultSuccessUrl("/")
			.successHandler(successHandler())
    			.failureUrl("/login")
    			.and()
    			.logout()
    			.and()
    			.addFilterBefore(ssoFilter(), BasicAuthenticationFilter.class);
	}
	
	@Bean
	public AuthenticationSuccessHandler successHandler() {
	    return new CustomLoginSuccessHandler("/defaultUrl");
	}
	
	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}
	
	@Autowired
	public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
		auth.userDetailsService(customUserDetailsService).passwordEncoder(passwordEncoder());
	}
	
	
	private Filter ssoFilter() {
	  CompositeFilter filter = new CompositeFilter();
	  List<Filter> filters = new ArrayList<>();
	  filters.add(ssoFilter(facebook(), "/login/facebook"));
	  filters.add(ssoFilter(github(), "/login/github"));
	  filter.setFilters(filters);
	  return filter;
	}
	
	private Filter ssoFilter(ClientResources client, String path) {
	  OAuth2ClientAuthenticationProcessingFilter filter = new OAuth2ClientAuthenticationProcessingFilter(path);
	  OAuth2RestTemplate template = new OAuth2RestTemplate(client.getClient(), oauth2ClientContext);
	  filter.setRestTemplate(template);
	  UserInfoTokenServices tokenServices = new UserInfoTokenServices(
	      client.getResource().getUserInfoUri(), client.getClient().getClientId());
	  tokenServices.setRestTemplate(template);
	  filter.setTokenServices(tokenServices);
	  return filter;
	}
	
	class ClientResources {
	  @NestedConfigurationProperty
	  private AuthorizationCodeResourceDetails client = new AuthorizationCodeResourceDetails();
	  @NestedConfigurationProperty
	  private ResourceServerProperties resource = new ResourceServerProperties();
	  public AuthorizationCodeResourceDetails getClient() {
	    return client;
	  }
	  public ResourceServerProperties getResource() {
	    return resource;
	  }
	}
	
	@Bean
	@ConfigurationProperties("github")
	public ClientResources github() {
	  return new ClientResources();
	}
	@Bean
	@ConfigurationProperties("facebook")
	public ClientResources facebook() {
	  return new ClientResources();
	}
	
	@Bean
	public FilterRegistrationBean oauth2ClientFilterRegistration(
	    OAuth2ClientContextFilter filter) {
	  FilterRegistrationBean registration = new FilterRegistrationBean();
	  registration.setFilter(filter);
	  registration.setOrder(-100);
	  return registration;
	}
}
```

#### [**https://xmfpes.github.io/spring/spring-security/**](https://xmfpes.github.io/spring/spring-security/)


# Untitled


# Untitled


