<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>코딩 일기..👊</title>
        <link>https://velog.io/</link>
        <description>화이팅!</description>
        <lastBuildDate>Mon, 21 Jul 2025 04:49:49 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>코딩 일기..👊</title>
            <url>https://velog.velcdn.com/images/jun_0y/profile/87ae3b5f-4a69-4b3f-81c1-82c0b3ca7c66/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. 코딩 일기..👊. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/jun_0y" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[OS] 오리엔테이션]]></title>
            <link>https://velog.io/@jun_0y/%EC%98%A4%EB%A6%AC%EC%97%94%ED%85%8C%EC%9D%B4%EC%85%98</link>
            <guid>https://velog.io/@jun_0y/%EC%98%A4%EB%A6%AC%EC%97%94%ED%85%8C%EC%9D%B4%EC%85%98</guid>
            <pubDate>Mon, 21 Jul 2025 04:49:49 GMT</pubDate>
            <description><![CDATA[<h1 id="os-오리엔테이션">OS 오리엔테이션</h1>
<p>취업 준비, 자격증 공부를 하면서 슬쩍슬쩍 OS 용어가 나오는데 
중요했다는 것과 두루뭉실한 역할만 기억나고 자세한 기억이 안난다.</p>
<p>그래서 학부생 시절 열심히 공부했던 운영체제(OS)를 복습하기로 결심🔥</p>
<p>우선 공부해야 될 용어를 먼저 나열해보자. 이래야 공부할 의지가 생긴다</p>
<table>
<thead>
<tr>
<th>용어</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><center>Process</center></td>
<td>처리단위!</td>
</tr>
<tr>
<td><center>Thread</center></td>
<td>멀티 쓰레드..? 한번에 여러개 돌릴 때 쓰는거..😅</td>
</tr>
<tr>
<td><center>Virtual Memory</center></td>
<td>그나마 이건 쫌 안다. Paging, Segmentation</td>
</tr>
<tr>
<td><center>Scheduling</center></td>
<td>들으면 아는 정도? OPT, LFU, LRU, SJF, RR, 등등 ...</td>
</tr>
<tr>
<td><center>Semaphores</center></td>
<td>이건 진짜 들었던 기억만난다..🫠</td>
</tr>
<tr>
<td><center>Synchronize</center></td>
<td>굉장히 중요했던 것 같다..🙄</td>
</tr>
<tr>
<td><center>System call</center></td>
<td>이런 것도 있었지...🤥</td>
</tr>
</tbody></table>
<p>세부적으로 가면 뭐가 더 많이 있는데 일단 여기까지!</p>
<p>공부하면서 하나씩 링크를 추가하도록 하자.</p>
<p>오늘은 가볍게 OS 정의부터!</p>
<h1 id="os란-무엇인가">OS란 무엇인가</h1>
<blockquote>
<p>오퍼레이팅 시스템(operating system, 약칭: OS) <br>
컴퓨터 하드웨어, 시스템 리소스, [사용자] - [응용 프로그램] 간의 상호 작용을 관리하고 제어하여
프로그램에 대한 일반적 서비스를 지원하는 시스템 소프트웨어이다.</p>
</blockquote>
<div align="right">- 위키백과 - &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;</div>


<p>우선 컴퓨터의 구성요소에 대해 간단하게 복기하자면</p>
<p>컴퓨터의 핵심 구성 요소로는 <span style="color: red"><strong><em>CPU</em></strong>, <strong><em>입•출력 장치</em></strong>, <strong><em>주기억장치</em></strong>, <strong><em>보조기억장치</em></strong></span> 가 있다.
이러한 것들을 <span style="color: red"><strong><em>시스템 리소스</em></strong></span> 라고 한다.</p>
<img src="https://velog.velcdn.com/images/jun_0y/post/d19e5264-ff1c-4ede-84d4-521f3737d1b7/image.png" width="700px">

</br>
컴퓨터를 쓴다는 것은 그 안에 설치된 프로그램을 사용한다는 것이고</br>
프로그램이 실행되기 위해선 시스템 리소스들이 상호작용을 이루며 동작해야 한다.

<p>이 과정을 관리하는게 바로 OS이다.</p>
<blockquote>
<p>쉽게말해서 OS는 사용자가 컴퓨터를 편하게 쓸 수 있도록 도와주는 도구이다.</p>
</blockquote>
<h1 id="개요">개요</h1>
<p>```
OS
├── 목적
│    ├── Throughput
│    ├── Turn Around Time
│    ├── Availability
│     └── Reliability
│
├── 종류
│     ├── Windows
│    ├── Unix<br>│    ├── Linux
│    ├── MacOS
│    ├── iOS
│     └── Android
│
├── Memory
│     ├── 관리전략
│    │       ├── Fetch            # 요구/예상 반입
│    │       ├── Placement        # First/Best/Worst Fit
│    │       └── Replacement        # Scheduling Algorithm
│    │
│     └── Virtual Memory
│           ├── Paging
│           └── Segmentation
│
├── 스케줄링
│    ├── 선점(Preemptive)
│    │     ├── SRT(SRTN)
│    │      ├── Round Robbin
│    │     ├── 다단계 큐
│    │     ├── 다단계 피드백 큐
│    │     └── 선점 우선순위
│     │
│    └── 비선점(Non-preemptive)
│         ├── FCFS(FIFO)
│         ├── SJF
│         ├── HRN
│         └── 우선순위
│
└── 프로세스 
    └── Submit --&gt; Hold --&gt; Ready  ---  Run -&gt; Exit
                               \        /<br>                                   \       /
                                     Wait</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[백준 #9095 : 1, 2, 3 더하기 ]]></title>
            <link>https://velog.io/@jun_0y/%EB%B0%B1%EC%A4%80-9095-1-2-3-%EB%8D%94%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@jun_0y/%EB%B0%B1%EC%A4%80-9095-1-2-3-%EB%8D%94%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 14 Apr 2023 07:49:18 GMT</pubDate>
            <description><![CDATA[<h1 id="dp--123-더하기백준-9095">DP : 1,2,3 더하기(백준 #9095)</h1>
<h2 id="문제">문제</h2>
<p><img src="https://velog.velcdn.com/images/jun_0y/post/25dcc03b-8045-4cf5-9457-2f6e92e3d44c/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/jun_0y/post/e3cbd651-0a98-4ad6-8e04-1be8758fd9c5/image.png" alt=""></p>
<p>DP는 규칙 찾기가 중요한 듯 하다</p>
<p>규칙 찾기
1 = 1
2 = 1+1 / 2
3 = 1+1+1 / 1+2 / 2+1 / 3</p>
<p>4 = 1+3 / 2+2 / 3+1 
5 = 1+4 / 2+3 / 3+2
6 = 1+5 / 2+4 / 3+3
...</p>
<blockquote>
<p>결론 : f(n) = f(n-1) + f(n-2) + f(n-3)</p>
</blockquote>
<p>이상하게 삽질하다가 규칙 찾아내는데만 한시간 넘게 걸렸다...</p>
<p>규칙을 알아냈으니 코드는 금방짜겠지?!</p>
<h3 id="방법-1">방법 1</h3>
<pre><code class="language-python">def f(n) :
    if n == 1 :
        return 1
    elif n == 2 :
        return 2
    elif n==3 :
        return 4
    else :
        return f(n-1) + f(n-2) + f(n-3)

n = int(input(&quot;정수 n 입력&quot;))    
print(f(n))</code></pre>
<p>근데 이렇게 하면 n이 커질수록 연산시간이 심각하게 오래걸린다. 재귀함수의 한계...
이거 전에 배울때 무슨 용어가 있었던 것 같은데.. 기억이... 아무튼!!</p>
<h4 id="해결방법---👉--배열을-이용하자">해결방법 --👉  배열을 이용하자!!</h4>
<h3 id="방법2">방법2</h3>
<pre><code class="language-python">n = int(input(&quot;정수 n 입력 : &quot;))

dp = [1,2,4]

for i in range(3,n+1):
    dp.append(dp[i-1] + dp[i-2] + dp[i-3])

print(dp[n-1])</code></pre>
<p>현재 dp[i] 기준으로 이미 지난 인덱스의 리스트 값(dp[i-1] ~ dp[0])은 저장돼있기 때문에 연산시간을 단축시킬 수 있다!</p>
<h3 id="백준-문제-풀이">백준 문제 풀이</h3>
<p>for문의 range를 <code>(3,n+1)</code>로 하면 append()를 이용했기 때문에 input을 반복해서 주면 문제가 생긴다.
append()는 배열의 마지막 index 뒤에 이어붙이는 것이기 때문에 
두번째 input을 입력할 때부터 배열의 index가 내가 원하는 index 가 아니게 된다.</p>
<h4 id="해결방법-">해결방법 :</h4>
<ol>
<li>배열을 초기화하던가 --&gt; 너무 비효율적인듯 하다</li>
<li>배열 마지막 인덱스 다음부터 구하기 --&gt; 이거다</li>
</ol>
<pre><code class="language-python">dp = [1,2,4]

cnt =  int(input())

for i in range(cnt):
    n = int(input())

    for j in range(len(dp),n+1):
        dp.append(dp[j-1] + dp[j-2] + dp[j-3])

    print(dp[n-1])</code></pre>
<p>for문의 range를 <code>(3,n+1)</code>에서 <code>(len(dp),n+1)</code>로 바꿨다.</p>
<h3 id="추가">추가</h3>
<p>재귀함수의 한계 해결방법 이거였다.</p>
<blockquote>
<p>Memoization : 
이미 계산한 결과는 배열에 저장함으로써 나중에 동일한 계산을 해야 할 때는 저장된 값을 단순히 반환하기만 하면 됨</p>
</blockquote>
<p>Memoization 방법을 쓰지 않는 것은 <code>분할 정복 기법</code>이라고 부르고
Memoization 방법을 쓰는 것을 DP(Dynamic Programming) 이라고 부르는 것!!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] EC2 인스턴스 삭제 했는데 비용 발생한 경우 해결 방법]]></title>
            <link>https://velog.io/@jun_0y/AWS-EC2-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4-%EC%82%AD%EC%A0%9C-%ED%96%88%EB%8A%94%EB%8D%B0-%EB%B9%84%EC%9A%A9-%EB%B0%9C%EC%83%9D%ED%95%9C-%EA%B2%BD%EC%9A%B0-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95</link>
            <guid>https://velog.io/@jun_0y/AWS-EC2-%EC%9D%B8%EC%8A%A4%ED%84%B4%EC%8A%A4-%EC%82%AD%EC%A0%9C-%ED%96%88%EB%8A%94%EB%8D%B0-%EB%B9%84%EC%9A%A9-%EB%B0%9C%EC%83%9D%ED%95%9C-%EA%B2%BD%EC%9A%B0-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95</guid>
            <pubDate>Fri, 03 Mar 2023 04:57:54 GMT</pubDate>
            <description><![CDATA[<h2 id="amazon-web-services-billing-statement-available">Amazon Web Services Billing Statement Available</h2>
<p>라는 제목의 메일이 날아왔다. 확인해보니 AWS에서 발생한 비용 정산 청구서..!</p>
<p>왜..?</p>
<p>AWS에 가입하고 EC2를 이용하여 <code>프리티어</code> 인스턴스를 생성했고, 분명히 테스트가 끝난 후 분명히 인스턴스를 삭제(종료)했다</p>
<p>해킹당했다고 생각하기엔 Google Authenticator(Google OTP)로 2차 암호도 걸어놨고, 무엇보다 너무 하찮은 비용이었기에.. 
인스턴스 삭제를 안했나 하고 AWS에 들어가서 사용중인 인스턴스가 있는지 확인해봤지만 역시 사용중인 인스턴스는 없었다.</p>
<p>비용이 어디서 청구된건지 뒤적거려봤지만 찾아내는데 실패해서 결국 구글링한 결과 <code>탄력적 IP</code>와 <code>보안정책</code>을 방치해둔 것이 문제였다!!</p>
<blockquote>
<p>AWS에서 EC2와 탄력적IP는 무료로 제공해주지만 사용하지 않으면 낭비되는 것으로 간주.
⭐️즉, EC2는 삭제했다 하더라도 탄력적 IP를 그대로 두면 사용료가 청구된다고 한다⭐️</p>
</blockquote>
<h2 id="aws-ec2탄력적ip보안정책-삭제">AWS EC2/탄력적IP/보안정책 삭제</h2>
<p>원인을 알았으니 해결해보자!
(하는김에 인스턴스 삭제 방법까지 기록해놓자)</p>
<p>먼저 AWS에 로그인 후 EC2에 들어가자 </p>
<h3 id="인스턴스-종료">인스턴스 종료</h3>
<blockquote>
<p>인스턴스 &gt; 사용중인 인스턴스 선택 &gt; 인스턴스 상태 &gt; 인스턴스 종료</p>
</blockquote>
<p>참고로 인스턴스를 생성했을 당시 설정해놨던 지역과 우측 상단에서 볼 수 있는 현재 선택한 지역이 다르면 현재 사용중인 인스턴스가 보이지 않는다. 
<del>전에 만들때는 서울로 지정해서 생성해놓고 미국 동부 지역으로 설정해놓고 내 인스턴스 어디갔냐면 10분동안 헤맸던 기억이..</del>
인스턴스는 종료하면 다시 시작이 안되기 때문에 <code>인스턴스 종료 = 인스턴스 삭제</code>이다</p>
<h3 id="탄력적ip-종료">탄력적IP 종료</h3>
<blockquote>
<p>탄력적 IP &gt; 사용중인 탄력적IP 선택 &gt; 작업 &gt; 탄력적 IP 주소 릴리즈</p>
</blockquote>
<p>너무 간단해서 더 할말이 없다</p>
<h3 id="보안-정책-종료">보안 정책 종료</h3>
<blockquote>
<p>보안 그룹 &gt; 사용중이 보안 그룹 선택 &gt; 작업 &gt; 보안 그룹 삭제</p>
</blockquote>
<p>만약 보안 정책까지 설정했다면 이것도 제거해야 한다.
제대로 안보고 탄력적IP만 삭제했다가 다음달에 또 청구서 얻어맞을 뻔했다.</p>
<p>여기까지 했으면 끝!!!</p>
<h2 id="청구비용-알림-설정">청구비용 알림 설정</h2>
<p>마지막으로 혹시나 또 비용이 청구 될 수 있으므로 <code>청구 비용 알림 설정</code>을 해놓자</p>
<p>AWS에서 청구금액이 내가 설정한 금액 이상으로 발생할 시 메일로 알려주는 서비스이다. 
⭐️<strong>무료</strong>⭐️이니 귀찮아도 설정해놓자!</p>
<p>우측 상단에 내 아이디가 적혀있는 메뉴에 들어가서</p>
<blockquote>
<p>결제 대시보드 &gt; Budget &gt; 예산생성 &gt; 월별 비용 예산 선택 &gt; 예산 금액 입력 &gt; 알림 받을 이메일 작성 &gt; 예산생성</p>
</blockquote>
<p>끝..! 아주 간단하다!!</p>
<p>입력한 예산 금액을 기준으로 </p>
<ol>
<li>실제 지출 85% 도달 시</li>
<li>실제 지출 100% 도달 시</li>
<li>예상 지출 100% 도달 시 
알림을 해주는데 나는 상남자답게 1달러로 해놨다💪<br>

</li>
</ol>
<hr>
<p>참고
<a href="https://codingbuza.tistory.com/entry/AWS%EC%97%90%EC%84%9C-%EA%B0%91%EC%9E%90%EA%B8%B0-%EC%B2%AD%EA%B5%AC%EC%84%9C%EA%B0%80-%EB%82%A0%EC%95%84%EC%99%94%EC%9D%84-%EB%95%8C-%ED%95%B4%EA%B2%B0-%EB%B0%A9%EB%B2%95">AWS에서 갑자기 청구서가 날아왔을 때 해결 방법</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Git] PUSH 충돌 : rejected non-fast-forward]]></title>
            <link>https://velog.io/@jun_0y/Git-PUSH-%EC%B6%A9%EB%8F%8C-rejected-non-fast-forward</link>
            <guid>https://velog.io/@jun_0y/Git-PUSH-%EC%B6%A9%EB%8F%8C-rejected-non-fast-forward</guid>
            <pubDate>Wed, 28 Dec 2022 05:09:45 GMT</pubDate>
            <description><![CDATA[<p>오랜만에 git을 사용하니 중요한 사실을 간과하고 작업해버린 코린이...</p>
<p>파일 수정 &amp; commit 후 push 하려고 보니까 </p>
<p><code>[rejected]  main -&gt; mian (non-fast-forward)</code> </p>
<p>라는 에러문구가 떴다😢
<img src="https://velog.velcdn.com/images/jun_0y/post/f9e00be6-6bb4-4cf8-a203-e30f76c0de72/image.png" alt=""></p>
<p>그래서 구글링한 결과</p>
<h3 id="non-fast-forward-에러">non-fast-forward 에러</h3>
<p><code>non-fast-forward</code> : <code>push</code> 충돌의 일종으로</p>
<ul>
<li>다른 User가 수정 &amp; <code>commit</code> 한 내역을 <code>pull</code> 하지 않은 상태에서</li>
<li>같은 브랜치에서 내가 수정 &amp; <code>commit</code>을 한뒤</li>
<li><code>push</code>를 시도하면 발생하는 충돌이다 </li>
</ul>
<p>그래서 이번에는 <code>git reset</code> 으로 이전 commit으로 <code>reset</code> 한 뒤 <code>pull</code>하고 코드를 다시 수정해서 해결했다</p>
<p>이번에는 수정하는 파일이 하나였기 때문에 간단하게 에러를 잡을 수 있었지만 만약 여러 파일을 수정한 상황이었다면 일일히 수정을 다시 해야되나..?</p>
<p>해서 다시 구글링한 결과
<a href="https://dogcowking.tistory.com/417">GIT PUSH 중 충돌 (rejected non-fast-forward)
</a>
여기에 그 해결방법이 나와있다..</p>
<p>나중에 문제생기면 그 때 확인해보도록 하자 🫠</p>
<p>그리고 이건 구글링 중에 알게된 git 튜토리얼 블로그인데 유용해 보인다!
<a href="https://backlog.com/git-tutorial/kr/stepup/stepup3_2.html">GIT 튜토리얼 Blog</a>
<br></p>
<hr>
<h3 id="오늘의-결론">오늘의 결론</h3>
<blockquote>
<p>작업 전에는 무조건 fetch &amp; pull 과정을 거치자</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Spring] SpringSecurity]]></title>
            <link>https://velog.io/@jun_0y/Spring-SpringSecurity</link>
            <guid>https://velog.io/@jun_0y/Spring-SpringSecurity</guid>
            <pubDate>Thu, 24 Nov 2022 01:42:18 GMT</pubDate>
            <description><![CDATA[<h3 id="spring-security">Spring Security</h3>
<blockquote>
<p>스프링 시큐리티는 스프링 기반의 애플리케이션의 보안(인증과 권한, 인가)을 담당하는 프레임워크이다.</p>
</blockquote>
<pre><code>스프링 시큐리티는
➡️ 스프링 기반 프레임워크
➡️ 애플리케이션의 보안을 담당하는 프레임워크
➡️ 애플리케이션의 인증과 인가 즉, 보안을 담당하는 프레임워크다</code></pre><ul>
<li>인증(Authentication) : 누구야?</li>
<li>인가(Authorize) : 권한</li>
</ul>
<p><span style="color:red"><strong><em>서블릿 필터</em></strong></span> 와 <span style="color:red"><strong><em>필터체인</em></strong></span>으로 구성된 위임모델 사용</p>
<br>

<hr>
<h3 id="기본-용어">기본 용어</h3>
<ul>
<li><h4 id="접근주체principal">접근주체(Principal)</h4>
<p>  보호된 리소스에 접근하는 대상</p>
</li>
<li><h4 id="인증authentication">인증(Authentication)</h4>
<p>  보호된 리소스에 접근한 대상이 누구인지 확인</p>
</li>
<li><h4 id="인가authorize">인가(Authorize)</h4>
<p>  해당 리소스에 대해 접근 가능한 권한을 가지고 있는지 확인</p>
</li>
<li><h4 id="권한">권한</h4>
<p>  어떠한 리소스에 대한 접근 제한
  모든 리소스는 접근 제어가 걸려있다
  인가 과정에서 해당 리소스에 대한 권한을 가졌는지 확인</p>
<br>

</li>
</ul>
<hr>
<h3 id="스프링-시큐리티의-특징">스프링 시큐리티의 특징</h3>
<ul>
<li>보안과 관련하여 (체계적으로 많은) 옵션을 제공</li>
<li><span style="color:red"><strong><em>Filter</em></strong></span> 기반으로 동작</li>
<li>MVC와 분리하여 관리 및 동작</li>
<li>annotation을 통한 간단한 설정</li>
<li>기본적으로 <span style="color:red"><strong><em>세션 &amp; 쿠키 방식</em></strong></span> 으로 인증</li>
<li><span style="color:red"><strong><em>인증 관리자 &amp; 접근 결정 관리자</em></strong></span></li>
</ul>
<p><br><br>
*Reference
<a href="https://devuna.tistory.com/55">https://devuna.tistory.com/55</a></p>
<hr>
<h3 id="스프링-시큐리티의-구조">스프링 시큐리티의 구조</h3>
<br>

<ul>
<li><h4 id="기본-구조">기본 구조</h4>
<img src="https://velog.velcdn.com/images/jun_0y/post/e1280002-e4b8-4ad6-b5a8-5a762bc88541/image.png" alt=""></li>
</ul>
<br>

<ul>
<li><h4 id="filter별-기능-설명">Filter별 기능 설명</h4>
<img src="https://velog.velcdn.com/images/jun_0y/post/1a932584-689a-43c1-81e8-dce40edeeb8b/image.png" alt=""></li>
</ul>
<hr>
<h3 id="서블릿servlet">서블릿(Servlet)</h3>
<blockquote>
<p>Dynamic Web Page를 만들 때 사용되는 자바 기반의 웹 애플리케이션 프로그래밍 기술</p>
</blockquote>
<p>웹에는 다양한&amp;많은 Request와 Response가 존재
Request/Response를 일일이 처리하려면 힘들다!
Request/Response의 흐름을 간단한 메서드 호출만으로 체계적으로 다룰 수 있게 해주는게 바로 서블릿</p>
<ul>
<li>기본적인 서블릿 구조
<img src="https://velog.velcdn.com/images/jun_0y/post/92015e36-9b80-4b90-9844-d45ab373ef85/image.png" alt=""></li>
</ul>
<Br>

<p>*Reference
<a href="https://coding-factory.tistory.com/742">https://coding-factory.tistory.com/742</a>
<a href="https://kohen.tistory.com/29">https://kohen.tistory.com/29</a></p>
<p>——————————————————————————————————————————</p>
<h3 id="서블릿-필터servlet-filter">서블릿 필터(Servlet Filter)</h3>
<blockquote>
<p><code>서블릿으로 전달되는 Client의 Request</code>  or  <code>서블릿에서 클라이언트로 전달되는 Response</code>를 
중간에 가로채서 필터링을 위한 객체와 메서드를 정의해 둔 <code>인터페이스</code></p>
</blockquote>
<p>서블릿과 거의 동일하지만 기능이 Filtering이기 때문에 서블릿 필터라고 부름</p>
<p>서블릿이 HTTPServletRquest 였다면
필터는 ServletRequest이다.
<br></p>
<p>*Reference
<a href="https://sgcomputer.tistory.com/238">https://sgcomputer.tistory.com/238</a></p>
<p>——————————————————————————————————————————</p>
<h3 id="필터filter">필터(Filter)</h3>
<blockquote>
<p>Request와 Response에 대한 정보를 변경할 수 있게 개발자들에게 제공하는 <code>서블릿 컨테이너</code></p>
</blockquote>
<p>필터체인은 필터들이 모여서 체인을 형성한 것으로</p>
<p>체인을 형성한 Filter들을 거쳐가는 순서가 있다!
<br></p>
<p>*Reference
<a href="https://jun-itworld.tistory.com/28">https://jun-itworld.tistory.com/28</a></p>
<p>——————————————————————————————————————————</p>
<p>딱히 이론적으로 공부할거는 없을것 같고 실습해보면서 진행해야 될듯</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[AWS] S3]]></title>
            <link>https://velog.io/@jun_0y/AWS-S3</link>
            <guid>https://velog.io/@jun_0y/AWS-S3</guid>
            <pubDate>Thu, 24 Nov 2022 01:17:24 GMT</pubDate>
            <description><![CDATA[<h3 id="s3">S3</h3>
<p>🧐 : 일단, 내가 알고있는 S3</p>
<p>서버에서 미디어 파일을 전송하는 기술.
원래는 static 디렉터리에다가 파일 저장해놓고 요청하면 보내주는 방식인데
S3를 이용하면 더 좋다나..</p>
<p>🧐 : 그래서 S3가 뭐야?</p>
<p>🤠 : 
Simple Storage Service의 약자로</p>
<p>파일 서버의 역할을 하는 서비스. 즉 <code>파일서버</code>야 </p>
<pre><code>파일서버 : 파일을 저장하고 제공하는 서버</code></pre><p>🧐 : 
아 SSS라서 S3 였구나
직역하면 간단한 저장소 서비스.. 정도?</p>
<p>🤠 : 
일반적인 파일서버는 트래픽이 증가함에 따라 장비를 증설하는 작업을 해야하는데 S3는 이런것들을 대신해줘.
즉, 트래픽에 따른 시스템적인 문제를 걱정할 필요가 없어지는거지!!</p>
<p>또 파일에 대한 접근 권한을 지정할 수 있어서 
서비스를 호스팅 용도로 사용하는 것을 방지할 수 있어</p>
<p>🧐 : 호스팅용도??</p>
<p>🤠 : 
호스팅이란 서버 컴퓨터의 전체 또는 일정 공간을 이용할 수 있도록 임대해주는 서비스를 말해.
사용자가 직접 서버를 구입하고 운영할 필요 없이 호스팅 업체가 미리 준비해 놓은 서버를 빌려 사용하는 형식이야</p>
<p>🧐 : 
흠.. 누군가 내 서버를 마음대로 호스팅 용도로 사용할 수 있는데 S3가 이걸 막아준다는건가?
이걸 어떻게 하지?</p>
<p>뭐 어떻게 하나보지..</p>
<p>🤠 :
다시 S3로 돌아와서
S3의 특징에 대해 알아보자</p>
<ul>
<li>많은 사용자가 접속을 해도(트래픽이 증가해도) 이를 감당하기 위한 시스템적인 작업을 하지 않아도 된다(고급기술)</li>
<li>저장할 수 있는 파일 수의 제한이 없다</li>
<li>최소 1바이트에서 최대 5TB의 데이터를 저장하고 서비스 할 수 있다 —&gt; 
🧐 이러면 제한 있는거아닌가..?라고 생각했지만 이건 ‘단일파일’ 기준이고 5TB파일을 여러개 저장할 수 있다!</li>
<li>파일에 인증을 붙여서 무단으로 엑세스 하지 못하도록 할 수 있다(고급기술)</li>
<li>HTTP 와 BitTorrent 프로토콜을 지원한다.
  🧐 BitTorrent가 프로토콜이었어..?</li>
<li>REST, SOAP 인터페이스를 제공한다
  🧐 SOAP이 뭐지.. REST 공부하면서 한번 봤는데…
  🧐 REST는 HTTP 프로토콜을 최대한 활용하는 통신방법으로 GET, POST, PUT 등을 사용</li>
<li>데이터를 여러 시설에서 중복으로 저장해서 데이터의 손실이 발생할 경우 자동으로 복원한다</li>
<li>버전관리 기능을 통해 사용자에 의한 실수도 복원이 가능</li>
<li>정보의 중요도에 따라서 보호 수준을 차등할 수 있고, 이에 따라서 비용을 절감할 수 있다 (RSS)</li>
</ul>
<p>🧐 : 뭔가 엄청나네</p>
<p>🤠 : 위에서 말한 것들 중 주요개념을 살펴보자!!</p>
<h4 id="객체object">객체(Object)</h4>
<pre><code>AWS는 S3에 저장된 데이터 하나 하나를 객체라고 하는데, 하나의 파일이라고 생각하면 된다.</code></pre><h4 id="버킷bucket">버킷(Bucket)</h4>
<pre><code>객체가 파일이라면, 버킷은 연관된 객체들을 그룹핑한 최상위 디렉토리라고 할 수 있다. 
버킷 단위로 지역을 지정 할 수 있고, 또 버킷에 포함된 모든 객체에 대해서 일괄적으로 인증과 접속제한을 걸 수 있다.</code></pre><h4 id="버전관리">버전관리</h4>
<pre><code>S3에 저장된 객체들의 변화를 저장.
예를 들어, A라는 객체를 사용자가 삭제하거나 변경해도 각각의 변화를 모두 기록하기 때문에 실수를 만회할 수 있다.</code></pre><h4 id="bittorrent">BitTorrent</h4>
<pre><code>P2P방식의 배포방식. 즉, 분산된 파일 배포 시스템
여기서 분산이란 하나의 서버에서 파일을 배포하는 것이 아니라, 
파일을 가지고 있는 컴퓨터들로부터 조금씩 파일을 다운받은 후에 다운받은 것들을 이어붙여서 완전한 파일을 만드는 방식이다.
대용량 파일을 배포할 때 이 방식을 사용하면 비용을 크게 절감할 수 있다</code></pre><h4 id="rss">RSS</h4>
<pre><code>Reduced Redundancy Storage의 약자로 ,일반 S3 객체에 비해서 데이터가 손실될 확률이 높은 형태의저장방식. 
대신 가격이 저렴하기 때문에 복원이 가능한 데이터, 이를 테면 섬네일 이미지 같은 것을 저장하는 데 적합하다. 
그럼에도 불구하고 물리적인 하드 디스크 대비 400배 가량 안전하다는 것이 아마존의 주장!
* 원본은 standard 방식으로 S3에 저장
* 썸네일은 RSS 방식으로 저장</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[소셜로그인 - OAuth 2.0]]></title>
            <link>https://velog.io/@jun_0y/%EC%86%8C%EC%85%9C%EB%A1%9C%EA%B7%B8%EC%9D%B8-OAuth-2.0</link>
            <guid>https://velog.io/@jun_0y/%EC%86%8C%EC%85%9C%EB%A1%9C%EA%B7%B8%EC%9D%B8-OAuth-2.0</guid>
            <pubDate>Fri, 18 Nov 2022 05:25:40 GMT</pubDate>
            <description><![CDATA[<h3 id="oauth를-쓰는-이유">OAuth를 쓰는 이유</h3>
<h4 id="oauth란">OAuth란?</h4>
<p>🧑🏻‍ : Resource Owner(서비스를 사용할 사용자)</p>
<p>🏢 : Resource Server(데이터를 가지고 있는 서버. Google, Naver, Kakao ...)</p>
<p>💻 : Client(앱,웹)</p>
<hr>
<p>🧑🏻‍  &amp;nbsp&nbsp; ➡️ &nbsp;&nbsp; 💻 : 
너가 제공하는 서비스를 사용하고 싶은데 그러기 위해선 로그인을 해야되네
근데 내 아이디, 비밀번호 등록해서 회원가입을 하기는 싫은데..</p>
<p>💻  &amp;nbsp&nbsp; ➡️ &nbsp;&nbsp; 🧑🏻‍ : 
다른 소셜App에 회원가입이 돼있으면 그 아이디로 우리 App에 로그인 할 수 있게 해줄게</p>
<p>🧑🏻‍  &amp;nbsp&nbsp; ➡️ &nbsp;&nbsp; 💻 :
내 아이디와 비번을 여러 서비스에서 돌려쓴다고?</p>
<p>🧑🏻‍ : 싫어!
💻 : 아니!
🏢 : 안돼!</p>
<p>해서 나온게 <code>OAuth</code>이다.</p>
<blockquote>
</blockquote>
<p>🧑🏻‍--🏢--💻 이 사이에서 안전하게 상호작용 할 수 있게 해주는게 바로 <code>OAuth</code></p>
<p>어떻게??</p>
<h4 id="참여">참여</h4>
<p>OAuth에는 네 종류의 주체가 참여</p>
<p>🧑🏻‍ Resource Owner (사용자: 내 서비스의 유저)
🏢 Authorization Server (인증 서버: 카카오 인증 서버)
🏢 Resource Server (자원 서버: 카카오 API 서버)
💻 Client (클라이언트: 내 서비스)</p>
<h4 id="등록">등록</h4>
<p>내 서비스에서 SNS에 미리 등록해놓는 과정 ( 나 니네꺼좀 쓸게~~)
등록시 공통적으로 필요한 정보</p>
<ul>
<li>Client ID</li>
<li>Client Secret</li>
<li>Authorized Redirect URIs : Client가 설정
Authorization 서버가 권한을 부여하는 과정에서 Client한테 Authorized CODE를 전달해 줄 주소 + redirect 주소가 아닌데서 요청을 해버리면 무시함</li>
</ul>
<h4 id="인증-과정">인증 과정</h4>
<ul>
<li>Step1</li>
</ul>
<ol>
<li>사용자가 SNS에 접속</li>
<li>SNS는 사용자가 로그인이 되어있는지/안되어있는지 확인해서 로그인</li>
<li>로그인이 되면 SNS는 내 서비스에있는 client id값이 유효한지 확인(SNS가 가지고 있는 id와 내가 갖고 있는 id가 같은지 확인)</li>
<li>확인된 Client_id의 redirect_uri 값이 유효한지 확인해서 다르면 차단, 같으면 사용자에게 내 서비스에 권한 넘겨준다는걸 사용자에게 확인받음.
결론: Resource Server를 통해서 Resource Owner(사용자)의 허락을 받고 인가 코드(Authorization CODE)를 발급 </li>
</ol>
<ul>
<li>Step2</li>
</ul>
<ol>
<li>Resource Server가  사용자에게  Authorization CODE를 포함한 Redirection주소(내 서비스의 주소)로 이동하라고 명령. </li>
<li>사용자는 저 주소로 이동하는지도 모르게 인가 코드를 가지고 내 서비스 사이트로 redirect </li>
<li>그럼이제 나는 Authorization CODE를 알 수 있네</li>
<li>Client는 CODE + Client_id + Client_Secret을 가지고 Resource Server에 토큰(Access Token)을 요청할거야</li>
<li>리소스 서버는 위의 세 정보가 유효한지 확인하고 Client에 토큰 발급해줌</li>
<li>Client는 이 토큰을가지고 API 요청.</li>
</ol>
<p>Header에 Bearer로 토큰을 넣어서 API를 요청</p>
<p>curl -H Authorization: Bearer <access_token> 이런식으로</p>
<p>Access Token도 유효기간이 있어서 기간이 지나면 Refresh Token으로 재발급받을 수 있음</p>
<p><br><br></p>
<p>쫌 더 자세히 설명하자면</p>
<p>OAuth 2.0은 네가지의 권환 획득 방식을 정의하고 있어</p>
<ol>
<li>권한부여코드 승인 타입 (Authorization Code Grant Type)</li>
<li>암시적 승인 타입 (Implicit Grant Type)</li>
<li>리소스 소유자 암호 자격 증명 타입 (Resource Owner Password Credentials Grant Type)</li>
<li>클라이언트 자격 증명 타입 (Client Credentials Grant Type)</li>
</ol>
<p>이 네가지 중 <code>권한부여코드 승인 타입</code>이 가장 많이 쓰이는 방법이고
저번에한 Django로 카카오 소셜로그인 구현할 때 사용한 방법이므로 1번 방법에 대해 알아보자</p>
<h4 id="권한부여코드-승인-타입-authorization-code-grant-type">권한부여코드 승인 타입 (Authorization Code Grant Type)</h4>
<ul>
<li>Client가 사용자 대신 특정 리소스에 접근을 요청할 때 사용해</li>
<li>사용자가 인증 서버에 로그인해서 건네받은 권한 코드를 Client에 넘겨주고</li>
<li>Client는 권한 코드를 받아 리소스 서버에서 엑세스 토큰과 리프레시 토큰으로 교환하여 사용할 수 있어</li>
</ul>
<ol>
<li>Client는 인증서버에 미리 등록된 Client_id와, 유저가 인가코드를 가져다 줄 리디렉션 주소를 유저에게 제공하며 유저를 인증 서버의 엔드포인트로 리디렉션시킨다.</li>
<li>유저는 인증 서버 엔드포인트에 도착해 자신을 인증하며 Client로부터 전해받은 클라이언트 신원증명과 리디렉션 주소를 인증서버에 전달한다.</li>
<li>인증서버는 유저를 인증한다.</li>
<li>만약 유저가 성공적으로 인증되었다면 인증 서버는 2에서 전해받은 리디렉션 주소로 유저를 리디렉트하며 인가코드를 전해준다.</li>
<li>유저는 Client의 리디렉션 주소로 도착해 Client에게 인가코드를 전달한다.</li>
<li>인가코드를 전해받은 Client는 인증서버로 인가코드와 1에서 유저에게 주었던 리디렉션 주소를 전해준다.</li>
<li>인증서버는 Client가 가져온 리디렉션 주소가 4에서 유저를 리디렉션 했던 주소가 맞는지 보고 가져온 인가코드를 검증한 후 유효하다면 Client에게 유저의 리소스에 대한 엑세스 토큰과 리프레시 토큰을 전달한다.</li>
<li>Client는 엑세스 토큰을 가지고 리소스 서버의 API를 호출한다.</li>
</ol>
<blockquote>
</blockquote>
<p>🏢  &amp;nbsp&nbsp; ➡️ &nbsp;&nbsp; 💻 : <code>AccessToken</code>을 줄테니까 뭐 필요한거 있으면 <code>AccessToken</code> 들고와</p>
<p>🏢의 아이디, 비번을 제공하는게 아니고, &amp;nbsp💻에 꼭 필요한 기능만 부분적으로 접근허용할 수 있게 해주는 일종의 key</p>
<hr>
<p>정리해보자면</p>
<p>🧑🏻‍ 사용자 : User : Resource Owner - 자원의 주인
💻 내서비스 : Mine(my service) : Client - 리소스 서버에 접근해서 자원을 가져오는 애
🏢 사용자가 회원가입한 SNS : Their(Google, Facebook, Twitter) :Resource Server - 자원을 갖고 있는 서버 / Authorization Server - 인증관련 처리하는 서버</p>
<p>🧑🏻‍가 💻에서 소셜 로그인(Google 등)을 통해 내 서비스의 기능을 사용하고 싶어.
이때 💻에서 🧑🏻‍의 google 아이디와 비밀번호를 알아서 그 아이디/비번을 이용하여 구글에 로그인한다면 🧑🏻‍는 개인정보 노출돼서 싫고, 💻 는 민감한 정보 관리해야돼서 싫고, 🏢은 User의 정보를 노출시켜줘서 싫어.
이 불편함을 해결해주는게 OAuth. 어떻게?
🏢 에서 Access Token을 발급해서 💻 한테 주면 아이디와 비밀번호 없이 토큰을 가지고 🧑🏻‍ 정보를 이용할 수 있어.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JPA] OSIV와 성능 최적화]]></title>
            <link>https://velog.io/@jun_0y/JPA-OSIV%EC%99%80-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94</link>
            <guid>https://velog.io/@jun_0y/JPA-OSIV%EC%99%80-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94</guid>
            <pubDate>Fri, 11 Nov 2022 06:21:27 GMT</pubDate>
            <description><![CDATA[<h3 id="osiv란">OSIV란?</h3>
<p>🧐 : OSIV가 뭐야?</p>
<p>🧙 : 
OSIV란 Open Session In View의 약자로
하이버네이트에선 <code>Open Session In View</code> 라고 표기하고
JPA에선 <code>Open EntityManager In View</code> 라고 표기하는데 그냥 OSIV라고 불러</p>
<p>Spring에서 표기할 때는 그냥 open in view 라고 쓰나봐!!</p>
<p>서버를 실행하면 콘솔창에
<img src="https://velog.velcdn.com/images/jun_0y/post/580f8b53-19ad-4fbb-ab00-a37705ca0005/image.png" alt=""></p>
<p>이렇게 WARNING이 뜨던게 하나 있었는데 warning의 원인을 보면 <code>spring.jpa.open-in-view</code>가 보여!! 
이게 뭐냐면 스프링과 DB의 커넥션과 관계가 있어</p>
<p>🧐 : 스프링은 언제 DB랑 커넥션이 일어나는데?</p>
<p>🧙 : 서비스 계층에서 트랜잭션이 시작될때!!</p>
<p>🧐 : 그럼 언제 DB에 값을 반환하는데?</p>
<p>🧙 : 
open-in-view가 true냐 false냐 에 따라 달라!</p>
<h4 id="osiv-on">OSIV ON</h4>
<p><img src="https://velog.velcdn.com/images/jun_0y/post/1eed9448-3d65-4fcd-9b25-000292523e3b/image.png" alt=""></p>
<p>open-in-view를 켜면(<code>spring.jpa.open-in-view: true</code>)  트랜잭션이 끝나고 밖으로 나갈 때까지 반환을 안해.
이게 뭔소리냐면 트랜잭션은 리포지토리 안에서 시작되고 끝나는데 리포지토리 밖으로 나와도 DB 커넥션이 연결돼있다는거</p>
<p>왜 안하냐면 지연로딩은 프록시 객체를 쓴다는거고 프록시 객체는 영속성 컨텍스트가 살아있어야 쓸 수있어. 
즉, open-in-view가 true 라는건 영속성 컨텍스트 &amp; DB를 끝까지 살려둔다는 거지. 
언제까지?? 렌더링 완성될 때까지. 그래서 지금까지 View Template이나 API 컨트롤러에서 지연로딩이 가능했던거야</p>
<p>🧐 : 오 그럼 좋은거아냐?</p>
<p>🧙 : 
좋은것만은 아닌게
너무 오랫동안 DB 커넥션을 물고있어서 실시간 트래픽이 중요한 애플리케이션에서는 커넥션이 모자랄수도있고 이러면 장애로 이어져</p>
<p><del>뭐 다 trade off 임..</del></p>
<h4 id="osiv-off">OSIV OFF</h4>
<p><img src="https://velog.velcdn.com/images/jun_0y/post/92d5b797-fe88-40ed-be31-c67ff116e02c/image.png" alt=""></p>
<p>그래서 open-in-view 끄면(<code>spring.jpa.open-in-view: false</code>) 트랜잭션 종료할 때 영속성 컨텍스트 닫고 디비 커넥션도 반환하기 때문에 커넥션 리소스 낭비안해</p>
<p>근데 끄면 <code>모든 지연로딩을 트랜잭션 안에서 처리</code>해야돼 </p>
<p>🧐 : 지연로딩을 트랜잭션 안에서 처리하는게 무슨뜻이야?</p>
<p>🧙 : 
지금까지 지연로딩 최적화는 View Template이나  컨트롤러에서 처리했잖아
근데 트랜잭션 안에서 처리해야 한다는 의미는 리포지토리 단계에서 끝내야 된다는거야.
안끝나면 에러발생!
즉, 지금까지 사용한 방식은 못 쓴다는거지</p>
<p>🧐 : 그럼 지금까지 해논 지연로딩 최적화는 말짱 도루묵이네..</p>
<p>🧙 :
트랜잭션이 끝나기 전에 지연 로딩을 강제로 호출해 두면돼!!</p>
<p>🧐 : 어떻게??</p>
<h3 id="커멘드와-쿼리-분리">커멘드와 쿼리 분리</h3>
<p>🧙 :
해결방법은 처리하는 서비스를 따로 만들어서 관리하는거야(커맨드와 쿼리를 분리)</p>
<ul>
<li>OrderService<ul>
<li>OrderService : 핵심비즈니스 로직 구현</li>
<li>OrderQueryService : 화면이나 API에 맞춘 서비스 구현</li>
</ul>
</li>
</ul>
<p>이런식으로 처리하면 해결!!</p>
<pre><code>💡꿀팁💡

고객 서비스의 실시간 API는 OSIV를 끄고, 
ADMIN 처럼 커넥션을 많이 사용하지 않는 곳에서는 OSIV를 켜놓자</code></pre><p><del>이번 프로젝트에 admin 담당이어서 다행이다</del></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JPA] API 개발 고급 - 컬렉션 조회  최적화]]></title>
            <link>https://velog.io/@jun_0y/JPA-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</link>
            <guid>https://velog.io/@jun_0y/JPA-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</guid>
            <pubDate>Fri, 11 Nov 2022 05:33:09 GMT</pubDate>
            <description><![CDATA[<p>🧙
이전 포스팅인 <a href="https://velog.io/@jun_0y/JPA-API-%EA%B0%9C%EB%B0%9C-%EA%B3%A0%EA%B8%89-%EC%A7%80%EC%97%B0%EB%A1%9C%EB%94%A9LAZY%EA%B3%BC-%EC%A1%B0%ED%9A%8C-%EC%84%B1%EB%8A%A5-%EC%B5%9C%EC%A0%81%ED%99%94">API 개발 고급 - 지연로딩(LAZY)과 조회 성능 최적화</a>에서 만든 주문내역에는
<code>주문번호</code> , <code>주문자 정보</code> , <code>주문날짜와 주문상태</code> , <code>주소</code> 이렇게 xToOne 관계만 존재했고 xToOne을 조회하는 방법과 최적화 시키는 방법을 공부 했어</p>
<p>이번에는 주문 정보를 가져올 때 주문 상품과 그 상품의 정보까지 조회하고 싶어서 <code>상품정보(OrderItem &amp; Item)</code>를 추가로 조회하려고 해!
Order와 OrderItem,Item은 <code>xToMany</code> 관계이기 때문에 <code>xToMany 관계</code>를 조회하고 최적화 시키는 방법을 공부할거야.</p>
<pre><code>참고로 `xToMany 관계`를 `컬렉션` 이라고 부른다</code></pre><p>이번에도 글이 쫌 긴데 꼭 끝까지 다 읽고 이해하자!!</p>
<p>본격적으로 시작하기전에 먼저 알고 넘어갈 것</p>
<ul>
<li>OrderItem의 overall
```    </li>
<li>주문상품ID : id(@Id)</li>
<li>상품 : item(@ManyToOne) </li>
<li>주문 : order(@ManyToOne)</li>
<li>주문가격 : orderPrice</li>
<li>주문 수량 : count</li>
</ul>
<p>생성메서드
    - createOrderItem(상품, 가격, 수량)</p>
<p>비즈니스 로직
    - cancel( )
    - getTotalPrice( )</p>
<pre><code>

•Item의 overall</code></pre><ul>
<li>상품ID : id</li>
<li>상품명 : name</li>
<li>상품가격 : price</li>
<li>상품수량 : stockQuantity</li>
<li>카테고리 : categories(앨범, 책, 영화)</li>
</ul>
<p>비즈니스 로직
    - addStock(수량)
    - removeStock(수량)</p>
<pre><code>
이제 진짜 시작!!

🧙 :
주문 조회 API를 만들건데 이제 배송정보, 회원 정보에 `주문상품정보`까지 들어있는..
API를 만들면서 컬렉션 때문에 발생하는 성능 문제를 해결해나갈거야

API Controller의 이름은 OrderApiController.

* OrderApiController
```java
package jpabook.jpashop.api;

@RestController
@RequiredArgsConstructor
public class OrderApiController {

    private final OrderRepository orderRepository;
    private final OrderQueryRepository orderQueryRepository;

}</code></pre><br>

<hr>
<br>

<h3 id="version_1--엔티티-직접-노출">version_1 : 엔티티 직접 노출</h3>
<p>결론부터 말하자면 version_1의 방법(엔티티를 직접 노출하는 방법)은 쓰면 안돼.</p>
<p>왜 안되는지는 저번 포스팅에도 있고 지금 더 자세히 알아보자!!</p>
<h4 id="엔티티를-직접-조회하는-코드">엔티티를 직접 조회하는 코드</h4>
<pre><code>    @GetMapping(&quot;/api/v1/orders&quot;)
    public List&lt;Order&gt; ordersV1() {
        List&lt;Order&gt; all = orderRepository.findAllByString((new OrderSearch()));  // 모든 주문 정보를 가져와
        for (Order order : all) {       //하나의 주문씩 돌면서 확인할건데
            order.getMember().getName();            // 주문 - 회원 - 이름, LAZY 강제 초기화
            order.getDelivery().getAddress();       // 주문 - 배달 - 배송지, LAZY 강제 초기화

            List&lt;OrderItem&gt; orderItems = order.getOrderItems();     // 모든 주문 상품의 정보를 가져와
            orderItems.stream().forEach(o -&gt; o.getItem().getName());    // 하나씩 돌면서 상품의 이름 조회, LAZY 강제 초기화
        }
        return all;
    }</code></pre><p>🤠 : 간단한 코드설명</p>
<pre><code>- orderRepository에서 findAllByString() 메서드를 사용하여 모든 주문정보를 List&lt;Order&gt;에 가져와요
- 모든 주문정보를 하나씩 돌면서(for) Member와 Delivery를 조회해요 --&gt; Lazy 강제 초기화
- 하나의 주문에는 여러개의 주문상품이 있을 수 있으므로 
- order에서 getOrderItems() 메서드를 사용하여 모든 주문상품정보를 List&lt;OrderItem&gt;에 가져와요
- 모든 주문상품정보를 하나씩 돌면서(stream) 주문상품의 이름을 조회해요</code></pre><p>🧙 : 
위의 코드 설명에서 주목할 것은 각각의 주문(Order)에 주문상품(OrderItem)이 여러개 있으므로 모든 주문상품을 다시 <code>List&lt;OrderItem&gt;</code>에 담아서 <code>stream()</code>으로 조회하고 있다는 거야. 즉, 두단계를 거쳐서 정보를 확인하고 있지는거지.
나중에 DTO 쓸 때 조심해야되니까 기억해둬</p>
<p>이렇게 OrderItem과 Item 관계를 직접 초기화하면 Hibernate5Module 설정에 의해 엔티티를 JSON으로 생성해. 그리고 여기서도 마찬가지로 양방향 관계는 <code>@JsonIgnore</code>꼭 걸어줘야해!! </p>
<p>하.지.만. 이제 말하기도 지겹다. 이건 엔티티를 직접 노출하는 방법이니까 쓰지마</p>
<br>

<hr>
<br>

<h3 id="version_2--엔티티를-dto로-변환">version_2 : 엔티티를 DTO로 변환</h3>
<p>🧙 :
엔티티를 직접 노출하지 말고 어떻게 하라고?? DTO를 만들라고!!</p>
<h4 id="엔티티를-dto로-변환">엔티티를 DTO로 변환</h4>
<pre><code class="language-java">@GetMapping(&quot;/api/v2/orders&quot;)
    public List&lt;OrderDto&gt; ordersV2() {
        List&lt;Order&gt; orders = orderRepository.findAllByString(new OrderSearch());
        List&lt;OrderDto&gt; result = orders.stream()
                .map(o -&gt; new OrderDto(o))
                .collect(toList());

        return result;
    }</code></pre>
<p>🤠 : 간단한 코드설명</p>
<pre><code>- findAllByString()메서드를 이용해서 모든 주문정보를 List&lt;Order&gt;에 가져와요
- 받아온 주문 정보를 stream()을 이용하여 하나씩 List&lt;OrderDto&gt;에 가져와서 모아놔요(map, collect)</code></pre><h4 id="orderdto">OrderDto</h4>
<pre><code class="language-java">@Getter
    static class OrderDto {

        private Long orderId;
        private String name;
        private LocalDateTime orderDate;
        private OrderStatus orderStatus;
        private Address address;
//      private List&lt;OrderItem&gt; orderItems;         // OrderItem도 엔티티이기 떄문에 DTO로 바꿔야 한다.
        private List&lt;OrderItemDto&gt; orderItems;

        public OrderDto(Order order) {
            orderId = order.getId();
            name = order.getMember().getName();
            orderDate = order.getOrderDate();
            orderStatus = order.getStatus();
            address = order.getDelivery().getAddress();
//          order.getOrderItems().stream().forEach(o -&gt; o.getItem().getName());
//          orderItems = order.getOrderItems();
            orderItems = order.getOrderItems().stream()
                    .map(orderItem -&gt; new OrderItemDto(orderItem))
                    .collect(toList());
        }
    }</code></pre>
<p>🤠 : 간단한 코드설명</p>
<pre><code>- 주문아이디, 주문자이름, 주문날짜, 주문상태, 배송지.. 까지는 저번에 했던 주문내역과 같아요
- 주문상품(List&lt;OrderItem&gt; orderItems)이 추가 됐어요
- ⭐️주문상품도 엔티티이기 때문에 DTO로 변환해줘야 해요⭐️
- 이에 맞춰 생성자를 선언해요
- orderItems은 DTO로 변환 과정이 필요해요 
- 모든 주문상품(OrderItem)을 가져온뒤 각각의 주문상품을 stream을 통해
  주문상품DTO(OrderItemDto)에 넣어주고 모아놔요(map, collect)
- *주석처리는 OrderItem 엔티티를 직접 노출하는 경우</code></pre><h4 id="orderitem-dto">OrderItem DTO</h4>
<pre><code class="language-java">@Getter
    static class OrderItemDto {

        private String itemName;    // 상품명
        private int orderPrice;     // 주문가격
        private int count;          // 주문수량

        public OrderItemDto(OrderItem orderItem) {
            itemName = orderItem.getItem().getName();
            orderPrice = orderItem.getOrderPrice();
            count = orderItem.getCount();
        }
    }</code></pre>
<p>🤠 : OrderItemDto. 상품명, 주문가격, 주문수량이 필요하고 생성자도 선언</p>
<p>🧙 : ❗️주의사항❗️
Order 엔티티를 DTO로 변환하기 위해 OrderDto라는 DTO를 만들어줬어.
근데 지금 주문내역(Order)안에서 주문상품(OrderItem) 엔티티가 또 외부로 노출되고있어.
이렇게 <code>엔티티(Order) 안에 엔티티(OrderItem)</code>가 있는 경우, <code>안쪽의 엔티티(OrderItem)</code>도 DTO로 변환해줘야해! </p>
<p>엔티티를 외부로 노출하면 안되니까 껍데기만 한번 감싸고 끝!!이 아니라 속에 있는것 까지 다 엔티티로 변환 해줘야 한다는거지.
(단, member, address같은 value 오브젝트는 노출해도 된다)</p>
<p>DTO 생성할 때 많이 실수하는 부분이니까 꼭 알아두도록해!</p>
<p>🧙 : 
엔티티를 DTO로 변환해서 엔티티 외부 노출을 막긴 했는데 이제 문제는 뭐다?? 성능이다~~
이렇게 하면 쿼리가 너어어어무 많이 나가</p>
<p>DB 입장에서 1대N join이 있을 때 데이터양이 N만큼으로 증가해버려</p>
<p>예제를 예시로 들어볼게</p>
<ul>
<li>전체 <code>주문내역</code>을 조회하는 쿼리 1번</li>
<li><code>Member, Address</code> : N번 (전체 주문의 수)</li>
<li><code>OrderItem</code> : N번 (전체 주문의 수)</li>
<li><code>Item</code> : M번 (orderItem의 수)</li>
</ul>
<p><code>Order</code> - <code>Member, Address</code> &amp; <code>OrderItem</code> - <code>Item</code></p>
<p>*참고로 지연 로딩은 영속성 컨텍스트에 있으면 영속성 컨텍스트에 있는 엔티티를 사용하고 없으면 SQL을 실행한다. 
이게 뭔말이냐고? 같은 영속성 컨텍스트에서 이미 로딩한 회원 엔티티를 추가로 조회하면 SQL을 실행하지 않는다!</p>
<br>

<hr>
<br>

<h3 id="version_3--엔티티를-dto로-변환---fetch-join">version_3 : 엔티티를 DTO로 변환 - Fetch Join</h3>
<p>🧐 : 그러면 fetch join을 이용해서 최적화해야 한댔어!!</p>
<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>🤠 : 간단한 코드설명</p>
<pre><code>- findAllWithItem() 메서드를 이용해서 모든 주문정보를 List&lt;Order&gt;에 가져와요
- 받아온 주문 정보를 stream()을 이용하여 하나씩 List&lt;OrderDto&gt;에 가져와서 모아놔요(map, collect)</code></pre><p>🧙 : 
v2랑 바뀐게 메서드 하나 밖에 없지? <code>findAllByString() ➡️ findAllWithItem</code>
다른건 다 똑같고 데이터를 어떻게 끌어오냐의 차이! 어떻게? fetch join으로!</p>
<p>그래서 할거는 <code>orderRepository</code>에 fetch join으로 테이블을 조회하는 함수인 <code>findAllWithItem()</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>🤠 : 간단한 코드설명</p>
<pre><code>- fetch join을 이용해서 엔티티를 조회하는 쿼리문을 작성해요
- ⭐️ `distinct`가 중요해요 ⭐️
</code></pre><p>🧙 :
여기서 주목할 것은 select절에 있는 <code>distinct</code>야.
JPA의 distinct는 MySQL에서 쓰이는 distinct랑은 쫌 다른게, order의 id가 같으면 중복을 제거해줘</p>
<p>이게 무슨말이냐면..</p>
<p>만약 distinct가 없다면
하나의 주문내역에는 여러개의 주문상품이 있을 수 있지? 바로 1대N 관계인 상황인데
여기서 그냥 join을 해버리면 데이터베이스의 row가 증가해버려.</p>
<p>order - orderItem1/orderItem2/orderItem3
join 후 —&gt;
order - orderItem1
order - orderItem2
order - orderItem3
여기서 order은 다 똑같은 값!</p>
<p>그 결과 같은 order의 엔티티 조회수도 증가해버려. </p>
<p>이때 <code>distinct</code>를 쓰면 SQL에 distinct 옵션을 추가하고 &amp; 같은 엔티티가 조회되면 애플리케이션에서 중복을 걸러줘!!</p>
<blockquote>
</blockquote>
<p>JPA - <code>distinct</code></p>
<ol>
<li><p>SQL에 distinct 옵션 추가</p>
</li>
<li><p>같은 엔티티가 조회된 경우애플리케이션에서 중복을 걸러줌</p>
<p>🧙 : 
아무튼 이렇게 하면 order가 fetch join 때문에 중복 조회 되는 것을 막아준다~ 즉, SQL이 1번만 실행된다~</p>
</li>
</ol>
<p>➡️ 전체 주문내역을 조회하는 쿼리가 1번 나갈건데 이때 Member, Address,OrderItem,Item이 싹다 fetch join으로 묶여서 같이 조회된다!</p>
<ul>
<li><code>전체 주문내역 + Member, Address,OrderItem,Item</code> 을 조회하는 쿼리 1번</li>
</ul>
<p>🧙 : 
하지만 이 방법은 ❗️<code>페이징이 불가능</code>❗️하다는 단점이 있어
정확히 말해서 가능은 한데 매우매우매우 위험해져
Hibernate가 경고 로그를 남기면서 모든 데이터를 DB에서 메모리로 읽어오고, 
메모리에서 페이징 하는데 이때 데이터가 방대하다면?? Out Of Memory로 심각한 오류가 발생!
이렇게 되는 이유는 1:N인 컬렉션에서 페이징은 1을 기준으로 해야되는데
fetch join을 하면 N을 기준으로 row가 생성되기 때문이야</p>
<pre><code>참고: 컬렉션 페치 조인을 사용하면 페이징이 불가능하다. 
하이버네이트는 경고 로그를 남기면서 모든 데이터를 DB에서 읽어오고, 
메모리에서 페이징 해버린다(매우 위험하다). 
자세한 내용은 자바 ORM 표준 JPA 프로그래밍의 페치 조인 부분을 참고하자.

참고: 컬렉션 페치 조인은 1개만 사용할 수 있다. 컬렉션 둘 이상에 페치 조인을 사용하면 안된다. 
데이터가 부정합하게 조회될 수 있다. 
자세한 내용은 자바 ORM 표준 JPA 프로그래밍을 참고하자.</code></pre><br>

<hr>
<br>

<h3 id="v31-엔티티를-dto로-변환---페이징과-한계-돌파">V3.1: 엔티티를 DTO로 변환 - 페이징과 한계 돌파</h3>
<p>🧙 :
윗부분을 정리하자면 </p>
<blockquote>
</blockquote>
<ul>
<li>컬렉션을 fetch join하면 페이징이 불가능하다<ul>
<li>컬렉션을 fetch join하면 1대N 조인이 발생하므로 데이터가 예측할 수 없이(겁나 많이) 증가한다</li>
<li>1대N 에서 1(Order)을 기준으로 페이징을 하는것이 목적이지만</li>
<li>데이터는 N(OrderItem)을 기준으로 row가 생성된다<br></li>
</ul>
</li>
<li>이 경우 Hibernate는 경고 로그를 남기고 모든 DB 데이터를 읽어서 메모리에서 페이징을 시도하며 Out of Memeory가 발생할 수 있다</li>
</ul>
<p>🧐 : 그럼 페이징 + 컬렉션 엔티티를 함께 조회 못해??</p>
<p>🧙 : 그럴리가. <del>안됐으면좋겠다</del></p>
<h4 id="페이징--컬렉션-엔티티-함께-조회하는-방법">페이징 + 컬렉션 엔티티 함께 조회하는 방법</h4>
<pre><code class="language-java">/**
* V3.1 엔티티를 조회해서 DTO로 변환 페이징 고려
*-ToOne 관계만 우선 모두 페치 조인으로 최적화
* - 컬렉션 관계는 hibernate.default_batch_fetch_size, @BatchSize로 최적화 
*/
@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); //xToOne 관계인 애들은 그냥 fetch join으로 가져온다

        List&lt;OrderDto&gt; result = orders.stream()
                .map(o -&gt; new OrderDto(o))
                .collect(toList());

        return result;
    }</code></pre>
<p>🤠 : 간단한 코드설명</p>
<pre><code>- (int)offset, (int)limit 파라미터를 받아서 
- findAllWithMemberDelivery(offset, limit) 메서드를 이용해서 모든 주문정보를 List&lt;Order&gt;에 가져와요
- 받아온 주문 정보를 stream()을 이용하여 하나씩 List&lt;OrderDto&gt;에 가져와서 모아놔요(map, collect)</code></pre><p>🧙 : 
v3때와 비교했을 때 바뀐건 메서드 <code>findAllWithItem ➡️ findAllWithMemberDelivery(offset, limit)</code> 밖에 없어!(인자를 받는 메서드를 오버로딩)</p>
<p>🧐 : <code>findAllWithMemberDelivery()</code>이거 어디서 본거같은데..?</p>
<p>🧙 : 지연로딩 최적화 v3에서 썼어!</p>
<p>그럼 <code>findAllWithMemberDelivery(offset, limit)</code>를 확인해봐야겠지?</p>
<h4 id="페이징-조회를-가능하게-해줄-findallwithmemberdelivery">페이징 조회를 가능하게 해줄 findAllWithMemberDelivery</h4>
<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>🤠 : 간단한 코드설명</p>
<pre><code>- fetch join을 이용해서 엔티티를 조회하는 쿼리문을 작성해요
- 여기서는 distinct를 쓰지 않아요
- `setFirstResult(offset)` , `setMaxResults(limit)`으로 페이징 처리를 해요
- 🧐 : ordetItem은 언제 조회해?
- 🤠 : orderItem은 OrderDto - 생성자에서 조회돼요</code></pre><h4 id="컬렉션-조회를-최적화-해줄-batchsize">컬렉션 조회를 최적화 해줄 BatchSize</h4>
<p>🧐 : <code>BatchSize</code> 가 뭔데?</p>
<p>🧙 : 컬렉션이나 프록시 객체를 한꺼번에 설정한 size만큼 IN 쿼리로 조회할 수 있게 해주는 친구야. (IN은 여러개를 한번에 땡겨오는 SQL명령어)</p>
<p>BatchSize를 안썼을 때 쿼리 호출수가 1 + N인데 
(<code>주문내역 + Member, Address</code> 조회를 위해 1번, <code>orderItem</code> 조회를 위해 N번 )</p>
<p>BatchSize를 사용하면 in 쿼리로 한번에 가져오기 때문에 쿼리 호출수가  1 + 1로 줄어들어
(<code>주문내역 + Member, Address</code> 조회를 위해 1번, <code>orderItem</code> 조회를 위해 1번)</p>
<p>🧐 : ❓
그럼 이때 orderItem을 조회 할 때의 저 1은 size에 해당하는 만큼만이겠네??
데이터가 1000개인데 size가 1000이면 1번 조회하는데
데이터가 2000개인데 size가 1000이면 2번 조회해야잖아
정확히 1 + (int(N/size) + 1) 인거 아닌가?</p>
<p>🧙 : ... 그러게</p>
<p>🧐 : 어떻게 적용해?</p>
<h4 id="컬렉션-조회를-최적화-해줄-hibernatedefault_batch_fetch_size-옵션-적용전역">컬렉션 조회를 최적화 해줄 hibernate.default_batch_fetch_size 옵션 적용(전역)</h4>
<pre><code class="language-yml">spring:
    jpa:
        propeties:
              hibernate:
                default_batch_fetch_size: 100
</code></pre>
<p>🤠 : application.yml 파일에 위와 같은 코드를 추가해요
<br></p>
<h4 id="컬렉션-조회를-최적화-해줄-batchsize-옵션-적용개별">컬렉션 조회를 최적화 해줄 @BatchSize 옵션 적용(개별)</h4>
<pre><code>@BatchSize(size = 1000)
    @OneToMany(mappedBy = &quot;order&quot;, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private List&lt;OrderItem&gt; orderItems = new ArrayList&lt;&gt;();</code></pre><p>🤠 : domain - Order 엔티티에서 ToMany 관계인 엔티티에게 개별적으로 @BatchSize를 달아요</p>
<br>

<p>🧙 : 그냥 application.yml에 한번세 설정해주는게 편하고 좋아🙂</p>
<blockquote>
<p>정리</p>
</blockquote>
<ul>
<li>xToOne 관계를 모두 fetch join한다. ToOne 관계는 row 수를 증가 시키지 않으므로 페이징 쿼리에 영향을 주지 않는다.</li>
<li>컬렉션은 지연 로딩으로 조회한다</li>
<li>지연 로딩 성능 최적화를 위해 hibernate.default_batch_fetch_size를 적용한다(전역설정)</li>
<li>개별로 적용하고 싶다면 @BatchSize를 적용한다</li>
</ul>
<p>참고: paging 가능하게 하는 함수 만들때 ToOne인 애들은 빼도 되는데 그러면 쿼리가 따로 나가서 그냥 같이 묶어주는게 좋다.</p>
<br>

<hr>
<br>

<h3 id="version_4--jpa에서-dto-직접조회">version_4 : JPA에서 DTO 직접조회</h3>
<p>🧙 : 
컬렉션에서도 최적화 of 최적화인 DTO를 직접 조회하는 방법도 알아보자!!</p>
<p>엔티티가 아닌 DTO를 바로 조회할 거니까 DTO와,  DTO를 조회 하는 함수를 만들거야
또 Order를 조회하면 OrderItem이 엔티티로 조회되기 때문에 OrderItem에 대한 DTO도 만들어야돼!!</p>
<p>DTO를 find하는 함수는 리포지토리에서 만들건데 이 리포지토리는 api에 맞는(화면 출력에 맞는) 전용 리포지토리 이기 때문에 핵심 비즈니스 로직은 아니야.
따라서 패키지를 따로 빼줘서 만들어주는게 좋아! DTO도 이 패키지 안에 따로 생성해주자.</p>
<ul>
<li><p>repository.order.query</p>
<p>  OrderQueryDto
  OrderItemQueryDto
  OrderQueryRepository - findOrder( ), findOrderQueryDtos( )</p>
</li>
</ul>
<p>🧐 : ❓
v2할 때 OrderApiController에서 DTO 만들었잖아?
DTO는 굳이 패키지에서 따로 안만들고 v2했던 것 처럼 controller에서 바로 만들어서 쓰면 안돼?</p>
<p>🧙 : 안돼❗️ 
리포지토리가 컨트롤러를 참조하는 경우가 발생하면 안되기 때문!!
v2에서는 DTO를 컨트롤러에서 생성해서 컨트롤러가 쓰는건데
지금은 리포지토리에서 쓸거라서 컨트롤러에서 만들면 리포지토리가 컨트롤러를 참조하는 꼴이돼.
또 어차피 저 리포지토리가 DTO알아야되니까 같은 패키지 안에 만들거야</p>
<h4 id="orderquerydto-코드">OrderQueryDto 코드</h4>
<pre><code class="language-java">@Data
@EqualsAndHashCode(of = &quot;orderId&quot;)
public class OrderQueryDto {

    // 필요한 필드
    private Long orderId;
    private String name;
    private LocalDateTime orderDate;
    private OrderStatus orderStatus;
    private Address address;
    private List&lt;OrderItemQueryDto&gt; orderItems;

    // 생성자
    // DTO가 엔티티를 파라미터로 받는게 아닌 필요한 데이터를 인자로 받아온다.
    public OrderQueryDto(Long orderId, String name, LocalDateTime
            orderDate, OrderStatus orderStatus, Address address) {
        this.orderId = orderId;
        this.name = name;
        this.orderDate = orderDate;
        this.orderStatus = orderStatus;
        this.address = address;
    }

}</code></pre>
<p>🤠 : 그냥.. DTO에요</p>
<h4 id="orderitemquerydto-코드">OrderItemQueryDto 코드</h4>
<pre><code class="language-java">@Data
public class OrderItemQueryDto {

    // 필요한 필드
    private Long orderId;
    private String itemName;
    private int orderPrice;
    private int count;

    // 생성자 
    public OrderItemQueryDto(Long orderId, String itemName, int orderPrice, int
            count) {
        this.orderId = orderId;
        this.itemName = itemName;
        this.orderPrice = orderPrice;
        this.count = count;
    }
}</code></pre>
<p>🤠 : 그냥.. DTO에요</p>
<h4 id="orderqueryrepository-코드">OrderQueryRepository 코드</h4>
<pre><code class="language-java">@Repository
@RequiredArgsConstructor
public class OrderQueryRepository {

    public final EntityManager em;

    private List&lt;OrderQueryDto&gt; findOrders() {
        return em.createQuery(
                &quot;select new jpabook.jpashop.repository.order.query.OrderQueryDto(o.id, m.name, o.orderDate, o.status, d.address) &quot; +
                        &quot; from Order o&quot; +
                        &quot; join o.member m&quot;+
                        &quot; join o.delivery d&quot;, OrderQueryDto.class)
                .getResultList();
    }

    private List&lt;OrderItemQueryDto&gt; findOrderItems(Long orderId) {
        return 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 = :orderId&quot;, OrderItemQueryDto.class)
                .setParameter(&quot;orderId&quot;, orderId)
                .getResultList();
    }

    public List&lt;OrderQueryDto&gt; findOrderQueryDtos() {
        List&lt;OrderQueryDto&gt; result = findOrders();      // order 조회하는 query 1번 --&gt; N개의 order 조회

        result.forEach(o -&gt; {
            List&lt;OrderItemQueryDto&gt; orderItems = findOrderItems(o.getOrderId());  // orderItem 조회 : query N번
            o.setOrderItems(orderItems);
        });
        return result;

    }

}</code></pre>
<p>🤠 : 코드 설명</p>
<pre><code>- findOrders( ) : 주문을 조회하는 메서드
- 어떤 데이터를 가져올지는 new 키워드써서 직접 정의하고
- Member와 Address를 join해요

- findOrderItems(Long orderId) : 주문상품을 조회하는 메서드
- 어떤 데이터를 가져올지는 new 키워드써서 직접 정의하고
- item을 join해요
- 파라미터로 주문아이디를 줘서 주문아이디 별로 조회해요

- findOrderQueryDtos() : 위에서 정의한 두 함수를 사용
- findOrder( )로 전체 주문 조회 1번 ➡️ 결과 : N개의 주문 조회
- findOrderItems( )로 N개의 주문 중 각각의 주문에서 주문상품 조회 N번 ➡️ 결과 : M개의 상품 조회</code></pre><p>🧙 :
힘들다.. 준비할게 너무 많아...
준비가 끝났으면 컨트롤러에서 v4 메서드를 작성하자!!</p>
<pre><code class="language-java">    @GetMapping(&quot;api/v4/orders&quot;)
    public List&lt;OrderQueryDto&gt; ordersV4() {
        return orderQueryRepository.findOrderQueryDtos();
    }</code></pre>
<p>🤠 : orderQueryRepository에서 findOrderQueryDtos( )를 호출해요</p>
<p>🧙 : 
...! DTO를 직접조회하기 위한 DTO와 리포지토리만 만들어주면 v4 메서드를 다 작성한거네</p>
<blockquote>
<p>정리</p>
</blockquote>
<ul>
<li>Query : fetch join 1번 ,  컬렉션 N번 </li>
<li>@xToOne - fetch join : order - member - delivery  : 조인해도 row 가 증가하지 않는다</li>
<li>@xToMany - 컬렉션 : order - orderItem  : 조인하면 row가 증가한다
➡️ 1+N 문제 발생</li>
</ul>
<p>그럼이제 1+N 문제를 해결해야겠군!!</p>
<br>

<hr>
<br>

<h3 id="version_5--jpa에서-dto-직접-조회---컬렉션-조회-최적화">version_5 : JPA에서 DTO 직접 조회 - 컬렉션 조회 최적화</h3>
<p>🧙 : 
컬렉션 조회를 최적화하기 위한 find 함수를 작성할거야.</p>
<p>find함수 —&gt; 리포지토리
화면 출력을 위한 메서드 —&gt; OrderQueryRepository</p>
<p>findAllByDto_optimization( ) 코드</p>
<pre><code class="language-java">    private List&lt;OrderQueryDto&gt; findOrders() {
        return em.createQuery(
                &quot;select new jpabook.jpashop.repository.order.query.OrderQueryDto(o.id, m.name, o.orderDate, o.status, d.address) &quot; +
                        &quot; from Order o&quot; +
                        &quot; join o.member m&quot;+
                        &quot; join o.delivery d&quot;, OrderQueryDto.class)
                .getResultList();
    }

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


    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();

        Map&lt;Long, List&lt;OrderItemQueryDto&gt;&gt; orderItemMap = orderItems.stream()
                .collect(Collectors.groupingBy(orderItemQueryDto -&gt; orderItemQueryDto.getOrderId()));
        return orderItemMap;
    }

    public List&lt;OrderQueryDto&gt; findAllByDto_optimization() {
        List&lt;OrderQueryDto&gt; result = findOrders();   // ToOne 한번에 조회

        List&lt;Long&gt; orderIds = toOrderIds(result);

        Map&lt;Long, List&lt;OrderItemQueryDto&gt;&gt; orderItemMap = findOrderItemMap(orderIds);

        result.forEach(o -&gt; o.setOrderItems(orderItemMap.get(o.getOrderId())));

        return result;
    }</code></pre>
<p>🤠 : 코드설명</p>
<pre><code>- findOrder() : 주문을 조회하는 메서드 (위에서 함)

- toOrderIds(List&lt;OrderQueryDto&gt; result) : 주문아이디를 조회하는 메서드
- 주문DTO를 파라미터로 받아서 stream()을 이용해 하나씩 orderIds에 넣어요
- 주문DTO 필드 중 orderId를 골라서 map으로 하나씩 저장해서 collect 해요

- findOrderItemMap(List&lt;Long&gt; orderIds) : 주문상품을 조회하는 메서드
- 어떤 데이터를 가져올지는 new 키워드써서 직접 정의하고 item을 join해서
- 파라미터로 받은 주문아이디별로 조회해요
- ⭐️ MAP을 사용해서 N번의 쿼리를 1번의 쿼리로 끝내요 ⭐️

- findAllByDto_optimization() : 위에서 정의한 세 함수를 사용
- findOrder()로 전체 주문 조회 1번 ➡️ 결과 : N개의 주문 조회
- toOrderIds()로 주문아이디를 조회한 후
- findOrderItemMap()으로 N개의 주문에 있는 상품들을 모아서 한번에 조회 

➡️ 쿼리수 1 + 1</code></pre><p>🧙 : 쓰다보니 코드설명에서 다 써놨는데 그래도 다시 설명하자면 </p>
<ul>
<li>xToOne을 한번에 조회 : 이거는 v4랑 같다(findOrder)</li>
<li>XToMany인 orderItem을 한번에 조회하려고 하는건데 이때 <code>MAP</code>이 필요해!!</li>
</ul>
<p>주문아이디는 주문 DTO에서 주문아이디(orderId)만 쏙 빼서 <code>stream</code> 으로 하나씩 하나씩 넣어줄거야.
그럼 이제 주문상품 DTO에서 가져올 데이터를 주문아이디 별로 받아서 조회할건데 이때 <code>MAP</code>을 이용하면 하나씩 하나씩 세팅해놓고 한번에 쿼리 처리하는 방식이 가능해져 ➡️ 컬렉션 쿼리 1번</p>
<p>🧐 : 
그럼 여기서 키포인트는 stream과 MAP이네
<code>MAP</code>을 이용하여 컬렉션을 한번의 쿼리로 줄일건데
MAP의 인자를 넣어주기 위해서 <code>stream</code>으로 orderId를 조회한다</p>
<p>🧙 : 
그럼 이제 컨트롤러에서 메서드 정의하고 매핑만 해주면 끝!</p>
<pre><code class="language-java">    @GetMapping(&quot;api/v5/orders&quot;)
    public List&lt;OrderQueryDto&gt; ordersV5() {
        return orderQueryRepository.findAllByDto_optimization();
    }</code></pre>
<br>

<hr>
<br>

<h3 id="version_6--jpa에서-dto로-직접-조회---플랫-데이터-최적화">version_6 : JPA에서 DTO로 직접 조회 - 플랫 데이터 최적화</h3>
<p>🧐 : 아 쿼리 두번도 싫어. 한번에 못끝내?</p>
<p><del>제발 안된다고 해줘</del></p>
<p>🧙 : 가능!
컬렉션 조회를 최적화하기 위한 find 함수를 작성할거야..
근데 이제 쿼리가 한번으로 끝나는!</p>
<pre><code>find 함수 정의 —&gt; 리포지토리
화면출력을 위한 메서드 —&gt; OrderQueryRepository</code></pre><p>그러기 위해서는 DTO를 따로 만들어줘야돼</p>
<h4 id="orderflatdto-코드">OrderFlatDto 코드</h4>
<pre><code class="language-java">@Data
public class OrderFlatDto {

    private Long orderId;
    private String name;
    private LocalDateTime orderDate; //주문시간
    private Address address;
    private OrderStatus orderStatus;

    private String itemName;//상품 명
    private int orderPrice; //주문 가격
    private int count; //주문 수량

    public OrderFlatDto(Long orderId, String name, LocalDateTime orderDate, OrderStatus orderStatus, Address address, String itemName, int orderPrice, int count) {
        this.orderId = orderId;
        this.name = name;
        this.orderDate = orderDate;
        this.orderStatus = orderStatus;
        this.address = address;
        this.itemName = itemName;
        this.orderPrice = orderPrice;
        this.count = count;
    }
}</code></pre>
<p>🤠 : 필요한 필드를 선언하고 생성자까지 만들어요</p>
<p>🧙 : FlatDto를 만들었으니 쿼리리 한번으로 조회할 수 있는 메서드를 만들어보자</p>
<h4 id="findallbydto_flat-메서드-코드">findAllByDto_flat() 메서드 코드</h4>
<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();
    }</code></pre>
<p>🤠 : OrderFlatDto에서 필요한 데이터를 뽑아서(new) 조회하는 쿼리문을 만들어요</p>
<p>🧙 : 
이렇게 모든 데이터를 한번에 뽑아왔으면 이 데이터를 화면출력 하기 위해서 가공해줘야돼</p>
<p>데이터를 한번에 뽑아왔다는건 ToOne과 ToMany가 한번에 조회된다는건데
지금까지 OrderQueryDto에서의 생성자는 ToOne, ToMany를 따로 조회하는 방법이었기(orderItem을 생성하기 위해 한번 더 들어감) 때문에 두단계를 거치는 것이 아닌 한번에 생성되는 생성자를 만들어줘야돼(= 그냥 파라미터에 List<OrderItemQueryDto> 넣어주는겨</p>
<h4 id="orderquerydto에-생성자-오버로딩">OrderQueryDto에 생성자 오버로딩</h4>
<pre><code class="language-java">  @Data
@EqualsAndHashCode(of = &quot;orderId&quot;)
public class OrderQueryDto {

    // 생성자2
    // 파라미터에 List&lt;OrderItemQueryDto&gt;가 추가 
    public OrderQueryDto(Long orderId, String name, LocalDateTime orderDate,OrderStatus orderStatus, Address address, List&lt;OrderItemQueryDto&gt; orderItems) {
          this.orderId = orderId;
          this.name = name;
          this.orderDate = orderDate;
          this.orderStatus = orderStatus;
          this.address = address;
          this.orderItems = orderItems;
    }
</code></pre>
<p>🤠 : 파라미터에 OrderItemQueryDto를 받는 부분이 추가됐어요</p>
<p>🧙 : 모든 준비는 끝났다..!</p>
<p>이제 컨트롤러에서 메서드 정의하고 매핑만 해주면 끝!</p>
<h4 id="orderapicontrollerp에-v6함수-정의하고-매핑">OrderApiControllerp에 v6함수 정의하고 매핑</h4>
<pre><code class="language-java">    @GetMapping(&quot;api/v6/orders&quot;)
    public List&lt;OrderQueryDto&gt; ordersV6() {
        List&lt;OrderFlatDto&gt; flats = orderQueryRepository.findAllByDto_flat();
        return flats.stream()
                .collect(groupingBy(o -&gt; new OrderQueryDto(o.getOrderId(), o.getName(), o.getOrderDate(), o.getOrderStatus(), o.getAddress()),
                        mapping(o -&gt; new OrderItemQueryDto(o.getOrderId(), o.getItemName(), o.getOrderPrice(), o.getCount()), toList())
                )).entrySet().stream()
                .map(e -&gt; new OrderQueryDto(e.getKey().getOrderId(), e.getKey().getName(), e.getKey().getOrderDate(), e.getKey().getOrderStatus(), e.getKey().getAddress(), e.getValue()))
                .collect(toList());
    }
</code></pre>
<p>🤠 : 복잡해요..</p>
<p>🧙 : 
이렇게 하면 쿼리가 1번만에 끝난다는 엄청난 장점이 있지만</p>
<p>한번에 뽑아온 데이터를 원하는대로 가공해서 써야되는데 추가작업(데이터를 가공)은 애플리케이션에서 해야된다는 단점이 있고 
  <del>(이렇게까지 해야돼?)</del></p>
<p>조인으로 인해서 DB에서 애플리케이션으로 전달되는 데이터에 중복 데이터가 추가되므로 상황에 따라 v5보다 느릴수도 있다는 단점이 있어.</p>
<hr>
<h3 id="api-개발-고급-총정리">API 개발 고급 총정리</h3>
<pre><code>
• 엔티티 조회
 - 엔티티를 조회해서 그대로 반환: V1 
 - 엔티티 조회 후 DTO로 변환: V2 
 - 페치 조인으로 쿼리 수 최적화: V3 
 - 컬렉션 페이징과 한계 돌파: V3.1
    - 컬렉션은 페치 조인시 페이징이 불가능
    - ToOne 관계는 페치 조인으로 쿼리 수 최적화
    - 컬렉션은 페치 조인 대신에 지연 로딩을 유지하고, hibernate.default_batch_fetch_size , @BatchSize 로 최적화

• DTO 직접 조회
 - JPA에서 DTO를 직접 조회: V4
 - 컬렉션 조회 최적화 - 일대다 관계인 컬렉션은 IN 절을 활용해서 메모리에 미리 조회해서 최적화: V5 
 - 플랫 데이터 최적화 - JOIN 결과를 그대로 조회 후 애플리케이션에서 원하는 모양으로 직접 변환: V6

• 권장 순서
 1. 엔티티조회방식으로우선접근
      1. 페치조인으로 쿼리 수를 최적화 
        2. 컬렉션 최적화
         1. 페이징 필요 hibernate.default_batch_fetch_size , @BatchSize 로 최적화
         2. 페이징 필요X 페치 조인 사용
 2. 엔티티조회방식으로해결이안되면DTO조회방식사용
 3. DTO 조회 방식으로 해결이 안되면 NativeSQL or 스프링 JdbcTemplate

 &gt; 참고: 엔티티 조회 방식은 페치 조인이나, hibernate.default_batch_fetch_size ,
           @BatchSize 같이 코드를 거의 수정하지 않고, 옵션만 약간 변경해서, 다양한 성능 최적화를 시도할 수 있다. 
          반면에 DTO를 직접 조회하는 방식은 성능을 최적화 하거나 성능 최적화 방식을 변경할 때 많은 코드를 변경해야 한다.

 &gt; 참고: 개발자는 성능 최적화와 코드 복잡도 사이에서 줄타기를 해야 한다. 
          항상 그런 것은 아니지만, 보통 성능 최적화는 단순한 코드를 복잡한 코드로 몰고간다.
&gt; 엔티티 조회 방식은 JPA가 많은 부분을 최적화 해주기 때문에, 단순한 코드를 유지하면서, 성능을 최적화 할 수 있다.
&gt; 반면에 DTO 조회 방식은 SQL을 직접 다루는 것과 유사하기 때문에, 둘 사이에 줄타기를 해야 한다.

• DTO 조회 방식의 선택지
    DTO로 조회하는 방법도 각각 장단이 있다. 
      V4, V5, V6에서 단순하게 쿼리가 1번 실행된다고 V6이 항상 좋은 방법인 것은 아니다.
    V4는 코드가 단순하다. 특정 주문 한건만 조회하면 이 방식을 사용해도 성능이 잘 나온다. 
      예를 들어서 조회한 Order 데이터가 1건이면 OrderItem을 찾기 위한 쿼리도 1번만 실행하면 된다.

      V5는 코드가 복잡하다. 여러 주문을 한꺼번에 조회하는 경우에는 V4 대신에 이것을 최적화한 V5 방식을 사용해야 한다. 
      예를 들어서 조회한 Order 데이터가 1000건인데, V4 방식을 그대로 사용하면, 쿼리가 총 1 + 1000번 실행된다. 
      여기서 1은 Order 를 조회한 쿼리고, 1000은 조회된 Order의 row 수다. 
      V5 방식으로 최적화 하면 쿼리가 총 1 + 1번만 실행된다. 
      상황에 따라 다르겠지만 운영 환경에서 100배 이상의 성능 차이가 날 수 있다.

      V6는 완전히 다른 접근방식이다. 
      쿼리 한번으로 최적화 되어서 상당히 좋아보이지만, Order를 기준으로 페이징이 불가능하다. 
      실무에서는 이정도 데이터면 수백이나, 수천건 단위로 페이징 처리가 꼭 필요하므로, 이 경우 선택하기 어려운 방법이다. 
      그리고 데이터가 많으면 중복 전송이 증가해서 V5와 비교해서 성능 차이도 미비하다.  </code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[JPA] API 개발 고급 - 지연로딩(LAZY)과 조회 성능 최적화]]></title>
            <link>https://velog.io/@jun_0y/JPA-API-%EA%B0%9C%EB%B0%9C-%EA%B3%A0%EA%B8%89-%EC%A7%80%EC%97%B0%EB%A1%9C%EB%94%A9LAZY%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/@jun_0y/JPA-API-%EA%B0%9C%EB%B0%9C-%EA%B3%A0%EA%B8%89-%EC%A7%80%EC%97%B0%EB%A1%9C%EB%94%A9LAZY%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>Thu, 03 Nov 2022 08:00:34 GMT</pubDate>
            <description><![CDATA[<h3 id="주문-조회-api">주문 조회 API</h3>
<p>최적화 파트는 매우매우매우매우 중요하니 100% 이해하고 넘어가자</p>
<p>내용이 많아서 길어질텐데 꼭 다 읽고 이해하기!!</p>
<p>시작!</p>
<p>🧙 :
주문 조회 API를 만들건데 이제 배송정보, 회원 정보까지 들어있는.. 
API를 만들면서 <code>지연 로딩(LAZY)</code> 때문에 발생하는 성능 문제를 해결해나갈거야</p>
<p>API Controller의 이름은 <code>OrderSimpleApiController</code>.
여기서는 <code>xToOne</code>의 관계(OneToOne, ManyToOne)만 다룰거야.</p>
<ul>
<li>OrderSimpleApiController<pre><code class="language-java">package jpabook.jpashop.api;
</code></pre>
</li>
</ul>
<p>@RestController
@RequiredArgsConstructor
public class OrderSimpleApiController {</p>
<pre><code>private final OrderRepository orderRepository;
private final OrderSimpleQueryRepository orderSimpleQueryRepository;</code></pre><p>}</p>
<pre><code>
![](https://velog.velcdn.com/images/jun_0y/post/99cf7136-e8b2-44fc-84ac-f2af563b4ea0/image.png)


---

### version_1 : 엔티티를 직접 조회

🧙 : 
결론부터 말하자면 version_1의 방법(엔티티를 직접 노출하는 방법)은 쓰면 안돼.

왜 안되는지는 [저번 포스팅](https://velog.io/@jun_0y/JPA-API-%EA%B0%9C%EB%B0%9C-%EA%B8%B0%EB%B3%B8)에도 있고 지금 더 자세히 알아보자!!

#### 엔티티를 직접 조회
```java
    @GetMapping(&quot;/api/v1/simple-orders&quot;)
    public List&lt;Order&gt; ordersV1(){
        List&lt;Order&gt; all = orderRepository.findAllByString((new OrderSearch()));
        return all;
    }</code></pre><p>🤠 : orderRepository에서 findAllByString() 메서드를 사용하여 객체를 파라미터로 받아요
<br></p>
<p>🧙 : 
지금 우리가 예제로 가지고 있는 Order, Member, Delivery를 보면
<code>Order —&gt; Member 와 Order —&gt; Delivery의 관계</code>를 가지고 있고 둘 다 <code>지연로딩(LAZY)</code>이야.</p>
<ul>
<li><p>Order —&gt; Member / Order —&gt; Delivery : 이건 <a href="https://velog.io/@jun_0y/JPA-%EB%8F%84%EB%A9%94%EC%9D%B8-%EB%B6%84%EC%84%9D-%EC%84%A4%EA%B3%84">도메인 분석 설계</a>에서 공부한건데 Order 개체에 속성으로 Member와 Delivery가 있어 
Order : Member = 1 : N  ➡️ ManyToOne
Order : Delivery = 1 : 1 ➡️ OneToOne</p>
</li>
<li><p>지연 로딩 : 지연로딩은 <a href="https://velog.io/@jun_0y/JPA-%EC%97%94%ED%8B%B0%ED%8B%B0-%ED%81%B4%EB%9E%98%EC%8A%A4-%EA%B0%9C%EB%B0%9C-12">엔티티 클래스 개발</a> 과 <a href="https://velog.io/@jun_0y/JPA-%EC%97%94%ED%8B%B0%ED%8B%B0-%EC%84%A4%EA%B3%84%EC%8B%9C-%EC%A3%BC%EC%9D%98%ED%95%A0-%EC%A0%90">엔티티 설계시 주의할 점</a>에서 공부한 내용인데 간단하게 복습하자면
<code>xToOne</code>인 애들은 기본 <code>fetch</code>가 <code>EAGER</code> 야.
EAGER에는 문제가 있는데 
하나의 개체(Order)만 조회하고 싶은데 하나의 개체를 조회하면 그 개체(Order)를 물고있는 애들(Member, Delivery)이 줄줄이 딸려와.
Order만 조회하고 싶어도 쓸데없이 Member와 Delivery를 조회하는 쿼리문이 작성돼서 성능이 안좋아지는거야. 
이렇게 하나의 개체를 호출했는데 애를 물고있는 애들이 줄줄이 따라오는걸 막기위한 전략이 <code>지연로딩(LAZY)</code> 이라는 거지!</p>
</li>
</ul>
<hr>
<p>🧐 : 지연로딩(LAZY)인게 뭐 어떻다고?</p>
<p> 🧙 :
 <code>지연로딩인 상황</code>에서 <code>엔티티를 직접 노출</code>해서 사용하는 경우 생기는 문제가 있는데</p>
<ul>
<li>양방향 관계 문제 발생 : 
Order - Member , Order - Delivery는 <code>양방향 관계</code>라 서로를 물고있어 
( <code>양방향 관계</code> - <a href="https://velog.io/@jun_0y/JPA-%EB%8F%84%EB%A9%94%EC%9D%B8-%EB%B6%84%EC%84%9D-%EC%84%A4%EA%B3%84">도메인 분석 설계</a> 참고 )
이 상황에서 쿼리문으로 개체를 호출하면??
서로가 서로를 계속 타고 넘어가면서 무한루프가 발생!!</li>
</ul>
<p>🧐 : 그럼 어떡해??</p>
<p> 🧙 
한쪽에 <code>@JsonIgnore</code>을 줘서 이 상황을 막아줘야돼. 
이번 예제에선 Member, Delivery, OrderItem 에 <code>@JsonIgnore</code>를 붙여줘야돼</p>
<blockquote>
<p>엔티티를 직접 노출할 때 양방향 관계가 걸린 곳은 한쪽에 <code>@JsonIgnore</code> 해줘야한다!!</p>
</blockquote>
<p>무한루프를 막았다고 끝난게 아니야!!</p>
<p>Order 개체를 조회한다고 생각해보자.</p>
<p>근데 Order에는 Member 속성이 있고 얘의 fetch 타입은 <code>LAZY</code>야. 
LAZY는 진짜 Member개체를 가져오는게 아니야!</p>
<p>위에서 <code>지연로딩(LAZY)</code>은 하나의 개체를 조회 할 때 얘를 물고있는 애들이 줄줄이 안딸려오게 하기 위해서 쓰는 거라고 했지?? </p>
<p>어떻게 이게 가능하냐면 애초에 디비에서 안가져와. 
LAZY. 게으른. 즉, 귀찮아서 안가져오는거야.</p>
<p>DB에서는 내가 조회한(주최가되는) 개체인 Order만 가져오는거지.</p>
<hr>
<p>🧐 : 그럼 그 안가져온 애들(Member, Delivery)은 어떻게 처리하는데?</p>
<p> 🧙 :
이때 나오는게 <code>Proxy</code>라는 개념인데 <code>fetch=LAZY</code>인 애들한테 임시 값인 <code>ByteBuddyIntercept</code>을 넣어놓는 거지.
이걸 <code>프록시 객체</code> 라고 하나봐..</p>
<p>그리고 마지막에 이 프록시 객체(ByteBuddyIntercept)를 어찌저찌해서 가져오나 본데 
아무튼 jackson 라이브러리는 이 프록시 객체를 json으로 어떻게 생성해야되는지 몰라.</p>
<p>이렇게 처리되기 때문에
Member나 Delivery같이 fetch 타입이 LAZY인 객체들의 값도 필요한 상황에서는 Order를 조회함과 동시에 Member 객체를 읽으려고하면 객체가 아니라 무슨 이상한 byteBuddy 뭐시기가 들어있으니까 오류가 발생해</p>
<hr>
<p>🧐 : 그럼  LAZY를 다시 EAGER로 바꾸면 되는거 아냐?</p>
<p> 🧙 :
❌ 이건 절대 안돼 ❌ 
필요없는 데이터 싹다 끌고오고, 다른 API에서도 문제생겨. 그냥 하지마.</p>
<blockquote>
<p>❗️ 무조건 지연로딩(LAZY) 가 기본❗️</p>
</blockquote>
<ul>
<li>Lazy를 디폴트로 하고 필요한것만 <code>fetch join</code> 이용하자</li>
</ul>
<p>fetch join은 V3에서 배울거야</p>
<hr>
<p>🧐 : 그럼 뭐 어떡하라고..</p>
<p> 🧙 
이렇게 지연 로딩인 경우에는 </p>
<ul>
<li><code>Hibernate5Module</code>을 이용 :</li>
</ul>
<ol>
<li>builde.gradle에서 라이브러리 추가해주고
<code>implementation &#39;com.fasterxml.jackson.datatype:jackson-datatype-hibernate5&#39;</code></li>
<li>@Bean 이용해서 hibernate5Module을 스프링 빈에 등록하면</li>
</ol>
<pre><code class="language-java">package jpabook.jpashop;

@SpringBootApplication
public class JpashopApplication {

    ...

    // OrderSimpleApiController v1을 위한 hibernate5Module 사용
    @Bean
    Hibernate5Module hibernate5Module(){
        Hibernate5Module hibernate5Module = new Hibernate5Module();
        // FORCE_LAZY_LOADING 옵션
        hibernate5Module.configure(Hibernate5Module.Feature.FORCE_LAZY_LOADING, true);
        return hibernate5Module();
    }

}</code></pre>
<ol start="3">
<li>hibernate에 옵션(Force Lazy Loading)을 추가해서 원하는 데이터를 뽑아올 수 있어.</li>
</ol>
<p>또다른 방법으로는
<code>.get</code>을 이용해서 원하는 데이터 조회하는 방법이 있는데</p>
<pre><code class="language-java">    @GetMapping(&quot;/api/v1/simple-orders&quot;)
      public List&lt;Order&gt; ordersV1() {
          List&lt;Order&gt; all = orderRepository.findAllByString(new OrderSearch());

          **************************************************
          ****** 이 부분이 get 이용하여 원하는 데이터 조회하기! ******

          for (Order order : all) {
            order.getMember().getName(); //Lazy 강제 초기화
            order.getDelivery().getAddress(); //Lazy 강제 초기화
          }

          **************************************************
          return all; 

    }</code></pre>
<p>이렇게 하면
fetch가 LAZY여서 아직 해당 객체의 데이터 안가져왔는데 <code>.get</code>으로 데이터를 가져오라는 명령어를 받으면 LAZY가 강제 초기화돼!</p>
<blockquote>
<p>BUT, 다시 한번 말하지만 엔티티를 외부로 노출하는 방법은 쓰지마!</p>
</blockquote>
<p><code>Hibernate5Module</code>, <code>.get</code> 을 쓰기보다는 엔티티를 <code>DTO 변환</code>해서 반환하는게 더 좋은 방법!!</p>
<hr>
<h3 id="version_2--엔티티를-dto로-변환">version_2 : 엔티티를 DTO로 변환</h3>
<p>🧙 :
이제 위에서 노래를 부르던 엔티티를 DTO로 변환하는 방법을 알아보자!</p>
<h4 id="엔티티를-dto로-변환하는-코드">엔티티를 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.findAllByString(new OrderSearch());
        List&lt;SimpleOrderDto&gt; result = orders.stream()
                .map(o -&gt; new SimpleOrderDto(o))
                .collect(Collectors.toList());

        return result;
    }

    // DTO 생성
    @Data
    static class SimpleOrderDto {

        private Long orderId;
        private String name;
        private LocalDateTime orderDate; // 주문시간
        private OrderStatus orderStatus;
        private Address address;

        // DTO가 엔티티를 파라미터로 받는건 괜찮다
        public SimpleOrderDto(Order order) {
            orderId = order.getId();
            name = order.getMember().getName();         // 여기서 LAZY 초기화
            orderDate = order.getOrderDate();
            orderStatus = order.getStatus();
            address = order.getDelivery().getAddress(); // 여기서 LAZY 초기화
        }
    }</code></pre>
<p>🤠 : 간단한 코드 설명</p>
<pre><code>* API 스펙에 맞는 DTO(OrderSimpleDto)를 만들고(with. @Data)
* orderRepository에서 정의된 findAll( )사용해서 모든 주문 객체를 조회해서 List&lt;Order&gt;에 넣고
* 그 List&lt;Order&gt;에 있는 애들을 List&lt;OrderSimpleDto&gt;에 옮겨주면 
* map을 사용해서 List&lt;OrderSimpleDto&gt; 안에 있는 애들을 하나씩 불러내면서 출력해주는 로직
* OrderSampleDto에서는 변수 선언하고 생성자에서 변수 초기화 해주면 끝!
🧐 : 엔티티 외부로 노출하지 (엔티티를 직접적으로 파라미터로 받지) 말라며?!
* DTO에선 엔티티 외부로 노출돼도 괜찮아~~!!</code></pre><br>

<p>🧙 : 
이 방법을 쓰면 엔티티를 외부로 노출 하지 않을 수 있지만 <code>N + 1 문제</code>라는 성능적 측면에서의 문제가 생겨. (쿼리 수가 v1이랑 똑같다)</p>
<br>

<blockquote>
<h4 id="n--1-문제란">N + 1 문제란?</h4>
</blockquote>
<p>userA는 [Book_A &amp; Book_B]를 주문했고,  userB는 [Book_C &amp; Book_D]를 주문한 상황
주문 조회(Orders)에는 Member, Delivery가 엮여있다. 이 때 주문 조회를 하면?? 
Orders 를 호출하는 쿼리(조회의 주최가되는 객체),
userA를 확인하기 위해서 Member 호출하는 쿼리, &amp; userA의 주소를 확인하기 위해서 Delivery를 호출하는 쿼리
userB를 확인하기 위해서 Member 호출하는 쿼리, &amp; userA의 주소를 확인하기 위해서 Delivery를 호출하는 쿼리</p>
<p>위에서 쿼리라는 말이 몇번나왔지?? —&gt; Member 2개, Delivery 2개, Order 1개. 총 다섯번
이게 바로 N + 1 문제라는 건데  물고있는게 많고, 주문수가 많을 수록 점점 더 커지겠지??</p>
<blockquote>
</blockquote>
<p>N + 1 문제 : 
하나의 쿼리를 날리는데 조회되는 결과의 개수만큼 쿼리가 추가적으로 나오는 것</p>
<p>위의 상황에서 최악의 경우 N(2) + N(2) + 1 번 호출된다!</p>
<p>🧐 : 최악이 아닌 경우는 뭐야?</p>
<p>🧙 : 
최악이 아닌경우는 userA가 [Book_A &amp;BookB] 와 [Book_C, Book_D] 를 주문한 경우인데
이런 경우는 Member 조회를 한번만 해도 되기 때문에(주문한 사람이 한명이니까) 최악이 아니야.
이게 가능한 이유는 지연로딩은 영속성 컨텍스트에서 조회 하므로 이미 조회된 경우 쿼리를 생략한다. 라고하네.</p>
<hr>
<h3 id="version_3--엔티티를-dto로-변환--fetch-join">version_3 : 엔티티를 DTO로 변환 &amp; Fetch Join</h3>
<p>🧐 : 뭐야 그럼 DTO로 변환해도 엔티티를 외부로 노출시키지만 않았지 성능은 완전 꽝이잖아?? </p>
<p>🧙 : 
DTO로 변환하면 맞아. 하지만 <code>Fetch Join</code>이란걸 사용하면 <code>N + 1 문제</code> 해결할 수 있어!!</p>
<br>

<blockquote>
<h4 id="fetch-join이란">Fetch Join이란?</h4>
</blockquote>
<p>간단히 말해서 조회의 주체가 되는 객체 이외에 Fetch Join이 걸린 연관 엔티티도 함께 SELECT 하여 모두 영속화하는것이 Fetch Join이다.</p>
<p>자세한 설명은 <a href="https://cobbybb.tistory.com/18">일반 Join과 Fetch Join의 차이</a>, <a href="https://velog.io/@heoseungyeon/Fetch-Join-vs-%EC%9D%BC%EB%B0%98-Joinfeat.DTO">Fetch Join vs 일반 Join(feat.DTO)</a>를 참고하자
<br></p>
<p>🧙 : 
이렇게 Fetch Join을 사용하면 쿼리 1번에 조회가 가능해져!!</p>
<h4 id="fetch-join-사용하여-엔티티를-dto로-변환하는-코드">Fetch Join 사용하여 엔티티를 DTO로 변환하는 코드</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(Collectors.toList());

        return result;
    }</code></pre>
