<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>gahyun_02.log</title>
        <link>https://velog.io/</link>
        <description>냥</description>
        <lastBuildDate>Wed, 26 Mar 2025 05:29:31 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>gahyun_02.log</title>
            <url>https://velog.velcdn.com/images/gahyun_02/profile/30dca8c1-87a9-44b4-81ce-caded35d3b50/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. gahyun_02.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/gahyun_02" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[알고리즘 코딩테스트를 위한 SQL 개념 정리]]></title>
            <link>https://velog.io/@gahyun_02/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%A5%BC-%EC%9C%84%ED%95%9C-SQL-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@gahyun_02/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%A5%BC-%EC%9C%84%ED%95%9C-SQL-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Wed, 26 Mar 2025 05:29:31 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>자주 출제되는 SQL 내장함수</p>
</blockquote>
<h3 id="날짜시간-데이터">날짜,시간 데이터</h3>
<ul>
<li>날짜 형식 변경<pre><code class="language-sql"># DATE_FORMAT(필드명, 형식)
DATE_FORMAT(CREATED_DATE, &quot;%Y-%m-%d&quot;)
# 날짜가 아닌걸 날짜로 바꿈.
TO_DATE(날짜, format)</code></pre>
</li>
<li>날짜 차이 계산<pre><code class="language-sql"># 30일을 기준으로 장기대여/단기대여 분류해 RENT_TYPE에 저장
CASE
  WHEN DATEDIFF(ENDTIME, STARTTIME)+1 &gt; 30 THEN &#39;장기대여&#39;
  WHEN DATEDIFF(ENDTIME, STARTTIME)+1 = 30 THEN &#39;일반대여&#39;
  ELSE &#39;단기대여&#39;
END RENT_TYPE
</code></pre>
</li>
</ul>
<h1 id="내가-지정한-단위-기준으로-차이-계산">내가 지정한 단위 기준으로 차이 계산</h1>
<p>TIMESTAMPDIFF(단위, 날짜1, 날짜2)</p>
<h1 id="날짜-더하기빼기">날짜 더하기/빼기</h1>
<p>DATE_ADD(DATE, INTERVAL 1 HOUR)
DATE_SUB(DATE, INTERVAL 3 HOUR)</p>
<pre><code>- 날짜 범위 계산
```sql
-- 2022-10-16일에 차를 빌릴 수 있는지 여부
SELECT CAR_ID,
        MAX(CASE WHEN &#39;2022-10-16&#39; BETWEEN START_DATE AND END_DATE THEN &quot;대여중&quot; ELSE &quot;대여 가능&quot; END) AS AVAILABILTY
FROM CAR_RENTAL_COMPANY_RENTAL_HISTORY
GROUP BY CAR_ID
ORDER BY CAR_ID DESC</code></pre><p><img src="https://velog.velcdn.com/images/gahyun_02/post/d78ebc13-fc6b-488e-a0f6-a73cfa3682ae/image.png" alt="">
<img src="https://velog.velcdn.com/images/gahyun_02/post/3d017583-b0ff-4d1d-92b2-8ec08260b94e/image.png" alt=""></p>
<h3 id="문자열-가공">문자열 가공</h3>
<ul>
<li>특정 문자열 찾기
   % : 0개 이상의 문자열
   _ : 1개의 문자열
   ^a : a로 시작하는 문자열
   a$ : a로 끝나는 문자열
   [abc] : a,b,c 중 하나의 문자
   [^abc] a,b,c를 제외한 하나의 문자
   [a-z] : a~z 중 하나의 문자
   .* : 모든 문자열</li>
</ul>
<pre><code class="language-sql"># 데이터 찾기
WHERE LANGAUGE LIKE &quot;%파이썬%&quot;
WHERE LANGAUGE REGEXP 정규식
WHERE LANGAUGE IN (&#39;가&#39;,&#39;나&#39;,&#39;다&#39;)

# 문자열 위치 찾기
LOCATE(str, 찾는 문자열)  # 문자열 내에서 찾는 문자열이 처음으로 나타내는 위치를 찾아서 해당 위치를 반환, 존재 안하면 0 반환 (시작 인덱스는 1)

# 문자열 길이
LENGTH(str)</code></pre>
<ul>
<li><p>특정 문자열 사용</p>
<pre><code class="language-sql"># 필드의 문자열 중 첫번쨰 문자부터 두번쨰 문자까지만 추출한다.
SUBSTRING(PRODUCT_CODE, 1, 2)
LEFT(PRODUCT_CODE, 2)
REPLACE(str, 바꾸고 싶은 문자, 바꿀 문자)
TRIM(제거하고 싶은 문자열 FROM str)
UPPER(대문자로 바꾸고 싶은 문자열)
LOWER(소문자로 바꾸고 싶은 문자열)</code></pre>
</li>
<li><p>문자열 합치기
   식별자 사용 x ; CONCAT()
   식별자 사용 o ; CONCAT_WS(&#39;식별자&#39;, ...)</p>
<pre><code class="language-sql">SELECT CONCAT_WS(&quot; &quot;, Address, PostalCode, City) AS Address
FROM Customers;</code></pre>
</li>
</ul>
<h3 id="숫자-데이터">숫자 데이터</h3>
<ul>
<li>숫자 조작
   ROUND(값, 반올림 결과 자릿수) : 반올림
   CEIL(값, 올림 결과 자릿수) : 올림
   FLOOR(값, 내림 결과 자릿수) : 내림
   TRUNCATE(값, 버릴 자릿수) : 버림
   POW([숫자],지수) : 제곱
   SQRT([숫자]) : 제곱근</li>
</ul>
<h3 id="윈도우-함수">윈도우 함수</h3>
<ul>
<li>순위 메기기 / 범위 자르기에 같이 사용됨.
  RANK()
  OVER ~ PARTITION BY()<ul>
<li>RANK() 대신, SUM() 등의 함수로 대체 가능.
DENSE_RANK() : 공동 순위 반영.<pre><code class="language-sql">SELECT
department_id,
employee_id,
salary,
RANK() OVER (PARTITION BY department_id ORDER BY salary DESC) AS salary_rank // 급여 순으로 순위 메기기 
FROM
employees;</code></pre>
</li>
</ul>
</li>
</ul>
<blockquote>
<p>SQL 핵심 개념</p>
</blockquote>
<h3 id="서브-쿼리">서브 쿼리</h3>
<p>*서브 쿼리 내부에 ORDER BY문 사용 불가능.</p>
<ul>
<li>단일 행 서브쿼리 : 쿼리 실행 결과가 항상 1건 이하인 경우.</li>
<li>다중 행 서브쿼리 : 결과가 2개 이상인 경우. -&gt; IN, ALL, ANY, EXISTS를 함께 사용 !!</li>
<li>FROM 서브쿼리<pre><code class="language-sql"># 단일 행 서브쿼리
SELECT * FROM A WHERE AGE = (SELECT MAX(AGE) FROM A)
</code></pre>
</li>
</ul>
<h1 id="다중-행-서브쿼리">다중 행 서브쿼리</h1>
<p>SELECT * FROM A WHERE AGE &gt; ANY (SELECT AGE FROM A)</p>
<h1 id="join-select--on-">JOIN (SELECT ...) ON ~</h1>
<p>SELECT USER_ID, NICKNAME,TOTAL_SALES
FROM USED_GOODS_USER as a 
JOIN (SELECT SUM(PRICE) AS TOTAL_SALES, WRITER_ID
      FROM USED_GOODS_BOARD
      WHERE STATUS = &#39;DONE&#39;
      GROUP BY WRITER_ID
      HAVING TOTAL_SALES &gt;= 700000
      ORDER BY TOTAL_SALES
      ) as b
      ON a.USER_ID = b.WRITER_ID</p>
<pre><code>
### 다양한 SQL 구문
1. **IF문**
- IF(조건문, 참일 때 값, 거짓일 때 값)
- IFNULL(컬럼명, 대체하고 싶은 값)

2. **WITH RECURSIVE 구문**
- 테이블 생성에 사용됨
```sql
WITH RECURSIVE [원하는 테이블명] AS (
    SELECT [초기값] AS [원하는 컬럼명]
    UNION ALL

    SELECT [원하는 컬럼명] + 1
    FROM [원하는 테이블명]
    WHERE [원하는 컬럼명] &lt; [원하는 컬럼 개수]
)</code></pre><ol start="3">
<li><strong>WITH 구문</strong></li>
</ol>
<ul>
<li>서브쿼리 정의할 때 사용됨<pre><code class="language-sql">WITH [원하는 테이블명] AS (
  SELECT ...
)</code></pre>
</li>
</ul>
<p>[예시] 특정 조건을 만족하는 그룹 ROW 뽑기</p>
<pre><code class="language-sql">SELECT *
FROM [테이블명] AS A
WHERE [컬럼명] = (SELECT MAX([컬럼명])
                FROM [테이블명] AS B
                WHERE A.[그룹 컬럼명] = B.[그룹 컬럼명])</code></pre>