<p>🤠 : 간단한 코드 설명</p>
<pre><code>* version_2에서 만든  OrderSimpleDto 객체를 이용
* orderRepository에서 모든 주문 객체를 조회해서 List에 넣을건데
V2에서는 findAll()을 사용해서 객체를 조회한 뒤 List에 넣었잖아?
* V3에서는 fetch join을 적용한 findAllWithMemberDelivery( ) 를 사용해서 객체를 조회한뒤 Lsit에 넣을거야.
* 그러기 위해서는 findAllWithMemberDelivery( ) 를 만들어주고
* orderRepository에 있는 findAllWithMemberDelivery()사용해서 모든 주문 객체를 조회해서 List&lt;Order&gt;에 넣고
* List&lt;Order&gt;에 있는 객체들을 List&lt;OrderSimpleDto&gt;에 옮겨주면
* map으로 List&lt;OrderSimpleDto&gt; 안에 있는 애들을 하나씩 조회!!
</code></pre><br>

<h4 id="findallwithmemberdelivery-메서드-정의-in-orderrepository"><code>findAllWithMemberDelivery()</code> 메서드 정의 (in. orderRepository)</h4>
<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>
<p>🤠 : 간단한 코드 설명.. 할거는 딱히 없고 이거는 그냥 sql 문이니까.. 공부하세용</p>
<br>

<hr>
<br>

<h3 id="version_4--jpa에서-dto로-바로-조회">version_4 : JPA에서 DTO로 바로 조회</h3>
<p>🧐 : 근데 v2도 그렇고 v3도 그렇고 뭔가 일을 두번 하고 있는 것 같은데??</p>
<p>🧙 : 
v2,v3 모두 엔티티를 DTO로 바꿔주고 있어.
OrderRepository에서 엔티티로 조회한걸 다시 DTO에 넣어서 조회하고 있잖아??</p>
<p><code>엔티티 ➡️ DTO 과정</code>을 없애고 바로 JPA에서 DTO로 바로 조회 할 수 있는 방법이 있는데 그게 바로 지금부터 배울 내용이야</p>
<p>v2,3에서는 <code>(entity)Repository</code>에서 <code>findAllWithMemberDelivery()</code>를 통해 <code>엔티티를 조회</code> 했는데 
v4에서는 <code>DtoRepository</code>에서 <code>findDtos( )</code>를 통해 <code>DTO를 조회</code>하려고해</p>
<ul>
<li>DtoRepository : <code>OrderSimpleQueryRepository</code></li>
<li>findDtos() : <code>findOrderDtos( )</code></li>
</ul>
<p>를 만들자!!</p>
<p><code>OrderSimpleQueryRepository</code> &amp;<code>findOrderDtos( )</code>는 오로지 API에서 조회를 위해서만 사용하는 리포지토리와 메서드이기 때문에 엔티티를 조회할 수 있는 리포지토리와 메서드랑은 성격이 달라서 따로 빼주는게 좋아</p>
<p><code>repository 패키지</code>에서 <code>orderSimpleQuery 패키지</code>를 만들고 여기에 
<code>OrderSimpleQueryRepository</code> 리포지토리와 
DTO인 <code>OrderSimpleQueryDto</code>를 만들자</p>
<blockquote>
<p>결론 : <code>OrderSimpleQueryRepository</code> &amp; <code>findOrderDtos( )</code> 와 <code>OrderSimpleQueryDto</code> 추가</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/jun_0y/post/3920d02b-f239-4cff-b88d-275367ec17c3/image.png" alt=""></p>
<br>