<p>[참고] SQL Join의 종류
<img src="https://velog.velcdn.com/images/gahyun_02/post/69afa5b9-8b62-4a65-956b-b326c06e67c0/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[알고리즘 코딩테스트를 위한 개념 정리]]></title>
            <link>https://velog.io/@gahyun_02/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%A5%BC-%EC%9C%84%ED%95%9C-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@gahyun_02/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EC%BD%94%EB%94%A9%ED%85%8C%EC%8A%A4%ED%8A%B8%EB%A5%BC-%EC%9C%84%ED%95%9C-%EA%B0%9C%EB%85%90-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Wed, 26 Mar 2025 05:28:16 GMT</pubDate>
            <description><![CDATA[<h3 id="그리디">그리디</h3>
<blockquote>
<p>기준에 따라 가장 좋은 것을 선택하는 알고리즘</p>
</blockquote>
<ol>
<li>&#39;가장 큰 순서대로&#39; 등의 기준을 은연 중에 제공해줌. -&gt; 정렬 알고리즘과 짝을 이룸.</li>
<li>문제 풀이를 위한 최소한의 아이디어 떠올리기 -&gt; 정당성 검토 -&gt; 안되면, DP 또는 그래프 알고리즘 고려해보기</li>
</ol>
<h3 id="구현">구현</h3>
<blockquote>
<p>풀이를 떠올리는 것은 쉽지만 소스코드로 옮기기 어려운 문제
예 :  완전 탐색, 시뮬레이션</p>
</blockquote>
<pre><code class="language-python"># 상하좌우 이동 문제
n = int(input())
x,y = 1,1
plans = input().split()

dx=[0,0,-1,1]
dy=[-1,1,0,0]
move_types=[&#39;L&#39;,&#39;R&#39;,&#39;U&#39;,&#39;D&#39;]

for plan in plans:
    # 이동 후 좌표 구하기
    for i in range(len(move_types)):
        if plan == move_types[i]:
            nx = x+dx[i]
            ny = y+dy[i]
    # 공간을 벗어나는 경우 무시
    if nx &lt; 1 or ny &lt; 1 or nx &gt; n or ny &gt; n:
        continue
    # 이동 수행
    x, y = nx, ny

print(x,y)</code></pre>
<pre><code class="language-python"># 왕실의 나이트 문제
input_data = input()
row = int(input_data[1])
column = int(ord(input_data[0]))-int(ord(&#39;a&#39;))+1

# 나이트가 이동할 수 있는 8가지 방향 정의
steps = [(-2,-1), (-1,-2), (1,-2), (2,-1), (2,1), (1,2), (-1,2), (-2,1)]

result = 0
for step in steps:
    # 이동하고자 하는 위치 확인
    next_row = row + step[0]
    next_column = column + step[1]
    if next_row &gt;= 1 and next_row &lt;= 8 and next_column &gt;= 1 and next_column &lt;= 8:
        result += 1

print(result)</code></pre>
<h3 id="dfsbfs">DFS/BFS</h3>
<p>스택, 큐, 재귀함수 개념 이용</p>
<p>DFS (깊이 우선 탐색) : 그래프에서 깊은 부분을 먼저 탐색하는 알고리즘.</p>
<ul>
<li>스택, 재귀함수 이용<pre><code class="language-python">def dfs(graph, v, visited):
  # 현재 노드를 방문 처리
  visited[v]=True
  # 현재 노드와 연결된 다른 노드를 재귀적으로 방문
  for i in graph[v]:
      if not visited[i]:
          dfs(graph,i,visited)
</code></pre>
</li>
</ul>
<h1 id="2차원-리스트로-graph-표현">2차원 리스트로 graph 표현</h1>
<h1 id="노드-개수--1-개의-요소">노드 개수 + 1 개의 요소</h1>
<p>graph = [ [], [2,3,8], [1,7] ]</p>
<p>visited = [False]*3</p>
<h1 id="dfs-함수-호출">dfs 함수 호출</h1>
<p>dfs(graph,1,visited)</p>
<pre><code>
BFS (너비 우선 탐색) : 가까운 노드부터 탐색하는 알고리즘.
- 큐 이용
```python
from collection import deque

def bfs(graph, start, visited):
    queue=deque([start])
    visited[start]=True

    # 큐가 빌 때까지 반복
    while queue:
        v = queue.popleft()
        for i in graph[v]:
            if not visited[i]:
              queue.append(i)
              visited[i]=True

# 2차원 리스트로 graph 표현
# 노드 개수 + 1 개의 요소
graph = [ [], [2,3,8], [1,7] ]

visited = [False]*3

# bfs 함수 호출
bfs(graph,1,visited)</code></pre><p>cf. 탐색 문제는 그래프 형태로 표현한 후 풀이하자 !
    - 연결된 덩어리 파악 =&gt; dfs
    - 가능한 최소 경로 탐색 =&gt; bfs</p>
<h3 id="정렬">정렬</h3>
<ol>
<li>선택 정렬</li>
</ol>
<ul>
<li>리스트 첫 요소부터 비교 시작.</li>
<li>비교 후, 매번 가장 작은 요소를 선택해서 맨 앞으로 보낸다.<pre><code class="language-python">array=[13,2,5,259,1]
for i in range(len(array)):
  min_index=i
  for j in range(i+1,len(array):
      if array[i] &gt; array[j]:
          min_index=j
  # 스왑핑 !
  array[i], array[min_index] = array[min_index], array[i]</code></pre>
</li>
</ul>
<ol start="2">
<li>삽입 정렬</li>
</ol>
<ul>
<li>필요할 때만 위치 변경 -&gt; 데이터가 거의 정렬 되어 있을 때, 효율적.</li>
<li>첫 요소는 정렬되어 있다고 판단 -&gt; 두번째 요소부터 시작.</li>
<li>오른쪽으로 이동하면서 그 이전 요소들 사이에 해당 요소를 끼워넣음.<pre><code class="language-python">array=[13,2,5,259,1]
for i in range(1,len(array)):
  # 인덱스 i부터 -1까지 감소하며 반복 !
  for j in range(i,0,-1):
      if array[j] &lt; array[j-1] :
          array[j], array[j-1] = array[j-1], array[j]
      else:
          # 자신보다 작은 요소를 만나면 그 위치에서 멈춤 !!
          break</code></pre>
</li>
</ul>
<ol start="3">
<li>퀵 정렬</li>
</ol>
<ul>
<li><p>병합 정렬처럼 처리 속도가 빠른 편.</p>
</li>
<li><p>리스트의 첫 요소를 &#39;피벗&#39;으로 설정 -&gt; 피벗보다 작은 부분, 큰 부분으로 나누기 위해 요소들을 교환함.</p>
</li>
<li><p>( -&gt; &lt;- ) 방향으로 대소 비교</p>
</li>
<li><p>이후, 부분 부분으로 나눈 것들도 똑같은 방법으로 정렬하자.</p>
<pre><code class="language-python">array=[13,2,5,259,1]
def quick_sort(array, start, end):
  if start &gt;= end:
      return
  pivot = start
  left = start + 1
  right = end
  while left &lt;= right:
      while left &lt;= end and array[left] &lt;= array[pivot]:
          left += 1
      while right &lt;= start and array[right] &gt;= array[pivot]:
          right -= 1
      if left &gt; right:
          array[pivot], array[right] = array[right], array[pivot]
      else:
          array[left], array[right] = array[right], array[left]

  # 분할 이후 왼쪽 부분과 오른쪽 부분에서 각각 정렬 수행
  quick_sort(array, start, right-1)
  quick_sort(array, right+1, end)
</code></pre>
</li>
</ul>
<h1 id="end는-인덱스니까-리스트-길이--1으로-지정-">end는 인덱스니까 (리스트 길이 -1)으로 지정 !</h1>
<p>quick_sort(array,0,len(array)-1)</p>
<pre><code>
4. 계수 정렬
- 데이터 크기 범위가 &#39;정수 형태&#39;로 표현할 수 있을 때만 사용 가능
- 최대 데이터 - 최소 데이터 &lt;= 1,000,000일 때 효과적.
- 모든 범위를 담을 수 있는 크기의 리스트를 선언 !
```python
array=[13,2,5,259,1]
# 초기화 리스트 선언
count=[0]*(len(array)+1)

for i in range(len(array)):
    count[array[i]] += 1

for i in range(len(range)):
    for j in range(range[i]):
        # 띄어쓰기를 구분으로, 등장한 횟수만큼 인덱스 출력 ~!
        print(i, end=&#39; &#39;)</code></pre><ol start="5">
<li>정렬 라이브러리</li>
</ol>
<ul>
<li>퀵 정렬보다 빠름.</li>
<li>문제의 별도의 요구가 없을 경우에 사용하는 방법<pre><code class="language-python">array=[13,2,5,259,1]
array_1=[(&#39;바나나&#39;,2),(&#39;사과&#39;,5),(&#39;당근&#39;,3)]
</code></pre>
</li>
</ul>
<h1 id="1">1</h1>
<p>result = sorted(array)</p>
<h1 id="2">2</h1>
<p>array.sort()</p>
<h1 id="3">3</h1>
<h1 id="key를-기준으로-정렬">key를 기준으로 정렬</h1>
<p>def setting(data):
    return data[1]
result = sorted(array_1, key=setting)</p>
<pre><code>
### 이진 탐색
- 요소들이 정렬되어 있을 경우에, 빠르게 데이터 탐색 가능 !!
- 찾으려는 데이터와 중간점 위치에 있는 데이터를 반복적으로 비교하는 알고리즘.
- 이진 탐색 트리에서도 이용됨.
```python
def binary_search(array,target,start,end):
    if start &gt; end:
        return None
    mid = (start+end)//2
    if array[mid] == target:
        return mid
    elif array[mid] &gt; target:
        return binary_search(array,target,mid+1,end)
    else:
        return binary_search(array,target,start,mid-1)

result = binary_search(array, target, 0, n-1)
if result == None:
    print(&quot;존재하지 않는 원소임&quot;)
else:
    # target이 리스트의 몇 번째에 존재하는 지 ?
    print(result-1)</code></pre><ul>
<li>파라메트릭 서치 유형의 문제가 대표적임.<ul>
<li>최적화 문제를 결정 문제로 바꾸어 해결 ! (예. 원하는 조건을 만족하는 가장 알맞은 값을 찾는 문제)</li>
</ul>
</li>
</ul>
<h3 id="dp">DP</h3>
<p>DP 풀이 문제의 조건</p>
<ol>
<li>큰 문제를 작은 문제로 나눌 수 있다.</li>
<li>작은 문제에서 구한 정답은 그것을 포함하는 큰 문제에서도 동일하다.</li>
<li>완전 탐색으로 했을 때 시간이 매우 오래 걸린다.</li>
</ol>
<p>DP 풀이 방법
    1. 재귀 함수 이용 (탑다운)
    2. 반복문 이용 (보텀업) ‼️</p>
<p>메모리제이션 기법 예시.</p>
<ul>
<li>DP 탑다운의 경우 -&gt; 리스트에 결과 저장.</li>
<li>&#39;수열&#39;처럼 연속적이지 않은 값의 경우 -&gt; dict 자료형에 결과 저장.</li>
</ul>
<pre><code class="language-python"># 적은 금액부터 큰 금액까지 확인 -&gt; 차례대로 만들 수 있는 최소한의 화폐 개수 찾는 문제.
n,m = map(int, input().split())
array = []
for i in range(n):
    array.append(int(input()))

# 보텀업 방식
d = [10001] * (m+1)
d[0] = 0

# ( 만들고자 하는 금액 - 화폐 단위들 )을 각각 계산하여 DP 테이블에 저장. 
for i in range(n):
    for j in range(array[i], m+1):
        if d[j-array[i]] != 10001:
            d[j-array[i]]=min(d[j-array[i]]+1, d[j])

if d[m] == 10001:
    print(-1)
else:
    print(d[m])</code></pre>
<h3 id="최단-경로">최단 경로</h3>
<ol>
<li>다익스트라 알고리즘</li>
</ol>
<ul>
<li>특정 노드에서 다른 노드로 가는 <strong>각각의 최단 경로</strong>를 구해주는 알고리즘</li>
<li>&#39;음의 간선&#39;이 없어야 함</li>
<li>그리디 알고리즘에 포함됨
  : 해당 노드와 연결되어 있으면서, 방문하지 않은 노드 중
<strong>최단 거리가 가장 짧은 노드를 매번 선택</strong> ! -&gt; 최단 거리 갱신</li>
<li><strong>힙</strong> 자료 구조 (우선순위 큐) 사용 -&gt; (거리, 노드)로 저장. -&gt; 자동으로 정렬됨.</li>
</ul>
<pre><code class="language-python">import heapq
import sys
input = sys.stdin.readline
INF = int(1e9)

n,m = map(int, input().split())     # 노드 개수, 간선 개수
start = int(input())
graph = [[] for i in range(n+1)]
distance = [INF] * (n+1)    # 최단 거리 테이블 초기화

for _ in range(m):
    a,b,c = map(int, input().split())
    graph[a].append((b,c))

def dijkstra(start):
    q = []
    heapq.heappush(q,(0,start))
    distance[start]=0
    while q:
        dist, now = heapq.heappop(q)
        # 현재 노드가 이미 처리된 적이 있는 노드라면 무시
        if distance[now] &lt; dist:
            continue
        for i in graph[now]:
            cost = dist + distance[i]
            if cost &lt; distance[i[0]]:
                distance[i[0]] = cost
                heapq.heappush(q,(cost,i[0]))

dijkstra(start)

# 모든 노드로 가기 위한 최단 거리 출력
for i in range(1, n+1):
    if distance[i] == INF:
        print(&quot;무한&quot;)
    else:
        print(distance[i])    </code></pre>
<ol start="2">
<li>플로이드 워셜 알고리즘</li>
</ol>
<ul>
<li><strong>모든 지점에서 다른 모든 지점까지</strong>의 최단 경로를 모두 구해야 하는 경우</li>
<li>DP 알고리즘에 포함됨.</li>
<li>3중 반복문으로 &#39;A-&gt;B로 가는 최소 비용&#39;과 &#39;A-&gt;K-&gt;B로 가는 비용&#39;을 비교 !!</li>
<li>노드의 개수가 적을 때 사용 가능.</li>
</ul>
<pre><code class="language-python">INF = int(1e9)

n=int(input())
m=int(input())
graph=[[INF]*(n+1) for _ in range(n+1)]

# 자기 자신 -&gt; 자기 자신으로 가는 비용은 0으로 초기화
for a in range(1,n+1):
    for b in range(1, n+1):
        if a==b:
            graph[a][b] = 0

# 각 간선에 대한 정보를 입력 받아, 그 값으로 초기화
for _ in range(m):
    a,b,c = map(int, input().split())
    graph[a][b] = c

# 점화식에 따라 알고리즘 수행
for k in range(1,n+1):
    for a in range(1,n+1):
        for b in range(1,n+1):
            graph[a][b]=min(graph[a][k]+graph[k][b], graph[a][b])

# 수행된 결과 출력
for a in range(1,n+1):
    for b in range(1,n+1):
        if graph[a][b] == INF:
            print(&quot;무한&quot;)
        else:
            print(graph[a][b], end=&quot; &quot;)
    print()</code></pre>
<h3 id="그래프-이론">그래프 이론</h3>
<ol>
<li>트리를 이용해 <strong>서로소 집합</strong> 구하는 알고리즘</li>
</ol>
<ul>
<li>union 연산을 반복 수행 -&gt; 노드들의 연결성 파악 -&gt; 서로소 집합 구하기</li>
<li>union 연산 : 각각 루트 노드 찾기 -&gt; 번호가 큰 노드가 작은 노드를 가리키도록 연결</li>
<li>&#39;경로 압축 기법&#39;을 사용하여 루트 노드를 찾는 과정을 최적화함.<pre><code class="language-python">def find_parent(parent,x):
#    if parent[x] != x:
#        return find_parent(parent,parent[x])
#    return x
  # 경로 압축 기법 사용
  if parent[x] != x:
      parent[x] = find_parent(parent,parent[x])
  return parent[x]
</code></pre>
</li>
</ul>
<p>def union_parent(parent, a, b):
    a = find_parent(parent, a)
    b = find_parent(parent, b)
    if a &lt; b:
        parent[b] = a
    else:
         parent[a] = b</p>
<h1 id="입력받기">입력받기</h1>
<p>v,e = map(int, input().split())
parent = [0] * (v+1)</p>
<h1 id="parent-테이블에서-자기-자신으로-초기화">parent 테이블에서 자기 자신으로 초기화</h1>
<p>for i in range(1,v+1):
    parent[i]=i</p>
<h1 id="union-연산-수행">union 연산 수행</h1>
<p>for i in range(e):
    a,b = map(int, input().split())
    union_parent(parent,a,b)</p>
<h1 id="집합-출력">집합 출력</h1>
<p>for i in range(1,v+1):
    print(find_parent(parent,i),end=&quot; &quot;)</p>
<p>print()</p>
<h1 id="부모-테이블-내용-출력">부모 테이블 내용 출력</h1>
<p>for i in range(1,v+1):
    print(parent[i], end=&quot; &quot;)</p>
<pre><code>- 무방향 그래프 내에서 **사이클을 판별**할 때 사용됨.
```python
# 부모노드를 자기 자신으로 초기화하는 것까지 동일.
cycle = False  # 사이클 발생 여부

for i in range(e):
    a,b = map(int, input().split())
    if find_parent(parent,a) == find_parent(parent,b):
        cycle=True
        break
     else:
         union_parent(parent,a,b)

if cycle:
    print(&quot;사이클 발생&quot;)
else:
    print(&quot;사이클 발생하지 않음&quot;)</code></pre><ol start="2">
<li><strong>최소한의 비용</strong>으로 <strong>신장 트리</strong>를 찾는 알고리즘</li>
</ol>
<ul>
<li>신장 트리 : 모든 노드를 포함하면서 사이클이 존재하지 않는 부분 그래프</li>
<li>크루스칼 알고리즘 ! : 모든 간선에 대하여 정렬 -&gt; 가장 거리가 짧은 간선부터 집합에 포함시키기 (사이클이 발생하지 않는 것만 !!)</li>
<li>최소 신장 트리에 포함되는 간선의 비용 합 = 총 비용<pre><code class="language-python"># 부모노드를 자기 자신으로 초기화하는 것까지 동일.
edges = []  # 모든 간선을 담을 변수
result = [] # 총 비용을 담을 변수
</code></pre>
</li>
</ul>
<p>for _ in range(e):
    a,b,cost = map(int, input().split())
    edges.append((cost,a,b))</p>
<p>edges.sort() # 비용순으로 정렬</p>
<h1 id="간선-하나씩-확인">간선 하나씩 확인</h1>
<p>for edge in edges:
    cost, a, b = edge  # 튜플
    if find_parent(parent,a) != find_parent(parent,b):
        union_parent(parent,a,b)
        result += cost</p>
<p>print(result)</p>
<pre><code>
3. **위상 정렬**을 사용하여 **방향 그래프를 순서대로 나열**하는 알고리즘
- 진입 차수 : 특정한 노드로 &#39;들어오는&#39; 간선의 개수
- 위상 정렬 알고리즘 : 진입 차수가 0인 노드를 큐에 삽입 -&gt; 큐가 빌 때까지, 꺼낸 노드에서 출발하는 간선을 그래프에서 제거 + 새롭게 진입차수가 0이 된 노드를 큐에 삽입
- 모든 원소를 방문하기 전에 큐가 빈다 ? = 사이클 발생 !!!!!

```python
from collections import deque

v,e = map(int,input().split())
# 모든 노드에 대해 진입 차수를 0으로 초기화
indegree = [0] * (v+1)
graph = [ [] for i in range(v+1) ]

# 방향 그래프의 모든 간선 정보를 입력받기
for _ in range(e):
    a,b = map(int, input().split())
    graph[a].append(b)  # a에서 b로 이동 가능 !!!!!
    indegree[b] += 1

# 위상 정렬 함수
def topology_sort():
    result = []
    q=deque()

    for i in range(1,v+1):
        if indegree == 0:
            q.append(i)

    while q:
        now = q.popleft()
        result.append(now)
        # 해당 원소와 연결된 모든 노드들의 진입차수 -1
        for i in graph[now]:
            indegree[i] -= 1
            if indegree[i] == 0:
                q.append(i)

    # 위상 정렬 결과 출력
    for i in result:
        print(i, end=&quot; &quot;)

topology_sort()</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[Solution Challenge - 아이디어 구체화]]></title>
            <link>https://velog.io/@gahyun_02/Solution-Challenge-%EC%95%84%EC%9D%B4%EB%94%94%EC%96%B4-%EA%B5%AC%EC%B2%B4%ED%99%94</link>
            <guid>https://velog.io/@gahyun_02/Solution-Challenge-%EC%95%84%EC%9D%B4%EB%94%94%EC%96%B4-%EA%B5%AC%EC%B2%B4%ED%99%94</guid>
            <pubDate>Fri, 03 Feb 2023 12:12:36 GMT</pubDate>
            <description><![CDATA[<h3 id="서비스-기능-구체화-초안">서비스 기능 구체화 초안</h3>
<p><strong>내끼니끼 🍚</strong></p>
<ul>
<li>소액씩 모으고 한 달에 한 번 모은 돈 직접 기부 - 결제 API</li>
<li>목표 금액 설정해서 금액 채워지면 기부</li>
<li>기부 시 기부하고싶은 분야 골라서 사이트 연결</li>
<li>끼니별 금액 설정 / 끼니에 지출한 금액의 일정 비율</li>
<li>지출 금액 파악 방법 : 직접 작성</li>
<li>기부 내역 정리, 기부한 만큼 기여한 끼니정도 보여주기</li>
<li>기부정도에 따라 뱃지/기부 온도 표시해주기</li>
<li>모인 금액 밥그릇으로 보여주기</li>
</ul>
<h3 id="우리가-풀어야-하는-문제들">우리가 풀어야 하는 문제들</h3>
<p><img src="https://velog.velcdn.com/images/gahyun_02/post/d2f34c47-d358-4a45-bf16-bcbe3200d6fa/image.png" alt="">
<img src="https://velog.velcdn.com/images/gahyun_02/post/d5827a53-b2e9-4a89-8073-065459c974e5/image.png" alt=""></p>
<ol>
<li>블록체인 구현</li>
<li>기부 캠페인 리스트업<ul>
<li>네이버 검색 api -&gt; &#39;결식아동후원&#39; 검색어 결과 중 기부 검색 키워드의 내용 크롤링</li>
</ul>
</li>
<li>메인 기능의 임팩트 부족<ul>
<li>&#39;기부&#39;로 이어지기 전 과정에서의 부자연스러움 존재</li>
</ul>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[Solution Challenge - 아이디어 결정]]></title>
            <link>https://velog.io/@gahyun_02/Solution-Challenge-%EC%95%84%EC%9D%B4%EB%94%94%EC%96%B4-%EA%B2%B0%EC%A0%95</link>
            <guid>https://velog.io/@gahyun_02/Solution-Challenge-%EC%95%84%EC%9D%B4%EB%94%94%EC%96%B4-%EA%B2%B0%EC%A0%95</guid>
            <pubDate>Thu, 02 Feb 2023 12:36:37 GMT</pubDate>
            <description><![CDATA[<h2 id="intro">Intro</h2>
<p><strong>Solution Challenge</strong> 여정을 담은 시리즈의 시작으로, 아이디어를 결정하기까지의 과정을 말해보려고 한다.</p>
<pre><code>1. 프로젝트 마감일까지 시간이 빠듯하다. -&gt; 근데 백엔드는 나 혼자다. -&gt; 백엔드 기능 구현에서 개발 시간 단축 필요
2. 팀원들 모두 플러터를 처음 배우는 것임. -&gt; 기술스택이 익숙하지 않은 상황에서, UI 구현만으로도 프론트 작업 시간이 많이 소요될 것으로 예상
3. 나를 제외한 팀원들은 팀플 경험이 없음. -&gt; 본인이 백엔드랑 api 통신까지 구현하려면 기본적인 플러터 사용 방법 + 플러터 인증 시스템 등을 공부해야 함</code></pre><p>백엔드와 배포를 담당한 나는 프레임워크를 고민하다가,
기본적인 CRUD부터 authentication까지 <strong>많은 유틸리티를 제공</strong>하기도 하고, <strong>프론트와 api 연결, DB 생성</strong>까지 비교적 <strong>쉽고 빠르게</strong> 할 수 있는 <strong>&#39;Django&#39;</strong>로 결정 !</p>
<blockquote>
<p><strong>프론트엔드</strong> - Flutter
<strong>백엔드</strong> - Django
<strong>배포</strong> - GCP</p>
</blockquote>
<h2 id="idea-brainstorming">Idea Brainstorming</h2>
<h3 id="최종-아이디어를-향한-방황">최종 아이디어를 향한 방황</h3>
<p>구글이 제시한 17개의 목표를 해결할 수 있는 서비스를 제안하면 되는 건데,</p>
<ol>
<li>저소득층 혜택관리 서비스 🔍<ul>
<li>사용자 정보 입력 -&gt; 해당되는 정부 지원 / 혜택 정리</li>
<li>정부 24 기능과의 차별성 필요</li>
</ul>
</li>
<li>Zero Waste 격려 서비스 </li>
<li>소상공인을 위한 협업 플랫폼 🔍<ul>
<li>주변 동종업계 가게들과의 협업 서비스 (재료 공동구매, 공동 고객관리 등)</li>
<li>사장님 발주 도우미 (발주 자동화, 발주 일정 관리 등)
⏺ ex. 업체 번호로 발주 문자 자동 발송</li>
</ul>
</li>
<li>어르신들을 위한 교육 플랫폼</li>
<li>수업진행도 관리 서비스</li>
</ol>
<p>나는 🔍로 표시한 아이디어들을 제안했고, 
팀원들과의 회의 결과, 크게 5가지 아이디어로 정리가 되었다.</p>
<p>이 중에 <strong>zero waste</strong>가 구글이 제시한 목표와 연관성도 높고 주제로서 시의성, 관심도가 높은 것 같아서 <strong>&#39;Zero Waste Kitchen&#39;</strong> (이하 Zero Kitchen)으로 서비스 방향을 잡았다.</p>
<blockquote>
</blockquote>
<p>Zero Kitchen <strong>기능</strong> ❗️ </p>
<ul>
<li>유통기한 또는 구매 순으로 정렬</li>
<li>유통기한 임박한 재료로 만들 수 있는 음식 추천</li>
<li>유통기한이 지난 음식을 활용할 수 있는 방법 찾아주기</li>
</ul>
<blockquote>
<p>Zero Kitchen <strong>목표</strong> ❗️</p>
</blockquote>
<ul>
<li>음식물 쓰레기 감소</li>
<li>과도한 소비 막기</li>
<li>식재료 낭비 없이 오래 먹는 친환경 식생활 촉진</li>
</ul>
<p><a href="https://www.figma.com/file/mTB7buz9l5F9ouw3HNYyFO/Solution-Challenge?node-id=0%3A1&amp;t=mM33E4QS4MmsVrsn-0">피그마 - Zero Kitchen 프로토타입</a></p>
<p><a href="https://www.notion.so/96680b8cac9d426f9906c585214bd4f4">노션 - Zero Kitchen 페이지별 기능 구체화</a></p>
<p>하지만, 아이디어 발표에서 큰 ❌<strong>혹평</strong>❌을 받게 되는데...</p>
<ol>
<li>냉장고 항목 하나하나 등록 굉장히 비효율</li>
<li>수많은 냉장고 관리 앱과의 차별점이 필요</li>
<li>앱 서비스를 통해 재활용을 쉽게 도와줄 수 있는 방법이 있으면 좋겠다</li>
<li>굳이 냉장고에만 신경쓰지 마라</li>
<li>기존 앱에 다 구현되어 있는데 왜 사람들이 안쓰는지 생각해봐라</li>
<li>가정집 냉장고가 아니라 식당 냉장고 관리에 앱을 사용할 수도 있다</li>
</ol>
<blockquote>
<p>피드백을 듣고, <strong>UX 관점</strong>에서 <strong>서비스의 사용성과 가치, 발전 가능성</strong>을 냉정하게 생각해봤다.
오랜 회의 끝에.... 위 아이디어를 <strong>버리기로 결정</strong>했다. 
&#39;새로운 아이디어가 낫겠다&#39;는 결론에 도달했기 때문 !</p>
</blockquote>
<h3 id="또-다시-처음으로">또 다시 처음으로..!</h3>
<p>좋아. 처음부터 다시 시작해보자. 도르마무 도르마무... 
끝없는 아이디어 회의 지옥... </p>
<p><strong>1.  기부 - 음식</strong></p>
<ul>
<li>내 끼니 기부하기   </li>
<li>고객 혜택/참여율 증진 - 퍼센트를 설정해서 일부는 적금 일부는 기부</li>
<li>달마다 기부 금액이 어떻게 쓰였는지 식사 관련 형태로 알려줌</li>
</ul>
<p><strong>2. 음주 관리 앱</strong>
기존 서비스에서 추가해서 만들었으면 하는 것</p>
<ul>
<li>적정 음주량 가이드라인 제공<ul>
<li>통계로 알코올중독 진단 결과 제공 (자가진단 리스트 이용)</li>
</ul>
</li>
<li>목표 설정<ul>
<li>적정량 (또는 그 이하)에 가까워지도록 목표 설정</li>
<li>월/주 n회 + 일 n잔/n병</li>
<li>이전 주에 비해 덜 마신 만큼 칼로리/비용 등... 감소했다는 표시</li>
</ul>
</li>
</ul>
<p><strong>3. 응급실 콜</strong> 🔍</p>
<ul>
<li>사용자 타겟팅: 응급실에서 근무하는 전공의들 대상</li>
<li>병원 교수님들 위치 확인, 제일 가까운 의사한테 콜</li>
<li>119 신고 정보랑 연결해서 의사들한테 알림 가게 하기</li>
</ul>
<p>-&gt; 교수님들의 예상 도착 시간 알 수 있게 함</p>
<p>이 중에 <strong>&#39;식사 기부&#39;</strong> 아이디어를 최종적으로 선택했다 ! 서비스 이름은 &#39;<strong>내끼니끼</strong>🍚&#39; !!! 제법 귀엽 ㅎ</p>
<p>다음엔 &#39;<strong>아이디어 구체화</strong>&#39; 편으로 돌아오겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 시큐리티]]></title>
            <link>https://velog.io/@gahyun_02/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0</link>
            <guid>https://velog.io/@gahyun_02/%EC%8A%A4%ED%94%84%EB%A7%81-%EC%8B%9C%ED%81%90%EB%A6%AC%ED%8B%B0</guid>
            <pubDate>Thu, 24 Nov 2022 15:58:00 GMT</pubDate>
            <description><![CDATA[<h3 id="💡-스프링시큐리티spring-security">💡 스프링시큐리티(Spring Security)?</h3>
<blockquote>
<p>스프링 기반의 애플리케이션의 보안(인증과 권한, 인가 등)을 담당하는 스프링 하위 프레임워크. 
즉, 인증(Authenticate, 누구인지?) 과 인가(Authorize, 어떤것을 할 수 있는지?)를 담당하는 프레임워크를 말함.</p>
</blockquote>
<p>스프링 시큐리티에서는 주로 <strong>서블릿 필터(filter)</strong>와 이들로 구성된 <strong>필터체인으로의 구성된 위임모델을 사용</strong>함. 
그리고 보안과 관련해서 체계적으로 많은 옵션을 제공해주기 때문에 개발자 입장에서는 일일이 보안관련 로직을 작성하지 않아도 된다는 장점이 있음.</p>
<p><code>기본용어</code></p>
<ul>
<li><strong>접근 주체(Principal)</strong> : 보호된 리소스에 접근하는 대상</li>
<li><strong>인증(Authentication)</strong> : 보호된 리소스에 접근한 대상에 대해  누구인지, 애플리케이션의 작업을 수행해도 되는 주체인지 확인하는 과정(ex. Form 기반 로그인) <strong>=&gt; 즉, 누구인지?</strong></li>
<li><strong>인가(Authorize)</strong> : 해당 리소스에 대해 <strong>접근 가능한 권한을 가지고 있는지 확인</strong>하는 과정(After Authentication, 인증 이후)  =&gt; 즉, 어떤 것을 할 수 있는지?</li>
<li><strong>권한</strong> : 어떠한 리소스에 대한 접근 제한, 모든 리소스는 접근 제어 권한이 걸려있음. 인가 과정에서 해당 리소스에 대한 제한된 최소한의 권한을 가졌는지 확인</li>
</ul>
<h3 id="💡-스프링-시큐리티-특징과-구조">💡 스프링 시큐리티 특징과 구조</h3>
<ul>
<li>보안과 관련하여 체계적으로 많은 옵션을 제공하여 편리하게 사용할 수 있음</li>
<li><strong>Filter 기반으로 MVC와 분리하여 관리 및 동작</strong><ul>
<li><strong>어노테이션</strong>을 통한 간단한 설정</li>
<li>Spring Security는 기본적으로 **세션 &amp; 쿠키방식으로 인증</li>
<li>*
<img src="https://velog.velcdn.com/images/gahyun_02/post/31753e42-b51e-4a36-bda4-b6d6c2e1032b/image.png" alt=""></li>
</ul>
</li>
</ul>
<ul>
<li><strong>인증관리자</strong>(Authentication Manager)와 <strong>접근 결정 관리자</strong>(Access Decision Manager)를 통해 <strong>사용자의 리소스 접근을 관리</strong>
( 인증 관리자는 <strong>UsenamePasswordAuthenticationFilter</strong>, 접근 결정 관리자는 <strong>FilterSecurityInterceptor</strong>가 수행 )</li>
</ul>
<h3 id="💡-스프링시큐리티-기본구조">💡 스프링시큐리티 기본구조</h3>
<p><img src="https://velog.velcdn.com/images/gahyun_02/post/a166115c-d9cb-4675-a965-735726fb611c/image.png" alt=""></p>
<p>📌 각 필터별 기능 설명</p>
<p><img src="https://velog.velcdn.com/images/gahyun_02/post/7c38ae8b-c741-448e-a36d-cf0704862f25/image.png" alt=""><img src="https://velog.velcdn.com/images/gahyun_02/post/70a17e95-6e1d-47b3-9ca0-5a5e1c22ce43/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[AWS S3]]></title>
            <link>https://velog.io/@gahyun_02/AWS-S3</link>
            <guid>https://velog.io/@gahyun_02/AWS-S3</guid>
            <pubDate>Thu, 24 Nov 2022 14:53:48 GMT</pubDate>
            <description><![CDATA[<p>S3 = simple storage service 의 줄임말.</p>
<p>*<em>기능 *</em></p>
<p>1) 파일 관리 자동화
2) 버전 관리</p>
<p>S3를 이용해서 내 파일 안전하게 보관하기!</p>
<p><strong>구성 요소</strong></p>
<p>1) 버킷 
    예) 하나의 프로젝트
    <img src="https://velog.velcdn.com/images/gahyun_02/post/17274b74-25b3-4d7c-aa31-3fb657ea00c8/image.png" alt=""></p>
<p>2) 폴더
<img src="https://velog.velcdn.com/images/gahyun_02/post/b80e57fe-37e7-4e4e-a6f7-6b3af4d0dcd7/image.png" alt=""></p>
<p>3) 오브젝트 (파일)</p>
<h3 id="1-버킷-생성">1. 버킷 생성</h3>
<h3 id="2-aws-iam-생성">2. AWS IAM 생성</h3>
<p><img src="https://velog.velcdn.com/images/gahyun_02/post/ae48d635-98c4-4b3f-81dc-7a535102f2be/image.png" alt=""></p>
<h3 id="3-로컬-환경에서-이미지-동영상-업로드하기">3. 로컬 환경에서 이미지, 동영상 업로드하기</h3>
<p><code>build.gradle</code>
&lt; 주요 사용 라이브러리 &gt;</p>
<blockquote>
</blockquote>
<p>spring-cloud-starter-aws
spring-boot-starter-web
lombok
Thymleaf</p>
<p>➕ <code>S3UploaderService.java</code></p>
<ul>
<li>S3에 정적 파일을 올리는 기능</li>
</ul>
<p>[[AWS] Springboot에 AWS S3 연동 (이미지, 동영상 업로드)]
(<a href="https://develop-writing.tistory.com/m/128)%EC%97%90%EC%84%9C">https://develop-writing.tistory.com/m/128)에서</a> 자세한 코드 참고 !</p>
<ul>
<li>MultipartFile 전달받음.</li>
<li>S3에 전달할 수 있도록 MultiPartFile을 File로 전환 및 저장. (S3에 Multipartfile 타입은 전송이 안되기 때문)</li>
<li>전환된 File을 S3에** public 읽기 권한**으로 업로드. (외부에서 정적 파일을 읽을 수 있도록 하기 위해)</li>
<li>로컬에 생성된 File 삭제. (Multipartfile -&gt; File로 전환됨)</li>
<li>업로드된 파일의 S3 URL 주소를 반환</li>
</ul>
<p>여기서 특이한 부분은 별다른 Configuration 코드 없이 AmazonS3Client 를 DI 받은것인데요. Spring Boot Cloud <strong>AWS</strong>를 사용하게 되면 <strong>S3 관련 Bean을 자동 생성</strong>해줍니다.</p>
<p>-&gt; 그래서 AmazonS3, AmazonS3Client, ResourceLoader는 직접 설정할 필요 X</p>
<p>💡 basicDir, fileDir는 *<em>Profile별 저장될 파일 경로 *</em>!</p>
<ul>
<li><p>로컬에서 테스트할 경우 {프로젝트 경로}/resource/static/files 경로에 파일을 저장할 수 있게 하기 위해 !</p>
</li>
<li><p>EC2에서 테스트할 경우 /home/ec2-user/files 에 저장이 될 것임.</p>
</li>
</ul>
<p>➕ <code>S3UploaderController.java</code></p>
<ul>
<li>4가지 기능
1) image-upload 웹 페이지 반환
2) video-upload 웹 페이지 반환
3) data로 넘어오는 이미지 MultipartFile을 S3UploaderService로 전달
4) data로 넘어오는 동영상 MultipartFile을 S3UploaderService로 전달</li>
</ul>
<p>➕ AWS S3에 필요한 정보를 <code>application.yml</code>에 추가</p>
<pre><code class="language-java">spring:
  profiles:
    group:
      &quot;local&quot;: &quot;local, common&quot;
      &quot;development&quot;: &quot;development,common&quot;
    active: local

---
# 공통
spring:
  config:
    activate:
      on-profile: &quot;common&quot;
  servlet:
    multipart:
      max-file-size: 1GB
      max-request-size: 1GB

# s3에 필요한 정보
cloud:
  aws:
    region:
      static: ap-northeast-2
    s3:
      bucket: 1-source
    stack:
      auto: false  // EC2에서 Spring Cloud 프로젝트를 실행시키면 기본으로 CloudFormation 구성을 시작함. 
                   // 설정한 CloudFormation이 없으면 프로젝트 시작이 안되기 때문에 해당 내용을 사용하지 않도록 false를 등록합니다.

logging:
  level:
    com:
      amazonaws:
        util:
          EC2MetadataUtils: error

---
# 로컬 환경
spring:
  environment: &quot;local&quot;
  config:
    activate:
      on-profile: &quot;local&quot;
  file-dir: /src/main/resources/static/files/
---
# 배포 환경
spring:
  environment: &quot;development&quot;
  config:
    activate:
      on-profile: &quot;development&quot;
  file-dir: /home/ec2-user/files/</code></pre>
<p>➕ <code>aws.yml</code>
AWS User의 access-key와, secret-key는 탈취되는 문제를 예방하기 위해 추가. 
그리고 .gitignore에 aws.yml 코드 추가하기 !</p>
<pre><code class="language-java">cloud:
  aws:
    credentials:
      access-key: 발급받은 access-key
      secret-key: 발급받은 secret-key</code></pre>
<p>➕ <code>S3uploaderApplication.java</code>에 아래 코드 추가 !</p>
<pre><code class="language-java">public static final String APPLICATION_LOCATIONS = &quot;spring.config.location=&quot;
            + &quot;classpath:application.yml,&quot;
            + &quot;classpath:aws.yml&quot;;</code></pre>
<p>➕ 프로파일 별로 파일이 저장될 폴더를 만들기 위해 스프링 부트가 시작할 때 폴더를 만드는 코드 추가</p>
<pre><code class="language-java">/**
     * @description 이미지, 영상 업로드할 폴더를 프로파일 별로 다른 경로에 생성한다.
     */
    @PostConstruct
    private void init() {

        if (environment.equals(&quot;local&quot;)) {
            String staticFolder = System.getProperty(&quot;user.dir&quot;) + &quot;/src/main/resources/static&quot;;
            mkdirResource(staticFolder);

            String files = System.getProperty(&quot;user.dir&quot;) + fileDir;
            mkdirResource(files);
        } else if (environment.equals(&quot;development&quot;)) {
            String filesFolder = &quot;/var/www/html/files&quot;;
            mkdirResource(filesFolder);
        }
    }

    /**
     * @param fileDir 생성을 위한 폴더명
     * @description 주어진 경로에 폴더를 생성함
     */
    private static void mkdirResource(String fileDir) {
        File Folder = new File(fileDir);

        // 해당 디렉토리가 없을경우 디렉토리를 생성합니다.
        if (!Folder.exists()) {
            try {
                Folder.mkdir(); //폴더 생성합니다.
            } catch (Exception e) {
                e.getStackTrace();
            }
        }
    }</code></pre>
<p>➕ 마지막으로 VM options에 아래 코드 추가 !</p>
<pre><code>Dcom.amazonaws.sdk.disableEc2Metadata=true</code></pre><p>( EC2에서 실행해주기 위해 )</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[API 개발 고급 - 컬렉션 조회 최적화]]></title>
            <link>https://velog.io/@gahyun_02/API-%EA%B0%9C%EB%B0%9C-%EA%B3%A0%EA%B8%89-%EC%BB%AC%EB%A0%89%EC%85%98-%EC%A1%B0%ED%9A%8C-%EC%B5%9C%EC%A0%81%ED%99%94-zovt4k36</link>
            <guid>https://velog.io/@gahyun_02/API-%EA%B0%9C%EB%B0%9C-%EA%B3%A0%EA%B8%89-%EC%BB%AC%EB%A0%89%EC%85%98-%EC%A1%B0%ED%9A%8C-%EC%B5%9C%EC%A0%81%ED%99%94-zovt4k36</guid>
            <pubDate>Fri, 11 Nov 2022 09:11:30 GMT</pubDate>
            <description><![CDATA[<p>컬렉션 (= 일대다 관계) 조회, 최적화 해보자.</p>
<h3 id="주문-조회-v1-엔티티-직접-노출">주문 조회 v1. 엔티티 직접 노출</h3>
<pre><code class="language-java">@GetMapping(&quot;/api/v1/orders&quot;)
public List&lt;Order&gt; ordersV1() {
    List&lt;Order&gt; all = orderRepository.findAll();
    // 직접 초기화 시키기
    for (Order order : all) {
        order.getMember().getName(); 
        order.getDelivery().getAddress(); 
        List&lt;OrderItem&gt; orderItems = order.getOrderItems(); 
        orderItems.stream().forEach(o -&gt; o.getItem().getName()); // 오더아이템도 직접 초기화
    }
    return all;
}</code></pre>
<blockquote>
<p>엔티티를 직접 노출하는 것은 좋지 않음 !</p>
</blockquote>
<h3 id="주문-조회-v2-엔티티를-dto로-변환">주문 조회 v2. 엔티티를 DTO로 변환</h3>
<pre><code class="language-java">@GetMapping(&quot;/api/v2/orders&quot;)
public List&lt;OrderDto&gt; ordersV2() {
      List&lt;Order&gt; orders = orderRepository.findAll();
      List&lt;OrderDto&gt; result = orders.stream()
              .map(o -&gt; new OrderDto(o))
              .collect(toList());
      return result;
}</code></pre>
<p><code>OrderApiController</code></p>
<pre><code class="language-java">@Data
static class OrderDto {
    private Long orderId;
    ...
    // 생성자
    public OrderDto(Order order) {
        // 초기화
        orderId = order.getId();
        name = order.getMember().getName();
        ...
        orderItems = order.getOrderItems().stream()
                  .map(orderItem -&gt; new OrderItemDto(orderItem))
                  .collect(toList());
    }
}
@Data
static class OrderItemDto {
    private String itemName;//상품 명
    private int orderPrice; //주문 가격 
    private int count; //주문 수량
    ...
}</code></pre>
<blockquote>
<p>지연 로딩으로 너무 많은 SQL이 실행됨.
SQL 실행 수 : 1+N</p>
</blockquote>
<h3 id="주문-조회-v3-페치-조인-최적화">주문 조회 v3. 페치 조인 최적화</h3>
<pre><code class="language-java">@GetMapping(&quot;/api/v3/orders&quot;)
public List&lt;OrderDto&gt; ordersV3() {
        List&lt;Order&gt; orders = orderRepository.findAllWithItem();
        List&lt;OrderDto&gt; result = orders.stream()
                .map(o -&gt; new OrderDto(o))
                .collect(toList());
        return result;
}</code></pre>
<p><code>OrderRepository</code></p>
<pre><code class="language-java">public List&lt;Order&gt; findAllWithItem() {
    return em.createQuery(

        &quot;select distinct o from Order o&quot; +
            &quot; join fetch o.member m&quot; +
            &quot; join fetch o.delivery d&quot; +
            &quot; join fetch o.orderItems oi&quot; +
            &quot; join fetch oi.item i&quot;, Order.class)
        .getResultList();
}</code></pre>
<p>📍** distinct를 쓰는 이유?**</p>
<ul>
<li>컬렉션을 페치 조인하면 일대다 조인이 발생하는데, </li>
<li><em>데이터는 다(N)를 기준으로 row 가 생성되기 때문에*</em> </li>
<li>*같은 엔티티가 중복 조회된다.</li>
<li>*</li>
<li>Order를 기준으로 페이징 하고 싶은데, 다(N)인 OrderItem을 조인하면 OrderItem이 기준이 되어버리면서 발생하는 문제 !</li>
</ul>
<p><strong>-&gt;</strong> <strong>Order을 기준으로 제시하면서  중복을 제거해주기 위해</strong> distinct 추가해줌</p>
<blockquote>
<p>💡 SQL <strong>1번</strong>만 실행됨</p>
</blockquote>
<ul>
<li>sql과 다르게 <strong>JPA에서는 id값이 같으면 중복처리</strong> 해주기 때문에 페치 조인으로 인해 <strong>쿼리가 1번 조회</strong>됨 !</li>
</ul>
<blockquote>
<p>💡  <strong>페이징</strong> 불가능</p>
</blockquote>
<ul>
<li><strong>1:N 연관관계인 두 엔티티</strong>가 즉시 로딩된다면 (엔티티 그래프나 <strong>fetch join 으로 연결</strong>되어 있다면) <strong>페이징 처리시 쿼리에 limit 가 붙지 않고 모든 데이터를 메모리에 불러오기 때문 !</strong></li>
</ul>
<p>▪️ <strong>페이징</strong>?
예:
<img src="https://velog.velcdn.com/images/gahyun_02/post/c545f920-e466-4ebd-a739-a0021dc160a8/image.png" alt="">
원하는 페이지 값과 사이즈를 넣어 데이터를 조회하는 것.
예를 들어 page=0, size=10 이면 첫번째 페이지의 주문 10개 결과가 나오게 된다.</p>
<h4 id="페이징과-한계-돌파">페이징과 한계 돌파</h4>
<p>페이징과 컬렉션 엔티티를 함께 조회하는 방법은?</p>
<ul>
<li>1️⃣ ToOne(OneToOne, ManyToOne) 관계를 모두 페치조인 
(row수를 증가시키지 않으므로 페이징 쿼리에 영향을 주지 X)</li>
<li>2️⃣ 컬렉션은 지연 로딩으로 조회</li>
<li>3️⃣ <code>applications.yml</code>에 설정 추가 or @BatchSize 사용 
(for 지연로딩 성능 최적화)<pre><code class="language-java">spring: jpa:
  properties:
      hibernate:
          default_batch_fetch_size: 1000</code></pre>
  -&gt; 주문 id 를 마지막쿼리의 in 절에 넣어 해당 주문상품만 가져오게 되는 구조 !</li>
</ul>
<p><code>OrderRepository</code></p>
<pre><code class="language-java">    public List&lt;Order&gt; findAllWithMemberDelivery(int offset, int limit) {
        return em.createQuery(
                &quot;select o from Order o&quot; +
                        &quot; join fetch o.member m&quot; +
                        &quot; join fetch o.delivery d&quot;, Order.class
        ).setFirstResult(offset)
                .setMaxResults(limit)
                .getResultList();
    }</code></pre>
<p><code>OrderApiController</code></p>
<pre><code class="language-java">@GetMapping(&quot;/api/v3.1/orders&quot;)
public List&lt;OrderDto&gt; ordersV3_page(@RequestParam(value = &quot;offset&quot;,
  defaultValue = &quot;0&quot;) int offset,
                                      @RequestParam(value = &quot;limit&quot;, defaultValue
  = &quot;100&quot;) int limit) {
      List&lt;Order&gt; orders = orderRepository.findAllWithMemberDelivery(offset,limit);
      List&lt;OrderDto&gt; result = orders.stream()
              .map(o -&gt; new OrderDto(o))
               .collect(toList());

      return result;
 }</code></pre>
<blockquote>
<p>1️⃣ 쿼리 호출 수가 <strong>1+N -&gt; 1+1로 최적화</strong>됨.
2️⃣ 조인보다 DB 데이터 전송량이 최적화 됨.
(페치 조인 방식과 비교해서 <strong>쿼리 호출 수가 약간 증가하지만, DB 데이터 전송량이 감소</strong>한다.) 
❗️ why ? <strong>IN</strong>절에 객체 담아서 <strong>한번에 보내기</strong> 때문!
3️⃣ <strong>페이징</strong> 가능 </p>
</blockquote>
<h3 id="주문-조회-v4-jpa에서-dto-직접-조회">주문 조회 V4: JPA에서 DTO 직접 조회</h3>
<p>엔티티는 리포지토리, 화면이나 API에 의존관계가 있는 쿼리는 따로 클래스를 분리 !
<img src="https://velog.velcdn.com/images/gahyun_02/post/6d2b9c9c-d789-493c-be3b-39dc36cd49ee/image.png" alt="">
<img src="https://velog.velcdn.com/images/gahyun_02/post/d0b105ee-8f0e-451f-a37b-8352db42b380/image.png" alt="">
<img src="https://velog.velcdn.com/images/gahyun_02/post/004ba834-c6cc-4800-a77e-89353e0cb45a/image.png" alt=""></p>
<blockquote>
<ul>
<li>루트 1번 + 컬렉션 N 번 쿼리 실행 
(컬렉션을 루프로 직접 넣어주기 때문에 N번 걸림.)</li>
</ul>
</blockquote>
<ul>
<li>ToOne(N:1, 1:1) 관계들을 먼저 조회하고, ToMany(1:N) 관계는 각각 별도로 처리
( row 수가 증가하지 않는 <strong>ToOne 관계는 조인으로 최적화 하기 쉬우므로 한번에 조회</strong>하고, <strong>ToMany 관계는</strong> 최적화 하기 어려우므로 <strong>findOrderItems() 같은 별도의 메서드로 조회</strong> )</li>
</ul>
<h3 id="주문-조회-v5-jpa에서-dto-직접-조회---컬렉션-조회-최적화">주문 조회 V5: JPA에서 DTO 직접 조회 - 컬렉션 조회 최적화</h3>
<p><code>OrderQueryRepository</code></p>
<pre><code class="language-java">public List&lt;OrderQueryDto&gt; findAllByDto_optimization() { //루트 조회(toOne 코드를 모두 한번에 조회)
      List&lt;OrderQueryDto&gt; result = findOrders();
//orderItem 컬렉션을 MAP 한방에 조회
      Map&lt;Long, List&lt;OrderItemQueryDto&gt;&gt; orderItemMap =
  findOrderItemMap(toOrderIds(result));
//루프를 돌면서 컬렉션 추가(추가 쿼리 실행X) in절에 담아서 한번에 가져오기 
result.forEach(o -&gt; o.setOrderItems(orderItemMap.get(o.getOrderId())));
      return result;
}

private List&lt;Long&gt; toOrderIds(List&lt;OrderQueryDto&gt; result) {
      return result.stream()
              .map(o -&gt; o.getOrderId())
              .collect(Collectors.toList());
}

private Map&lt;Long, List&lt;OrderItemQueryDto&gt;&gt; findOrderItemMap(List&lt;Long&gt;
  orderIds) {
      List&lt;OrderItemQueryDto&gt; orderItems = em.createQuery(
              &quot;select new

   jpabook.jpashop.repository.order.query.OrderItemQueryDto(oi.order.id, i.name,
  oi.orderPrice, oi.count)&quot; +
                      &quot; from OrderItem oi&quot; +
                      &quot; join oi.item i&quot; +
                      &quot; where oi.order.id in :orderIds&quot;, OrderItemQueryDto.class)
              .setParameter(&quot;orderIds&quot;, orderIds)
              .getResultList();
    return orderItems.stream()
        .collect(Collectors.groupingBy(OrderItemQueryDto::getOrderId));
}</code></pre>
<blockquote>
</blockquote>
<p>1️⃣ Query: 루트 1번, 컬렉션 1번 (총 2번~)
2️⃣ <strong>ToOne 관계들을 먼저 조회하고, 여기서 얻은 식별자 orderId로 ToMany 관계인 OrderItem 을 한꺼번에 조회</strong>
3️⃣ <strong>MAP</strong>을 사용해서 매칭 성능 향상(O(1))
4️⃣ 데이터를 <strong>한꺼번에 처리</strong>할 때 많이 씀</p>
<h3 id="주문-조회-v6-jpa에서-dto로-직접-조회-플랫-데이터-최적화">주문 조회 V6: JPA에서 DTO로 직접 조회, 플랫 데이터 최적화</h3>
<p><img src="https://velog.velcdn.com/images/gahyun_02/post/1aa81d85-7eb1-4e40-88c4-fe0df7556a4e/image.png" alt=""></p>
<p><code>OrderQueryRepository</code></p>
<pre><code class="language-java">public List&lt;OrderFlatDto&gt; findAllByDto_flat() {
      return em.createQuery(
              &quot;select new
  jpabook.jpashop.repository.order.query.OrderFlatDto(o.id, m.name, o.orderDate,
  o.status, d.address, i.name, oi.orderPrice, oi.count)&quot; +
                        &quot; from Order o&quot; +
                      &quot; join o.member m&quot; +
                      &quot; join o.delivery d&quot; +
                      &quot; join o.orderItems oi&quot; +
                      &quot; join oi.item i&quot;, OrderFlatDto.class)
              .getResultList();
OrderFlatDto
  package jpabook.jpashop.repository.order.query;
  import jpabook.jpashop.domain.Address;
}</code></pre>
<blockquote>
<p>1️⃣ Query: 1번 
2️⃣ 쿼리는 한번이지만 조인으로 인해 <strong>DB에서 애플리케이션에 전달하는 데이터에 중복 데이터가 추가되기 때문에</strong> 상황에 따라 V5 보다 더 <strong>느릴 수도</strong> 있음
3️⃣ 애플리케이션에서 추가 작업이 큼
4️⃣ 페이징 불가능</p>
</blockquote>
<hr>
<p>결론 !
<img src="https://velog.velcdn.com/images/gahyun_02/post/c548b1d3-297f-4e4f-8d57-3fae8a61dd85/image.png" alt=""></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[API 개발 고급 - 지연 로딩과 조회 성능 최적화]]></title>
            <link>https://velog.io/@gahyun_02/API-%EA%B0%9C%EB%B0%9C-%EA%B3%A0%EA%B8%89-%EC%A7%80%EC%97%B0-%EB%A1%9C%EB%94%A9%EA%B3%BC-%EC%A1%B0%ED%9A%8C-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94</link>
            <guid>https://velog.io/@gahyun_02/API-%EA%B0%9C%EB%B0%9C-%EA%B3%A0%EA%B8%89-%EC%A7%80%EC%97%B0-%EB%A1%9C%EB%94%A9%EA%B3%BC-%EC%A1%B0%ED%9A%8C-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94</guid>
            <pubDate>Fri, 04 Nov 2022 07:06:02 GMT</pubDate>
            <description><![CDATA[<p>간단한 주문 조회에서 벗어나서
주문, 배송정보, 회원을 조회하는 API ➕ 지연 로딩 때문에 발생하는 성능 문제를 해결해보자.</p>
<h3 id="간단한-주문-조회---recommended-way">간단한 주문 조회 - recommended way</h3>
<h4 id="◦-1-엔티티-조회---dto로-변환">◦ 1. 엔티티 조회 -&gt; DTO로 변환</h4>
<pre><code class="language-java">@GetMapping(&quot;/api/v2/simple-orders&quot;)
public List&lt;SimpleOrderDto&gt; ordersV2() {
    List&lt;Order&gt; orders = orderRepository.findAll();
    List&lt;SimpleOrderDto&gt; result = orders.stream()
        .map(o -&gt; new SimpleOrderDto(o))
        .collect(toList());
    return result;
}</code></pre>
<blockquote>
<p>지연로딩으로 쿼리 N번 호출하기 때문에 효율적이지 않아서 성능이  상황에 따라서 성능이 저하될 수 있음.</p>
</blockquote>
<h4 id="◦-2-엔티티-조회---dto로-변환--fetch-join-최적화-">◦ 2. 엔티티 조회 -&gt; DTO로 변환 ( fetch, join 최적화 )</h4>
<pre><code class="language-java">@GetMapping(&quot;/api/v3/simple-orders&quot;)
public List&lt;SimpleOrderDto&gt; ordersV3() {
    List&lt;Order&gt; orders = orderRepository.findAllWithMemberDelivery();
    List&lt;SimpleOrderDto&gt; result = orders.stream()
        .map(o -&gt; new SimpleOrderDto(o))
        .collect(toList());
    return result;
}</code></pre>
<p><code>OrderRepository</code> </p>
<pre><code class="language-java">public List&lt;Order&gt; findAllWithMemberDelivery() {
      return em.createQuery(

        &quot;select o from Order o&quot; +
        &quot; join fetch o.member m&quot; +
        &quot; join fetch o.delivery d&quot;, Order.class)
            .getResultList();
}</code></pre>
<blockquote>
<p>쿼리 1번에 조회 가능 !</p>
</blockquote>
<ul>
<li>order -&gt; member , order -&gt; delivery 는 이미 조회 된 상태 이므로 지연로딩 X</li>
</ul>
<h4 id="◦-3-jpa에서-dto로-바로-조회">◦ 3. JPA에서 DTO로 바로 조회</h4>
<pre><code class="language-java">@GetMapping(&quot;/api/v4/simple-orders&quot;)
public List&lt;OrderSimpleQueryDto&gt; ordersV4() {
      return orderSimpleQueryRepository.findOrderDtos();
}</code></pre>
<blockquote>
<p>쿼리 1번에 select절에서 원하는 데이터만 조회 가능 !</p>
</blockquote>
<blockquote>
<p><strong>💡 쿼리 방식 선택 권장 순서</strong>
<strong>1.</strong> 우선 엔티티를 DTO로 변환하는 방법을 선택한다.
<strong>2.</strong> 필요하면 페치 조인으로 성능을 최적화 한다. 대부분의 성능 이슈가 해결된다.
<strong>3.</strong> 그래도 안되면 DTO로 직접 조회하는 방법을 사용한다.
<strong>4.</strong> 최후의 방법은 JPA가 제공하는 네이티브 SQL이나 스프링 JDBC Template을 사용해서 SQL을 직접
사용한다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[API 개발 기본]]></title>
            <link>https://velog.io/@gahyun_02/API-%EA%B0%9C%EB%B0%9C-%EA%B8%B0%EB%B3%B8</link>
            <guid>https://velog.io/@gahyun_02/API-%EA%B0%9C%EB%B0%9C-%EA%B8%B0%EB%B3%B8</guid>
            <pubDate>Fri, 04 Nov 2022 07:05:17 GMT</pubDate>
            <description><![CDATA[<h3 id="회원-등록-api">회원 등록 API</h3>
<p>❌ <strong>잘못된 방법</strong> ( 요청 값으로 Member 엔티티를 직접 받음. )</p>
<pre><code class="language-java">@PostMapping(&quot;/api/v1/members&quot;)
public CreateMemberResponse saveMemberV1(@RequestBody @Valid Member member)
{
    Long id = memberService.join(member);
    return new CreateMemberResponse(id);
}</code></pre>
<p>여러 곳에서 수정될 가능성이 있는 Entity가 바뀜으로 인해서
만들어놓은 api스펙이 변경될 수 있도록 하면 안된다.
<strong>-&gt;</strong> ⭕️ <strong>API 요청</strong> 스펙<strong>에 맞추어 별도의 DTO를 파라미터로 받는다.</strong></p>
<blockquote>
<p>-&gt; 파라미터로 넘어오는 값이 무엇인지 확실히 알 수 있음 ! 
-&gt; 유지보수에 유리함.</p>
</blockquote>
<pre><code class="language-java">@PostMapping(&quot;/api/v2/members&quot;)
public CreateMemberResponse saveMemberV2(@RequestBody @Valid
  CreateMemberRequest request) {
          Member member = new Member();
          member.setName(request.getName());
          Long id = memberService.join(member);
          return new CreateMemberResponse(id);
}

@Data
static class CreateMemberRequest {
    private String name;
}</code></pre>
<blockquote>
<p>❗️❗️ 실무에서는 <strong>엔티티를 API 스펙에 노출하면 안된다!</strong></p>
</blockquote>
<h3 id="회원-수정-api">회원 수정 API</h3>
<p>회원 조회와 똑같이 DTO를 요청 파라미터에 넘겨준다.</p>
<pre><code class="language-java">@PostMapping(&quot;/api/v2/members/{id}&quot;) // 부분 업데이트를 하려면 post나 patch를 사용하는 것이 restful한 방법.
public UpdateMemberResponse updateMemberV2(@PathVariable(&quot;id&quot;) Long id, @RequestBody @Valid UpdateMemberRequest request) {
        memberService.update(id, request.getName());
        Member findMember = memberService.findOne(id); // 커맨드와 쿼리를 구분하기 위한 코드
        return new UpdateMemberResponse(findMember.getId(), findMember.getName());
}</code></pre>
<pre><code class="language-java">public class MemberService {
        private final MemberRepository memberRepository;

        // 변경 감지를 이용해서 데이터 수정함.
        @Transactional
        public void update(Long id, String name) {
            Member member = memberRepository.findOne(id);
            member.setName(name);
        }
}</code></pre>
<h3 id="회원-조회-api">회원 조회 API</h3>
<p>🔴 <strong>다양한 API 요구에 따라서</strong> 유연하게 데이터를 주고받기 위해서 
응답 값으로 엔티티를 직접 외부에 노출하지 않고,
<strong>별도의 DTO를 반환</strong>해야 한다.</p>
<pre><code class="language-java">@GetMapping(&quot;/api/v2/members&quot;)
public Result membersV2() {
    List&lt;Member&gt; findMembers = memberService.findMembers(); // 엔티티 -&gt; DTO 변환
    List&lt;MemberDto&gt; collect = findMembers.stream()
        .map(m -&gt; new MemberDto(m.getName()))
        .collect(Collectors.toList());
    return new Result(collect);  // 컬렉션을 감싸서 향후 필요한 필드 추가 가능
}
@Data
@AllArgsConstructor
static class Result&lt;T&gt; {
    private T data;
}</code></pre>
<p><code>Application.yml</code> 에서</p>
<pre><code>ddl-auto:None</code></pre><p>위 코드 추가
-&gt; 한번 데이터를 넣어놓으면 테이블을 들어가지 않고 계속해서 db에 있는 데이터들을 쓸 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[웹 계층 개발]]></title>
            <link>https://velog.io/@gahyun_02/%EC%9B%B9-%EA%B3%84%EC%B8%B5-%EA%B0%9C%EB%B0%9C</link>
            <guid>https://velog.io/@gahyun_02/%EC%9B%B9-%EA%B3%84%EC%B8%B5-%EA%B0%9C%EB%B0%9C</guid>
            <pubDate>Fri, 28 Oct 2022 05:30:13 GMT</pubDate>
            <description><![CDATA[<h2 id="홈-화면과-레이아웃">홈 화면과 레이아웃</h2>
<h3 id="홈-컨트롤러-등록">홈 컨트롤러 등록</h3>
<ul>
<li><code>SLF4J (Simple Logging Facade for Java)</code> 란?
java.util.logging, logback 및 log4j와 같은 <strong>다양한 로깅 프레임 워크에 대한 추상화(인터페이스) 역할</strong>을 하는 라이브러리</li>
</ul>
<p>최종 사용자가 배포시 원하는 로깅 프레임워크를 결정하고 사용해도 SLF4J가 인터페이스화 되어있기 때문에, SLF4J를 의존하는 클라이언트 코드에서는 실제 구현을 몰라도 됨.</p>
<p>(의존관계 역전 법칙)</p>
<hr>
<h2 id="회원">회원</h2>
<h3 id="회원-등록">회원 등록</h3>
<ul>
<li><code>폼 객체</code>를 사용해서 <strong>화면 계층</strong>과 <strong>서비스 계층</strong>을 명확하게 분리하기 !! <blockquote>
<p>📍 참고: <strong>폼 객체</strong> (MemberForm) vs <strong>엔티티</strong> (Member) <strong>직접 사용</strong>
요구사항이 정말 단순할 때를 제외하고 엔티티 (Member)를 직접 등록하면 유지보수가 어려워진다.
실무에서 <strong>엔티티는 핵심 비즈니스 로직만</strong> 가지고 있고, 화면을 위한 로직은 없어야 한다.</p>
</blockquote>
</li>
<li><em>화면이나 API 요구사항을 폼 객체나 DTO로 처리*</em>하고, 엔티티는 최대한 순수 하게 유지하자.</li>
</ul>
<h4 id="회원-등록-폼-객체">회원 등록 폼 객체</h4>
<pre><code class="language-java">package jpabook.jpashop.web;
import lombok.Getter;
import lombok.Setter;
import javax.validation.constraints.NotEmpty;
@Getter @Setter
public class MemberForm {
    @NotEmpty(message = &quot;회원 이름은 필수 입니다&quot;) private String name;
    private String city;
    private String street;
    private String zipcode;
}</code></pre>
<h4 id="회원-등록-컨트롤러">회원 등록 컨트롤러</h4>
<pre><code class="language-java">@Controller
@RequiredArgsConstructor
public class MemberController {
    private final MemberService memberService;
    @GetMapping(value = &quot;/members/new&quot;)
    public String createForm(Model model) {
        model.addAttribute(&quot;memberForm&quot;, new MemberForm());
        return &quot;members/createMemberForm&quot;;
    }
    @PostMapping(value = &quot;/members/new&quot;)
    public String create(@Valid MemberForm form, BindingResult result) {
        if (result.hasErrors()) {
            return &quot;members/createMemberForm&quot;;
}
        Address address = new Address(form.getCity(), form.getStreet(),
form.getZipcode());
        Member member = new Member();
        member.setName(form.getName());
 member.setAddress(address);
          memberService.join(member);
          return &quot;redirect:/&quot;;
      }
}</code></pre>
<h3 id="회원-목록-조회">회원 목록 조회</h3>
<pre><code class="language-java">package jpabook.jpashop.web;

@Controller
@RequiredArgsConstructor
public class MemberController {

    //추가
    @GetMapping(value = &quot;/members&quot;) public String list(Model model) {
    List&lt;Member&gt; members = memberService.findMembers();
    model.addAttribute(&quot;members&quot;, members); // 조회한 상품을 뷰에 전달하기 위해 모델 객체에 보관

    return &quot;members/memberList&quot;; // 실행할 뷰 이름 반환

    }
}</code></pre>
<hr>
<h2 id="상품">상품</h2>
<h3 id="상품-등록">상품 등록</h3>
<p>상품 등록 폼은 회원 등록의 경우와 매우 유사하므로 생략.</p>
<h4 id="상품-등록-컨트롤러">상품 등록 컨트롤러</h4>
<pre><code class="language-java">@Controller
@RequiredArgsConstructor
public class ItemController {
    private final ItemService itemService;
    @GetMapping(value = &quot;/items/new&quot;)
    public String createForm(Model model) {
        model.addAttribute(&quot;form&quot;, new BookForm());
         return &quot;items/createItemForm&quot;;
    }

    @PostMapping(value = &quot;/items/new&quot;) // POST 요청
    public String create(BookForm form) {
        Book book = new Book();
        book.setName(form.getName());
        book.setPrice(form.getPrice());
        book.setStockQuantity(form.getStockQuantity());
        book.setAuthor(form.getAuthor());
        book.setIsbn(form.getIsbn());
        itemService.saveItem(book);
        return &quot;redirect:/items&quot;; // 상품 목록 화면으로 리다이렉트
    }
 }
</code></pre>
<h4 id="상품-수정">상품 수정</h4>
<pre><code class="language-java">@Controller
@RequiredArgsConstructor
public class ItemController {
    /**
    * 상품 수정 폼
    */
    @GetMapping(value = &quot;/items/{itemId}/edit&quot;) // GET 요청
    public String updateItemForm(@PathVariable(&quot;itemId&quot;) Long itemId, Model
model) {
        Book item = (Book) itemService.findOne(itemId); // 준영속 상태 ( book 객체에 해당 ), 수정할 상품 조회
        BookForm form = new BookForm();
        form.setId(item.getId());
        form.setName(item.getName());
        form.setPrice(item.getPrice());
        form.setStockQuantity(item.getStockQuantity());
        form.setAuthor(item.getAuthor());
        form.setIsbn(item.getIsbn());
        model.addAttribute(&quot;form&quot;, form);
        return &quot;items/updateItemForm&quot;;
    }

    /**
    * 상품 수정
    */
    @PostMapping(value = &quot;/items/{itemId}/edit&quot;)  // POST 요청
    public String updateItem(@ModelAttribute(&quot;form&quot;) BookForm form) {
        Book book = new Book();
        book.setId(form.getId());
        book.setName(form.getName());
        book.setPrice(form.getPrice());
        book.setStockQuantity(form.getStockQuantity());
        book.setAuthor(form.getAuthor());
        book.setIsbn(form.getIsbn());
        itemService.saveItem(book);
        return &quot;redirect:/items&quot;;
       }
}</code></pre>
<hr>
<h2 id="변경-감지와-병합">변경 감지와 병합</h2>
<h4 id="준영속-엔티티란">준영속 엔티티란?</h4>
<blockquote>
<p>영속성 컨텍스트가 더는 관리하지 않는 엔티티를 말한다.</p>
</blockquote>
<p>*상품 수정 코드에서처럼 임의로 만들어낸 엔티티도 기존 식별자를 가지고 있으면 준 영속 엔티티로 볼 수 있다.</p>
<h4 id="준영속-엔티티-수정하는-방법-2">준영속 엔티티 수정하는 방법 (2)</h4>
<p>1)  ** ⭐️ ⭐️ ⭐️ 변경 감지 기능 사용 ⭐️ ⭐️ ⭐️ **</p>
<pre><code class="language-java">@Transactional
void update(Item itemParam) { //itemParam: 파리미터로 넘어온 준영속 상태의 엔티티
    Item findItem = em.find(Item.class, itemParam.getId()); //같은 엔티티를 조회한 다.
    findItem.setPrice(itemParam.getPrice()); //데이터를 수정한다. 
}</code></pre>
<blockquote>
<p><strong>영속성</strong> 컨텍스트에서 <strong>엔티티를 다시 조회**</strong> -&gt; 변경할 값 선택 트랜잭션 커밋 시점에 <strong>변경 감지</strong>(Dirty Checking) -&gt; 데이터베이스에 <strong>UPDATE SQL 실행</strong> 
-&gt; 데이터 수정</p>
</blockquote>
<p>권장 코드</p>
<pre><code class="language-java">@Controller
@RequiredArgsConstructor
    public class ItemController {
        private final ItemService itemService;
        /**
        *상품 수정,권장 코드
        */
        @PostMapping(value = &quot;/items/{itemId}/edit&quot;)
        public String updateItem(@ModelAttribute(&quot;form&quot;) BookForm form) {
            itemService.updateItem(form.getId(), form.getName(), form.getPrice());
            return &quot;redirect:/items&quot;;
        }
    }</code></pre>
<pre><code class="language-java">@Service
@RequiredArgsConstructor

public class ItemService {
     private final ItemRepository itemRepository;
      /**
      * 영속성 컨텍스트가 자동 변경
      */
      @Transactional
      public void updateItem(Long id, String name, int price) {
          Item item = itemRepository.findOne(id);
          item.setName(name);
          item.setPrice(price);
    }
}</code></pre>
<p>2) <strong>병합 사용</strong>
<img src="https://velog.velcdn.com/images/gahyun_02/post/ed19de14-d192-427e-a63f-1bf88465f0f3/image.png" alt=""></p>
<blockquote>
<ol>
<li>준영속 엔티티의 <strong>식별자 값으로 영속 엔티티 조회</strong></li>
<li>영속 엔티티의 값을 <strong>준영속 엔티티의 값으로 모두 교체</strong> (** = 병합** )</li>
<li>트랜잭션 커밋 시점에 변경 감지 기능이 동작해서 DB에 UPDATE SQL이 실행됨. </li>
</ol>
</blockquote>
<pre><code class="language-java">@Transactional
    void update(Item itemParam) { //itemParam: 파리미터로 넘어온 준영속 상태의 엔티티 Item mergeItem = em.merge(item);
}</code></pre>
<hr>
<h2 id="상품-주문">상품 주문</h2>
<p>위의 과정과 매우 유사하므로 생략.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[상품 도메인 개발과 주문 도메인 개발]]></title>
            <link>https://velog.io/@gahyun_02/%EC%83%81%ED%92%88-%EB%8F%84%EB%A9%94%EC%9D%B8-%EA%B0%9C%EB%B0%9C%EA%B3%BC-%EC%A3%BC%EB%AC%B8-%EB%8F%84%EB%A9%94%EC%9D%B8-%EA%B0%9C%EB%B0%9C</link>
            <guid>https://velog.io/@gahyun_02/%EC%83%81%ED%92%88-%EB%8F%84%EB%A9%94%EC%9D%B8-%EA%B0%9C%EB%B0%9C%EA%B3%BC-%EC%A3%BC%EB%AC%B8-%EB%8F%84%EB%A9%94%EC%9D%B8-%EA%B0%9C%EB%B0%9C</guid>
            <pubDate>Fri, 28 Oct 2022 04:48:28 GMT</pubDate>
            <description><![CDATA[<h3 id="1️⃣-상품-도메인-개발">1️⃣ 상품 도메인 개발</h3>
<p>개발 순서 리마인드 : 엔티티 -&gt; 레포지토리 -&gt; 서비스</p>
<h4 id="상품-레포지토리-개발">상품 레포지토리 개발</h4>
<p>📍</p>
<pre><code class="language-java">public void save(Item item) {
    if (item.getId() == null) { // 새로운 회원일 때
        em.persist(item);
    }
    else {                      // DB에 이미 저장된 엔티티 &#39;수정&#39;
        em.merge(item);
    }
}</code></pre>
<h4 id="상품-서비스-개발">상품 서비스 개발</h4>
<ul>
<li>레포지토리 클래스에 위임만 하는 클래스 !!</li>
</ul>
<hr>
<h3 id="2️⃣-주문-도메인-개발">2️⃣ 주문 도메인 개발</h3>
<p>상품 도메인 개발과 방법이 유사함.</p>
<h4 id="주문-검색-기능-개발">주문 검색 기능 개발</h4>
<ul>
<li>JPA에서 동적 쿼리를 어떻게 처리해야 하는가?</li>
</ul>
<p>1) 검색 조건으로 파라미터에 OrderSearch 클래스를 추가한다.</p>
<pre><code class="language-java">package jpabook.jpashop.domain;
public class OrderSearch {
    private String memberName; //회원 이름
    private OrderStatus orderStatus;//주문 상태[ORDER, CANCEL] //Getter, Setter
}</code></pre>
<p>그 다음, 리포지토리 클래스에 코드 추가 !</p>
<pre><code class="language-java">public List&lt;Order&gt; findAll(OrderSearch orderSearch) { 
    //... 검색 로직
}</code></pre>
<p>2) JPA Criteria로 처리</p>
<pre><code class="language-java">public List&lt;Order&gt; findAllByCriteria(OrderSearch orderSearch) {
    CriteriaBuilder cb = em.getCriteriaBuilder();
    CriteriaQuery&lt;Order&gt; cq = cb.createQuery(Order.class);
    Root&lt;Order&gt; o = cq.from(Order.class);
    Join&lt;Order, Member&gt; m = o.join(&quot;member&quot;, JoinType.INNER); //회원과 조인
    List&lt;Predicate&gt; criteria = new ArrayList&lt;&gt;();
    //주문 상태 검색
    if (orderSearch.getOrderStatus() != null) {
          Predicate status = cb.equal(o.get(&quot;status&quot;),
          orderSearch.getOrderStatus());
          criteria.add(status);
    }
    //회원 이름 검색
    if (StringUtils.hasText(orderSearch.getMemberName())) {
          Predicate name = cb.like(m.&lt;String&gt;get(&quot;name&quot;), &quot;%&quot; + orderSearch.getMemberName() + &quot;%&quot;);
          criteria.add(name);
    }
     cq.where(cb.and(criteria.toArray(new Predicate[criteria.size()])));
    TypedQuery&lt;Order&gt; query = em.createQuery(cq).setMaxResults(1000); //최대 1000건
        return query.getResultList();
}</code></pre>
<blockquote>
<p>실무에서 쓰기엔 너무 복잡함. -&gt; <strong>Querydsl</strong> 사용 ! </p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[회원 도메인 개발]]></title>
            <link>https://velog.io/@gahyun_02/%ED%9A%8C%EC%9B%90-%EB%8F%84%EB%A9%94%EC%9D%B8-%EA%B0%9C%EB%B0%9C</link>
            <guid>https://velog.io/@gahyun_02/%ED%9A%8C%EC%9B%90-%EB%8F%84%EB%A9%94%EC%9D%B8-%EA%B0%9C%EB%B0%9C</guid>
            <pubDate>Fri, 07 Oct 2022 05:17:51 GMT</pubDate>
            <description><![CDATA[<h3 id="💡-개발-순서">💡 개발 순서</h3>
<p>(회원 엔티티 코드 다시 보기) ➡️ 회원 리포지토리 개발 ➡️ 
회원 서비스 개발 ➡️ 회원 기능 테스트</p>
<p><code>MemberRepository</code></p>
<pre><code class="language-javascript">@Repository  // 스프링 빈으로 등록, JPA 예외를 스프링 기반 예외로 예외 변환
public class MemberRepository {
    @PersistenceContext  // 엔티티 메니저(EntityManager) 주입
    private EntityManager em;

      // 기능 1
    public void save(Member member) {
          em.persist(member);
    }
    // 기능 2
    public Member findOne(Long id) {
          return em.find(Member.class, id);
    }
    // 기능 3
    public List&lt;Member&gt; findAll() {
          return em.createQuery(&quot;select m from Member m&quot;, Member.class)
                  .getResultList();
    }
    // 기능 4
    public List&lt;Member&gt; findByName(String name) {
          return em.createQuery(&quot;select m from Member m where m.name = :name&quot;,
Member.class)
            .setParameter(&quot;name&quot;, name)
            .getResultList();
      }

}</code></pre>
<p>cf) <code>@PersistenceUnit</code> : 엔티티 메니터 팩토리( EntityManagerFactory ) 주입</p>
<hr>
<p><code>MemberService</code></p>
<pre><code class="language-javascript">@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
    public class MemberService {
        private final MemberRepository memberRepository;   // 레포지토리 객체 만들기

        /**
        * 회원가입 */
        @Transactional // 변경
        public Long join(Member member) {
            validateDuplicateMember(member); // 중복 회원 검증 memberRepository.save(member);
            memberRepository.save(member);
            return member.getId();
        }

        private void validateDuplicateMember(Member member) {
            List&lt;Member&gt; findMembers = memberRepository.findByName(member.getName());
            if (!findMembers.isEmpty()) {
                throw new IllegalStateException(&quot;이미 존재하는 회원입니다.&quot;); 
            }
        }
        /**
        *전체 회원 조회
        */
        public List&lt;Member&gt; findMembers() {
            return memberRepository.findAll();
        }
        public Member findOne(Long memberId) {
            return memberRepository.findOne(memberId);
        }
    }
</code></pre>
<blockquote>
<p><strong>@RequiredArgsConstructor</strong> : 생성자 주입
    - 변경이 불가능한 안전한 객체 생성 가능</p>
</blockquote>
<pre><code class="language-javascript">public class MemberService {
        private final MemberRepository memberRepository;  
          // final 키워드를 추가하면 컴파일 시점에 memberRepository 를 설정하지 않는 오류를 체크할 수 있다.
        public MemberService(MemberRepository memberRepository) {
            this.memberRepository = memberRepository;
      }
          ... 
}</code></pre>
<p><code>lombok</code> 라이브러리가 자동완성 해주는  부분이다 !</p>
<hr>
<p><code>MemberServiceTest</code></p>
<pre><code class="language-javascript">@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional  // 테스트케이스에서 쓰일 때는, 테스트가 끝나면 강제로 롤백한다.
public class MemberServiceTest {
    @Autowired MemberService memberService;
    @Autowired MemberRepository memberRepository;
    @Test
    public void 회원가입() throws Exception {
        //Given
        Member member = new Member();
        member.setName(&quot;kim&quot;);

        //When
        Long saveId = memberService.join(member);

        //Then
        assertEquals(member, memberRepository.findOne(saveId));
    }
    @Test(expected = IllegalStateException.class)
    public void 중복_회원_예외() throws Exception {
        //Given
        Member member1 = new Member();
        member1.setName(&quot;kim&quot;);
        Member member2 = new Member();
        member2.setName(&quot;kim&quot;);

        //When
        memberService.join(member1);
          memberService.join(member2);  // 예외가 발생해야 한다.

        //Then
        fail(&quot;예외가 발생해야 한다.&quot;);
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[애플리케이션 구현 준비]]></title>
            <link>https://velog.io/@gahyun_02/%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EA%B5%AC%ED%98%84-%EC%A4%80%EB%B9%84</link>
            <guid>https://velog.io/@gahyun_02/%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%EA%B5%AC%ED%98%84-%EC%A4%80%EB%B9%84</guid>
            <pubDate>Fri, 07 Oct 2022 05:08:10 GMT</pubDate>
            <description><![CDATA[<h3 id="구현-요구사항">구현 요구사항</h3>
<p><img src="https://velog.velcdn.com/images/gahyun_02/post/98848447-c918-473b-a70c-ea464f03067e/image.png" alt="">
<img src="https://velog.velcdn.com/images/gahyun_02/post/eabfd05a-b7ce-4e5f-b0fd-a44065808e5f/image.png" width=30%, height=30%></p>
<h3 id="애플리케이션-아키텍처">애플리케이션 아키텍처</h3>
<p><img src="https://velog.velcdn.com/images/gahyun_02/post/9b8e8ed6-bf66-49b1-9898-c6c816ebb4cb/image.png" alt="">
<strong>계층형 구조 사용</strong></p>
<ul>
<li><code>controller, web</code> : 웹 계층</li>
<li><code>service</code> : 비즈니스 로직, 트랜잭션 처리</li>
<li><code>repository</code> : JPA를 직접 사용하는 계층, 엔티티 매니저 사용</li>
<li><code>domain</code> : 엔티티가 모여 있는 계층, 모든 계층에서 사용</li>
</ul>
<blockquote>
<p><strong>서비스, 리포지토리 계층 개발</strong> ➡️ <strong>테스트 케이스 작성</strong> ➡️ <strong>웹 계층에 적용</strong></p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[도메인 분석 설계]]></title>
            <link>https://velog.io/@gahyun_02/%EB%8F%84%EB%A9%94%EC%9D%B8-%EB%B6%84%EC%84%9D-%EC%84%A4%EA%B3%84</link>
            <guid>https://velog.io/@gahyun_02/%EB%8F%84%EB%A9%94%EC%9D%B8-%EB%B6%84%EC%84%9D-%EC%84%A4%EA%B3%84</guid>
            <pubDate>Thu, 06 Oct 2022 07:34:56 GMT</pubDate>
            <description><![CDATA[<h3 id="도메인-모델과-테이블-설계">도메인 모델과 테이블 설계</h3>
<p><img src="https://velog.velcdn.com/images/gahyun_02/post/fed9220f-3bb2-4429-8185-ead704a12cab/image.png" alt="">
<code>@OneToMany</code>
<code>@ManyToOne</code>
<code>@OneToOne</code></p>
<h3 id="엔티티-클래스-개발">엔티티 클래스 개발</h3>
<ul>
<li>Setter를 호출하면 데이터가 변하기 때문에 변경 사항을 추적하기 힘들어진다.
➡️ 실무에서는 가급적 <strong>Getter는 열어두고, Setter는 꼭 필요한 경우에만</strong> 사용하는 것을 추천! </li>
<li><strong>엔티티를 변경</strong>할 때는 Setter 대신에 변경 지점이 명확하도록 <strong>변경을 위한 비즈니스 메서드를 별도로 제공</strong>해야 한다.
➡️ 엔티티나 임베디드 타입은 기본생성자를 public보다는 <strong>protected</strong>로 설정하는게 안전합니다.</li>
</ul>
<p>*<em>임베디드 타입은 <code>@Embeded</code> 나 <code>@Embeddable</code>으로 표현</em></p>
<hr>
<h4 id="💡-상속관계-매핑">💡 상속관계 매핑</h4>
<ul>
<li>1️⃣ <strong>각각의 테이블</strong>로 변환
<code>@Inheritance(stragy = InheritanceType.JOINED)</code><blockquote>
<p>✅ 자식 테이블들을 DTYPE으로 구분하여 사용할 수 있음. 
✅ 상속받는 자식 테이블을 각각 만들고, <strong>부모 테이블의 PK를 자식 테이블의 PK이자 FK로 사용함.</strong>
➡️ 이를 통해 다른 테이블에서 부모 테이블만 보도록 설계할 수 있음.</p>
</blockquote>
</li>
<li>2️⃣ <strong>단일 테이블</strong>로 변환
<code>@Inheritance(stragy = InheritanceType.SINGLE_TABLE)</code><blockquote>
<p>✅ <strong>자식 테이블들을 통합</strong>한 단일 테이블로 사용할 수 있음.
✅ <strong>@DiscriminatorValue(&quot;...&quot;)</strong> = 각각의 테이블들이 하나의 싱글테이블로 합쳐지기에 DB에서 테이블을 식별할 수 있도록 <strong>구분할 수 있는 값</strong>을 넣어줘야 함.</p>
</blockquote>
</li>
<li>3️⃣ <strong>서브타입 테이블</strong>로 변환
<code>@Inheritance(stragy = InheritanceType.TABLE_PER_CLASS)</code><blockquote>
<p>✅ <strong>자식 엔티티</strong>들을 <strong>각각의 테이블</strong>로 만들어 사용함.
✅ 여러 자식들을 조회할 때, UNION을 사용하여 성능이 저하되며 다른 여러 단점들이 존재하여 자주 사용 X</p>
</blockquote>
</li>
</ul>
<hr>
<h4 id="💡-즉시로딩-vs-지연로딩">💡 즉시로딩 vs 지연로딩</h4>
<blockquote>
<p>모든 연관관계에서 FetchType을 🔴 *<em>지연로딩(LAZY) *</em>🔴 으로 설정해야 한다 !!</p>
</blockquote>
<ul>
<li><p><strong>즉시로딩</strong> (FetchType - EAGER) : 엔티티를 조회할 때 연관된 엔티티를 즉시 조인해서 값을 넣어주고, 함께 조회함.
◦ <strong>단점</strong> : 예상하지 못한 SQL이 발생하고, 굉장히 많은 문제를 발생시킬 가능성 존재</p>
</li>
<li><p><strong>지연로딩</strong> (FetchType - LAZY) : 엔티티를 조회할 때 연관된 엔티티를 조인하지 않음. 즉 값을 넣어주지 않는다. 
대신, 연관된 엔티티를 참조할 때 <strong>조인 쿼리를 작성하여 값을 넣어준다</strong>. 
(*<em>필요할 때 *</em>넣어준다는 뜻 !) 
➡️ 해당 객체를 조회할 때 그 객체를 조회하는 쿼리가 발생하게 된다.</p>
</li>
</ul>
<p><strong>➡️ <code>@...ToOne</code> 의 경우, *<em>FetchType의 디폴트가 즉시로딩으로 되어있으므로 
어떤 SQL이 실행될지 추적하기 어렵기 때문에 *</em>컬렉션은</strong> null문제 해결을 보장하기위해 <strong>필드에서 초기화하는 것이 안전</strong>함.
(<code>@OneTo...</code>의 디폴트는 LAZY이기에 따로 설정안해도 됨.)</p>
<hr>
<h4 id="💡-jpa-연관관계-매핑">💡 JPA 연관관계 매핑</h4>
<blockquote>
<p>데이터베이스에서 <strong>무조건 다(N)쪽이 외래 키(FK)를 갖는다</strong> !!</p>
</blockquote>
<ul>
<li>1️⃣ <strong>다대일</strong> (<strong>N:1</strong>)
다(N)쪽인 엔티티에 <strong>@ManyToOne</strong>을 추가해주고, <strong>@JoinColumn(&quot;반대 쪽_id&quot;)</strong>을 추가해 반대 쪽(1)의 외래키를 갖음.</li>
</ul>
<pre><code class="language-javascript">    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;parent_id&quot;)
    private Category parent;</code></pre>
<ul>
<li>2️⃣ <strong>일대다</strong> (<strong>1:N</strong>)
일대다<strong>(단방향)</strong>는 다대일의 반대 입장이므로 같은 것이라고 생각할 수 있지만
이는 다(N)쪽이 아닌 <strong>일(1)쪽에 연관관계의 주인을 두는 방식</strong> ! 
이는 일(1)만 수정했을 때, 다(N)쪽에서도 수정이 생겨 쿼리에 문제가 생기기 때문에</li>
<li><em>다대일(N:1) 양방향 연관 관계를 매핑하는게 추후에 유지보수에도 수월함.*</em></li>
</ul>
<pre><code class="language-javascript">     @OneToMany(mappedBy = &quot;parent&quot;)
    private List&lt;Category&gt; child = new ArrayList&lt;&gt;();</code></pre>
<ul>
<li>3️⃣ <strong>다대다</strong> (<strong>N:M</strong>)
두 테이블 사이에 양쪽의 PK를 FK로 갖는 연결 테이블을 만들어 사용함.</li>
<li><em>즉, 중간 엔티티를 만들고 @ManyToOne , @OneToMany 로 매핑해서 사용하자. *</em></li>
</ul>
<hr>
<h4 id="💡-영속성-전이-jpa-cascade">💡 영속성 전이 (JPA cascade)</h4>
<p>CascadeType의 종류로는 ALL, PERSIST, REMOVE, MERGE, REFRESH, DETACH가 있으나 자주 사용하는 건 세 개 정도.</p>
<ul>
<li><strong>ALL</strong> : 모두 적용 (PERSIST + REMOVE + ...)</li>
<li><strong>PERSIST</strong> : 영속</li>
<li><strong>REMOVE</strong> : 삭제</li>
</ul>
<blockquote>
<p>이는 엔티티를 영속화할 때 연관된 엔티티들도 함께 영속화해준다는 점에서 편리함을 제공하는 것이지,연관관계를 매핑하는 것과는 상관 ✖︎</p>
</blockquote>
<p>ex)</p>
<pre><code class="language-javascript">@OneToMany(mappedBy = &quot;order&quot;, cascade = CascadeType.ALL)
 private List&lt;OrderItem&gt; orderItems = new ArrayList&lt;&gt;();</code></pre>
<hr>
<h3 id="엔티티-설계시-주의점">엔티티 설계시 주의점</h3>
<h4 id="💡-컬렉션은-필드에서-초기화-">💡 컬렉션은 필드에서 초기화 !</h4>
<p>컬렉션은 필드에서 바로 초기화 하는 것이 안전하다. (선언과 초기화를 같이 하자 !)</p>
<ul>
<li><code>null</code> 문제에서 안전하다.</li>
<li><strong>하이버네이트는 엔티티를 영속화 할 때</strong> 컬랙션을 감싸서 하이버네이트가 제공하는 <strong>내장 컬렉션으로 변경</strong>하기 때문에, 만약 getOrders() 처럼 임의의 메서드에서 컬력션을 잘못 생성하면 하이버네이트 내부 메커니즘에 문제가 발생할 수 있다. </li>
</ul>
<p>➡️ 따라서 필드레벨에서 생성하는 것이 가장 안전하고, 코드도 간결하다.</p>
<p>ex)</p>
<pre><code class="language-javascript">@OneToMany(mappedBy = &quot;order&quot;, cascade = CascadeType.ALL)
private List&lt;OrderItem&gt; orderItems = new ArrayList&lt;&gt;();</code></pre>
<hr>
<h4 id="💡-테이블-컬럼명-생성-전략">💡 테이블, 컬럼명 생성 전략</h4>
<p>스프링 부트에서 하이버네이트 기본 매핑 전략을 변경해서 <strong>실제 테이블 필드명은 다름</strong> !</p>
<p><code>스프링 부트 신규 설정</code> (엔티티(필드) ➡️ 테이블(컬럼))</p>
<ul>
<li><ol>
<li>카멜 케이스 ➡️ 언더스코어(memberPoint ➡️ member_point)</li>
</ol>
</li>
<li><ol start="2">
<li>.(점) ➡️ _(언더스코어)</li>
</ol>
</li>
<li><ol start="3">
<li>대문자 ➡️ 소문자</li>
</ol>
</li>
</ul>
<hr>
<h4 id="💡-enum-타입-지정-시-주의할-점">💡 Enum 타입 지정 시 주의할 점</h4>
<pre><code class="language-javascript">@Enumerated(EnumType.STRING)
private DeliveryStatus status; //ENUM [READY(준비), COMP(배송)]</code></pre>
<blockquote>
<p>새로운 게 추가 되었을 때를 고려해줘야 하기 때문에,
<strong>EnumType</strong>은 꼭 <strong>스트링</strong>으로 해주기 !!</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[프로젝트 환경설정]]></title>
            <link>https://velog.io/@gahyun_02/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%99%98%EA%B2%BD%EC%84%A4%EC%A0%95</link>
            <guid>https://velog.io/@gahyun_02/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8-%ED%99%98%EA%B2%BD%EC%84%A4%EC%A0%95</guid>
            <pubDate>Thu, 06 Oct 2022 04:36:15 GMT</pubDate>
            <description><![CDATA[<p>./h2.sh     : h2 데이터베이스 실행</p>
<p><code>application.yml</code></p>
<pre><code class="language-javascript">spring:
  database:
    url: jdbc:h2:tcp://localhost/~/jpashop;MVCC=TRUE  // 여러개가 한번에 접근했을 때 빨리 처리할 수 있게 도와줌.
    username: sa
    password:
    driver-class-name: org.h2.Driver

  jpa:
    hibernate:
      ddl-auto: create // 자동으로 창을 만들어줌. 어플리케이션 실행 시점에 탭을 실행함.
    properties:
      hibernate:
//        show_sql: true
        format_sql: true
logging:
  level:
    org.hibernate.SQL: debug
    rg.hibernate.type: trace // 쿼리 파라미터를 로그에 남기는 것이 가능해짐.
</code></pre>
<p>만약, 장고에서 하는 것처럼 request로 데이터를 받는다고 가정해보면
request에 대한 모든 프로퍼티를 작성해줘야 하기 때문에 그 번거러움을 해소하기 위해서 <code>커맨드 객체</code>가 존재한다.</p>
<p><strong>커맨드 객체</strong>는 우리가 일반적으로 사용하는 getter/setter 메서드가 있는 VO라고 생각하면 되는데,</p>
<p>우리는 매개변수로 객체를 받을 수 있게 되고 객체의 생성자, 혹은 getter/setter메서드를 통해서 컨트롤러가 ObjectMapper를 통해 바인딩을 시키게 되는 것이다.</p>
<h4 id="💡">💡</h4>
<p>강의에서 <strong>&quot;커맨드( 명령 )와 쿼리를 구분</strong>하는 것이 좋다!&quot; 라고 하는데, 그게 무엇이냐</p>
<blockquote>
<ul>
<li>전통적 MVC에서는 하나의 모델이 DB 조회·갱신을 모두 담당했으나,</li>
<li><em>DB조회*</em>를 담당하는 <strong>쿼리</strong> 모델, <strong>DB갱신</strong>을 담당하는 <strong>커맨드</strong> 모델로 역할을 분리하는 것 !</li>
</ul>
</blockquote>
<p>➡️ 즉, 상태조회 쿼리(query), 상태변경 명령(command) 모델로 분리하는 패턴이다.</p>
<p><code>MemberRepository</code></p>
<pre><code class="language-javascript">@Repository
public class MemberRepository {

    @PersistenceContext
    private EntityManager em;

    public Long save(Member member) {
        em.persist(member);
        return member.getId();    // 보통은 저장하고 나면 거의 리턴값을 만들지 않는다.
    }
    public Member find(Long id) {
        return em.find(Member.class, id);
    }
}</code></pre>
<p><code>MemberRepositoryTest</code></p>
<pre><code class="language-javascript">@RunWith(SpringRunner.class)
@SpringBootTest
class MemberRepositoryTest {
    @Autowired
    MemberRepository memberRepository;

    @Test
    @Transactional  // 엔티티 매니저를 통한 모든 데이터 변경은 트랜젝션 안에서 이뤄져야 함 !!! 
    @Rollback(false)  // Test에 있는 transaction은 실행이 끝나면 바로 롤백이 되기 때문에, Test 결과를 디비에 저장하려면 Rollback(false)를 해줘야 함
    public void testMember() {
        Member member = new Member();
        member.setUsername(&quot;memberA&quot;);

        Long savedId = memberRepository.save(member);
        Member findMember = memberRepository.find(savedId);

        Assertions.assertThat(findMember.getId()).isEqualTo(member.getId());
        Assertions.assertThat(findMember.getUsername()).isEqualTo(member.getUsername());
        Assertions.assertThat(findMember).isEqualTo(member);
      // 같은 트랜잭션 안에서 저장하고 조회하면 같은 엔티티로 취급한다. =&gt; 결과는 true !
      // JPA 엔티티 동일성 보장

    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[AOP]]></title>
            <link>https://velog.io/@gahyun_02/AOP</link>
            <guid>https://velog.io/@gahyun_02/AOP</guid>
            <pubDate>Fri, 30 Sep 2022 09:22:07 GMT</pubDate>
            <description><![CDATA[<h3 id="aspect-oriented-programming">Aspect Oriented Programming</h3>
<p> <img src="https://velog.velcdn.com/images/gahyun_02/post/a0b35826-c548-43e4-8fd7-1d80096f237e/image.png" alt=""></p>
<ul>
<li>메소드의 <strong>호출 시간을 측정</strong>하고 싶을 때 사용</li>
<li><strong>공통 관심 사항</strong>(cross-cutting concern) vs <strong>핵심 관심 사항</strong>(core concern) 분리<pre><code class="language-javascript">@Component
@Aspect
public class TimeTraceAop {
      @Around(&quot;execution(* hello.hellospring..*(..))&quot;) //시간을 계산하는 위 클래스를 공통 관심 사항으로 지정
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        System.out.println(&quot;START: &quot; + joinPoint.toString());
        try {
            return joinPoint.proceed();
        } finally {
            long finish = System.currentTimeMillis();
            long timeMs = finish - start;
            System.out.println(&quot;END: &quot; + joinPoint.toString()+ &quot; &quot; + timeMs +
        }
    }
 }
</code></pre>
</li>
</ul>
<pre><code>### ✅ 적용 전
![](https://velog.velcdn.com/images/gahyun_02/post/d82d3712-19d3-4ce6-87fa-ce7c50a8bb3b/image.png)
### ✅ 적용 후
![](https://velog.velcdn.com/images/gahyun_02/post/79292031-1a6a-4302-8061-17e50ef35a79/image.png)</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 DB 접근 기술]]></title>
            <link>https://velog.io/@gahyun_02/%EC%8A%A4%ED%94%84%EB%A7%81-DB-%EC%A0%91%EA%B7%BC-%EA%B8%B0%EC%88%A0</link>
            <guid>https://velog.io/@gahyun_02/%EC%8A%A4%ED%94%84%EB%A7%81-DB-%EC%A0%91%EA%B7%BC-%EA%B8%B0%EC%88%A0</guid>
            <pubDate>Fri, 30 Sep 2022 05:21:09 GMT</pubDate>
            <description><![CDATA[<h3 id="✍️-h2-데이터베이스">✍️ h2 데이터베이스</h3>
<blockquote>
<p>값을 세팅하지 않고 DB에 넣으면 <strong>자동으로 generated by default as identity</strong>로 들어감</p>
</blockquote>
<h3 id="✍️-스프링-통합-테스트">✍️ 스프링 통합 테스트</h3>
<ul>
<li><strong>@SpringBootTest</strong> : 스프링 컨테이너와 테스트를 함께 실행한다.</li>
<li><strong>@Transactional</strong> : 테스트 케이스에 이 애노테이션이 있으면, 테스트 시작 전에 트랜잭션을 시작하고, 테스트 완료 후에 항상 롤백한다. 이렇게 하면 DB에 데이터가 남지 않으므로 다음 테스트에 영향을 주지 않는다.</li>
</ul>
<h3 id="✍️-스프링-jdbctemplate">✍️ 스프링 JdbcTemplate</h3>
<blockquote>
<ul>
<li>JDBC API에서 본 <strong>반복 코드</strong>를 대부분 <strong>제거</strong>해줌.</li>
<li><strong>SQL은 직접 작성</strong>해야 한다. 
✅ <strong>but</strong> ❗️ 나중에 SQL query도 작성해주는 <strong>JPA</strong> 존재. </li>
</ul>
</blockquote>
<pre><code class="language-javascript">public class JdbcTemplateMemberRepository implements MemberRepository {
    private final JdbcTemplate jdbcTemplate;
    public JdbcTemplateMemberRepository(DataSource dataSource) {
          jdbcTemplate = new JdbcTemplate(dataSource);  // 생성자가 한 개면 @Autowired 생략 가능
    }
    @Override
    public Member save(Member member) {
        SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
        jdbcInsert.withTableName(&quot;member&quot;).usingGeneratedKeyColumns(&quot;id&quot;);


        Map&lt;String, Object&gt; parameters = new HashMap&lt;&gt;();
        parameters.put(&quot;name&quot;, member.getName());
        Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters));
        member.setId(key.longValue());
        return member;
    }

    @Override
    public Optional&lt;Member&gt; findById(Long id) {
        List&lt;Member&gt; result = jdbcTemplate.query(&quot;select * from member where id
= ?&quot;, memberRowMapper(), id);
        return result.stream().findAny();
    }

    @Override
    public List&lt;Member&gt; findAll() {
        return jdbcTemplate.query(&quot;select * from member&quot;, memberRowMapper());
    }

    @Override
    public Optional&lt;Member&gt; findByName(String name) {
        List&lt;Member&gt; result = jdbcTemplate.query(&quot;select * from member where
name = ?&quot;, memberRowMapper(), name);
        return result.stream().findAny();
    }

    private RowMapper&lt;Member&gt; memberRowMapper() {
        return (rs, rowNum) -&gt; {
            Member member = new Member();
            member.setId(rs.getLong(&quot;id&quot;));
            member.setName(rs.getString(&quot;name&quot;));
            return member;
        }; 
    }
}</code></pre>
<h3 id="jpa">JPA</h3>
<blockquote>
<ul>
<li>SQL과 데이터 중심의 설계에서 <strong>객체 중심의 설계</strong>로 패러다임을 전환을 할 수 있다. </li>
<li><em>-&gt;*</em> <strong>개발 생산성</strong>을 크게 높일 수 있다.</li>
</ul>
</blockquote>
<h4 id="jpa-entity-mapping">JPA Entity Mapping</h4>
<pre><code class="language-javascript">    @Entity
    public class Member {
        @Id @GeneratedValue(strategy = GenerationType.IDENTITY) // pk, identity 순
        private Long id;
        private String name;
        public Long getId() {
            return id;
        }
        public void setId(Long id) {
            this.id = id;
        }
        public String getName() {
            return name;
        }
        public void setName(String name) {
            this.name = name;
        }

     }</code></pre>
<h4 id="jpamemberrepository">JpaMemberRepository</h4>
<pre><code class="language-javascript">public class JpaMemberRepository implements MemberRepository {
      private final EntityManager em;
      public JpaMemberRepository(EntityManager em) {
          this.em = em;
      }
      public Member save(Member member) {
          em.persist(member);
          return member;
      }
      public Optional&lt;Member&gt; findById(Long id) {
          Member member = em.find(Member.class, id);
          return Optional.ofNullable(member);
      }
      public List&lt;Member&gt; findAll() {
          return em.createQuery(&quot;select m from Member m&quot;, Member.class)
                  .getResultList();
      }
      public Optional&lt;Member&gt; findByName(String name) {

           List&lt;Member&gt; result = em.createQuery(&quot;select m from Member m where
  m.name = :name&quot;, Member.class)
                  .setParameter(&quot;name&quot;, name)
                  .getResultList();
          return result.stream().findAny();
      } 
}</code></pre>
<h4 id="트랜젝션">트랜젝션</h4>
<p>서비스 계층에 <strong>@Transactional</strong> 추가</p>
<blockquote>
<ul>
<li>해당 클래스의 메서드를 실행할 때 트랜잭션을 시작하고, 메서드가 정상 종료되면 트랜잭션을 커밋한다. (런타임 예외가 발생하면 롤백) </li>
</ul>
</blockquote>
<ul>
<li><strong>JPA</strong>를 통한 모든 <strong>데이터 변경</strong>은 <strong>트랜잭션 안에서</strong> 실행해야 한다.</li>
</ul>
<h4 id="스프링-설정-변경할-것들">스프링 설정 변경할 것들</h4>
<pre><code class="language-javascript">@Configuration
public class SpringConfig {

    private final DataSource dataSource;
    private final EntityManager em;
    public SpringConfig(DataSource dataSource, EntityManager em) {
        this.dataSource = dataSource;
        this.em = em;
    }
    @Bean
    public MemberRepository memberRepository() {
        return new MemoryMemberRepository();
        return new JdbcMemberRepository(dataSource);
        return new JdbcTemplateMemberRepository(dataSource);
        return new JpaMemberRepository(em);
    }

}</code></pre>
<h4 id="jpa-제공-기능">JPA 제공 기능</h4>
<ul>
<li><strong>인터페이스</strong>를 통한 기본적인 <strong>CRUD</strong></li>
<li>메서드 <strong>이름 만으로 조회</strong> 기능 제공 ( 예: findByName() )</li>
<li>*<em>페이징 *</em>기능 자동 제공</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[회원 관리 예제 - 웹 MVC 개발]]></title>
            <link>https://velog.io/@gahyun_02/%ED%9A%8C%EC%9B%90-%EA%B4%80%EB%A6%AC-%EC%98%88%EC%A0%9C-%EC%9B%B9-MVC-%EA%B0%9C%EB%B0%9C</link>
            <guid>https://velog.io/@gahyun_02/%ED%9A%8C%EC%9B%90-%EA%B4%80%EB%A6%AC-%EC%98%88%EC%A0%9C-%EC%9B%B9-MVC-%EA%B0%9C%EB%B0%9C</guid>
            <pubDate>Fri, 30 Sep 2022 04:38:31 GMT</pubDate>
            <description><![CDATA[<h3 id="homecontroller">HomeController</h3>
<pre><code class="language-javascript">@Controller
  public class HomeController {
      @GetMapping(&quot;/&quot;)
      public String home() {
          return &quot;home&quot;;
      }
}
</code></pre>
<h3 id="homehtml">Home.html</h3>
<pre><code>&lt;!DOCTYPE HTML&gt;
  &lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;
  &lt;body&gt;
  &lt;div class=&quot;container&quot;&gt;
      &lt;div&gt;
&lt;h1&gt;Hello Spring&lt;/h1&gt; &lt;p&gt;회원 기능&lt;/p&gt;
&lt;p&gt;
&lt;a href=&quot;/members/new&quot;&gt;회원 가입&lt;/a&gt; &lt;a href=&quot;/members&quot;&gt;회원 목록&lt;/a&gt;
&lt;/p&gt; &lt;/div&gt;
   &lt;/div&gt; &lt;!-- /container --&gt;
   &lt;/body&gt;
&lt;/html&gt;</code></pre><blockquote>
<p>클라이언트가 요청하는 데이터와 일치하는 것이 컨트롤러에 있는지 먼저 확인한다. (static 파일보다 우선순위가 높다.)</p>
</blockquote>
<h3 id="membercontroller">MemberController</h3>
<pre><code class="language-javascript">@Controller
    public class MemberController {
        private final MemberService memberService;
        @Autowired
        public MemberController(MemberService memberService) {
            this.memberService = memberService;
        }

        @GetMapping(value = &quot;/members/new&quot;)

        public String createForm() {
            return &quot;members/createMemberForm&quot;;
        }

        @GetMapping(value = &quot;/members&quot;)
          // 데이터를 조회할 때 씀.

          public String list(Model model) {
            List&lt;Member&gt; members = memberService.findMembers(); // 모든 멤버 불러오기
            model.addAttribute(&quot;members&quot;, members);
                return &quot;members/memberList&quot;;
        }


         @PostMapping(value = &quot;/members/new&quot;)
           // 데이터를 폼에 넣어서 전달할 때 씀.

           public String create(MemberForm form) {
           Member member = new Member();
           member.setName(form.getName());
           memberService.join(member); // join()는 정보 보내는 함수임
               return &quot;redirect:/&quot;;
         }
         }
   }
</code></pre>
<h3 id="memberlisthtml">memberList.html</h3>
<pre><code>&lt;!DOCTYPE HTML&gt;
  &lt;html xmlns:th=&quot;http://www.thymeleaf.org&quot;&gt;
  &lt;body&gt;
  &lt;div class=&quot;container&quot;&gt;
      &lt;div&gt;
          &lt;table&gt;
              &lt;thead&gt;
                               &lt;tr&gt;
                    &lt;th&gt;
                    &lt;/th&gt;
                &lt;th&gt;이름&lt;/th&gt; &lt;/tr&gt;
              &lt;/thead&gt;
              &lt;tbody&gt;
                      &lt;tr th:each=&quot;member : ${members}&quot;&gt;
                    &lt;td th:text=&quot;${member.id}&quot;&gt;&lt;/td&gt;
                    &lt;td th:text=&quot;${member.name}&quot;&gt;&lt;/td&gt;

                    &lt;!-- th:each문에 의해 루프를 돌면서 템플릿 언어가 모델에서 members의 정보를 찾음.--&gt;

                    &lt;/tr&gt;
              &lt;/tbody&gt;
            &lt;/table&gt;
      &lt;/div&gt;
   &lt;/div&gt; &lt;!-- /container --&gt;
   &lt;/body&gt;
   &lt;/html&gt;
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[스프링 빈과 의존관계]]></title>
            <link>https://velog.io/@gahyun_02/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B9%88%EA%B3%BC-%EC%9D%98%EC%A1%B4%EA%B4%80%EA%B3%84</link>
            <guid>https://velog.io/@gahyun_02/%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B9%88%EA%B3%BC-%EC%9D%98%EC%A1%B4%EA%B4%80%EA%B3%84</guid>
            <pubDate>Fri, 23 Sep 2022 08:28:33 GMT</pubDate>
            <description><![CDATA[<h3 id="📒-컴포넌트-스캔과-자동-의존관계-설정">📒 컴포넌트 스캔과 자동 의존관계 설정</h3>
<p><strong>📍 스프링 빈을 등록하는 2가지 방법</strong></p>
<ul>
<li>컴포넌트 스캔과 자동 의존관계 설정 </li>
<li>자바 코드로 직접 스프링 빈 등록하기
<img src="https://velog.velcdn.com/images/gahyun_02/post/75bf0037-adee-4191-a866-0f0286cdf36b/image.png" alt=""></li>
</ul>
<p><strong>📍 컴포넌트 스캔 원리</strong></p>
<pre><code class="language-javascript">@Service
    public class MemberService {
        private final MemberRepository memberRepository;

        @Autowired
        public MemberService(MemberRepository memberRepository) {
            this.memberRepository = memberRepository;
        }
}</code></pre>
<ul>
<li><p>생성자에서 @Autowired로 연결 시켜주기 (스프링 컨테이너에 <strong>의존 관계 주입</strong>시켜주는 것 = <strong>DI</strong> ) </p>
</li>
<li><blockquote>
<p>여러 컨트롤러 간에 공유하면서 사용가능 </p>
</blockquote>
</li>
<li><p>@Controller
@Service
@Repository
-&gt; 스프링 빈으로 자동 등록됨</p>
</li>
</ul>
<blockquote>
<p>🔴 <strong>주의</strong>: @Autowired 를 통한 DI는 helloController , memberService 등과 같이 스프링이 관리하는 객체에서만 동작한다. </p>
</blockquote>
<h3 id="📒-자바-코드로-직접-스프링-빈-등록하기">📒 자바 코드로 직접 스프링 빈 등록하기</h3>
<pre><code class="language-javascript">@Configuration
public class SpringConfig {
    @Bean
    public MemberService memberService() {
        return new MemberService(memberRepository());
   }

   @Bean
   public MemberRepository memberRepository() {
           return new MemoryMemberRepository();
   }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[회원 관리 예제 - 백엔드 개발]]></title>
            <link>https://velog.io/@gahyun_02/%ED%9A%8C%EC%9B%90-%EA%B4%80%EB%A6%AC-%EC%98%88%EC%A0%9C-%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C</link>
            <guid>https://velog.io/@gahyun_02/%ED%9A%8C%EC%9B%90-%EA%B4%80%EB%A6%AC-%EC%98%88%EC%A0%9C-%EB%B0%B1%EC%97%94%EB%93%9C-%EA%B0%9C%EB%B0%9C</guid>
            <pubDate>Thu, 22 Sep 2022 20:49:28 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/gahyun_02/post/a253b1ba-3bf5-43aa-9368-8ae00f7f649a/image.png" alt=""></p>
<blockquote>
<ul>
<li>컨트롤러: 웹 MVC의 컨트롤러 역할</li>
</ul>
</blockquote>
<ul>
<li>서비스: 핵심 비즈니스 로직 구현</li>
<li>리포지토리: 데이터베이스에 접근, 도메인 객체를 DB에 저장하고 관리</li>
<li>도메인: 비즈니스 도메인 객체, 예) 회원, 주문, 쿠폰 등등 주로 데이터베이스에 저장하고 관리됨</li>
</ul>
<p><img src="https://velog.velcdn.com/images/gahyun_02/post/db3d4afa-55ab-48a9-8847-46c8765a51f7/image.png" alt=""></p>
<ul>
<li><p><strong>회원 레포지토리 인터페이스</strong>  <em>( *인터페이스는 C++의 가상함수와 비슷 )</em></p>
</li>
<li><ul>
<li>기능별로 작성<pre><code class="language-javascript">public interface MemberRepository {
   Member save(Member member);
   Optional&lt;Member&gt; findById(Long id);
   Optional&lt;Member&gt; findByName(String name);
   List&lt;Member&gt; findAll();
}</code></pre>
</li>
</ul>
</li>
<li><p><strong>회원 레포지토리 메모리</strong> <em>( 구현부 )</em></p>
<pre><code class="language-javascript">public class MemoryMemberRepository implements MemberRepository {
    private static Map&lt;Long, Member&gt; store = new HashMap&lt;&gt;(); // 정보를 저장하는 곳
    private static long sequence = 0L;
    @Override
    public Member save(Member member) {
        member.setId(++sequence);
        store.put(member.getId(), member);
        return member;
}
    @Override
    public Optional&lt;Member&gt; findById(Long id) {
        return Optional.ofNullable(store.get(id));
    }
    @Override
    public List&lt;Member&gt; findAll() {
return new ArrayList&lt;&gt;(store.values());
}
    @Override
    public Optional&lt;Member&gt; findByName(String name) {
        return store.values().stream()
                .filter(member -&gt; member.getName().equals(name))
                .findAny();
}
    public void clearStore() {
        store.clear();
} }</code></pre>
<h3 id="📍-회원-레포지토리-테스트-케이스-작성">📍 회원 레포지토리 테스트 케이스 작성</h3>
</li>
<li><p>자바의 JUnit을 이용하여 , 속도가 빠르고 반복 실행이 가능하며 여러 테스트를 한번에 실행할 수 있다.</p>
<pre><code class="language-javascript">class MemoryMemberRepositoryTest {
  MemoryMemberRepository repository = new MemoryMemberRepository();
  @Test
  public void save() {
      //given
      Member member = new Member();
      member.setName(&quot;spring&quot;);

      //when
      repository.save(member);

      //then
      Member result = repository.findById(member.getId()).get();
      assertThat(result).isEqualTo(member); // assertThat은 test 결과를 확인하기 위한 JUnit 중 하나
  }
}</code></pre>
</li>
<li><p><strong>given, when, then</strong>으로 나눠서 작성</p>
</li>
<li><p>테스트는 각각 독립적으로 실행되어야 한다. 테스트 순서에 의존관계가 있는 것은 좋은 테스트가 아니다.</p>
</li>
</ul>
<p>** 📒 AfterEach 📒**</p>
<pre><code class="language-javascript">@AfterEach
    public void afterEach() {
        repository.clearStore();
    }</code></pre>
<ul>
<li>한번에 여러 테스트를 실행하면 메모리 DB에 직전 테스트의 결과가 남을 수 있다. </li>
<li>-&gt; @AfterEach 를 사용해서 각 테스트가 종료될 때 마다 메모리 DB에 저장된 데이터를 삭제한다.</li>
</ul>
<p>** 📒 BeforeEach 📒 **</p>
<pre><code class="language-javascript">@BeforeEach
  public void beforeEach() {
      memberRepository = new MemoryMemberRepository();
      memberService = new MemberService(memberRepository);
  }</code></pre>
<ul>
<li>각 테스트 실행 전에 호출된다. 테스트가 서로 영향이 없도록 항상 새로운 객체를 생성하고, 의존관계도 새로 맺어준다.</li>
</ul>
<h3 id="📍-회원-서비스-개발">📍 회원 서비스 개발</h3>
<p>❌ 기존 방식 ❌</p>
<pre><code class="language-javascript">public class MemberService {
    private final MemberRepository memberRepository = new MemoryMemberRepository();
}</code></pre>
<ul>
<li>회원 서비스가 메모리 회원 리포지토리를 <strong>직접</strong> 생성하게 함<pre><code>                      ⬇️</code></pre><pre><code class="language-javascript">public class MemberService {
    private final MemberRepository memberRepository;
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
}
...
}</code></pre>
</li>
<li>MemberService 입장에서, 외부에서 memberRepository를 넣어줄 수 있도록 
(=DI 가능하게 ) 변경</li>
</ul>
<h3 id="📍-회원-서비스-테스트">📍 회원 서비스 테스트</h3>
<pre><code class="language-javascript">class MemberServiceTest {
    MemberService memberService;
    MemoryMemberRepository memberRepository;

    @BeforeEach
    public void beforeEach() {
        memberRepository = new MemoryMemberRepository();
        memberService = new MemberService(memberRepository);
    }

    // MemoryMemberRepository와 MemberService는 서로 다른 리포지토리인데 같은 메모리를 사용하게 하기 위한 코드

    @AfterEach
    public void afterEach() {
        memberRepository.clearStore();
    }

    // 테스트 주도 개발에서 함수 이름은 한글로 많이 씀.
    @Test
    public void 중복_회원_예외() throws Exception {
        //Given
        Member member1 = new Member();
        member1.setName(&quot;spring&quot;);
        Member member2 = new Member();
        member2.setName(&quot;spring&quot;);

         //When
        memberService.join(member1);
        IllegalStateException e = assertThrows(IllegalStateException.class,
            () -&gt; memberService.join(member2)); 
            // 예외가 발생해야 한다. 
            // assertThrows는 Try-catch 대신에 예외 처리하려고 쓰는 것임.

        assertThat(e.getMessage()).isEqualTo(&quot;이미 존재하는 회원입니다.&quot;);
    }
}</code></pre>
]]></description>
        </item>
    </channel>
</rss>