<h4 id="jpa가-직접-조회할-dto-생성--ordersimplequerydto">JPA가 직접 조회할 DTO 생성 : <code>OrderSimpleQueryDto</code></h4>
<pre><code class="language-java">package jpabook.jpashop.repository.order.simplequery;

@Data
public class OrderSimpleQueryDto {

    private Long orderId;
    private String name;
    private LocalDateTime orderDate; //주문시간 private OrderStatus orderStatus;
    private OrderStatus orderStatus;
    private Address address;

    // DTO가 엔티티를 파라미터로 받는게 아니라 받아올 파라미터를 일일히 열거해주어야 한다
    public OrderSimpleQueryDto(Long orderId, String name, LocalDateTime
            orderDate, OrderStatus orderStatus, Address address) {
        this.orderId = orderId;
        this.name = name;
        this.orderDate = orderDate;
        this.orderStatus = orderStatus;
        this.address = address;
    }
}
</code></pre>
<p>🤠 : 간단한 코드 설명</p>
<pre><code>* 기본적인 틀은 v2,v3에서 사용한 DTO와 똑같아요
* 차이점은 생성자에 파라미터를 엔티티로 받는것이 아니라 받아올 데이터 하나하나 가져와야 해요</code></pre><br>

<h4 id="dtorepository인-ordersimplequeryrepository">DtoRepository인 &#39;OrderSimpleQueryRepository&#39;</h4>
<pre><code class="language-java">package jpabook.jpashop.repository.order.simplequery;

@Repository
@RequiredArgsConstructor
public class OrderSimpleQueryRepository {

    private final EntityManager em;


}</code></pre>
<p>🤠 : 리포지토리이기 때문에 EntityManger 하나 생성해주세용
<br></p>
<h4 id="객체-조회를-도와줄-findorderdtos--메서드">객체 조회를 도와줄 &#39;findOrderDtos( )&#39; 메서드</h4>
<pre><code class="language-java">    public List&lt;OrderSimpleQueryDto&gt; findOrderDtos() {
        return em.createQuery(
                &quot;select new jpabook.jpashop.repository.order.simplequery.OrderSimpleQueryDto(o.id, m.name,\n&quot; +
                        &quot;  o.orderDate, o.status, d.address) &quot;+
                        &quot; from Order o&quot; +
                        &quot; join o.member m&quot;+
                        &quot; join o.delivery d&quot;, OrderSimpleQueryDto.class)
                .getResultList();
    }</code></pre>
<p>🤠 : 간단한 코드 설명</p>
<pre><code>* SQL문은 공부하세용
* v3에서는 엔티티를 조회한 후에 DTO에 넣는 과정이었기 때문에 SELECT 뒤에 객체(Orders)가 들어갔는데 
* 이제는 DTO를 직접 조회하는 것이기 때문에 OrderSimpleQueryDto를 넣어줄거야(경로까지 다 넣어줘야돼..? 그냥 OrderSimpleQueryDto 이렇게 넣으면 안돼..?)
* 이때 new 명령어를 사용해서 JPQL의 결과를 DTO로 즉시 변환
* v3에서와는 다르게 일반 join을 사용</code></pre><br>

<p>🧙 : 
이제 귀찮아지는게 </p>
<ol>
<li><p>DTO에서는 생성자에 파라미터를 엔티티로 받는것이 아니라 받아올 데이터 하나하나 가져와야하고</p>
</li>
<li><p>find( ) 메서드에서 v3에서는 엔티티를 가져온 뒤에 DTO로 감싸는 거였기 때문에 SELECT문에 객체(Order)가 들어가면 됐는데 
지금은 DTO를 직접 조회하는 것이기 때문에 new 명령어와 함께  OrderSimpleQueryDto를 경로까지 포함해서 넣어줘야돼</p>
</li>
</ol>
<p>귀찮다 =  유지보수 귀찮/어려워진다</p>
<p>아무튼 이렇게 findOrderDtos( ) 가 만들어졌으면 컨트롤러에서 
그냥 저 findOrderDtos( )를 불러오면 끝!!!</p>
<h4 id="contoller에서-findorderdtos-불러오기">Contoller에서 findOrderDtos() 불러오기</h4>
<pre><code class="language-java">    @GetMapping(&quot;/api/v4/simple-orders&quot;)
    public List&lt;OrderSimpleQueryDto&gt; ordersV4() {
        return orderSimpleQueryRepository.findOrderDtos();
    }</code></pre>
<p>🤠 : orderSimpleQueryRepository에서 작성한 findOrderDtos()메서드를 호출해요
<br></p>
<p>🧙 : 
v4같은 경우 SELECT절에서 원하는 데이터를 직접 선택하므로 성능 최적화가 이뤄지는데 사실 이 정도 성능은 무시해도 될 정도로 작다고해.
쿼리문의 성능을 결정 짓는건 그 밑에 있는 조인이나, 조건..?
그래도 select에서 뽑아낼 데이터가 엄청 많으면 이렇게 하는게 유의미 하겠지?! 
근데 이렇게 하면 <code>리포지토리의 재사용성이 떨어지고</code>, <code>API 스펙에 맞춘 코드가 리포지토리에 들어간다</code>는 단점이 있어서 v3와 v4 중 뭘 쓸지는 너가 선택할 사항 😉
<br></p>
<hr>
<br>

<h4 id="엔티티를-dto로-변환하기-vs-dto로-직접-받기">&#39;엔티티를 DTO로 변환하기&#39; vs &#39;DTO로 직접 받기&#39;</h4>
<p>엔티티를 DTO로 변환하거나, DTO로 바로 조회하는 두가지 방법은 각각 장단점이 있다.
둘중 상황에 따라서 더 나은 방법을 선택하면 된다.</p>
<ul>
<li>엔티티를 DTO로 변환하기 : 엔티티로 조회하면 리포지토리 재사용성도 좋고, 개발도 단순해진다</li>
<li>DTO로 직접 받기 : SELECT 절에서 원하는 데이터를 직접 선택하므로 최적화 중에 최적화</li>
</ul>
<br>

<h4 id="쿼리-방식-선택-권장-순서">쿼리 방식 선택 권장 순서</h4>
<ol>
<li>엔티티 외부로 노출 할 생각은 하지마라</li>
<li>엔티티를 DTO로 변환하는 방법 선택하고 필요하면 fetch join으로 성능 최적화(여기까지하면 대부분 성능 이슈 해결된다)</li>
<li>그래도 안되면 DTO로 직접 조회하는 방법 사용</li>
<li>이래도 안돼??? 최후의 방법은 JPA가 제공하는 네이티브 SQL이나 스프링 JDBC Template을 사용해서 SQL을 직접 사용한다.</li>
</ol>
<br>

<hr>
<br>

<h4 id="추가-공부할-것">추가 공부할 것</h4>
<p>🧐 : API Response 받을 때 배열로 받으면 안좋다고..?
🧐 : orderRepository에서 Controller쪽으로 의존관계가 생기면 큰일난다고..?</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JPA] API 개발 -  조회용 샘플 데이터 입력]]></title>
            <link>https://velog.io/@jun_0y/JPA-API-%EA%B0%9C%EB%B0%9C-%EC%A1%B0%ED%9A%8C%EC%9A%A9-%EC%83%98%ED%94%8C-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%9E%85%EB%A0%A5</link>
            <guid>https://velog.io/@jun_0y/JPA-API-%EA%B0%9C%EB%B0%9C-%EC%A1%B0%ED%9A%8C%EC%9A%A9-%EC%83%98%ED%94%8C-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%9E%85%EB%A0%A5</guid>
            <pubDate>Wed, 02 Nov 2022 09:16:17 GMT</pubDate>
            <description><![CDATA[<p>API의 성능 문제에 대해 알아보자</p>
<p>API 개발 할 때 등록/수정은 성능문제가 거의 없다.
주로 조회에서 문제가 발생하는데 최적화를 시킬 수 있는 방법이 뭐가 있는지 간단하게 보고 넘어가자</p>
<pre><code>- 지연 로딩과 조회 성능 최적화
- 컬렉션 조회 최적화
- 페이징과 한계 돌파
- OSIV와 성능 최적화</code></pre><ul>
<li>조회용 샘플 데이터 입력
서버 재접속 할 때 마다 매번 DB 넣어주기 귀찮으니 
서버 실행과 동시에 DB에 원하는 데이터를 초기화 하는 코드 작성<pre><code class="language-java">package jpabook.jpashop;
</code></pre>
</li>
</ul>
<p>@Component // 컴포넌트 스캔의 대상이됨
@RequiredArgsConstructor
public class InitDb {</p>
<pre><code>private final InitService initService;

@PostConstruct
public void init() {
    initService.dbInit1();
    initService.dbInit2();
}

@Component
@Transactional
@RequiredArgsConstructor
static class InitService {

    private final EntityManager em;

    public void dbInit1() {
        Member member = createMember(&quot;userA&quot;, &quot;서울&quot;, &quot;1&quot;, &quot;1111&quot;);
        em.persist(member);

        Book book1 = createBook(&quot;JPA1 BOOK&quot;, 10000, 100);
        em.persist(book1);

        Book book2 = createBook(&quot;JPA2 BOOK&quot;, 20000, 100);
        em.persist(book2);

        OrderItem orderItem1 = OrderItem.createOrderItem(book1, 10000, 1);
        OrderItem orderItem2 = OrderItem.createOrderItem(book2, 20000, 2);

        Delivery delivery = createDelivery(member);
        Order order = Order.createOrder(member, delivery, orderItem1, orderItem2);
        em.persist(order);

    }

    public void dbInit2() {
        Member member = createMember(&quot;userB&quot;, &quot;진주&quot;, &quot;2&quot;, &quot;2222&quot;);
        em.persist(member);

        Book book1 = createBook(&quot;SPRING1 BOOK&quot;, 20000, 200);
        em.persist(book1);

        Book book2 = createBook(&quot;SPRING2 BOOK&quot;, 40000, 300);
        em.persist(book2);

        OrderItem orderItem1 = OrderItem.createOrderItem(book1, 20000, 3);
        OrderItem orderItem2 = OrderItem.createOrderItem(book2, 40000, 4);

        Delivery delivery = createDelivery(member);
        Order order = Order.createOrder(member, delivery, orderItem1, orderItem2);
        em.persist(order);

    }

    // 회원(Member) 생성 함수 : 
    private Member createMember(String name, String city, String street, String zipcode) {
        Member member = new Member();
        member.setName(name);
        member.setAddress(new Address(city, street, zipcode));
        return member;
    }

    // 상품(Book) 생성 함수
    private Book createBook(String name, int price, int stockQuantity) {
        Book book1 = new Book();
        book1.setName(name);
        book1.setPrice(price);
        book1.setStockQuantity(stockQuantity);
        return book1;
    }

    // 배송지 정보 생성 함수 
    private Delivery createDelivery(Member member) {
        Delivery delivery = new Delivery();
        delivery.setAddress(member.getAddress());
        return delivery;
    }
}</code></pre><p>}</p>
<pre><code>
설명을 좀 하자면

- `@Component` : DB의 정보를 스프링 빈의 스캔 대상이 되게 하기 위함
- `@PostConstruct` : 말그대로 Construct 이전에 실행시키세요. DB니까 먼저 초기화 시켜야지
- @PostConstruct로 `init()`이 먼저 실행 될건데 init( ) 함수에는 dbInit1( )과 dbInit2( )가 있다.
dbInit1,2를 굳이 따로 함수로 만들어서 init()에 넣는 이유는... 뭐 그런게 있대. 싸이클이 어쨌다나.. 

- dbInit1,2가 담길 `class InitService` 를 선언하고 `initService`를 생성해서 init()에 넣어준다.

- `class InitService`
: createMember( ), createBook( ), createDelivery( )를 선언하고 dbInit( )에서 생성해준다.

끝~~
</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[JPA] API 개발 - 기본]]></title>
            <link>https://velog.io/@jun_0y/JPA-API-%EA%B0%9C%EB%B0%9C-%EA%B8%B0%EB%B3%B8</link>
            <guid>https://velog.io/@jun_0y/JPA-API-%EA%B0%9C%EB%B0%9C-%EA%B8%B0%EB%B3%B8</guid>
            <pubDate>Wed, 02 Nov 2022 08:45:31 GMT</pubDate>
            <description><![CDATA[<p>실무에서는 <code>기능개발</code>문제를 넘어서 <code>기술 개발</code>문제가 더 중요해진다.
처리할 데이터가 많은 실무에서는 성능, 유지 보수를 만족하는 <code>기술 개발</code>을 하는 것이 어렵다!!</p>
<h3 id="기능개발과-기술개발">기능개발과 기술개발</h3>
<p>이번 강의에서는 <code>API 개발</code>과 <code>성능 최적화</code> 이 두가지에 초점을 맞춘 강의가 될 것 같다.</p>
<blockquote>
</blockquote>
<ul>
<li>REST API 개발</li>
<li>성능 최적화</li>
</ul>
<hr>
<p>먼저 API에 대해 알아보자</p>
<blockquote>
</blockquote>
<p>API 란?</p>
<p>간단하게 말하자면 API는 두 소프트웨어가 서로 <code>통신</code>할 수 있게 하는 메커니즘이다.
API를 일종의 함수라고 보면 서로 다른 두 소프트웨어(클라이언트, 서버)가 <code>통신</code>을 하기 위해 API라는 함수를 이용하는 것이다!</p>
<p>지금까지는 템플릿 엔진(thymleaf)를 이용하여 서버에서 이것 저것 렌더링 해주는 방식을 사용했다.
API를 이용하면 클라이언트가 서버에 <code>Request</code>를 보내면 서버가 클라이언트에 <code>Response</code> 해주는 방식이 사용된다. </p>
<h4 id="한마디로-통신을-위한-함수라고-정리할-수-있다">&quot;한마디로 통신을 위한 함수라고 정리할 수 있다!!&quot;</h4>
<p>API에 대한 자세한 설명은 <a href="https://aws.amazon.com/ko/what-is/api/">여기에 들어가보자</a></p>
<p>JPA는 엔티티 개념이 있기 때문에 지금까지 해왔던 API와 다른점이 있다고 하니 주의하며 공부하자!!</p>
<blockquote>
<p>REST API 란?</p>
</blockquote>
<p>API의 작동 방식 중 하나로
클라이언트와 서버는 HTTP를 사용하여 데이터를 교환하는 방식을 사용한다.
HTTP의 편리성을 최대한 이용한 API 방식이라고 보면 될 것 같다.
REST는 GET, PUT, DELETE 등의 함수 집합을 정의한다.</p>
<h4 id="한마디로-서버와-클라이언트가-get-put-delete등을-사용하여-http로-통신하는-방식이라고-정리할-수-있다">&quot;한마디로 서버와 클라이언트가 GET, PUT, DELETE등을 사용하여 HTTP로 통신하는 방식이라고 정리할 수 있다!!&quot;</h4>
<p>REST API에 대한 자세한 설명은 <a href="https://meetup.toast.com/posts/92">여기에 들어가보자</a></p>
<hr>
<h2 id="api-개발">API 개발</h2>
<p>API와 템플릿 엔진을 이용한 VIEW는 공통적으로 처리하는게 서로 다르기 때문에
API를 개발할 때는 템플릿 엔진을 사용하여 렌더링하는 컨트롤러와 API 스타일 컨트롤러를 분리하는게 좋다.</p>
<ul>
<li><p>API Controller 생성</p>
<pre><code class="language-java">@RestController    // @Controller + @ResponseBody
@RequiredArgsConstructor
public class MemberApiController {

  private final MemberService memberService;

  /** 여기에 클래스 및 함수 작성 **/
</code></pre>
</li>
</ul>
<p>}</p>
<pre><code>`@RestController`는 `@Controller` 와 `@ResponseBody`의 기능을 둘다 가지고 있는 친구이다
 &lt;br&gt; &lt;br&gt;

### 회원 등록 API
version_1(v1) 과 version_2(v2) 두가지 방법을 사용해서 회원 등록을 처리하는 API를 구현할 것이다
v1의 방식으로 하면 안되니까 v2까지 만들었겠지??

* version_1 : 엔티티를 파라미터로 받는 방식
```java
    private final MemberService memberService;
    /**
     *   회원 등록 API v1 : 엔티티를 파라미터로 받아서 사용
     *   결론 : 이렇게 쓰면안됨
     */
    @PostMapping(&quot;/api/v1/members&quot;)

    public CreateMemberResponse saveMemberV1(@RequestBody @Valid Member member) {
        Long id = memberService.join(member);
        return new CreateMemberResponse(id);
    }

    @Data
    static class CreateMemberRequest {
        private String name;
    }
</code></pre><ol>
<li><p>회원 등록 요청과 응답을 위한 <code>CreateMemberRequest()</code> &amp; <code>CreateMemberResponse()</code> 클래스를 만들고 함수를 정의</p>
</li>
<li><p><code>@RequestBody</code> : json 데이터를 처리해주는 친구. json으로 온 body를 그대로 매핑해서 넣어준다.</p>
</li>
<li><p><code>@Valid</code> : 데이터가 유효한지 검증해주는 친구. javax.validation을 검증한다</p>
</li>
</ol>
<h4 id="v1은-엔티티member를-파라미터로-받아서-사용한-경우이다">v1은 엔티티(Member)를 파라미터로 받아서 사용한 경우이다.</h4>
<p>이게 왜 문제냐?!</p>
<pre><code>API에 맞추기 위해 엔티티를 수정하면 API 스펙 자체가 변한다.
엔티티는 여러곳에서 쓰이기 때문에 바뀔 확률이 높은데 
API가 엔티티를 그대로 받아서 사용하면 엔티티가 바뀔 때 마다 API 스펙이 바뀌어 버려서 오류가난다.</code></pre><p>API 스펙이 바뀌어?? 무슨소리지</p>
<pre><code>예를 들어줄게
v1 버전을 통해 회원 가입을 할 때 이름에 아무 값도 안넣고(NULL) 회원가입을 하면 성공적으로 회원가입이 완료된다.
이름이 null인게 마음에 안들어!
회원가입시 이름 입력은 필수로 하기 위해서 Member 엔티티에 들어가서 name 값에 &#39;@NotEmpty&#39;를 붙여주면  
&#39;@Valid&#39;가 검증을 해주기 때문에 이름이 null이면 회원 가입 에러!!

이러면 API를 위해서 엔티티가 수정 돼버린상황..

어떤 API는 @NonEmpty가 필요할수도 있지만 다른 API는 필요하지 않을수도 있다</code></pre><p>그럼 어떡해??</p>
<blockquote>
<p>API 스펙을 위한 별도의 DTO(Data Transfer Object)를 만들어주자!!!
--&gt; API 스펙에 맞춰 별도의 파라미터를 생성해야한다.</p>
</blockquote>
<p>이 방식을 사용한게 바로 v2.
DTO를 만들기 위해 <code>@Data</code>를 붙여주고 API 스펙에 맞는 클래스 생성!!</p>
<ul>
<li>version_2 : API스펙을 위한 DTO 생성</li>
</ul>
<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);
    }

    // DTO 생성
    @Data
    static class CreateMemberRequest {
        private String name;
    }

    @Data
    static class CreateMemberResponse {
        public CreateMemberResponse(Long id) {
            this.id = id;
        }

        private Long id;
    }</code></pre>
<p> <br> <br></p>
<h3 id="회원-조회-api">회원 조회 API</h3>
<p>회원 등록과 마찬가지로 version_1(v1) 과 version_2(v2) 두가지 방법을 사용해서 회원 조회를 처리하는 API를 구현할 것이다
v1의 방식으로 하면 안되니까 v2까지 만들었다.</p>
<ul>
<li><p>version_1 : 엔티티가 노출되는 방식</p>
<pre><code class="language-java">  @GetMapping(&quot;/api/v1/members&quot;)
  public List&lt;Member&gt; MemberV1() { return memberService.findMembers(); }</code></pre>
<p>와.. 간단 그 자체..!! 
이지만,  회원 등록에서와 같이 회원 조회에서도 엔티티를 직접 쓰면 안된다.</p>
<p>  엔티티를 외부에 노출해선 안된다고 표현하는데, 회원 정보를 가져오는데 엔티티를 직접 노출해서 가져오면 문제가 생긴다.</p>
</li>
</ul>
<p>어떤문제??</p>
<pre><code>관련된 모든 정보를 가져오는 문제!! join된 모든 정보를 가져와버린다</code></pre><p>이게 왜 문제야??</p>
<pre><code>예를 들어줄게
회원 정보만 조회 할건데 엔티티와 관련된 모든 정보를 가져와
쓸데없이 많은 데이터를 가져오는 상황이 발생하는거지
또 노출되면 안되는 정보까지 가져와버린다(패스워드, 개인정보, 등...)</code></pre><p>회원 등록때와 마찬가지로 이걸 막기 위해서 Member 엔티티에서 join된 애한테 @JsonIgnore를 붙여주면 해결 되긴해
근데 이러면 위에서와 마찬가지로 엔티티를 수정하는 것이기 때문에 스펙 자체가 바뀌게된다</p>
<p>이러면 다른 API를 만들때 문제가 된다~~ 이말이지.
또 @JsonIgnore은 화면에 표시하기위한 로직이잖아. 이런 로직은 지양한다.</p>
<p>그럼 어떡해??</p>
<blockquote>
<p>API 스펙을 위한 별도의 DTO(Data Transfer Object)를 만들어주자!!!
--&gt; API 스펙에 맞춰 별도의 파라미터를 생성해야한다.</p>
</blockquote>
<p>이 방식을 사용한게 바로 v2.
DTO를 만들기 위해 <code>@Data</code>를 붙여주고 API 스펙에 맞는 클래스 생성!!</p>
<ul>
<li><p>version_2 : API스펙을 위한 DTO 생성</p>
<pre><code class="language-java">  @GetMapping(&quot;/api/v2/members&quot;)
  public Result memberV2() {
      List&lt;Member&gt; findMembers = memberService.findMembers();
      List&lt;MemberDto&gt; collect = findMembers.stream()
              .map(m -&gt; new MemberDto(m.getName()))
              .collect(Collectors.toList());

      return new Result(collect.size(), collect);
      //return new Result(collect);

  }

  @Data
  @AllArgsConstructor
  static class Result&lt;T&gt; {
      private int count;
      private T data;
  }

  @Data
  @AllArgsConstructor
  static class MemberDto {
      private String name;
  }</code></pre>
<p>회원 등록과 마찬가지로 DTO를 만들어서 엔티티가 노출되지 않게 한다.</p>
<br>
</li>
<li><p>수정 API</p>
</li>
</ul>
<p>저번 포스트에서 수정의 방법에는 <code>변경 감지</code>와 <code>병합(merge)</code>이 있다는 것을 공부했고 우리는 <code>변경 감지</code>를 사용하기로 했다!!</p>
<pre><code class="language-java">@PutMapping(&quot;/api/v2/members/{id}&quot;)
    public UpdateMemberResponse updateMemberV2(
            @PathVariable(&quot;id&quot;) Long id,    // url에  {id}부분 값을 넘겨주는 역할
            @RequestBody @Valid UpdateMemberRequest request) {

        // 수정할 떈 변경감지!
        // 커맨드와 쿼리를 분리해서 사용
        memberService.update(id, request.getName());
        Member findMember = memberService.findOne(id);
        return new UpdateMemberResponse(findMember.getId(), findMember.getName());

    }

    @Data
    static class UpdateMemberRequest {
        private String name;
    }

    @Data
    @AllArgsConstructor
    static class UpdateMemberResponse {
        private Long id;
        private String name;
    }</code></pre>
<p>수정도 등록,조회와 마찬가지로 DTO를 생성해서 파라미터에 매핑해야 한다.
한가지 더 짚고 넘어갈 만한 것은 url에 <code>{id}</code>를 넣고     <code>@PathVariable(&quot;id&quot;)</code>을 이용해 id값을 넘겨받는것 정도??</p>
<ul>
<li>참고
REST 함수에서 수정의 방법에는 <code>POST</code> / <code>PUT</code> / <code>PATCH</code> 가 있는데 
<code>POST</code>, <code>PATCH</code>는 부분 수정.
<code>PUT</code>은 전체 수정이다.</li>
</ul>
<h3 id="결론">결론</h3>
<blockquote>
</blockquote>
<p>API를 만들때 엔티티를 파라미터로 받지도 말고 엔티티를 외부에 노출해서도 안된다</p>
<blockquote>
<p>API에서 요청/응답 할 때 절대 엔티티를 사용하지 않고 DTO를 생성해서 요청/응답 해야한다.</p>
</blockquote>
<blockquote>
</blockquote>
<p>API를 만들때는 엔티티를 DTO 로 변환하는 작업을 추가하자
➡️ 엔티티가 변경돼도 API 스펙이 변하지 않는다
➡️ 한번 감싸서 반환했기 때문에 유연성이 좋아진다</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JPA] 웹 계층 개발]]></title>
            <link>https://velog.io/@jun_0y/JPA-%EC%9B%B9-%EA%B3%84%EC%B8%B5-%EA%B0%9C%EB%B0%9C</link>
            <guid>https://velog.io/@jun_0y/JPA-%EC%9B%B9-%EA%B3%84%EC%B8%B5-%EA%B0%9C%EB%B0%9C</guid>
            <pubDate>Tue, 01 Nov 2022 22:07:49 GMT</pubDate>
            <description><![CDATA[<h3 id="jpa-웹-계층-개발">JPA 웹 계층 개발</h3>
<p>웹 계층을 개발하기 위해서 필요한 것은 Contoller</p>
<p>Controller가 뭔데??</p>
<p>URL Mapping 해주고 POST 방법 지정하는 친구  ➡️  model과 view 사이의 징검다리</p>
<p>웹 계층 개발의 전체적인 로직은 <a href="https://velog.io/@jun_0y/Spring-%ED%9A%8C%EC%9B%90-%EA%B4%80%EB%A6%AC-%EC%98%88%EC%A0%9C-3">회원 관리 예제 포스팅</a>에서 공부했었다.
간단하게 그 로직을 복습해보자면</p>
<blockquote>
</blockquote>
<ol>
<li>컨트롤러 등록</li>
<li>Mapping &amp; return</li>
<li>return 파일(.html) 등록</li>
</ol>
<p>추가로 이번 예제에서는 뷰 템플릿을 최대한 간단하게 하기 위해 header, footer와 같은 템플릿 파일을 반복해서 포함한다. 
이러한 레이아웃을 <code>Hierarchical-style layout</code>이라고 하며 thymeleaf의 fragments기능을 이용한다 <code>th:replace=&quot;fragments/header :: header&quot;</code>
마지막으로 이쁜 화면을 위해 bootstrap의 css,js 와 jumbotron-narrow.css을 추가해준다</p>
<pre><code>resource/static : css, js 추가
resource/static/css : jumbotron-narrow.css 추가</code></pre><p>SpringBoot에서 VIEW를 잘 다룬다는 것은 thymleaf를 얼마나 잘 다룬다는 것을 의미하는 것 같다.
추후에 thymeleaf를 이해하고 활용하는 공부를 시작해보자!!</p>
<p>이제 본격적인 웹 계층 개발을 시작해보자</p>
<blockquote>
</blockquote>
<p>회원 등록</p>
<ol>
<li>회원 등록 폼 객체</li>
<li>회원 등록 컨트롤러</li>
<li>회원 등록 폼 화면</li>
</ol>
<ul>
<li>회원등록 폼 객체 : 화면 계층과 서비스 계층을 명확하게 분리하기 위한 목적<pre><code class="language-java">package jpabook.jpashop.controller;
</code></pre>
</li>
</ul>
<p>import lombok.Getter;
import lombok.Setter;</p>
<p>import javax.validation.constraints.NotEmpty;</p>
<p>@Getter @Setter
public class MemberForm {</p>
<pre><code>@NotEmpty(message = &quot;회원 이름은 필수 입니다.&quot;)
private String name;

private String city;
private String street;
private String zipcode;</code></pre><p>}</p>
<pre><code>
* 회원 등록 컨트롤러
```java
@Controller
@RequiredArgsConstructor
public class MemberController {

    private final MemberService memberService;

    @GetMapping(&quot;/members/new&quot;)
    public String createForm(Model model) {
        model.addAttribute(&quot;memberForm&quot;, new MemberForm());
        return &quot;members/createMemberForm&quot;;
    }

    @PostMapping(&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;;

    }

    @GetMapping(&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><ul>
<li><p>회원 등록 폼 화면</p>
<pre><code class="language-html">
placeholder=&quot;도시를 입력하세요&quot;&gt; &lt;/div&gt;
&lt;div class=&quot;form-group&quot;&gt;
&lt;label th:for=&quot;street&quot;&gt;거리&lt;/label&gt;
&lt;input type=&quot;text&quot; th:field=&quot;*{street}&quot; class=&quot;form-control&quot; placeholder=&quot;거리를 입력하세요&quot;&gt;
        &lt;/div&gt;
        &lt;div class=&quot;form-group&quot;&gt;
&lt;label th:for=&quot;zipcode&quot;&gt;우편번호&lt;/label&gt;
&lt;input type=&quot;text&quot; th:field=&quot;*{zipcode}&quot; class=&quot;form-control&quot;
placeholder=&quot;우편번호를 입력하세요&quot;&gt; &lt;/div&gt;
        &lt;button type=&quot;submit&quot; class=&quot;btn btn-primary&quot;&gt;Submit&lt;/button&gt;
    &lt;/form&gt;
&lt;br/&gt;
    &lt;div th:replace=&quot;fragments/footer :: footer&quot; /&gt;
&lt;/div&gt; &lt;!-- /container --&gt;
&lt;/body&gt;
&lt;/html&gt;</code></pre>
<p>html에 코드는 여기에서만 작성하고 이후부터는 생략하겠다.</p>
</li>
</ul>
<hr>
<br>
중간에 에러가 나서 찾아보니
springboot 2.3 버전부터 `validation-starter`가 web이랑 분리가 되었다고 한다. web 라이브러리를 땡겨와도 `validation-starter` 라이버리리가 자동으로 받아지지 않는다. 그래서 build.gradle에 `spring-boot-starter-validation-web`을 추가해주었다.

<hr>
<p>회원 목록 조회</p>
<blockquote>
</blockquote>
<p>회원 목록 컨트롤러
회원 목록 뷰</p>
<p>상품 등록</p>
<blockquote>
</blockquote>
<p>상품 등록 폼
상품 등록 컨트롤러
상품 등록 뷰</p>
<p>상품 목록</p>
<blockquote>
</blockquote>
<p>상품 목록 컨트롤러
상품 목록 뷰</p>
<p>상품 수정</p>
<blockquote>
</blockquote>
<p>상품 수정 컨트롤러
상품 수정 폼 화면</p>
<h3 id="변경-감지와-병함">변경 감지와 병함</h3>
<p>이번 강의중 가장 중요한 내용이다!!
매우매우 중요하니 꼭 완벽하게 이해하고 넘어가자</p>
<ul>
<li>준영속 엔티티(내 안의 엔티티..ㅎ)
영속성 컨테스트가 더는 관리하지 않는 엠티티를 말한다</li>
</ul>
<p>영속성에 관에서는 <a href="https://velog.io/@neptunes032/JPA-%EC%98%81%EC%86%8D%EC%84%B1-%EC%BB%A8%ED%85%8D%EC%8A%A4%ED%8A%B8%EB%9E%80">여기</a>에서 자세히 살펴보도록 하자</p>
<blockquote>
</blockquote>
<p>준영속 엔티티를 수정하는 2가지 방법</p>
<ol>
<li>변경 감지</li>
<li>병합(merge)</li>
</ol>
<ul>
<li><p>변경 감지</p>
<pre><code class="language-java">// 준영속 데이터 수정 - 1.변경감지
@Transactional
  public void updateItem(Long itemId, String name, int price, int stockQuantity) {
      Item findItem = itemRepository.findOne(itemId);
      findItem.setName(name);
      findItem.setPrice(price);
      findItem.setStockQuantity(stockQuantity);
  }</code></pre>
</li>
<li><p>병합(merge)</p>
<pre><code class="language-java">@Transactional
void update(Item itemParam) { //itemParam: 파리미터로 넘어온 준영속 상태의 엔티티 
  Item mergeItem = em.merge(item);
}</code></pre>
<p>병합을 사용하는 방법이 정말 간단하고 쉬워 보이지만 문제가 있다.
변경감지는 원하는 속성만 바꿀 수 있지만 병합은 모든 속성을 바꿔야한다.
만약 입력값 안넣은 채로 병합시키면 null로 채워진다.</p>
</li>
</ul>
<blockquote>
<p>결론, merge쓰지 말고 변경감지를 쓰자!!</p>
</blockquote>
<blockquote>
<p>추가 팁, Setter 없이 해라.. —&gt; 추적하기 어려워진다</p>
</blockquote>
<p>상품주문</p>
<blockquote>
</blockquote>
<ol>
<li>상품 주문 컨트롤러</li>
<li>상품 주문 폼</li>
</ol>
<p>주문목록 검색, 취소</p>
<blockquote>
</blockquote>
<ol>
<li>주문 목록 검색/취소 컨트롤러</li>
</ol>
<hr>
<pre><code>* 뷰 템플릿 변경사항을 서버 재시작 없이 즉식 변경하기

1. build.gradle : spring-boot-devtools 추가하기
2. html 파일 수정 후 build-&gt; Recompile </code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[JPA] 주문 기능 테스트]]></title>
            <link>https://velog.io/@jun_0y/JPA-%EC%A3%BC%EB%AC%B8-%EA%B8%B0%EB%8A%A5-%ED%85%8C%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@jun_0y/JPA-%EC%A3%BC%EB%AC%B8-%EA%B8%B0%EB%8A%A5-%ED%85%8C%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Tue, 01 Nov 2022 12:53:28 GMT</pubDate>
            <description><![CDATA[<h3 id="주문-도메인-개발한-것을-테스트해보자">주문 도메인 개발한 것을 테스트해보자</h3>
<p>테스트 코드의 기본적인 형태는 다음과 같다.</p>
<pre><code class="language-java">@Test
public void  throws Exception{
    //given

    //when

    //then

}</code></pre>
<p><a href="https://velog.io/@jun_0y/JPA-H2DB-%EC%84%A4%EC%B9%98-%EB%B0%8F-JPA%EC%99%80-DB%EC%84%A4%EC%A0%95-%EB%8F%99%EC%9E%91%ED%99%95%EC%9D%B8">이전포스트</a>에서 해당 코드를 단축어(tdd)로 설정하는 작업을 했는데 
이번에 또 써먹으니까 완전 유용하다!!</p>
<p>이제 무엇을 테스트 해봐야 되는지 알아보자</p>
<ol>
<li>상품 주문이 성공해야 한다.</li>
<li>상품 주문 시 재고 수량을 초과하면 안된다.</li>
<li>주문 취소가 성공해야 한다.</li>
</ol>
<pre><code class="language-java">@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional
public class OrderServiceTest {

    @Autowired EntityManager em;
    @Autowired OrderService orderService;
    @Autowired OrderRepository orderRepository;

    @Test
    public void 상품주문() throws Exception{
        //given
        Member member = createMember();

        Book book = createBook(&quot;시골 JPA&quot;, 10000, 10);

        int orderCount = 2;

        //when
        Long orderId = orderService.order(member.getId(), book.getId(), orderCount);

        //then
        Order getOrder = orderRepository.findOne(orderId);

        assertEquals(&quot;상품 주문시 상태는 ORDER&quot;, OrderStatus.ORDER, getOrder.getStatus());
        assertEquals(&quot;주문한 상품 종류 수가 정확해야 한다.&quot;, 1, getOrder.getOrderItems().size());
        assertEquals(&quot;주문 가격은 가격 * 수량이다.&quot;, 10000 * orderCount, getOrder.getTotalPrice());
        assertEquals(&quot;주문 수량만큼 재고가 줄어야 한다.&quot;, 8, book.getStockQuantity());
    }

    @Test(expected = NotEnoughStockException.class)
    public void 상품주문_재고수량초과() throws Exception{
        //given
        Member member = createMember();
        Item item = createBook(&quot;시골 JPA&quot;, 10000, 10);

        int orderCount = 11;

        //when
        orderService.order(member.getId(), item.getId(), orderCount);

        //then
        fail(&quot;재고 수량 부족 예외가 발생해야 한다.&quot;);

    }
    @Test
    public void 주문취소() throws Exception{
        //given
        Member member = createMember();
        Book item = createBook(&quot;시골 JPA&quot;, 10000, 10);

        int orderCount = 2;
        Long orderId = orderService.order(member.getId(), item.getId(), orderCount);

        //when
        orderService.cancelOrder(orderId);

        //then
        Order getOrder = orderRepository.findOne(orderId);

        assertEquals(&quot;주문 취소시 상태는 CANCEL 이다.&quot;, OrderStatus.CANCEL, getOrder.getStatus());
        assertEquals(&quot;주문이 취소된 상품은 그만큼 재고가 증가히야 한다.&quot;, 10, item.getStockQuantity());
    }


    private Book createBook(String name, int price, int stockQuantity) {
        Book book = new Book();
        book.setName(name);
        book.setPrice(price);
        book.setStockQuantity(stockQuantity);
        em.persist(book);
        return book;
    }

    private Member createMember() {
        Member member = new Member();
        member.setName(&quot;회원1&quot;);
        member.setAddress(new Address(&quot;서울&quot;, &quot;강가&quot;, &quot;123-123&quot;));
        em.persist(member);
        return member;
    }

}
</code></pre>
<ul>
<li><p>상품주문
Given : 테스트를 위한 회원과 상품을 만든다
When  : 실제 상품을 주문
Then  : 주문 가격이 올바른지, 주문 후 재고 수량이 정확히 줄었 는지 검증</p>
</li>
<li><p>재고 수량 초과
<code>@Test(expected = NotEnoughStockException.class)</code>로 예외 발생
Given : 상품 주문
When  : 주문 취소
Then  : 주문상태가 주문취소 상태인지, 취소한 만큼의 재고가 증가했는지 확인</p>
</li>
</ul>
<hr>
<h4 id="주문-검색-기능">주문 검색 기능</h4>
<p>JPA에서 동적 쿼리를 어떻게 해결해야 하는가?</p>
<p>OrderRepository에 검색 기능을 추가해주어야 한다.
findAll__(OrderSearch orderSearch) 메서드는 검색 조건에 동적으로 쿼리를 생성해서 주문 엔티티를 조회하는 함수이다.</p>
<ul>
<li>findAll( ) 메서드를 만드는 세가지 방법이 있다.</li>
</ul>
<blockquote>
</blockquote>
<ol>
<li>JPQL - 번거롭고, 실수로 인해 오류 발생 가능성 높다</li>
<li>JPA Criteria - JPA 표준스펙 but, 실무에서 사용하기 복잡하다</li>
<li>Querydsl - 코드가 엄청 간결해진다!!</li>
</ol>
<p>그래서 Querydsl 을 쓰는게 좋은데 이번에는 이런게 있다고만 알아두고 JPA 표준 스펙인 JPA Criteria 코드를 사용하고 넘어가자.</p>
<p>*JPA Criteria에 대한 자세한 내용은 자바  ORM 표준 JPA 프로그래밍 책을 참고하자</p>
<pre><code class="language-java">package jpabook.jpashop.repository;

@Repository
@RequiredArgsConstructor
public class OrderRepository {

    @PersistenceContext
    private final EntityManager em;

    public void save(Order order) {em.persist(order);}

    public Order findOne(Long id) {return em.find(Order.class, id);}


    // 검색기능 - 동적쿼리
    /**
     * JPQL
     */
    public List&lt;Order&gt; findAllByString(OrderSearch orderSearch) {
        String jpql = &quot;select o From Order o join o.member m&quot;;
        boolean isFirstCondition = true;

        //주문 상태 검색
        if (orderSearch.getOrderStatus() != null) {
            if (isFirstCondition) {
                jpql += &quot; where&quot;;
                isFirstCondition = false;
            } else {
                jpql += &quot; and&quot;;
            }
            jpql += &quot; o.status = :status&quot;;
        }
        //회원 이름 검색
        if (StringUtils.hasText(orderSearch.getMemberName())) {
            if (isFirstCondition) {
                jpql += &quot; where&quot;;
                isFirstCondition = false;
            } else {
                jpql += &quot; and&quot;;
            }
            jpql += &quot; m.name like :name&quot;;
        }

        TypedQuery&lt;Order&gt; query = em.createQuery(jpql, Order.class) .setMaxResults(1000); //최대 1000건
        if (orderSearch.getOrderStatus() != null) {
            query = query.setParameter(&quot;status&quot;, orderSearch.getOrderStatus());
        }
        if (StringUtils.hasText(orderSearch.getMemberName())) {
            query = query.setParameter(&quot;name&quot;, orderSearch.getMemberName());
        }

        return query.getResultList();
    }

    /**
     * JPA Criteria : JPA 표준 스펙.. 근데 유지보수가 안됨
     * 그냥 이런식으로 할 수 있다는 정도
     */
    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();
    }


/*
    // Query DSL
    public List&lt;Order&gt; findAll(OrderSearch orderSearch) {

        QOrder order = Qorder.order;
        QMember member = QMember.member;

        return query
                .select(order)
                .from(order)
                .join(order.member, member)
                .where(statusEq(orderSearch.getOrderStatus()),
                        nameLike(orderSearch.getMemberName()))
                .limit(1000)
                .fetch();
    }
*/


}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JPA] 주문 도메인 개발]]></title>
            <link>https://velog.io/@jun_0y/JPA-%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/@jun_0y/JPA-%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 03:41:52 GMT</pubDate>
            <description><![CDATA[<h3 id="주문도메인-개발">주문도메인 개발</h3>
<p>이번시간에서의 두가지 키워드는 <code>기능</code>과 <code>순서</code>다</p>
<ul>
<li>구현해야하는 기능은 다음과 같다</li>
</ul>
<ol>
<li>상품 <code>주문</code></li>
<li>주문내역 <code>조회</code></li>
<li>주문 <code>취소</code></li>
</ol>
<ul>
<li>각각의 기능을 개발하는 순서는 다음과 같다</li>
</ul>
<ol>
<li>주문&amp;주문상품 엔티티 개발</li>
<li>주문 리포지토리 개발 <code>repository</code></li>
<li>주문 서비스 개발 <code>service</code></li>
<li>주문 검색 기능 개발</li>
<li>기능 테스트 <code>Test</code> </li>
</ol>
<blockquote>
<p>구현 순서는 어떤 기능 구현에서도 적용되니 꼭 기억하기❗️❗️</p>
</blockquote>
<p>구현해야할 각 기능마다 개발 순서를 따라가며 하나씩 코드 작성해보자</p>
<ul>
<li><h3 id="주문-엔티티--order">주문 엔티티 : Order</h3>
<pre><code class="language-java">@Entity
@Table(name = &quot;orders&quot;)
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Order {

  @Id @GeneratedValue
  @Column(name = &quot;order_id&quot;)  // column을 테이블명의 아이디. DB들이 이 방식을 선호
  private Long id;

  // ___ToOne 인 애들은 기본 fetch가 EAGER
  // 그래서 LAZY로 바꿔줘야함
  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = &quot;member_id&quot;)  //추가로 joinColumn. 매핑을 뭘로 할거냐..?
  // Member orders와 양방향 연관관계
  // 양방향 연관관계는 관계의 주인을 정해줘야헤. Order의 회원을 바꿀떄 여기의 값을 바꿀 수 있고 반대로 Member에서 orderList의 값을 바꿀수도있어
  // 양방향 참조인데 fk를 가지고 있는건 orders!!
  // 그래서 누가 주인이라고? fk가 가까운애?
  // Order에 있는 member를 주인으로 잡아야한다는데
  // 주인이라는게  Member 개체 vs Order개체에 있는 Member 를 비교하는거였어?
  private Member member;

  //___ToMany 인 애들은 기본 fetch가 LAZY
  // cascade: 뭘 한번에 해준대..
  @OneToMany(mappedBy = &quot;order&quot;, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
  private List&lt;OrderItem&gt; orderItems = new ArrayList&lt;&gt;();

  @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
  @JoinColumn(name = &quot;delivery_id&quot;)
  private Delivery delivery;

  private LocalDateTime orderDate; // 주문시간

  @Enumerated(EnumType.STRING)
  private OrderStatus status; // 주문상태

  //==연관관계 메서드=//
  public void setMember(Member member) {
      this.member = member;
      member.getOrders().add(this);
  }

  public void addOrderItem(OrderItem orderItem) {
      orderItems.add(orderItem);
      orderItem.setOrder(this);
  }

  public void setDelivery(Delivery delivery) {
      this.delivery = delivery;
      delivery.setOrder(this);
  }

  //==생성 메서드==//
  // 각각을 set, set, set.. 하는게 아니라 생성 메서드로 한번에!!
  public static Order createOrder(Member member, Delivery delivery, OrderItem... orderItems) {
      Order order = new Order();
      order.setMember(member);
      order.setDelivery(delivery);
      for (OrderItem orderItem : orderItems) {
          order.addOrderItem(orderItem);
      }
      order.setStatus(OrderStatus.ORDER);
      order.setOrderDate(LocalDateTime.now());

      return order;
  }

  //==비즈니스 로직==//
  // 이것도 domain차원에서 비즈니스 로직 구현
  /**
   * 주문 취소
   */
  public void cancel() {
      if (delivery.getStatus() == DeliveryStatus.COMP) {
          throw new IllegalStateException(&quot;이미 배송완료된 상품은 취소가 불가능합니다.&quot;);
      }

      this.setStatus(OrderStatus.CANCEL);
      for (OrderItem orderItem : orderItems) {        // this를 쓰냐 안쓰냐는 알아서..
          orderItem.cancel();
      }
  }

  //==조회 로직==//
  /**
   * 전체 주문 가격 조회
   */
  public int getTotalPrice() {
      int totalPrice = 0;
      for (OrderItem orderItem : orderItems) {
          totalPrice += orderItem.getTotalPrice();
      }
      return totalPrice;
  }
</code></pre>
</li>
</ul>
<p>}</p>
<pre><code>코드의 디테일한 설명은 주석 확인하기!!
* Order 엔티티에서 지원하는 메서드
    -createOrder() : 회원, 배송지, 주문상품 정보 받아서 주문을 생성한다.
    -cancel() : 주문을 취소한다. 이미 배송이 완료된 상품이면 취소 못하게 예외 발생시킨다(get/setStatus 사용)
    -getTotalPrice() : 전체주문 가격을 조회한다




* ### 주문상품 엔티티 : OrderItem

```java
@Entity
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED) // 서비스에서 new Order() 이렇게 직접 생성 못하게
public class OrderItem {

    @Id @GeneratedValue
    @Column(name = &quot;order_item_id&quot;)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;item_id&quot;)
    private Item item;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;order_id&quot;)
    private Order order;

    private int orderPrice; // 주문 가격
    private int count;      // 주문 수량

    //==생성 메서드==//
    // 주문이 들어오면 OrderItem이 생성되면서 재고를 차감하는 로직
    public static OrderItem createOrderItem(Item item, int orderPrice, int count) {
        OrderItem orderItem = new OrderItem();
        orderItem.setItem(item);
        orderItem.setOrderPrice(orderPrice);
        orderItem.setCount(count);

        item.removeStock(count);
        return orderItem;
    }

    //==비즈니스 로직==//
    /**
     * 주문 취소 : 재고 수량을 다시 돌려놓는다
     */
    public void cancel() {
        getItem().addStock(count);
    }


    //==조회 로직==//
    /**
     * 주문상품 전체 가격조회
     */
    public int getTotalPrice() {
        return getOrderPrice() * getCount();
    }
}
</code></pre><br>

<p>주목할 코드 : <code>@NoArgsConstructor(access = AccessLevel.PROTECTED)</code>
이게 뭐냐면 서비스에서 직접 Order order1 = new Order() 이렇게 직접 생성 못하게 막아주는 annotation이다.</p>
<p>왜 막아줘야 되냐고??.. 다시듣자...</p>
<ul>
<li>OrderItem 엔티티에서 지원하는 메서드 : 직접 구현하는 것은 없고 Order에서 구현한 메서드를 이용하여 구현한다
  -<code>createOrder(item, price, count)</code> : 상품, 가격, 수량을 전달받아 주문상품 엔티티를 생성하고 item.removeStock(count)를 사용하여 주문받은 수량 만큼 상품의 재고를 줄인다
  -<code>cancel()</code> : 주문을 취소한다. getItem().addStock(count)로  수량을 다시 돌려놓는다
  -<code>getTotalPrice()</code> : 주문 가격에 수량을 곱한 값을 반환한다</li>
</ul>
<Br>


<ul>
<li><h3 id="주문-리포지토리--orderrepository">주문 리포지토리 : OrderRepository</h3>
<pre><code class="language-java">@Repository
@RequiredArgsConstructor
public class OrderRepository {

  private final EntityManager em;

  public void save(Order order) {
      em.persist(order);
  }

  public Order findOne(Long id) {
      return em.find(Order.class, id);
  }

  // 검색기능 - 동적쿼리</code></pre>
<p>다른 리포지토리와 마찬가지로 <code>EntityManager em</code>을 생성해준다</p>
<ul>
<li>OrderRepository에서 지원하는 메서드</li>
<li><code>save(order)</code> : 주문 저장</li>
<li><code>findOne(id)</code> : 주문 id로 주문을 조회</li>
</ul>
<p>검색기능은 나중에!!</p>
<Br>
</li>
<li><h3 id="주문-서비스--orderservice">주문 서비스 : OrderService</h3>
<pre><code class="language-java">@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class OrderService {

  private final OrderRepository orderRepository;
  private final MemberRepository memberRepository;
  private final ItemRepository itemRepository;

  /**
   * 주문
   */
  @Transactional
  public Long order(Long memberId, Long itemId, int count) {

      //엔티티 조회
      Member member = memberRepository.findOne(memberId);     // member id 가져오기
      Item item = itemRepository.findOne(itemId);             // item id 가져오기

      // 배송정보 생성
      Delivery delivery = new Delivery();
      delivery.setAddress(member.getAddress());

      // 주문상품 생성
      // 생성 메서드가 있으니 직접 생성하지마!
      OrderItem orderItem = OrderItem.createOrderItem(item, item.getPrice(), count);

      //주문 생성
      Order order = Order.createOrder(member, delivery, orderItem);

      //주문 저장
      // 이거는 왜 위에랑 다르게 이것만 띡 써놔도 되는거야?
      // Delivery나 OrderItem 보면 각자의 repository로 가서 save를 하던 뭘하든 어쩄든 본인 repository?로 가잖아
      // 쟤네는 cascade라서..? or private이라서..?
      // delveiry나 orderitem 같은애들은 Order에서만 쓰잖아
      // 만약 다른데에서도 갖다 쓰는애들이면 cascade 막 쓰면 안됨
      // JAP 활용_1 - &#39;주문 서비스 개발&#39; 편 강의
      orderRepository.save(order);

      return order.getId();
  }

  /**
   * 주문 취소
   */
  @Transactional
  public void cancelOrder(Long orderId) {
      // 주문 엔티티 조회
      Order order = orderRepository.findOne(orderId);
      // 주문 취소
      order.cancel();
  }

</code></pre>
</li>
</ul>
<pre><code>//검색

public List&lt;Order&gt; findOrders(OrderSearch orderSearch) {
    return orderRepository.findAllByString(orderSearch);
}</code></pre><p>}</p>
<pre><code>* 주문에서는 회원과 상품의 정보를 조회할 수 있어야 되니까 `Order`, `Member`, `Item`의 Repository를 모두 생성해주자&lt;br&gt;

* order 생성자에 `@Transactional`을 붙여주는 이유는 위에서 Transactional의 default값을 readonly로 해놨기 때문에 write가 필요한 order에서 Transaction을 write도 가능하게 다시 붙여주기 위함!!&lt;br&gt;

* OrderService에서 지원하는 메서드
    - `order(member_Id, item_Id, count)` : 회원ID, 상품ID, 수량을 받아서 주문 엔티티를 생성 &amp; 저장
    - `cancelOrder(order_Id)` : 주문ID를 받아서 조회 후 주문 취소
    - `findOrders(orderSearch)` : 주문 검색


&gt;참고 
CASCADE는 참조하는 애가 private owner일 때 / persist life cycle이 똑같을 때만 써라

엔티티, 리포지토리, 서비스 구조를 살펴보면 Delivery는 Order말고 아무도 안쓰고 Orderitme 도 Order만 참조해서 쓴다. 이런 애들만 ON DELETE 속성을 CASCADE로 줘야된다.

뭔소린지는 알겠는데 언제 어디서 적용해야 될지 막막하다.
==&gt; `CASCADE` 막 쓰지 마라. `일단 쓰지마`

---

### 도메인 모델 패턴  vs  트랜잭션 스크립트 패턴

저번 상품 도메인 개발 포스팅에서 `이럴거면 service layer 왜 만드는거야..` 라고 한적이 있다.

이번 주문 서비스에서도 주문과 주문 취소 메서드를 보면 비즈니스 로직을 구현하는 메서드가 대부분 엔티티에 있다.
즉, service에서 메서드를 새로 정의하는게 아니라 엔티티 에서 이미 만든 메서드를 호출하는 방식으로 비즈니스 로직을 짜고 있단 말이지?? 

이런 방식을 `도메인 모델 패턴` 이라고 한다!!
도메인에서 이미 핵심 메서드 다 짜놨으니까 서비스야 너는 이거 가져가서 쓰기나해. 라는 식의 개발 방법이다.

반대로 엔티티에는 최소한의 메서드(Getter &amp; Setter) 만 만들어놓고 서비스에서 메서드를 구현해서 비즈니스 로직을 짜는 방식을  `트랜잭션 스크립트 패턴` 이라고 한다

.
.
그렇대.. 각각의 장단점이 있는데
    도메인 모델 패턴 개발이 한눈에 들어와서 유지보수가 편리해보인다!


</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[JPA] 상품도메인 개발]]></title>
            <link>https://velog.io/@jun_0y/JPA-%EC%83%81%ED%92%88%EB%8F%84%EB%A9%94%EC%9D%B8-%EA%B0%9C%EB%B0%9C</link>
            <guid>https://velog.io/@jun_0y/JPA-%EC%83%81%ED%92%88%EB%8F%84%EB%A9%94%EC%9D%B8-%EA%B0%9C%EB%B0%9C</guid>
            <pubDate>Fri, 28 Oct 2022 02:05:57 GMT</pubDate>
            <description><![CDATA[<p>시험기간 때문에 너무 오랜만에 들어왔다.. 쫌 까먹은것 같은데... 잘 기억하고 있는지 모르겠지만 일단 다시 시작해보자!!</p>
<h3 id="상품도메인-개발">상품도메인 개발</h3>
<p>이번시간에서의 두가지 키워드는 <code>기능</code>과 <code>순서</code>다</p>
<ul>
<li>구현해야하는 기능은 다음과 같다</li>
</ul>
<ol>
<li>상품 <code>등록</code></li>
<li>상품 목록 <code>조회</code></li>
<li>상품 <code>수정</code></li>
</ol>
<ul>
<li>각각의 기능을 개발하는 순서는 다음과 같다</li>
</ul>
<ol>
<li>상품 엔티티 개발하고 <code>domain</code></li>
<li>상품 리포지토리 개발 <code>repository</code></li>
<li>상품 서비스 개발 <code>service</code></li>
<li>기능 테스트 <code>Test</code> </li>
</ol>
<blockquote>
<p>구현 순서는 어떤 기능 구현에서도 적용되니 꼭 기억하기❗️❗️</p>
</blockquote>
<p>구현해야할 각 기능마다 개발 순서를 따라가며 하나씩 코드 작성해보자</p>
<ul>
<li><h3 id="상품-엔티티--item">상품 엔티티 : Item</h3>
<pre><code class="language-java">@Entity
//상속관계 매핑이기 때문에 상속관계 전략을 지정해야되는데 이 전략을 부모클래스에 잡아야해
//여기선 싱글 테이블 전략
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = &quot;dtype&quot;)  //ex. book이면 어떻게 할거야.. 뭔소린지 더 알아보자..
@Getter @Setter
public abstract class Item {

  @Id                            // PRIMARY KEY 표시 annotation
  @GeneratedValue                // 자동생성 설정 annotation
  @Column(name = &quot;item_id&quot;)
  private Long id;

  private String name;
  private int price;
  private int stockQuantity;

  @ManyToMany(mappedBy = &quot;items&quot;)
  private List&lt;Category&gt; categories = new ArrayList&lt;&gt;();

  //==비즈니스 로직==//
  // 보통 stock quantity를 가져와서 거기서 지지고 볶고 삶고 마지막에 setQuantity 같은 방식으로 코딩을 했읉텐데
  // BUT, 객체 지향적으로 생각해보면 데이터를 가지고 있는 쪽에 비즈니스 메서드가 있는게 가장 좋음. 응집력 있음
  // quantity를 변경할일 있으면 setter를 쓰는게 아니라 이렇게 핵심 비즈니스 로직으로 변경하도록!!
  /**
   * stock(재고) 증가
   */
  public void addStock(int quantity) {
      this.stockQuantity += quantity;
  }

  /**
   * stock(재고) 감소
   */
  public void removeStock(int quantity) {
      int restStock = this.stockQuantity - quantity;
      if (restStock &lt; 0) {
          throw new NotEnoughStockException(&quot;need more stock&quot;);
      }
      this.stockQuantity = restStock;
  }
}</code></pre>
</li>
</ul>
<p>자세한 설명은 코드에 주석을 달아놨다.
이해가 잘 안되는건 <code>@DiscriminatorColumn(name = &quot;dtype&quot;)</code> 이 annotation인데 
구글링해서 쫌 더 찾아보자.</p>
<p>비즈니스 로직 : addStock(), removeStock() 은 나중에 다시 돌아와서 하는데 그냥 한번에 하자.</p>
<ul>
<li>Item 엔티티 <ul>
<li>addStock() : 파라미터로 넘어온 수량만큼 재고를 늘리는 메서드<ul>
<li>removeStock() : 파라미터로 너머온 수량만큼 재고를 줄이는 메서드. 만약 재고가 부족하면 예외가 발생한다</li>
</ul>
</li>
</ul>
</li>
</ul>
<ul>
<li><h3 id="상품-리포지토리--itemrepository">상품 리포지토리 : ItemRepository</h3>
<pre><code class="language-java">@Repository
@RequiredArgsConstructor // final 변수만 생성자 만들어주는 친구
public class ItemRepository {

  private final EntityManager em;

  public void save(Item item) {

      if (item.getId() == null) {     // item 값이 없다 --&gt; 새거다
          em.persist(item);
      } else {                        // item 값이 있다 --&gt; 있던거다
          em.merge(item);             // merge : update 느낌
      }

  }

  public Item findOne(Long id) {
      return em.find(Item.class, id);
  }

  public List&lt;Item&gt; findAll() {
      return em.createQuery(&quot;select i from Item i&quot;, Item.class)
              .getResultList();
  }
}
</code></pre>
</li>
</ul>
<pre><code>- EntityManger em은 `persist`, `merge`, `find`, `createQeury` 를 지원해준다
- save()에서 `em.persist(item)` / `em.merge(item)`를 기억하자
- ItemRepository에서 지원하는 메서드
    -save(item) : 상품을 저장
    -findOne(id) : 상품 id로 해당 상품 조회
 -findAll() : 전체 상품조회


* ### 상품 서비스 : ItemService
```java
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class ItemService {

    public final ItemRepository itemRepository;

    // 위에 있는 @Transactional은 전체에 적용 : readOnly니까
    // 가까이 있는 annotation이 우선권을 가짐  : write 해주기 위해 다시 annotation 붙임
    @Transactional
    public void saveItem(Item item) {
        itemRepository.save(item);
    }

    public List&lt;Item&gt; findItems() {
        return itemRepository.findAll();
    }

    public Item findOne(Long itemId) {
        return itemRepository.findOne(itemId);
    }
}
</code></pre><p>ItemService는 ItemRepository에 있는 기능을 이름만 바꿔서 쓰고있다.
-saveItem(item)
-findItems()
-findOne(itemID)
이게 꼭 필요한가..?</p>
<p>엔티티, 리포지토리, 서비스를 모두 구현 했으면 이제 테스트 하고 마무리~~
하면 되는데 회원 테스트랑 비슷하다고 생략했다...
.
.
.
아싸 강의들을거 줄었다ㅎㅋ</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JPA] 회원 도메인 개발
]]></title>
            <link>https://velog.io/@jun_0y/JPA-%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/@jun_0y/JPA-%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 09:31:39 GMT</pubDate>
            <description><![CDATA[<p>회원 도메인 개발하기 스프링 부트의 아키텍쳐에 대해 살펴보자
아키텍쳐는 저번 포스터에서 했으니까 간단하게 사진으로 살펴보자</p>
<h3 id="애플리케이션-아키텍쳐">애플리케이션 아키텍쳐</h3>
<p><img src="https://velog.velcdn.com/images/jun_0y/post/b7bceb8a-5940-4b7e-9cce-de906f23fdc4/image.png" alt=""></p>
<h3 id="회원-도메인-개발">회원 도메인 개발</h3>
<ol>
<li>먼저 Member Repository를 생성한다</li>
</ol>
<pre><code class="language-java">package jpabook.jpashop.repository;

import jpabook.jpashop.domain.Member;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.swing.plaf.metal.MetalMenuBarUI;
import java.util.List;

@Repository
@RequiredArgsConstructor
public class MemberRepository {

    /*
    // 1. 원래 이렇게 하던걸
    @PersistenceContext
    private EntityManager em;

    // 2. 이렇게 해도 되는데
    // 이건 스프링 부트가 지원하는 기능
    @Autowired
    private EntityManager em;

    public MemberRepository(EntityManager em) {
        this.em = em;
    }
    */

    // 3. 결론은 여기에 @RequiredArgsConstructor 이거 붙여서 쓸거임.
    // EntityManger를 Injection해서 써야한다
    // 원래는 @PersistenceContext 이걸로 해야되는데
    // 스프링부트가 생성자 만들어서 @Autowired 붙이는 것도 지원해줌
    private final EntityManager em;

    public void save(Member member) {
        em.persist(member);
    }

    public Member findOne(Long id) {
        return em.find(Member.class, id);
    }

    public List&lt;Member&gt; findAll() {
        // SQL과 차이가 있다. SQL은 개체에 대한 쿼리
        // JPQL은 Entity에 대한 쿼리.. 기본편을 들어야될
        List&lt;Member&gt; result = em.createQuery(&quot;select m from Member m&quot;, Member.class)
                .getResultList();

        // option + command + N : inline 단축키
        return result;
    }

    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>
<ul>
<li>리포지토리 만들었으면 Member Service 생성!!</li>
</ul>
<pre><code class="language-java">@Service
@Transactional //JPA의 모든 데이터 변경이나 로직들은 가급적이면 @Transactional 안에서 실행되어야함 / spring에서 제공하는걸로 쓰자. (javax아님)
// @AllArgsConstructor 알아서 생성자 만들어준다
@RequiredArgsConstructor // final이 있는 애들만 생성자를 만들어준다
public class MemberService {


    private final MemberRepository memberRepository;

    /*
    //@Autowired
    // 생성자가 하나만 있는경우 @Autowired 안달아도 스프링이 알아서 스프링빈에 등록 해준다
    public MemberService(MemberRepository memberRepository) {
        this.memberRepository = memberRepository;
    }
    */

    // 회원가입
    @Transactional // readOnly 는 default가 false. 이렇게 안에서 쓰면 이게 우선순위 transaction이 됨
    public Long join(Member member) {

        validateDuplicateMember(member);// 중복 회원 검증
        memberRepository.save(member);

        return member.getId();
    }

    private void validateDuplicateMember(Member member) {

        // 동시에 가입할수도 있잖아 (MultiThread)
        // 실무에서는 member에 유니크 제약조건을 걸어서 쓰면 안전하대
        List&lt;Member&gt; findMembers = memberRepository.findByName(member.getName());

        if (!findMembers.isEmpty()) {
            throw new IllegalStateException(&quot;이미 존재하는 회원입니다.&quot;);
        }
    }

    // 회원 전체 조회
    @Transactional(readOnly = true) // 읽기에 readOnly=ture 해주면 최적화 해준대. 쓰기에는 넣으면 안됨
    public List&lt;Member&gt; findMembers() {
        return memberRepository.findAll();
    }

    @Transactional(readOnly = true)
    public Member findOne(Long memberId) {
        return memberRepository.findOne(memberId);
    }
}
</code></pre>
<h4 id="꿀팁">꿀팁</h4>
<p>MemberService에 커서 올리고 <code>command + shift + T</code> 누르면 테스트 코드가 자동으로 작성된다</p>
<pre><code class="language-java">@RunWith(SpringRunner.class)
@SpringBootTest
@Transactional //Rollback 역할
public class MemberServiceTest {

    @Autowired MemberService memberService;
    @Autowired MemberRepository memeberRepository;
    // @Autowired EntityManager em; DB에 쿼리문 남는거 보고싶을 때

    @Test
    //@Rollback(false)
    public void 회원가입() throws Exception{
        //given
        Member member = new Member();
        member.setName(&quot;kim&quot;);

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

        //then
        assertEquals(member, memeberRepository.findOne(saveId));
        //em.flush();

    }

    @Test(expected = IllegalStateException.class) // try-catch문 없이 작성할 수 있음
    public void 중복_회원_예외() throws Exception{
        //given
        Member member1 = new Member();
        member1.setName(&quot;kim1&quot;);

        Member member2 = new Member();
        member2.setName(&quot;kim1&quot;);

        //when

        memberService.join(member1);
        memberService.join(member2);

        /*
        memberService.join(member1);
        try {
            memberService.join(member2);
        } catch (IllegalStateException e) {
            return;
        }
        */

        //then
        Assert.fail(&quot;예외가 발생해야 한다&quot;); // assertj에서 지원하는 fail이라는 함수. 이코드가 실행되면 안됨
    }
}</code></pre>
<blockquote>
<p>@Transactional 달아주는거 잊지말기!!</p>
</blockquote>
<p>쓸시간이 없네.. 자세한 설명은 주석에 있으니 주말에 찬찬히 살펴보면서 다시 작성하자!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JPA] 엔티티 설계시 주의할 점]]></title>
            <link>https://velog.io/@jun_0y/JPA-%EC%97%94%ED%8B%B0%ED%8B%B0-%EC%84%A4%EA%B3%84%EC%8B%9C-%EC%A3%BC%EC%9D%98%ED%95%A0-%EC%A0%90</link>
            <guid>https://velog.io/@jun_0y/JPA-%EC%97%94%ED%8B%B0%ED%8B%B0-%EC%84%A4%EA%B3%84%EC%8B%9C-%EC%A3%BC%EC%9D%98%ED%95%A0-%EC%A0%90</guid>
            <pubDate>Fri, 07 Oct 2022 07:59:37 GMT</pubDate>
            <description><![CDATA[<h3 id="엔티티-설계시-주의할-점에-대해서-알아보자">엔티티 설계시 주의할 점에 대해서 알아보자</h3>
<blockquote>
<ol>
<li>엔티티에는 가급적 Setter를 사용하지 말자</li>
</ol>
</blockquote>
<p>Setter가 열려있어서 Setter를 통해 데이터를 변경하게 되면 변경 포인트가 너무 많아서 유지보수가 어려워진다</p>
<blockquote>
<ol start="2">
<li>모든 연관관계는 지연로딩으로 설정해야한다!!!</li>
</ol>
</blockquote>
<p>⭐️매우매우매우 중요해보인다.⭐️</p>
<p>즉시로딩(EAGER)은 예측이 어렵고, 어떤 SQL이 실행될지 추적하기 어렵다. 특히 JPQL을 실행할 때 N+1 문제가 자주 발생한다고 한다. 예를 들어 100개의 sql문을 가져왔는데 100개 각각마다 또 다른 테이블을 참조하고 있으면 대참사..!</p>
<p>실무에서 모든 연관관계는 지연로딩(LAZY)으로 설정해야 한다.</p>
<p>즉, <code>@__ToOne(OneToOne, ManyToOne) 관계는 기본이 즉시로딩이므로 직접 지연로딩으로 설정한다</code></p>
<p>프로젝트 파일 눌러놓고 <code>command + shift + T</code> 누르면 이렇게 모든 코드를 검색할 수 있다
여기서 바꿔주기!!
<img src="https://velog.velcdn.com/images/jun_0y/post/43c85b01-5427-4dee-8c10-bb7b8ddef442/image.png" alt=""></p>
<blockquote>
<ol start="3">
<li>컬렉션은 필드에서 초기화 하자.</li>
</ol>
</blockquote>
<p>컬렉션은 필드에서 바로 초기화 하는 것이 null 문제에서 안전하다.
그렇대..</p>
<blockquote>
<p>테이블, 컬럼명 생성 전략</p>
</blockquote>
<p>스프링 부트에서 하이버네이트 기본 매핑 전략을 변경해서 실제 테이블 필드명은 다르다</p>
<p>스프링 부트 신규 설정 (엔티티(필드) 테이블(컬럼))</p>
<ol>
<li>카멜 케이스 언더스코어(memberPoint member_point) 2. .(점) _(언더스코어)</li>
<li>대문자 소문자</li>
</ol>
<h4 id="적용-2-단계">적용 2 단계</h4>
<ol>
<li><p>논리명 생성: 명시적으로 컬럼, 테이블명을 직접 적지 않으면 ImplicitNamingStrategy 사용
spring.jpa.hibernate.naming.implicit-strategy : 테이블이나, 컬럼명을 명시하지 않을 때 논리명 적용</p>
</li>
<li><p>물리명 적용:
spring.jpa.hibernate.naming.physical-strategy : 모든 논리명에 적용됨, 실제 테이블에 적용</p>
</li>
</ol>
<p>이건 더 찾아보면서 공부해야 될 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JPA] 엔티티 클래스 개발 1,2]]></title>
            <link>https://velog.io/@jun_0y/JPA-%EC%97%94%ED%8B%B0%ED%8B%B0-%ED%81%B4%EB%9E%98%EC%8A%A4-%EA%B0%9C%EB%B0%9C-12</link>
            <guid>https://velog.io/@jun_0y/JPA-%EC%97%94%ED%8B%B0%ED%8B%B0-%ED%81%B4%EB%9E%98%EC%8A%A4-%EA%B0%9C%EB%B0%9C-12</guid>
            <pubDate>Fri, 07 Oct 2022 07:41:05 GMT</pubDate>
            <description><![CDATA[<h3 id="설계한-도메인을-보고-엔티티-클래스-개발하기">설계한 도메인을 보고 엔티티 클래스 개발하기</h3>
<p>우선, 코드 작성하다 보면 매핑 시킨 애들마다 빨간줄 뜨는데 매핑이 완전히 안되면 뜨는거니까 일단 무시하고 코드 작성하면 된다.</p>
<p>시작..!</p>
<ol>
<li><p><code>Member</code> 클래스 만들고 속성추가. id에 @Id, @GenerateValue, @Column( )…</p>
</li>
<li><p>(1)<code>Address</code> 과 (2)<code>Order</code> 클래스 생성</p>
<ol>
<li><p>Address에서는 @Embeddable —&gt; Member의 Address에서는 @Embedded // 둘중 한나만 해도 됨</p>
</li>
<li><p>Order에서 id에 @Id, @GenerateValue, @Column( )… </p>
<blockquote>
</blockquote>
<ol>
<li><p>Member속성- Member개체와 주인 관계 따짐 —&gt; Order에 있는 member가 주인 :
@JoinColumn( )… 이거 달린애가 주인이라는 뜻 같음 / column이랑 비교해보자
@ManyToOne 달았는데 여기서 달았으니까 Member에서는 @OneToMany 달아주고 mappedBy = “member”  // mappedBy는 나는 주인아님 표시. “member”랑 연결시킨거는 Order의 member 속성이랑 매핑했다는 뜻인듯</p>
</li>
<li><p><code>OrderItem</code> 클래스 생성후 속성추가</p>
<ol>
<li><p>OrderItem 에서 @Id, @GenerateValue, @Column( )…</p>
</li>
<li><p><code>Item</code> 클래스 생성 후 속성추가</p>
<ol>
<li><p>얘는 추상 클래스로 만들거래 : class 앞에 abstract 쓰면됨</p>
</li>
<li><p>얘는 상속관계 매핑을 해야돼 (기본편 참고), domain 에서 얘를 상속할 애들을 만듦(book, album, movie)</p>
<blockquote>
<ol start="3">
<li>상속관계 매핑이기 때문에 상속관계 전략을 지정해야되는데 이 전략을 
부모 클래스에 잡아야해. 여기선 싱글테이블 전략임
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = “dtype”) —&gt; “book”이면 뭘할꺼야..</li>
</ol>
</blockquote>
</li>
<li><p>Album, Book, Movie가서 기본세팅해주고 
<code>@DiscriminatorValue</code> 설정</p>
</li>
</ol>
</li>
<li><p>Order속성 - Order개체와 주인 관계 —&gt; OrderItem에 있는 order가 주인:
@JoinColumn( )… 이거 달린애가 주인이라는 뜻 같음 / column이랑 비교
@ManyToOne 달았는데 여기(OrderItem)서 달았으니까 
Order가서는 @OneToMany 달아주고 mappedBy=“order” 나는 주인이 아니에요.
“order”랑 매핑돼서 얘가 주인이에요  라는 뜻 같음</p>
</li>
<li><p>Item 속성이 있는데 Item 개체와 주인관계 따짐. 
여기있는(OrderItem) item 속성이 주인이야.
@JoinColumn 이랑 @ManyToOne 달아줘</p>
</li>
</ol>
</li>
</ol>
</li>
<li><p><code>Delivery</code> 클래스 생성 후 속성추가</p>
<ol>
<li><p>Delivery 클래스에서 각 속성추가(DeliveryStatus)</p>
<blockquote>
<ol start="2">
<li>DeliveryStatus는 enum으로 생성 후 속성추가 해주는데 Enum 생성시 주의할점이 있음
Enum 타입은 @Enumerated를 넣어야 되는데 enum타입에는 ordinary랑 string이 있는데 default가 ordinary. 
ordinary는 숫자인데 밀리면 대참사(?) 라 무튼 string 타입으로 넣어줘—&gt;
<code>@Enumerated(EnumType.STRING)</code></li>
</ol>
</blockquote>
</li>
<li><p>Order랑은 1대1 관계. JPA 1대1 관계는 fk를 어디에 둬도 상관없는데 여기선 order 개체에 둘거임
 그럼 이제 연관관계의 주인은 fk랑 가까이있는 order에 있는 delivery로 주면 된다!!
 여기선(Delivery) @OneToOne(mappedBy = ‘delivery’) 붙여주고
 Order에 있는 delivery에 @OneToOne , @JoinColumn(name = “delivery_id”) 붙여주면 된다!</p>
</li>
</ol>
</li>
<li><p>LocalDateTime 속성 추가</p>
</li>
<li><p><code>OrderStatus</code> Enum으로 생성</p>
</li>
</ol>
</li>
<li><p><code>Category</code> 클래스 생성 후 속성추가</p>
<ol>
<li><p>items 속성이랑 뭔 짓 했음</p>
</li>
<li><p>카테고리 구조 -&gt; 계층구조 인데 어떻게 표현해야되지? 자기자신을 클래스 타입으로.. 코드참고</p>
</li>
</ol>
</li>
</ol>
<hr>
<h4 id="코드로-다시-확인하기">코드로 다시 확인하기</h4>
<ul>
<li><p>Member 도메인</p>
<pre><code class="language-java">@Entity
@Getter @Setter
public class Member {

  @Id
  @GeneratedValue
  @Column(name = &quot;member_id&quot;)
  private Long id;

  private String name;

  @Embedded
  private Address address;

  @OneToMany(mappedBy = &quot;member&quot;)
  private List&lt;Order&gt; orders = new ArrayList&lt;&gt;();
</code></pre>
</li>
</ul>
<p>}</p>
<pre><code>
- Order 도메인
```java
@Entity
@Table(name = &quot;orders&quot;)
@Getter @Setter
public class Order {

    @Id @GeneratedValue
    @Column(name = &quot;order_id&quot;)  // column을 테이블명의 아이디. DB들이 이 방식을 선호
    private Long id;

    // ___ToOne 인 애들은 기본 fetch가 EAGER
    // 그래서 LAZY로 바꿔줘야함
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;member_id&quot;)  //추가로 joinColumn. 매핑을 뭘로 할거냐..?
    // Member orders와 양방향 연관관계
    // 양방향 연관관계는 관계의 주인을 정해줘야헤. Order의 회원을 바꿀떄 여기의 값을 바꿀 수 있고 반대로 Member에서 orderList의 값을 바꿀수도있어
    // 양방향 참조인데 fk를 가지고 있는건 orders!!
    // 그래서 누가 주인이라고? fk가 가까운애?
    // Order에 있는 member를 주인으로 잡아야한다는데
    // 주인이라는게  Member 개체 vs Order개체에 있는 Member 를 비교하는거였어?
    private Member member;

    //___ToMany 인 애들은 기본 fetch가 LAZY
    // cascade: 뭘 한번에 해준대..
    @OneToMany(mappedBy = &quot;order&quot;, fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private List&lt;OrderItem&gt; orderItems = new ArrayList&lt;&gt;();

    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = &quot;delivery_id&quot;)
    private Delivery delivery;

    private LocalDateTime orderDate; // 주문시간

    @Enumerated(EnumType.STRING)
    private OrderStatus status; // 주문상태

    //==연관관계 메서드=//
    public void setMember(Member member) {
        this.member = member;
        member.getOrders().add(this);
    }

    public void addOrderItem(OrderItem orderItem) {
        orderItems.add(orderItem);
        orderItem.setOrder(this);
    }

    public void setDelivery(Delivery delivery) {
        this.delivery = delivery;
        delivery.setOrder(this);
    }


}
</code></pre><ul>
<li><p>Address 도메인</p>
<pre><code class="language-java">@Embeddable // 내장타입..?
@Getter
// 값 타입은 변경 불가능하게 설계해야 함
// 그래서 @Setter를 제거하고 생성자에서 값을 초기화해서 변경 불가능하게 만들자!
// JPA에서 @Entity나 @Embeddable은 기본 생성자를 public 또는 protected 로 설정해야 한다.
// public 으로 두는 것 보다는 protected 로 설정하는 것이 더 안전 하다.
public class Address {

  private String city;
  private String street;
  private String zipcode;

  protected Address() {

  }

  public Address(String city, String street, String zipcode) {
      this.city = city;
      this.street = street;
      this.zipcode = zipcode;
  }
}
</code></pre>
</li>
</ul>
<pre><code>
- OrderItem 도메인

```java
@Entity
@Getter @Setter
public class OrderItem {

    @Id @GeneratedValue
    @Column(name = &quot;order_item_id&quot;)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;item_id&quot;)
    private Item item;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;order_id&quot;)
    private Order order;

    private int orderPrice;
    private int count;
}
</code></pre><ul>
<li><p>Item 도메인 (abstract)</p>
<pre><code class="language-java">@Entity
//상속관계 매핑이기 때문에 상속관계 전략을 지정해야되는데 이 전략을 부모클래스에 잡아야해
//여기선 싱글 테이블 전략
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = &quot;dtype&quot;)  //ex. book이면 어떻게 할거야.. 뭔소리야
@Getter @Setter
public abstract class Item {

  @Id
  @GeneratedValue
  @Column(name = &quot;item_id&quot;)
  private Long id;

  private String name;
  private int price;
  private int stockQuantity;

  @ManyToMany(mappedBy = &quot;items&quot;)
  private List&lt;Category&gt; categories = new ArrayList&lt;&gt;();
</code></pre>
</li>
</ul>
<p>}</p>
<pre><code>
- Album, Book, Movie 도메인(Item을 상속)
```java
@Entity
@DiscriminatorValue(&quot;A&quot;)
@Getter @Setter
public class Album extends Item {

    private String artist;
    private String etc;
</code></pre><ul>
<li>OrderStatus 도메인(Enum)<pre><code class="language-java">public enum OrderStatus {
  ORDER, CANCEL
}</code></pre>
</li>
</ul>
<ul>
<li><p>Delivery 도메인</p>
<pre><code class="language-java">@Entity
@Getter @Setter
public class Delivery {

  @Id
  @GeneratedValue
  @Column(name = &quot;delivery_id&quot;)
  private Long id;

  //JPA 1대1 관게에서는 fk를 어디에 둬도 됨
  //여기선 fk를 order에 둘거임
  //그럼이제 연관관계 주인은 fk랑 가까이있는 order에 있는 delivery로 주면 됨
  @OneToOne(mappedBy = &quot;delivery&quot;, fetch = FetchType.LAZY)
  private Order order;

  @Embedded
  private Address address;

  //Enum으로 만들건데 enum타입은 조심해야되는게있음
  // @Enumerated를 넣어야되는데 이넘 타입 넣을때 ordinary랑 string이 있는데 ordinary 가 default. 꼭 string으로 쓰세요
  @Enumerated(EnumType.STRING)
  private DeliveryStatus status; //READY, COMP
}</code></pre>
</li>
<li><p>Category 도메인</p>
<pre><code class="language-java">@Entity
@Getter @Setter
public class Category {

  @Id
  @GeneratedValue
  @Column(name = &quot;category_id&quot;)
  private Long id;

  private String name;

  @ManyToMany
  @JoinTable(name = &quot;category_item&quot;,
          joinColumns = @JoinColumn(name = &quot;category_id&quot;),
          inverseJoinColumns =  @JoinColumn(name = &quot;item_id&quot;)) // 중간 테이블 매핑을 해줘야함.  JPA에서 다대다  --&gt;  1대다-다대1.. 근데 쓰지말
  private List&lt;Item&gt; items = new ArrayList&lt;&gt;();

</code></pre>
</li>
</ul>
<pre><code>// 카테고리 구조, 계층구조 어떻게하지?
// 부모가 타입이니까 Category 로 넣어주고
// 부모니까 ManyToOne이겠지? --&gt; JoinColumn()..
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = &quot;parent_id&quot;)
private Category parent;
// 자식은 여러개 가질 수 있잖여
// mappedBy에 부모 넣어주면 됨
@OneToMany(mappedBy = &quot;parent&quot;)
private List&lt;Category&gt; child = new ArrayList&lt;&gt;();


//==연관관계 메서드==//
public void addChildCategory(Category child) {
    this.child.add(child);
    child.setParent(this);

}</code></pre><p>}</p>
<p>```</p>
<p>후우.. 힘들어따...</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JPA] 도메인 분석 설계]]></title>
            <link>https://velog.io/@jun_0y/JPA-%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/@jun_0y/JPA-%EB%8F%84%EB%A9%94%EC%9D%B8-%EB%B6%84%EC%84%9D-%EC%84%A4%EA%B3%84</guid>
            <pubDate>Fri, 07 Oct 2022 07:05:04 GMT</pubDate>
            <description><![CDATA[<h3 id="도메인-분석-및-설계">도메인 분석 및 설계</h3>
<p>우선 쇼핑몰 요구사항을 분석한다</p>
<h4 id="기능목록">기능목록</h4>
<ul>
<li><p>회원기능</p>
<ul>
<li>회원등록</li>
<li>회원조회</li>
</ul>
</li>
<li><p>상품기능</p>
<ul>
<li>상품등록</li>
<li>상품수정</li>
<li>상품조회</li>
</ul>
</li>
<li><p>주문기능</p>
<ul>
<li>상품주문</li>
<li>주문내역조회</li>
<li>주문취소</li>
</ul>
</li>
<li><p>기타요구사항</p>
<ul>
<li>상품은 제고 관리가 필요하다</li>
<li>상품의 종류는 도서,음반,영화가 있다</li>
<li>상품을 카테고리로 구분할 수 있다</li>
<li>상품 주문시 배송 정보를 입력할 수 있다</li>
</ul>
</li>
</ul>
<h3 id="도메인-모델과-테이블-설계">도메인 모델과 테이블 설계</h3>
<p><img src="https://velog.velcdn.com/images/jun_0y/post/15ef4659-343d-4704-b2fb-77013b73e3e1/image.png" alt=""></p>
<ul>
<li><p>실무에서는 다대다를 쓰면 안된대.. -&gt; 1대다 다대1  이렇게 하래</p>
</li>
<li><p>양방향 쓰지말래..</p>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jun_0y/post/dd9af1eb-81b9-451a-accf-3f66bbdaf4f1/image.png" alt=""></p>
<ul>
<li>member 와 order가 있을 때  회원이 주문을 하니까 Member에 ‘orders : List’를 두면 되겠다! 했는데 이렇게 하면 안된다!! 회원이 주문을 생성하는게 아니라 주문을 생성할 때 회원이 필요하다!!!!</li>
</ul>
<ul>
<li>fk가 있는게 1대다 주인</li>
<li>1대N 연관관계에서는 보통 N 쪽이 주인임</li>
</ul>
<p><img src="https://velog.velcdn.com/images/jun_0y/post/67bcd8c7-5694-4bca-a4dc-452930f43949/image.png" alt=""></p>
<blockquote>
<p>뭐 주저리주저리 설명이 많았는데 그냥 도메인 설계한 것에 대한 설명이고 결론은 이거 JPA 기본 강의를 들어야 될 것 같다... 수업 때 들은거랑 너무다른데..?</p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>