<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>Polymor!</title>
    <link>https://mysoftworld.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Wed, 17 Jun 2026 13:34:35 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Megan Kim</managingEditor>
    <item>
      <title>kernel parameters</title>
      <link>https://mysoftworld.tistory.com/54</link>
      <description></description>
      <category>Linux</category>
      <author>Megan Kim</author>
      <guid isPermaLink="true">https://mysoftworld.tistory.com/54</guid>
      <comments>https://mysoftworld.tistory.com/54#entry54comment</comments>
      <pubDate>Sun, 4 Dec 2022 13:56:46 +0900</pubDate>
    </item>
    <item>
      <title>ssh 로그인 없이 통신하기</title>
      <link>https://mysoftworld.tistory.com/48</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;client&amp;gt; ssh &lt;b&gt;server&lt;/b&gt;Hostname@&lt;b&gt;serverIP&lt;/b&gt; 인 상황일때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;b&gt;client&lt;/b&gt;&amp;nbsp; &amp;nbsp;---------------------&amp;gt;---------------&amp;gt;--------------------&amp;gt;---------&amp;nbsp; &amp;nbsp;&lt;b&gt;server&lt;/b&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 1. &lt;b&gt;ssh-keygen -t rsa&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; -&amp;gt; ~/.ssh/ 밑에 id_rsa(비밀키) , id_rsa.pub(공개키)생김&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 2. id_rsa.pub(공개키)를 server로 옮김&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &lt;b&gt;&amp;nbsp; ~/.ssh/authorized_keys 파일에&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 3.&amp;nbsp; ~/.ssh/known_hosts 에는 서버 rsa공유키&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;cat&amp;nbsp; /etc/ssh/ssh_host_rsa_key.pub&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; 값이 ~/.ssh/id_rsa.pub 이랑 다를수있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp; &amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이랬는데도 안되는 케이스가 있다..&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1. REMOTE HOST IDENTIFICATION HAS CHANGED!&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; &amp;nbsp; &amp;gt; client-server 연결이 최초가 아닐때&amp;nbsp; 발생할 수 있는 에러&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screen Shot 2022-03-09 at 1.52.10 PM.png&quot; data-origin-width=&quot;1482&quot; data-origin-height=&quot;674&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCyw07/btrvxkVaIjH/LsesAdxwske3OxWZiWwfm1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCyw07/btrvxkVaIjH/LsesAdxwske3OxWZiWwfm1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCyw07/btrvxkVaIjH/LsesAdxwske3OxWZiWwfm1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCyw07%2FbtrvxkVaIjH%2FLsesAdxwske3OxWZiWwfm1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;468&quot; height=&quot;213&quot; data-filename=&quot;Screen Shot 2022-03-09 at 1.52.10 PM.png&quot; data-origin-width=&quot;1482&quot; data-origin-height=&quot;674&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;client&amp;gt; ~/.ssh/known_hosts 라는 파일은 기존에 연결된 적이있는 서버의 rsa 공유키가 저장된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 현재 접속하려는 서버의 rsa 공유키가 달라져있다면 문제가 생기는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[3] 과정대로 하던가, 혹은 서버가 내 접속권한이 없는 상황이라면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;client&amp;gt; ssh-keygen -R serverIP 로 초기화 시켜준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;추가적으로, [3] 에서 /etc/ssh/ssh_host_rsa_key.pub 가 아니라 ~/.ssh/id_rsa.pub 을 했을때 안될 수 있으니 유의해라.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ssh 통신 과정에서 서버의 rsa 공유키 체크를 etc/ssh/ssh_host_rsa_key.pub 이 위치에 있는 것을 하는 듯하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.&amp;nbsp; rsa 공유키 인증 로그인이 아예 안될 때&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #444444;&quot;&gt;server&amp;gt; vi /etc/ssh/sshd_config [루트 권한으로 들어가야함]&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #444444;&quot;&gt;&lt;span&gt;#주석이 있다면 풀어주세요.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #444444;&quot;&gt;&lt;span&gt;&lt;span style=&quot;background-color: #ffffff; color: #444444;&quot;&gt;PubkeyAuthentication yes&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #ffffff; color: #444444;&quot;&gt;AuthorizedKeysFile&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp; .ssh/authorized_keys&lt;/span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. client&amp;gt; ~/.ssh/id_rsa 의 권한 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;owner를 제외한 그룹,other 에 read,write,execute권한이 부여되있을 경우 이 비밀키를 신뢰하지않아 효력잃게됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;client&amp;gt;&amp;nbsp; chmod 600 ~/.ssh/id_rsa&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Linux</category>
      <author>Megan Kim</author>
      <guid isPermaLink="true">https://mysoftworld.tistory.com/48</guid>
      <comments>https://mysoftworld.tistory.com/48#entry48comment</comments>
      <pubDate>Thu, 28 Apr 2022 15:21:25 +0900</pubDate>
    </item>
    <item>
      <title>내가 너무 일하기가 싫어서 ... 젠킨스 (1)</title>
      <link>https://mysoftworld.tistory.com/47</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 글을 쓴지 1년이 되었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난 해 3월을 끝으로 다시 글을 쓰게 된건 조금 부끄러운 일이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그동안 글을 못쓴 이유와 다시 쓰는 이유는,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 회사에 취업했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 회사의 인터넷망은 웃기게도 티스토리 24시간 차단이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 이 회사는 놀랍도록 &lt;b&gt;레거시하게&lt;/b&gt; 서비스를 운영한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 그래서 불만이 많은 사람들은 아름다운 &lt;b&gt;CI/CD&lt;/b&gt;를 구축하고자한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;5. 그 대표적인 젠킨스를 어느정도 공부했고, 현재 이 툴을 사용하고 있고 이제 이걸 정리할 시간이 된 것같다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screen Shot 2022-03-09 at 1.10.45 PM.png&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;616&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c466y3/btrvyhXSQUJ/loeQbZSJdU6I5uHkOo4s01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c466y3/btrvyhXSQUJ/loeQbZSJdU6I5uHkOo4s01/img.png&quot; data-alt=&quot;으악!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c466y3/btrvyhXSQUJ/loeQbZSJdU6I5uHkOo4s01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc466y3%2FbtrvyhXSQUJ%2FloeQbZSJdU6I5uHkOo4s01%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;564&quot; height=&quot;414&quot; data-filename=&quot;Screen Shot 2022-03-09 at 1.10.45 PM.png&quot; data-origin-width=&quot;840&quot; data-origin-height=&quot;616&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;으악!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 자만한 표정의 아저씨가 젠킨스다. 그리고 저 고래는 Docker..&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;솔직히 말해서 젠킨스 별거 없다 생각했다. 한 2주면 다 하지않겠어? 라고 생각했지만 그건 내가 자만했던것..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;어떠한 테크를 현업에 도입할 때엔 아래와 같은 순서가 필요하다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 현 업무 중 사람이 하지 않아도 되는일과 사람이 꼭 해야하는 일 구분 하기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 사람이 하지 않아도 되는 일을 대신 하게될 적당한 툴 찾기/만들기 (여기서 나는 젠킨스를 채택)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 구체적으로 업무 흐름도에 맞게 설계하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- git commit 되면 개발팀이 운영팀에게 구두로 전달 ====&amp;gt; git polling 커밋 시점 인지 [젠킨스가 해줌]&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- git 에서 local pc 로 다운로드 받기&amp;nbsp; ====&amp;gt;&amp;nbsp; git clone해서&amp;nbsp; jenkins서버로 가져옴 [젠킨스가해줌]&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- stage 서버에 반영사항 배포 하고 테스트 ====&amp;gt; 개발자가 작성한 shell 실행하여 배포 자동화 [젠킨스가 해줌]&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- 이전 버전의 백업 파일 local pc에 보관(규칙따윈없음) ====&amp;gt; 날짜에 맞게 파일 백업 및 관리해줌 [젠킨스가 해줌]&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- 실서버에 반영사항을 서버에 배포하기 ====&amp;gt; 개발자가 작성한 shell 실행하여 배포 자동화 [젠킨스가 해줌, 실수없이!]&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- 배포 후 테스트 진행 ====&amp;gt; test 시나리오 job 으로 테스트 진행 및 결과 로깅 [젠킨스가 해줌]&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- rollback 을 해야할 때 직접 백업 파일 옮겨다 재배포 ====&amp;gt; rollback 시나리오 job으로 자동 재 배포 [젠킨스가 해줌]&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 젠킨스를 설치한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 필요한 플러그인을 다운로드 받는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 젠킨스 서버와 당사 서버들 간 방화벽 설정을 한다. (인프라팀 요청)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 스테이지 서버를 타켓 서버로 설정하고 3의 과정의 flow를 구현해낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. 테스트를 거듭하고 실제 사용을 하며 안정화 및 고도화 작업 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9. 팀장님께 결과를 보여드린다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10. 실서버에 젠킨스를 도입하여 프로세스를 전환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;11. 모니터링 툴, 로그 툴, 그리고 도커까지 계속하여 고도화 작업을 진행한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;끝 ! 그럼 이제 젠킨스 빌드 버튼을 누르면 알아서 내가 1시간 넘게 해야 했던 일들을 2~3초만에 해준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rollback이 더이상 두렵지도않다. 사람보다 소프트웨어가 정확하고 신속하니까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;다시한번 말하지만 정말 일이 하기가 싫어서 젠킨스를 구축한거다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Infrastructure</category>
      <author>Megan Kim</author>
      <guid isPermaLink="true">https://mysoftworld.tistory.com/47</guid>
      <comments>https://mysoftworld.tistory.com/47#entry47comment</comments>
      <pubDate>Wed, 9 Mar 2022 13:30:18 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] JPA 쿼리 분석 (1) 연관관계 정의</title>
      <link>https://mysoftworld.tistory.com/45</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;u&gt;&lt;i&gt;1 : N (일대다) 연관 관계를 갖는 Cart와 CartItem을 상상하자.&amp;nbsp;&lt;/i&gt;&lt;/u&gt;&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1개의 Cart는 N개의 CartItem을 가질 수 있다. 양방향 연관관계 매핑을 아래와 같이 한다.&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;i&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;CascadeType.ALL은 영속성을 전이하는 것이다. Cart의 영속성이 연관관계를 갖는 CartItem list Entities에 전이되는 것 뿐이다.그러나 Cart가 CartItem의 변경감지를 대신해주진 않음 절대로.&lt;/span&gt;&lt;/i&gt;&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1615430892018&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Cart Entity 

@OneToMany(fetch = FetchType.LAZY,mappedBy=&quot;cart&quot;,cascade = CascadeType.ALL, orphanRemoval = true)    // 02-15 Megan
    private List&amp;lt;CartItem&amp;gt; cartItems;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1615430909280&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// CartItem Entity

@ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;cart_id&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;* Cart가 가지고 있는 연관 엔티티 CartItem 들 중 하나의 quantity 값을 수정한다고 가정하자.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. Cart 영속성 : 없음&lt;/b&gt; -&amp;gt; &lt;b&gt;CartItem 영속성 : 없음&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;( @Service logic에는 @transactional 없고, getCartItems()는 cart 엔티티 내부 메서드이다. != repo)&lt;/p&gt;
&lt;pre id=&quot;code_1615431046206&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; public void UpdateItemQuantity(long cartid,Integer quantity) {

        
         Cart cart = this.findById(cartid);

         cart.getCartItems().get(0).UpdateQuantity(quantity);
        
        

    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;결과: Select 2회&lt;/b&gt; (FetchType.Lazy로 설정했기때문에 getCartItems() 호출 시에 두번째 쿼리가 날라간다.)&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1615431148060&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Hibernate: select cart0_.cart_id as cart_id1_1_0_, cart0_.account_id as account20_1_0_, cart0_.address as address2_1_0_, cart0_.city as city3_1_0_, cart0_.content as content4_1_0_, cart0_.country as country5_1_0_, cart0_.created_at as created_6_1_0_, cart0_.grand_total as grand_to7_1_0_, cart0_.item_discount as item_dis8_1_0_, cart0_.item_price_total as item_pri9_1_0_, cart0_.province as provinc10_1_0_, cart0_.road_address as road_ad11_1_0_, cart0_.session_id as session12_1_0_, cart0_.shipping as shippin13_1_0_, cart0_.status as status14_1_0_, cart0_.tax as tax15_1_0_, cart0_.token as token16_1_0_, cart0_.updated_at as updated17_1_0_, cart0_.user_discount as user_di18_1_0_, cart0_.zip_code as zip_cod19_1_0_ from shop.cart cart0_ where cart0_.cart_id=?


Hibernate: select cartitems0_.cart_id as cart_id10_2_0_, cartitems0_.cart_item_id as cart_ite1_2_0_, cartitems0_.cart_item_id as cart_ite1_2_1_, cartitems0_.active as active2_2_1_, cartitems0_.cart_id as cart_id10_2_1_, cartitems0_.content as content3_2_1_, cartitems0_.created_at as created_4_2_1_, cartitems0_.discount_price as discount5_2_1_, cartitems0_.price as price6_2_1_, cartitems0_.product_id as product11_2_1_, cartitems0_.quantity as quantity7_2_1_, cartitems0_.sku as sku8_2_1_, cartitems0_.updated_at as updated_9_2_1_ from shop.cart_item cartitems0_ where cartitems0_.cart_id=?&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. Cart 영속성 : 저장 -&amp;gt; CartItem 영속성 : 저장&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1615431477576&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; public void UpdateItemQuantity(long cartid,Integer quantity) {

        
         Cart cart = this.findById(cartid);

         cart.getCartItems().get(0).UpdateQuantity(quantity);
        
        
         save(cart);
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;결과 : Select 2회 + update 1회 (Cart는 변경감지 없으므로 CartItem만 update 쿼리날림)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1615431654349&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Hibernate: select cart0_.cart_id as cart_id1_1_0_, cart0_.account_id as account20_1_0_, cart0_.address as address2_1_0_, cart0_.city as city3_1_0_, cart0_.content as content4_1_0_, cart0_.country as country5_1_0_, cart0_.created_at as created_6_1_0_, cart0_.grand_total as grand_to7_1_0_, cart0_.item_discount as item_dis8_1_0_, cart0_.item_price_total as item_pri9_1_0_, cart0_.province as provinc10_1_0_, cart0_.road_address as road_ad11_1_0_, cart0_.session_id as session12_1_0_, cart0_.shipping as shippin13_1_0_, cart0_.status as status14_1_0_, cart0_.tax as tax15_1_0_, cart0_.token as token16_1_0_, cart0_.updated_at as updated17_1_0_, cart0_.user_discount as user_di18_1_0_, cart0_.zip_code as zip_cod19_1_0_ from shop.cart cart0_ where cart0_.cart_id=?
Hibernate: select cartitems0_.cart_id as cart_id10_2_0_, cartitems0_.cart_item_id as cart_ite1_2_0_, cartitems0_.cart_item_id as cart_ite1_2_1_, cartitems0_.active as active2_2_1_, cartitems0_.cart_id as cart_id10_2_1_, cartitems0_.content as content3_2_1_, cartitems0_.created_at as created_4_2_1_, cartitems0_.discount_price as discount5_2_1_, cartitems0_.price as price6_2_1_, cartitems0_.product_id as product11_2_1_, cartitems0_.quantity as quantity7_2_1_, cartitems0_.sku as sku8_2_1_, cartitems0_.updated_at as updated_9_2_1_ from shop.cart_item cartitems0_ where cartitems0_.cart_id=?
2021-03-11 11:42:33.445  INFO 1088 --- [  restartedMain] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: findOrderIdUsingGET_1
Hibernate: update shop.cart_item set active=?, cart_id=?, content=?, created_at=?, discount_price=?, price=?, product_id=?, quantity=?, sku=?, updated_at=? where cart_item_id=?&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. Cart 영속성 : 변경감지로 인한 저장 -&amp;gt; CartItem 영속성 : 저장&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1615431533767&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@transactional 
public void UpdateItemQuantity(long cartid,Integer quantity) {

        
         Cart cart = this.findById(cartid);

         cart.getCartItems().get(0).UpdateQuantity(quantity);
        
        
         cart.updateItem(1000,10,10);
         
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;b&gt;결과 : Select 2회 + update 2회&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1615431693138&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Hibernate: select cart0_.cart_id as cart_id1_1_0_, cart0_.account_id as account20_1_0_, cart0_.address as address2_1_0_, cart0_.city as city3_1_0_, cart0_.content as content4_1_0_, cart0_.country as country5_1_0_, cart0_.created_at as created_6_1_0_, cart0_.grand_total as grand_to7_1_0_, cart0_.item_discount as item_dis8_1_0_, cart0_.item_price_total as item_pri9_1_0_, cart0_.province as provinc10_1_0_, cart0_.road_address as road_ad11_1_0_, cart0_.session_id as session12_1_0_, cart0_.shipping as shippin13_1_0_, cart0_.status as status14_1_0_, cart0_.tax as tax15_1_0_, cart0_.token as token16_1_0_, cart0_.updated_at as updated17_1_0_, cart0_.user_discount as user_di18_1_0_, cart0_.zip_code as zip_cod19_1_0_ from shop.cart cart0_ where cart0_.cart_id=?
Hibernate: select cartitems0_.cart_id as cart_id10_2_0_, cartitems0_.cart_item_id as cart_ite1_2_0_, cartitems0_.cart_item_id as cart_ite1_2_1_, cartitems0_.active as active2_2_1_, cartitems0_.cart_id as cart_id10_2_1_, cartitems0_.content as content3_2_1_, cartitems0_.created_at as created_4_2_1_, cartitems0_.discount_price as discount5_2_1_, cartitems0_.price as price6_2_1_, cartitems0_.product_id as product11_2_1_, cartitems0_.quantity as quantity7_2_1_, cartitems0_.sku as sku8_2_1_, cartitems0_.updated_at as updated_9_2_1_ from shop.cart_item cartitems0_ where cartitems0_.cart_id=?
2021-03-11 11:42:33.445  INFO 1088 --- [  restartedMain] .d.s.w.r.o.CachingOperationNameGenerator : Generating unique operation named: findOrderIdUsingGET_1
Hibernate: update shop.cart set account_id=?, address=?, city=?, content=?, country=?, created_at=?, grand_total=?, item_discount=?, item_price_total=?, province=?, road_address=?, session_id=?, shipping=?, status=?, tax=?, token=?, updated_at=?, user_discount=?, zip_code=? where cart_id=?
Hibernate: update shop.cart_item set active=?, cart_id=?, content=?, created_at=?, discount_price=?, price=?, product_id=?, quantity=?, sku=?, updated_at=? where cart_item_id=?&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;(참고) FetchType.Lazy vs FetchType.Eager&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1615431854169&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# fetchType.LAZY - cartItem 정보 없음

Hibernate: select cart0_.cart_id as cart_id1_1_0_, cart0_.account_id as account20_1_0_, cart0_.address as address2_1_0_, cart0_.city as city3_1_0_, cart0_.content as content4_1_0_, cart0_.country as country5_1_0_, cart0_.created_at as created_6_1_0_, cart0_.grand_total as grand_to7_1_0_, cart0_.item_discount as item_dis8_1_0_, cart0_.item_price_total as item_pri9_1_0_, cart0_.province as provinc10_1_0_, cart0_.road_address as road_ad11_1_0_, cart0_.session_id as session12_1_0_, cart0_.shipping as shippin13_1_0_, cart0_.status as status14_1_0_, cart0_.tax as tax15_1_0_, cart0_.token as token16_1_0_, cart0_.updated_at as updated17_1_0_, cart0_.user_discount as user_di18_1_0_, cart0_.zip_code as zip_cod19_1_0_ 
           from shop.cart cart0_ 
           where cart0_.cart_id=?



#fetchType.EAGER - join 해서 다 가져옴

Hibernate: select cart0_.cart_id as cart_id1_1_0_, cart0_.account_id as account20_1_0_, cart0_.address as address2_1_0_, cart0_.city as city3_1_0_, cart0_.content as content4_1_0_, cart0_.country as country5_1_0_, cart0_.created_at as created_6_1_0_, cart0_.grand_total as grand_to7_1_0_, cart0_.item_discount as item_dis8_1_0_, cart0_.item_price_total as item_pri9_1_0_, cart0_.province as provinc10_1_0_, cart0_.road_address as road_ad11_1_0_, cart0_.session_id as session12_1_0_, cart0_.shipping as shippin13_1_0_, cart0_.status as status14_1_0_, cart0_.tax as tax15_1_0_, cart0_.token as token16_1_0_, cart0_.updated_at as updated17_1_0_, cart0_.user_discount as user_di18_1_0_, cart0_.zip_code as zip_cod19_1_0_, cartitems1_.cart_id as cart_id10_2_1_, cartitems1_.cart_item_id as cart_ite1_2_1_, cartitems1_.cart_item_id as cart_ite1_2_2_, cartitems1_.active as active2_2_2_, cartitems1_.cart_id as cart_id10_2_2_, cartitems1_.content as content3_2_2_, cartitems1_.created_at as created_4_2_2_, cartitems1_.discount_price as discount5_2_2_, cartitems1_.price as price6_2_2_, cartitems1_.product_id as product11_2_2_, cartitems1_.quantity as quantity7_2_2_, cartitems1_.sku as sku8_2_2_, cartitems1_.updated_at as updated_9_2_2_ 
           from shop.cart cart0_ 
           left outer join shop.cart_item cartitems1_ on cart0_.cart_id=cartitems1_.cart_id 
           where cart0_.cart_id=?&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Web</category>
      <category>JPA</category>
      <category>Spring</category>
      <author>Megan Kim</author>
      <guid isPermaLink="true">https://mysoftworld.tistory.com/45</guid>
      <comments>https://mysoftworld.tistory.com/45#entry45comment</comments>
      <pubDate>Thu, 11 Mar 2021 12:08:17 +0900</pubDate>
    </item>
    <item>
      <title>JPA , hibernate, 그리고 SpringDataApi</title>
      <link>https://mysoftworld.tistory.com/37</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;JPA ?&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span&gt;Java Persistence API 라는 뜻으로 &lt;/span&gt;&lt;span&gt;Java 진영의 &lt;/span&gt;&lt;span&gt;ORM(Object-Relational Mapping) &lt;/span&gt;&lt;span&gt;기술의 표준이다.&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;객체와 데이터베이스 간의 적절하고 이상적인 매핑은 개발자들의 가장 큰 고민이 될테며 JPA는 이를 해결해주기 위한 솔루션이다.&lt;span style=&quot;color: #333333;&quot;&gt;개발자가 JDBC 를 직접적으로 쓰지않고 JPA가 쿼리를 날려주고 영속성을 관리해주는 등 큰 메리트가 있어 Java 개발에 있어 매우 편리함을 제공한다.&amp;nbsp; 가장 큰 이점은 '&lt;span style=&quot;color: #ee2323;&quot;&gt;패러다임의 불일치&lt;/span&gt;'(&lt;u&gt;RDB엔 상속,다형성,객체,참조 등과같은 객체지향 성격이없다&lt;/u&gt;) 문제를 해결해주는 것이다. 데이터베이스에는 상속관계가 없고 양방향 연관관계가 존재하지않는데, 객체에선 이를 어떻게 바라보고 데이터를 담아내야하는지 등에대한 고민을 하게된다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Screen Shot 2021-02-06 at 4.17.45 PM.png&quot; data-origin-width=&quot;1734&quot; data-origin-height=&quot;864&quot; width=&quot;740&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vkYyQ/btqV8FeAUO0/DmEVPaiuujXjfXBjkJvvGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vkYyQ/btqV8FeAUO0/DmEVPaiuujXjfXBjkJvvGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vkYyQ/btqV8FeAUO0/DmEVPaiuujXjfXBjkJvvGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvkYyQ%2FbtqV8FeAUO0%2FDmEVPaiuujXjfXBjkJvvGK%2Fimg.png&quot; data-filename=&quot;Screen Shot 2021-02-06 at 4.17.45 PM.png&quot; data-origin-width=&quot;1734&quot; data-origin-height=&quot;864&quot; width=&quot;740&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1614143733638&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface EntityManager {

    public void persist(Object entity);

    public &amp;lt;T&amp;gt; T merge(T entity);

    public void remove(Object entity);

    public &amp;lt;T&amp;gt; T find(Class&amp;lt;T&amp;gt; entityClass, Object primaryKey);

}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;u&gt;**참고로 MyBatis는 SPL Mapper이고 JPA는 Object Mapper 이기에 서로 다른 개념이다.&lt;/u&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;&lt;i&gt;&lt;b&gt;Hibernate vs JPA?&amp;nbsp;&lt;/b&gt;&lt;/i&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;JPA는 Interface이고 Hibernate는 그 JPA 를 구현하는 구현체(Class)이다. 물론 구현체로 hibernate외에 &lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;DataNucleus, EclipseLink등 도 있으니 꼭 hibernate를 사용하는 것은 아니다. 'spring-starter-data-jpa'에는 hibernate 기능을 사용하는건 참고하자.&amp;nbsp;&lt;/span&gt;&lt;span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;아래와 같은 관계라고 생각하면 편하다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1614143638735&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// JPA interface

public interface JPAInterface{

    void method1(){}
    void method2(){}
    
    
    }&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1614143701379&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// Hibernate Implementation

public class hiberateclass implements JPAInterface{

	void method1(){ System.out.println(&quot;method1&quot;); }
    void method1(){ System.out.println(&quot;method2&quot;); }
    
   }
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;&lt;i&gt;&lt;b&gt;Spring Data JPA?&amp;nbsp;&lt;/b&gt;&lt;/i&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;쉽게말하면 JPA를 한단계 더 추상화한 것이라고 생각하자. 이를테면 우리는 Spring 개발에 있어 entity manager을 직접 가져다 쓰기 보단 repository를 사용하는데, 이것이 바로 JPA를 추상화한 인터페이스이다. Repository&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;인터페이스의 기본 구현체인&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;SimpleJpaRepository&lt;span&gt;의 코드를 보면 아래와 같이 내부적으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;EntityManager&lt;span&gt;을 사용하고 있는 것을 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1614143838648&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SimpleJpaRepository&amp;lt;T, ID&amp;gt; implements JpaRepositoryImplementation&amp;lt;T, ID&amp;gt; {

    private final EntityManager em;

    public Optional&amp;lt;T&amp;gt; findById(ID id) {

        Assert.notNull(id, ID_MUST_NOT_BE_NULL);

        Class&amp;lt;T&amp;gt; domainType = getDomainClass();

        if (metadata == null) {
            return Optional.ofNullable(em.find(domainType, id));
        }

        LockModeType type = metadata.getLockModeType();

        Map&amp;lt;String, Object&amp;gt; hints = getQueryHints().withFetchGraphs(em).asMap();

        return Optional.ofNullable(type == null ? em.find(domainType, id, hints) : em.find(domainType, id, type, hints));
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Screen Shot 2021-02-24 at 2.19.04 PM.png&quot; data-origin-width=&quot;1394&quot; data-origin-height=&quot;1316&quot; width=&quot;662&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JxWcP/btqYoZQaT7m/FmC8H2z35QCsjYuY89c7P0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JxWcP/btqYoZQaT7m/FmC8H2z35QCsjYuY89c7P0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JxWcP/btqYoZQaT7m/FmC8H2z35QCsjYuY89c7P0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJxWcP%2FbtqYoZQaT7m%2FFmC8H2z35QCsjYuY89c7P0%2Fimg.png&quot; data-filename=&quot;Screen Shot 2021-02-24 at 2.19.04 PM.png&quot; data-origin-width=&quot;1394&quot; data-origin-height=&quot;1316&quot; width=&quot;662&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;i&gt;&lt;span style=&quot;color: #ffc1c8;&quot;&gt;추가적으로, JPARepository는 무엇이냐?&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1614144094020&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// JPArepository를 이용하여 memberrepository 만들기

public interface MemberRepository extends JpaRepository&amp;lt;Member, Long&amp;gt; {

Member findByName(String name);

Page&amp;lt;Member&amp;gt; findByName(String name, Pageable pageable);
}

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;필자는 위와같이 JpaRepository를 뭔지도 모르면서 무작정 갖다썼던 때가 있다.&amp;nbsp; 그러다 생각을 해보니,&amp;nbsp;&lt;/p&gt;
&lt;p&gt;MemberRepository 도 인터페이스이고, JpaRepository도 인터페이스일텐데, 대체 class가 없는데 이걸 어떻게 쓰고 있었던거지?&lt;/p&gt;
&lt;p&gt;그리고 @Repository는 왜 안붙여도 동작하는거지? 하는 원초적인 질문을 갖게 되었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;JpaRepository는 기본적으로 CRUD 관련 메소드들만 갖고있는 인터페이스인데 Spring에서 관리를 해주어 실행될때 클래스도 생성되고 빈으로 자동 등록되어 사용가능하게 되는 것이다. 더불어 쿼리까지 생성하여 날려주니 더할 나위없이 편리한 기능들을 제공한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;JpaRepository 내에 entitymanager 가 있을 것이고 그래서 @Repository 어노테이션을 안붙여도 된다고 한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Web</category>
      <author>Megan Kim</author>
      <guid isPermaLink="true">https://mysoftworld.tistory.com/37</guid>
      <comments>https://mysoftworld.tistory.com/37#entry37comment</comments>
      <pubDate>Wed, 24 Feb 2021 14:27:05 +0900</pubDate>
    </item>
    <item>
      <title>[네이버 라이브 쇼핑]스트리밍 서버 분석 1편</title>
      <link>https://mysoftworld.tistory.com/46</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;영상 기반의 이커머스 서비스에 관심을 가지게 된지 벌써 6개월이 지났다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;예상한대로 2021년,&amp;nbsp; 네이버, 쿠팡, 배민 할 것 없이 국내에 내놓아라 하는 IT기업에서 이 서비스를 수면위로 하나 둘 들어내고있다.&amp;nbsp; 그들의 채용 공고만 보아도 앞으로 이 서비스가 얼마나 핫해질지 예상할 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1682&quot; data-origin-height=&quot;1368&quot; data-filename=&quot;Screen Shot 2021-02-19 at 1.42.27 PM.png&quot; width=&quot;447&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/otQ7I/btqXRvbpm2E/OzIxILk7bKkf4P9IMQNQL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/otQ7I/btqXRvbpm2E/OzIxILk7bKkf4P9IMQNQL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/otQ7I/btqXRvbpm2E/OzIxILk7bKkf4P9IMQNQL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FotQ7I%2FbtqXRvbpm2E%2FOzIxILk7bKkf4P9IMQNQL1%2Fimg.png&quot; data-origin-width=&quot;1682&quot; data-origin-height=&quot;1368&quot; data-filename=&quot;Screen Shot 2021-02-19 at 1.42.27 PM.png&quot; width=&quot;447&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;2588&quot; data-origin-height=&quot;1802&quot; data-filename=&quot;Screen Shot 2021-02-19 at 1.42.45 PM.png&quot; width=&quot;458&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kIOhQ/btqXU4D0914/dNEbDHbucL9LZVcuNqQQT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kIOhQ/btqXU4D0914/dNEbDHbucL9LZVcuNqQQT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kIOhQ/btqXU4D0914/dNEbDHbucL9LZVcuNqQQT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkIOhQ%2FbtqXU4D0914%2FdNEbDHbucL9LZVcuNqQQT1%2Fimg.png&quot; data-origin-width=&quot;2588&quot; data-origin-height=&quot;1802&quot; data-filename=&quot;Screen Shot 2021-02-19 at 1.42.45 PM.png&quot; width=&quot;458&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;그리고 또&amp;nbsp; 하나 주목해야할&lt;i&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt; 중국의 '콰이쇼우'라는 중국판 유튜브&lt;/span&gt;&lt;/i&gt;로 불리는 이 기업. 올 해 2월 드디어 주식시장에 등판하면서 엄청난 주목을 받고 있다.&amp;nbsp;머지않아 영상 기술이 시장을 장악할 것이고 이에 대한 관심도는 상승할 것이라고 예상한다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Screen Shot 2021-02-19 at 1.46.09 PM.png&quot; data-origin-width=&quot;1644&quot; data-origin-height=&quot;1626&quot; width=&quot;373&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Mj9QZ/btqXQ5Yf2UQ/ciBNITFKnGXZr0vtWnmWKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Mj9QZ/btqXQ5Yf2UQ/ciBNITFKnGXZr0vtWnmWKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Mj9QZ/btqXQ5Yf2UQ/ciBNITFKnGXZr0vtWnmWKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMj9QZ%2FbtqXQ5Yf2UQ%2FciBNITFKnGXZr0vtWnmWKk%2Fimg.png&quot; data-filename=&quot;Screen Shot 2021-02-19 at 1.46.09 PM.png&quot; data-origin-width=&quot;1644&quot; data-origin-height=&quot;1626&quot; width=&quot;373&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;*오늘은 지난달 첫 서비스를 개시한 국내 라이브 커머스 &lt;b&gt;&lt;i&gt;&lt;span style=&quot;color: #409d00;&quot;&gt;네이버의 라이브 쇼핑&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;을 이야기 해보고자한다. 기술적인 측면에서 개인적으로 분석해보고 느낀 점들을 작성한 것이니 참고만 하면 좋을 것 같다.&lt;/h4&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://shoppinglive.naver.com/home&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;shoppinglive.naver.com/home&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1613710283838&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;네이버 쇼핑라이브&quot; data-og-description=&quot;산지에서 해외까지, 생생한 리얼쇼핑&quot; data-og-host=&quot;shoppinglive.naver.com&quot; data-og-source-url=&quot;https://shoppinglive.naver.com/home&quot; data-og-url=&quot;https://shoppinglive.naver.com/home&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bdVd1E/hyJjTb6ECc/aem3u5WJ5e2rFYcpzdggkK/img.png?width=192&amp;amp;height=192&amp;amp;face=0_0_192_192&quot;&gt;&lt;a href=&quot;https://shoppinglive.naver.com/home&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://shoppinglive.naver.com/home&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bdVd1E/hyJjTb6ECc/aem3u5WJ5e2rFYcpzdggkK/img.png?width=192&amp;amp;height=192&amp;amp;face=0_0_192_192');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;네이버 쇼핑라이브&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;산지에서 해외까지, 생생한 리얼쇼핑&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;shoppinglive.naver.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Screen Shot 2021-02-24 at 2.31.36 PM.png&quot; data-origin-width=&quot;3246&quot; data-origin-height=&quot;1288&quot; width=&quot;943&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dnKLfT/btqYkbqGYj1/TitpdSQenwy7cm0ryE29kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dnKLfT/btqYkbqGYj1/TitpdSQenwy7cm0ryE29kk/img.png&quot; data-alt=&quot;출처; Deview 2019&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dnKLfT/btqYkbqGYj1/TitpdSQenwy7cm0ryE29kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdnKLfT%2FbtqYkbqGYj1%2FTitpdSQenwy7cm0ryE29kk%2Fimg.png&quot; data-filename=&quot;Screen Shot 2021-02-24 at 2.31.36 PM.png&quot; data-origin-width=&quot;3246&quot; data-origin-height=&quot;1288&quot; width=&quot;943&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;출처; Deview 2019&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;영상 미디어 스트리밍 개념을 잘 이해할 수 있는 그림이니 참고하면 좋을 것 같다.&amp;nbsp; &lt;b&gt;오늘 주제는 라스트 마일의 HLS over HTTP이다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저 html 소스 헤더를 잠깐 살펴보면,&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Screen Shot 2021-02-19 at 2.08.51 PM.png&quot; data-origin-width=&quot;1828&quot; data-origin-height=&quot;1310&quot; width=&quot;732&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dJC5ct/btqXRwg2CAJ/4zkIaKTv6rmCXREIgcrbfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dJC5ct/btqXRwg2CAJ/4zkIaKTv6rmCXREIgcrbfK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dJC5ct/btqXRwg2CAJ/4zkIaKTv6rmCXREIgcrbfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdJC5ct%2FbtqXRwg2CAJ%2F4zkIaKTv6rmCXREIgcrbfK%2Fimg.png&quot; data-filename=&quot;Screen Shot 2021-02-19 at 2.08.51 PM.png&quot; data-origin-width=&quot;1828&quot; data-origin-height=&quot;1310&quot; width=&quot;732&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;async src에 해당하는&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span&gt;&lt;a href=&quot;https://cdn.jsdelivr.net/npm/hls.js@0.14.16/dist/hls.min.js&quot;&gt;https://cdn.jsdelivr.net/npm/hls.js@0.14.16/ &lt;/a&gt;&lt;/span&gt;라는 것이 궁금해져서 들어가 봤다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;i&gt;jsdelivr이라는게 무료로 CDN을 제공&lt;/i&gt;&lt;/b&gt;해주고 이에 필요한 오픈소스같은 것을 제공받고 있는 것같다. 정확히는 모르겠으나 CDN이 대용량 트레픽과 분산서버를 고려할때엔 영상서비스에서 반드시 필요한 것인걸로 알고 있으나 아직은 서비스 초기단계니 크게 비용을 안들이고 있는게 아닐까?하는 생각이 들었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://www.jsdelivr.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;www.jsdelivr.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1613711747164&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;jsDelivr - A free, fast, and reliable CDN for Open Source&quot; data-og-description=&quot;Supports npm, GitHub, WordPress, Deno, and more. Largest network and best performance among all CDNs. Serving more than 80 billion requests per month. Built for production use.&quot; data-og-host=&quot;www.jsdelivr.com&quot; data-og-source-url=&quot;https://www.jsdelivr.com/&quot; data-og-url=&quot;https://www.jsdelivr.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/mZXMN/hyJkPzactd/jLxnWVJBcUqKRAuyC3LUIk/img.png?width=280&amp;amp;height=68&amp;amp;face=0_0_280_68,https://scrap.kakaocdn.net/dn/bl7TRd/hyJjZi7hen/ycU3qzQOqIEbYOb0MBKRmk/img.png?width=280&amp;amp;height=68&amp;amp;face=0_0_280_68,https://scrap.kakaocdn.net/dn/kGYas/hyJjWs81fa/D69aTiLPfdYRpZ4nWvQC6k/img.png?width=750&amp;amp;height=488&amp;amp;face=0_0_750_488&quot;&gt;&lt;a href=&quot;https://www.jsdelivr.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.jsdelivr.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/mZXMN/hyJkPzactd/jLxnWVJBcUqKRAuyC3LUIk/img.png?width=280&amp;amp;height=68&amp;amp;face=0_0_280_68,https://scrap.kakaocdn.net/dn/bl7TRd/hyJjZi7hen/ycU3qzQOqIEbYOb0MBKRmk/img.png?width=280&amp;amp;height=68&amp;amp;face=0_0_280_68,https://scrap.kakaocdn.net/dn/kGYas/hyJjWs81fa/D69aTiLPfdYRpZ4nWvQC6k/img.png?width=750&amp;amp;height=488&amp;amp;face=0_0_750_488');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;jsDelivr - A free, fast, and reliable CDN for Open Source&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;Supports npm, GitHub, WordPress, Deno, and more. Largest network and best performance among all CDNs. Serving more than 80 billion requests per month. Built for production use.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;www.jsdelivr.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #ef6f53;&quot;&gt;* 홈 화면&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Screen Shot 2021-02-19 at 1.56.28 PM.png&quot; data-origin-width=&quot;2790&quot; data-origin-height=&quot;1546&quot; width=&quot;686&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E67Ah/btqXSoiKXPT/ksKme97w2NvReW3VkH1swK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E67Ah/btqXSoiKXPT/ksKme97w2NvReW3VkH1swK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E67Ah/btqXSoiKXPT/ksKme97w2NvReW3VkH1swK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE67Ah%2FbtqXSoiKXPT%2FksKme97w2NvReW3VkH1swK%2Fimg.png&quot; data-filename=&quot;Screen Shot 2021-02-19 at 1.56.28 PM.png&quot; data-origin-width=&quot;2790&quot; data-origin-height=&quot;1546&quot; width=&quot;686&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;처음 홈화면은 210.89.167.4 에 접속해서 일반적인 이커머스 처럼 필요한 정보들을 GET으로 가져오는 것을 볼 수있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;8064780 등은 홈 화면에서 보여지는 video 들 정보를 가지고 오는 것 같다. &lt;span style=&quot;color: #333333;&quot;&gt;그리고 각각의 LIVE 비디오들이 꾸준하게 스트리밍 되는 것을 확인 할 수 있다. 이때 접속하는 서버가 달라짐을 볼 수 있다. 아래에서 구체적으로 살펴보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;* 라이브 스트리밍&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;u&gt;총 세가지가 반복되는 것을 볼 수 있다.&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li data-ke-size=&quot;size18&quot;&gt;상품,판매자에 대한 정보 GET 110.XX.XX.XX --&amp;gt; 좋아요나 커멘트 남기면 POST 110.XX.XX.XX&lt;/li&gt;
&lt;li data-ke-size=&quot;size18&quot;&gt;chunklist.m3u8&amp;nbsp; GET 211.XX.XX.XX.XX [영상에 대한 정보]&lt;/li&gt;
&lt;li data-ke-size=&quot;size18&quot;&gt;720.stream.ts GET 211.XX.XX.XX.XX&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt; [실제 영상 데이터]&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;211.XX.XX.XX은 CDN 서버가 아닐까 싶다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Screen Shot 2021-02-19 at 2.44.20 PM.png&quot; data-origin-width=&quot;1436&quot; data-origin-height=&quot;984&quot; width=&quot;803&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tqeg1/btqXRvo5tqD/H5lnVBgvgp5iyQgVdxg5Q1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tqeg1/btqXRvo5tqD/H5lnVBgvgp5iyQgVdxg5Q1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tqeg1/btqXRvo5tqD/H5lnVBgvgp5iyQgVdxg5Q1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftqeg1%2FbtqXRvo5tqD%2FH5lnVBgvgp5iyQgVdxg5Q1%2Fimg.png&quot; data-filename=&quot;Screen Shot 2021-02-19 at 2.44.20 PM.png&quot; data-origin-width=&quot;1436&quot; data-origin-height=&quot;984&quot; width=&quot;803&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-type=&quot;image&quot; data-video-width=&quot;764&quot; data-video-height=&quot;NaN&quot; data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;라이브스트리밍과 VOD의 가장 큰 차이점은, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;i&gt;라이브 스트리밍은 계속 GET으로 chunk된 영상을 받아온다.&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이는 지난번 포스팅에서 말했던 progressive-download / adapt-download 와 연관되있는 것처럼 보였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;그렇다면 사용되는 프로토콜은? 정답은 HLS 이다.&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Content-Type : application/vnd.apple.mpegurl&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;chunk파일의 확장자 m3u8&amp;nbsp; - metadata를 가진 manifest 파일&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;stream파일 확장자 ts&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;등을 볼 수 있듯, apple사의 HLS 프로토콜을 사용한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;mpeg-dash 로 변경하겠다는 이야기를 들은 바가 있는데 아직은 거까지 구현은 안된것 같다. free loyalty기술이 아니다 보니 현실적으로 어려움이 있을 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Screen Shot 2021-02-19 at 2.02.57 PM.png&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;410&quot; width=&quot;564&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdzFoR/btqXXUVoifK/kXRLQ0pO1OHieACnQrAo00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdzFoR/btqXXUVoifK/kXRLQ0pO1OHieACnQrAo00/img.png&quot; data-alt=&quot;HLS official developer site&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdzFoR/btqXXUVoifK/kXRLQ0pO1OHieACnQrAo00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdzFoR%2FbtqXXUVoifK%2FkXRLQ0pO1OHieACnQrAo00%2Fimg.png&quot; data-filename=&quot;Screen Shot 2021-02-19 at 2.02.57 PM.png&quot; data-origin-width=&quot;1010&quot; data-origin-height=&quot;410&quot; width=&quot;564&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;HLS official developer site&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Screen Shot 2021-02-19 at 2.29.26 PM.png&quot; data-origin-width=&quot;1520&quot; data-origin-height=&quot;258&quot; width=&quot;731&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boyrbH/btqXXjgRt6p/TX8R9llqWX5YrOYtbgsfLK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boyrbH/btqXXjgRt6p/TX8R9llqWX5YrOYtbgsfLK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boyrbH/btqXXjgRt6p/TX8R9llqWX5YrOYtbgsfLK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboyrbH%2FbtqXXjgRt6p%2FTX8R9llqWX5YrOYtbgsfLK%2Fimg.png&quot; data-filename=&quot;Screen Shot 2021-02-19 at 2.29.26 PM.png&quot; data-origin-width=&quot;1520&quot; data-origin-height=&quot;258&quot; width=&quot;731&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;더불어 html5 &amp;lt;video&amp;gt; 지원 브라우저라면 &amp;lt;video&amp;gt;태그로 웹페이지를 구성하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;또한가지 주목해볼만한 것은&lt;span style=&quot;background-color: #f6e199;&quot;&gt; &lt;i&gt;&lt;b&gt;connection : keep-alive &lt;/b&gt;&lt;/i&gt;&lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #f89009;&quot;&gt;엄밀히 말하면 TCP connection을 유지 시켜주는 기능이라 생각하면 된다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;기본적으로 http는 단방향 프로토콜이다. 그래서 매번 Connection은 끊어지고 새로 생성해야한다&lt;/i&gt;. keep-alive의 특징은 연결된 소켓에 I/O access가 마지막으로 종료된 시점부터 지정 time-out 시간동안 access가 없어도 세션이 유지된다는 것이다.&amp;nbsp;이로 인해 성능이 개선 될 수도 있지만, 정적인 웹서버의 경우는 큰 효과는 없고 또한 여러 Client 동시 접속시 과부하를 일으킬 수 도있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Screen Shot 2021-02-19 at 2.49.00 PM.png&quot; data-origin-width=&quot;1556&quot; data-origin-height=&quot;1012&quot; width=&quot;491&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mbJcR/btqXRuDN5Cp/QkJDaXSKRyBVrtRCSa8pf1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mbJcR/btqXRuDN5Cp/QkJDaXSKRyBVrtRCSa8pf1/img.png&quot; data-alt=&quot;커넥션 연결시간을 아낄수 있다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mbJcR/btqXRuDN5Cp/QkJDaXSKRyBVrtRCSa8pf1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmbJcR%2FbtqXRuDN5Cp%2FQkJDaXSKRyBVrtRCSa8pf1%2Fimg.png&quot; data-filename=&quot;Screen Shot 2021-02-19 at 2.49.00 PM.png&quot; data-origin-width=&quot;1556&quot; data-origin-height=&quot;1012&quot; width=&quot;491&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;커넥션 연결시간을 아낄수 있다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;* VOD 스트리밍&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;VOD는 라이브와는 전혀다른 방식으로 영상을 스트리밍하는 것을 한눈에 볼수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Screen Shot 2021-02-19 at 2.55.13 PM.png&quot; data-origin-width=&quot;1858&quot; data-origin-height=&quot;682&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beO2Ku/btqXXVz8oKI/mhlCruivvJKNcb38NHaS11/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beO2Ku/btqXXVz8oKI/mhlCruivvJKNcb38NHaS11/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beO2Ku/btqXXVz8oKI/mhlCruivvJKNcb38NHaS11/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeO2Ku%2FbtqXXVz8oKI%2FmhlCruivvJKNcb38NHaS11%2Fimg.png&quot; data-filename=&quot;Screen Shot 2021-02-19 at 2.55.13 PM.png&quot; data-origin-width=&quot;1858&quot; data-origin-height=&quot;682&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;하나의 커넥션으로 지속적으로 받아오는 것을 볼 수 있고&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Screen Shot 2021-02-19 at 2.55.59 PM.png&quot; data-origin-width=&quot;1630&quot; data-origin-height=&quot;676&quot; width=&quot;665&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBRWNP/btqXXVz8tgZ/KMc1AcWOjgpFXnAY69uN60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBRWNP/btqXXVz8tgZ/KMc1AcWOjgpFXnAY69uN60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBRWNP/btqXXVz8tgZ/KMc1AcWOjgpFXnAY69uN60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBRWNP%2FbtqXXVz8tgZ%2FKMc1AcWOjgpFXnAY69uN60%2Fimg.png&quot; data-filename=&quot;Screen Shot 2021-02-19 at 2.55.59 PM.png&quot; data-origin-width=&quot;1630&quot; data-origin-height=&quot;676&quot; width=&quot;665&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;i&gt;content-type : video/mp4 &lt;/i&gt;&lt;/span&gt;로 우리가 보통 익히 아는 mp4확장자임을 알 수 있다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Web</category>
      <category>네이버</category>
      <category>라이브커머스</category>
      <author>Megan Kim</author>
      <guid isPermaLink="true">https://mysoftworld.tistory.com/46</guid>
      <comments>https://mysoftworld.tistory.com/46#entry46comment</comments>
      <pubDate>Fri, 19 Feb 2021 14:40:14 +0900</pubDate>
    </item>
    <item>
      <title>비동기에 대한 이해( feat. Spring @Async)</title>
      <link>https://mysoftworld.tistory.com/42</link>
      <description>&lt;p&gt;지난번 포스팅에서도 한번 작성한 적이 있는 '비동기'라는 주제의 이야기. 아직도 그 끝을 파진 못했지만 공부를 하다 깨달은 점들이 있어서 글을 작성해 봅니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;i&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;*** 결론은 무엇이 비동기,동기 그리고 Blocking, Nonblocking 인지는 어떤&amp;nbsp; '&lt;span style=&quot;color: #ee2323;&quot;&gt;맥락&lt;/span&gt;' 에서인지에 따라 논하는게 맞는 것같다.&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그렇지 않으면 상이한 두 개념을 오롯이 이해하기가 만만치않다. 의식의 흐름만 계속되면서 악순환이 마련된다.. 내얘기.....&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;[비동기 : Asynchronous]&amp;nbsp;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;i&gt;우선 두가지 맥락을 이해하자. 비동기로 '&lt;span style=&quot;color: #ef5369;&quot;&gt;동시성(Concurrency)'&lt;/span&gt; 혹은 &lt;span style=&quot;color: #1a5490;&quot;&gt;'병렬성(Parallelism)'&lt;/span&gt; 을 구현할 수 있다.&amp;nbsp;&lt;/i&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-ke-size=&quot;size18&quot;&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;동시성(Concurrency)&amp;nbsp; &amp;nbsp;- '논리적인개념' : 쓰레드가 혹은 프로세스가 다수개&lt;/i&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;동시성은 CPU코어의 시분할 멀티 태스킹으로 이해하면 될 것 같다. 멀티 프로레싱, 멀티 쓰레딩도 포함 시켜도 된다. 단, CPU 싱글코어라고 가정 했을 때 말이다. (&lt;i&gt;CPU코어는 일을하는 주체 &lt;/i&gt;이다. CPU는 매 순간 어떤 프로세스이건 딱 1줄의 명령어를 Fetch해서 '처리'하는 점을 기억하자.)이말은 무엇이냐, 아무리 '동시'에 10개의 독립적인 프로세스(메모리 상에 로드 된 프로그램) 이 실행된다 하여도, 그게 '진짜' 동시간대에 실행되는 것이 아니라, 그렇게 보이는 것 뿐이다. 시분할되어 여러 프로세스를 어떤 우선 순위 큐같은 자료구조에 넣어두고, 사실상 '순차적으로 하나씩' 매우 빠르게 실행되어 동시에 실행되는 것 처럼 보이는 것 뿐이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Screen Shot 2021-02-12 at 1.12.19 PM.png&quot; data-origin-width=&quot;2080&quot; data-origin-height=&quot;878&quot; width=&quot;306&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6x069/btqWUMyXlAd/5uvw067Sfnv3L58lqpkosK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6x069/btqWUMyXlAd/5uvw067Sfnv3L58lqpkosK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6x069/btqWUMyXlAd/5uvw067Sfnv3L58lqpkosK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6x069%2FbtqWUMyXlAd%2F5uvw067Sfnv3L58lqpkosK%2Fimg.png&quot; data-filename=&quot;Screen Shot 2021-02-12 at 1.12.19 PM.png&quot; data-origin-width=&quot;2080&quot; data-origin-height=&quot;878&quot; width=&quot;306&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;u&gt;&lt;i&gt;자, 그럼 이게 비동기랑 무슨 연관이 있냐?&amp;nbsp;&lt;/i&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다시 10개의 프로세스가 실행되고 있다는 이야기로 되돌아가보자. 1번 프로세스가 종료되어야 2번 프로레스를 실행할 수 있고 2번이 끝나야 3번이 시작될 수 있고..&lt;b&gt;&lt;i&gt; 이러한 맥락의 상황은 '동기'식 방식이자 앞 프로세스가 'Blocking'을 하고있다 라고 표현 할 수 있다.&lt;/i&gt;&lt;/b&gt; 그렇다면 1번 프로세스가 버퍼링이 걸려 10분동안 끝나지않는다면, 2번~10번 프로세스는 무한 '&lt;b&gt;대기&lt;/b&gt;'상태에 있다. 그렇기때문에, 1~10번 프로세스는 서로 완전 독립적으로 ('비동기') 실행되어야만 동시성이 보장된다. Non-blocking과 물론 연관이 있지만, 이 이야기는 다른 특정 상황의 맥락에서 다시 자세히 살펴보자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-ke-size=&quot;size18&quot;&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;병렬성(Parallelism) - '물리적인 개념' : CPU 코어 즉 프로세서가 여러개&amp;nbsp;&lt;/i&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;병렬성은 동시성과 다른 개념으로, '한 시점에 정말 여러개의 프로세스가 실행되는 것'을 생각하면 된다. 즉, 최소 듀얼 코어가 보장되어야 병렬성을 구현할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Screen Shot 2021-02-12 at 1.23.51 PM.png&quot; data-origin-width=&quot;2090&quot; data-origin-height=&quot;858&quot; width=&quot;327&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZGKCx/btqWWtk91xz/4kEKi4cp80GempzMEf2Fbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZGKCx/btqWWtk91xz/4kEKi4cp80GempzMEf2Fbk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZGKCx/btqWWtk91xz/4kEKi4cp80GempzMEf2Fbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZGKCx%2FbtqWWtk91xz%2F4kEKi4cp80GempzMEf2Fbk%2Fimg.png&quot; data-filename=&quot;Screen Shot 2021-02-12 at 1.23.51 PM.png&quot; data-origin-width=&quot;2090&quot; data-origin-height=&quot;858&quot; width=&quot;327&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;우리가 통상적으로 다룰 '비동기'는 &lt;u&gt;&lt;i&gt;동시성 맥락&lt;/i&gt;&lt;/u&gt;이 대부분이라고 생각하면된다. 결국 하나의 자원(CPU)를 어떻게 효율적으로 여러 프로세스 혹은 쓰레드가 사용하면 좋을지에 대한 이야기가 앞으로의 내용들이다. 아니, 사실상 하나의 어플리케이션 내의 멀티 쓰레드를 이야기 하게 될 것이다. 비동기 구현을 위해 Fork 로 프로세스를 새로 생성하면서 발생하는 컨텍스트 스위칭은 하는 것보다 Thread를 생성하는 것이 훨씬 효율적이다. Stack외엔 공유된 메모리 자원을 사용하기 때문에 복사될 데이터의 양이 훨씬 더 적기때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;비동기를 그럼 어떻게 이해하면 좋을까? &lt;span style=&quot;color: #f89009;&quot;&gt;&quot;&lt;i&gt;A라는 작업과 B라는 작업이 발생 순서에 서로 영향을 주지않는 것&quot;&lt;/i&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;통상적으로 비동기적 프로그래밍을 구현하기 위해 &lt;u&gt;멀티쓰레드를&lt;/u&gt; 기반으로 하게된다고 알고 있다. 그렇다면 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;'비동기는 무조건 멀티 쓰레드이고 Non-Blocking이네?' 라고 이해하면 될 것인가?&amp;nbsp; 정답은 아니다.&lt;/span&gt;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;I/O 를 핸들링 하는 대표적인 '비동기' 함수로 알려져 있는 &lt;i&gt;&lt;b&gt;SELECT()&lt;/b&gt; &lt;/i&gt;함수를 떠올려보자. (Blocking + Async)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;SELECT는 기본적으로 커널에게 다수의 file descriptor들에 대한 감지된 I/O 이벤트 정보를 요청하여 변화를&amp;nbsp;&lt;span style=&quot;color: #ee2323;&quot;&gt;포착하였을 때 리턴된다.&amp;nbsp;&lt;span style=&quot;color: #000000;&quot;&gt;이 맥락에서 즉 SELECT함수는 &lt;span style=&quot;background-color: #f89009;&quot;&gt;&lt;span style=&quot;background-color: #f3c000;&quot;&gt;Blocking&lt;/span&gt;&lt;/span&gt;함수라는 것이다. 포착되기 전엔 호출한 곳으로 커널에서 제어권을 넘기지 않는다.&amp;nbsp; &lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;단 여기서 중요한 것은, 일단 감지가 되어 호출한 곳으로 돌아가고 난 후에 다음 로직들이 &lt;span style=&quot;color: #ee2323;&quot;&gt;실행되는 동안에도 파일 디스크립터에 대한 I/O 이벤트 감지 업무는 실행&lt;/span&gt; 되고 있다. 여기서 이 맥락이 바로 &lt;span style=&quot;background-color: #f3c000;&quot;&gt;비동기&lt;/span&gt;라는 것이다! 꼭 쓰레드를 생성하지 않아도, 맥락상 비동기가 구현되는 것이다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;참고로 epoll은 select 처럼&amp;nbsp;&lt;span&gt;계속해서 정보를 넘기지 않기 위해서 관찰 대상인 fd들의 정보를 담은 저장소를 직접 운영체제가 담당한다.&amp;nbsp;&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;i&gt;* &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Spring에서 두가지 맥락의 비동기가 있다.&lt;/span&gt; - Controller layer VS Service layer&lt;/i&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다음 두 상황을 가정하고 비교해보자.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;하나의 서버에 10만명이 A라는 &lt;b&gt;CONTROLLER&lt;/b&gt;에 request를 보냈을때, 어떻게 처리할 것인가?&amp;nbsp;&lt;/span&gt;&lt;/h4&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;먼저 알아야 할 점은, Spring의 Bean은 &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;i&gt;Singleton-Pattern&lt;/i&gt;&lt;/span&gt;을 따른다. 싱글톤 패턴은 다수의 요청에도 해당 클래스에 대한 인스턴스는 하나만 생성이 된다. 불필요한 메모리 누수를 방지할 수 있다는 장점이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Screen Shot 2021-02-12 at 2.08.30 PM.png&quot; data-origin-width=&quot;1344&quot; data-origin-height=&quot;620&quot; width=&quot;470&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bDDYjH/btqWX4SKi2A/jxkEGyJesDk6GKQATfSIek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bDDYjH/btqWX4SKi2A/jxkEGyJesDk6GKQATfSIek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bDDYjH/btqWX4SKi2A/jxkEGyJesDk6GKQATfSIek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbDDYjH%2FbtqWX4SKi2A%2FjxkEGyJesDk6GKQATfSIek%2Fimg.png&quot; data-filename=&quot;Screen Shot 2021-02-12 at 2.08.30 PM.png&quot; data-origin-width=&quot;1344&quot; data-origin-height=&quot;620&quot; width=&quot;470&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Spring bean에 등록된 객체는 싱글톤을 따르기때문에, 하나의 객체만이 Heap영역에 있고 , 클라이언트 Thread 들에서 그 객체를 공유케된다. 즉, 메소드 영역의 메소드만 공유하게 해주는 하나의 인스턴스가 등록되는 것이라 생각하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그래서 필연적으로 해당 객체에는 상태(State) field를 가지고 있으면 하나의 공유 자원에 대한 Thread-Safe가 보장이 되지않겠다. &lt;span style=&quot;color: #ee2323;&quot;&gt;다시 말해, Thread-Safe는 기본적으로 보장되지않는다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Singleton 패턴을 조금 더 생각해보자. - &lt;b&gt;&lt;i&gt;어떻게 해야 Thread-Safe하게 구현 할 수 있을 것인가?&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #a6bc00;&quot;&gt;Eager Initialization&lt;/span&gt; 은 컴파일 타임에 Class Loader 가 정적 바인딩을 통해 인스턴스를 생성하는 것을 의미한다. 이렇게 인스턴스를 생성하지 않고 Runtime에 동적 바인딩으로 객체가 필요할 때, 즉 클라이언트의 요청이 왔을 때 생성하면 synchroized 를 통해 생성해야하는데, 이는 불필요하게 매번 블로킹이 되기때문에 비효율적이다.&lt;/p&gt;
&lt;pre id=&quot;code_1613107661359&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Singleton {
    // Eager Initialization
    private static Singleton uniqueInstance = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() {
      return uniqueInstance; 
    } 
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;다시 이야기해서 &lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;i&gt;Client들의 요청은 개별 쓰레드로 동시에 처리가 되는 맥락에서 '비동기'적인 처리&lt;/i&gt;&lt;/span&gt;라고 말할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;아~ 그런데 오해하지말자. 100만개의 요청을 위해 100만개를 직접 Thread 생성하고 삭제하는 것은 절대아니다!! Thread는 기본적으로 임계값이 있기 때문에 이것은 Thread Pool 로 JVM이 관리하는 영역으로 문제가 해결이 된다. Thread Pool 을 사용하면 매번 생성, 삭제를 할 필요없이 재사용 가능하다. Thread도 Context-Switching 비용이 부담이 있기때문에 훨씬 효율적이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Screen Shot 2021-02-12 at 2.41.15 PM.png&quot; data-origin-width=&quot;1774&quot; data-origin-height=&quot;1050&quot; width=&quot;596&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pChAx/btqWU7iFv42/GcphwCRop9a8d1RAgQTQHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pChAx/btqWU7iFv42/GcphwCRop9a8d1RAgQTQHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pChAx/btqWU7iFv42/GcphwCRop9a8d1RAgQTQHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpChAx%2FbtqWU7iFv42%2FGcphwCRop9a8d1RAgQTQHK%2Fimg.png&quot; data-filename=&quot;Screen Shot 2021-02-12 at 2.41.15 PM.png&quot; data-origin-width=&quot;1774&quot; data-origin-height=&quot;1050&quot; width=&quot;596&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;*(번외) Thread Pool 와 DB Connection pool 의 크기, 실행 쓰레드 최대 개수 등은 같을까?&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;당연히 다르다. 매번 Request 마다 DB를 접속하는 것은 아니기때문에 1:1 매핑일리없다.&amp;nbsp; 참고로 Connection Pool도 싱글톤 패턴이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;A라는 &lt;b&gt;SERVICE&lt;/b&gt; 안에서 a라는 작업과 b라는 작업을 서로 순서 무관하게 동시에 진행하고 싶을때 어떻게 할 것인가?&amp;nbsp;&lt;/span&gt;&lt;/h4&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;=&amp;gt; 이 맥락에서 비동기 @Async 가 사용되는 것이다.&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;물론 앞서 이야기했던 기본적인 자바,스프링의 쓰레드 개념과 동일한 개념이지만, 강조하고 싶은것은 '비동기'를 논할 때는 항상 맥락을 우선적으로 파악하는 연습이 중요하다고 생각해서 굳이 분리를 한 것이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;i&gt;*본론 : Spring에서의 비동기 @Async 에 대해 이야기해보자&lt;/i&gt;&lt;/h2&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Web</category>
      <category>@async</category>
      <category>async</category>
      <category>singleton</category>
      <category>Spring</category>
      <category>멀티쓰레드</category>
      <category>비동기</category>
      <category>스프링</category>
      <category>싱글톤</category>
      <category>쓰레드</category>
      <author>Megan Kim</author>
      <guid isPermaLink="true">https://mysoftworld.tistory.com/42</guid>
      <comments>https://mysoftworld.tistory.com/42#entry42comment</comments>
      <pubDate>Fri, 12 Feb 2021 14:33:40 +0900</pubDate>
    </item>
    <item>
      <title>[AWS] RDB 와 스프링 연동하기</title>
      <link>https://mysoftworld.tistory.com/41</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;background-color: #ffc1c8;&quot;&gt;&lt;i&gt;AWS 제품 중 RDB 인스턴스를 스프링과 연동하는 법에 대해 살펴봅시다.&lt;/i&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;i&gt;* Amazon EC2 : Amazon Elastic Computing Cloud - A사 클라우드 컴퓨팅 제품들 중 가장 핫한 제품이라고 볼 수 있다.&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;우선, AWS에서 프리티어로 RDB 인스턴스를 생성합니다. 프리티어는 12개월간 무료로 한정된 리소스를 사용하는 타입입니다.&amp;nbsp;제공 범위 초과하여 사용할 시 요금이 부과되는데, 예를들어 SSD 20GB가 최대인데, 21GB 사용시 요금 부과된다. 금수저가 아니라면 주의하자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1896&quot; data-origin-height=&quot;430&quot; data-filename=&quot;Screen Shot 2021-02-10 at 4.42.01 PM.png&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/t5oTj/btqWLXUZH1N/MjJrgksxZrrKO4U41OMmUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/t5oTj/btqWLXUZH1N/MjJrgksxZrrKO4U41OMmUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/t5oTj/btqWLXUZH1N/MjJrgksxZrrKO4U41OMmUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ft5oTj%2FbtqWLXUZH1N%2FMjJrgksxZrrKO4U41OMmUk%2Fimg.png&quot; data-origin-width=&quot;1896&quot; data-origin-height=&quot;430&quot; data-filename=&quot;Screen Shot 2021-02-10 at 4.42.01 PM.png&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;인스턴스 생성은 몇가지 옵션만 설정하면 곧 바로 10분내로 생성이된다. 이때 신경써야할 옵션은 다음과 같다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 본인이 필요로하는 데이터베이스의 엔진 선택.&amp;nbsp;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 MariaDB(MySQL과흡사) 를 사용할 예정이므로 다음과 같이 택했다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Screen Shot 2021-02-10 at 4.43.16 PM.png&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;830&quot; width=&quot;748&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4s0IQ/btqWU7hbYZ8/p3vmkJTp2swGvaIx20WmtK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4s0IQ/btqWU7hbYZ8/p3vmkJTp2swGvaIx20WmtK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4s0IQ/btqWU7hbYZ8/p3vmkJTp2swGvaIx20WmtK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4s0IQ%2FbtqWU7hbYZ8%2Fp3vmkJTp2swGvaIx20WmtK%2Fimg.png&quot; data-filename=&quot;Screen Shot 2021-02-10 at 4.43.16 PM.png&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;830&quot; width=&quot;748&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. &lt;span style=&quot;color: #333333;&quot;&gt;'Public Access Allow' 옵션 사용하기&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;인스턴스 생성 시 'Public Access Allow'를 해주어야만 범용적으로 사용이 가능하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Screen Shot 2021-02-10 at 4.44.48 PM.png&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;980&quot; width=&quot;413&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/br34S1/btqWK0dlL5h/iqCFiWKiZ9RltYAPhoGJwk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/br34S1/btqWK0dlL5h/iqCFiWKiZ9RltYAPhoGJwk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/br34S1/btqWK0dlL5h/iqCFiWKiZ9RltYAPhoGJwk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbr34S1%2FbtqWK0dlL5h%2FiqCFiWKiZ9RltYAPhoGJwk%2Fimg.png&quot; data-filename=&quot;Screen Shot 2021-02-10 at 4.44.48 PM.png&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;980&quot; width=&quot;413&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. VPC 에서 인바운드 규칙 추가하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;보안을 위해 허용 ip와 포트번호 범위를 추가한다.&amp;nbsp;VPC 보안 그룹에서 인바운드 규칙을 설정하여 allow/deny 할 IP 등을 설정해줄 수 있다. 참고로 VPC는 Virtural Private Cloud의 약자로, EC2의 네트워킹 계층으로서 사용자가 정의하는 가상 네트워크라고 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;인바운드 규칙을 설정하지않으면 스프링 부트 실행시 TCP CONNECTION TIMEOUT 에러가 발생한다.&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Screen Shot 2021-02-10 at 4.50.44 PM.png&quot; data-origin-width=&quot;2408&quot; data-origin-height=&quot;1106&quot; width=&quot;705&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmGVzL/btqWNdccXmL/eL12FSo9jmbgheIlQWJkPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmGVzL/btqWNdccXmL/eL12FSo9jmbgheIlQWJkPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmGVzL/btqWNdccXmL/eL12FSo9jmbgheIlQWJkPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmGVzL%2FbtqWNdccXmL%2FeL12FSo9jmbgheIlQWJkPK%2Fimg.png&quot; data-filename=&quot;Screen Shot 2021-02-10 at 4.50.44 PM.png&quot; data-origin-width=&quot;2408&quot; data-origin-height=&quot;1106&quot; width=&quot;705&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;*인스턴스 생성이 완료 되었으면, 이제 스프링 Web Application에서 기존에 사용하던 Local DB가 아닌, AWS RDB를 연결해보자. [Gradle 기준]&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;1. Dependencies 추가&lt;/p&gt;
&lt;pre id=&quot;code_1612943705470&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// build.gradle

runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2. applications.properties (혹은 application.yml)&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1612943896413&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;server.port=스프링 서버의 포트 

/* 필수 */
spring.datasource.url=jdbc:mariadb://인스턴스 엔드포인트/생성데이터베이스명
spring.datasource.username= 인스턴스 유저명
spring.datasource.password= 인스턴스 패스워드

driver-class-name= org.mariadb.jdbc.Driver
spring.jpa.database-platform=org.hibernate.dialect.MariaDBDialect

/* 선택 */
spring.jpa.show-sql=true
spring.jackson.property-naming-strategy=SNAKE_CASE
spring.jpa.generate-ddl=true
spring.jpa.hibernate.ddl-auto=validate
createDatabaseIfNotExist=true
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;* 생성 데이터베이스 명은 아래에서와 같이 미리 만들어야 사용할 수 있다. 'DB 식별자'와는 다른 것이니 에러에 주의하자.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;* DDL-AUTO = CREATE를 두고 스키마에 테이블을 생성해도 되지만, 나는 별도로 Command Line으로 셋팅하였다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp; 또한 Docker 가상 환경 MySQL 에서 작성한 것을 참고하길 바란다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1612944155266&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker exec -i -t mariadb bash &lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1612944234846&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;root@e7e28e122e3d:/# mysql -h &amp;lt;인스턴스엔드포인트&amp;gt; -P &amp;lt;포트번호&amp;gt; -u &amp;lt;유저네임&amp;gt; -p&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1612944263605&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Enter Password : &amp;lt;유저패스워드&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1612944310071&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mysql &amp;gt; CREATE SCHEMA `shop`

		CREATE TABLE `shop`.`product`
(
    `product_id`     BIGINT      NOT NULL AUTO_INCREMENT,
    `seller_id`      BIGINT      NOT NULL,
    `title`          VARCHAR(50) NOT NULL,
    `meta_title`     VARCHAR(50),
    `slug`           VARCHAR(50) ,
    `sku`            VARCHAR(50),
    `price`          INT         NOT NULL DEFAULT 0,
    `discount_rate`  INT        DEFAULT 0,
    `quantity`       INT         NOT NULL DEFAULT 0,
    `thumbnail_path` VARCHAR(200)         DEFAULT NULL,
    `image_path`     VARCHAR(200)         DEFAULT NULL,
    `content`        TEXT                 DEFAULT NULL,
    `created_at`     DATETIME    NOT NULL,
    `updated_at`     DATETIME             DEFAULT NULL,
    `published_at`   DATETIME             DEFAULT NULL,
    `starts_at`      DATETIME             DEFAULT NULL,
    `ends_at`        DATETIME             DEFAULT NULL,
    PRIMARY KEY (`product_id`),
    UNIQUE INDEX `uq_slug` (`slug` ASC),

    CONSTRAINT `fk_product_account`
        FOREIGN KEY (`seller_id`) REFERENCES `shop`.`account` (`account_id`)

);
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;위와 같은 방법으로 스키마와 테이블을 잘 생성하였으면 이제 스프링을 돌려보자.&lt;/p&gt;
&lt;p&gt;그럼 다음과 같이 API를 통해 실행하였을 때 Query문이 잘 실행되는 것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1482&quot; data-origin-height=&quot;1262&quot; data-filename=&quot;Screen Shot 2021-02-10 at 5.06.25 PM.png&quot; width=&quot;469&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/csTsXX/btqWQ9tFwM0/omVuvZ4PflIHu5FxTcBdG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/csTsXX/btqWQ9tFwM0/omVuvZ4PflIHu5FxTcBdG1/img.png&quot; data-alt=&quot;API request 실행 to Spring&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/csTsXX/btqWQ9tFwM0/omVuvZ4PflIHu5FxTcBdG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcsTsXX%2FbtqWQ9tFwM0%2FomVuvZ4PflIHu5FxTcBdG1%2Fimg.png&quot; data-origin-width=&quot;1482&quot; data-origin-height=&quot;1262&quot; data-filename=&quot;Screen Shot 2021-02-10 at 5.06.25 PM.png&quot; width=&quot;469&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;API request 실행 to Spring&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Screen Shot 2021-02-10 at 5.06.15 PM.png&quot; data-origin-width=&quot;2068&quot; data-origin-height=&quot;364&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3nubc/btqWOgs5ix4/1uGwK2qAURGNd75cGp3ik1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3nubc/btqWOgs5ix4/1uGwK2qAURGNd75cGp3ik1/img.png&quot; data-alt=&quot;insert query 실행 to RDB&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3nubc/btqWOgs5ix4/1uGwK2qAURGNd75cGp3ik1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3nubc%2FbtqWOgs5ix4%2F1uGwK2qAURGNd75cGp3ik1%2Fimg.png&quot; data-filename=&quot;Screen Shot 2021-02-10 at 5.06.15 PM.png&quot; data-origin-width=&quot;2068&quot; data-origin-height=&quot;364&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;insert query 실행 to RDB&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이렇게 하면 스프링과 EC2 DB 연동을 마무리 되었다.&lt;/p&gt;
&lt;p&gt;Spring Web 어플리케이션을 EC2 클라우드 서버를 통해 배포하는 것과 스트리밍 서버를 연동 시키는 과정을 해볼 예정이다.&lt;/p&gt;</description>
      <category>Web</category>
      <category>AWS</category>
      <category>Gradle</category>
      <category>JPA</category>
      <category>mariaDB</category>
      <category>MySQL</category>
      <category>RDB</category>
      <category>Spring</category>
      <author>Megan Kim</author>
      <guid isPermaLink="true">https://mysoftworld.tistory.com/41</guid>
      <comments>https://mysoftworld.tistory.com/41#entry41comment</comments>
      <pubDate>Wed, 10 Feb 2021 17:10:58 +0900</pubDate>
    </item>
    <item>
      <title>[e-commerce] JPA 영속성 전이(feat.상품을 장바구니에 담기)</title>
      <link>https://mysoftworld.tistory.com/40</link>
      <description>&lt;p&gt;본격적으로 구체적인 기술 구현에 초점을 둔 글을 써본다. 나와 같은 상황에 처해 고민을 하는 누군가에게 도움이 조금이나마 되길 바라며 공유의 차원에서 성실하게 작성해본다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;기본적으로, 이커머스에서 상품을 구입하는 과정에서 크게 세가지 프로세스를 거친다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;
&lt;h4&gt;상품을 장바구니에 담는다.&lt;/h4&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h4&gt;장바구니에 담긴 아이템들을 주문한다.&lt;/h4&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;h4&gt;주문한 아이템들에 대한 결제를 한다.&lt;/h4&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;상품을 장바구니에 담는 것 vs 장바구니 아이템을 주문하는 것&lt;/h4&gt;
&lt;p&gt;이 두가지를 비교하고자 실제 상용화된 이커머스 서비스를 들여다보면, 후자보다 전자의 프로세스가 훨씬 가볍다는 것을 알 수 있다.&lt;/p&gt;
&lt;p&gt;장바구니에선 수량을 변경하고 배송지를 수정하고 아이템을 삭제할 수 있다. 설령 품절이 되어도 주문전에만 감지하면 되니 빠른 동기화는 필요없다. 반면 오더창으로 넘어가게 되면 모든게 FIX된 정확한 데이터들이어야만 하므로 훨씬 견고하게 움직여야한다.&amp;nbsp;&lt;/p&gt;
&lt;h4&gt;*상품을 장바구니에 담을 때 DB 에 쿼리를 날릴 것인가?&amp;nbsp;&lt;/h4&gt;
&lt;p&gt;이 점을 두고 팀원들이랑 정말 생각 이상의 긴 회의를 하게 되었다. Frontend 에서 product 와 관련된 데이터들을 보내줄 것이고, 이걸 이용해서 cart item 을 만들면 된다라는 주장과, 아니다, DB 에서 해당 productid로 쿼리를 날려 데이터를 받아와야한다는 주장이었다.&lt;/p&gt;
&lt;p&gt;결론적으로 말하면, 전자의 방법이 이상적이나, 현재 우리의 시스템에선 cart item 은 productid를 참조하고 있고 , 이를 위해선 &lt;i&gt;&lt;b&gt;필연적으로&lt;/b&gt;&lt;/i&gt; DB에 쿼리를 날릴 수 밖에 없었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1612704316468&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/***** cartitem entity ****/

@Entity
public class CartItem {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long cartItemId;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;product_id&quot;)
    private Product product;
    
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;cart_id&quot;)
    private Cart cart;
    
    이하 생략 
    
    
    }&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1612704538578&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/****** product entity  ******/

@Entity
public class Product {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long productId;


    @OneToMany(mappedBy= &quot;productId&quot;, cascade = CascadeType.ALL, orphanRemoval= true)
    private List&amp;lt;cartItem&amp;gt; cartItems = new ArrayList&amp;lt;cartItem&amp;gt;();
    
    이하 생략 
    
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1612704985494&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/***** cart entity  *****/

@Entity
public class Cart {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long cartId;


    @OneToMany(mappedBy= &quot;cartId&quot;, cascade = CascadeType.ALL, orphanRemoval= true)
    private List&amp;lt;cartItem&amp;gt; cartItems = new ArrayList&amp;lt;cartItem&amp;gt;();
    
    이하 생략 
    
    }
&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;i&gt;cartItem 을 저장하려면, 즉 em.persist(cartItem) 이 가능해지려면, cartItem 엔티티와 &lt;span style=&quot;color: #ee2323;&quot;&gt;연관관계를 가지고 있는 엔티티&lt;/span&gt;가 JPA &lt;span style=&quot;color: #ee2323;&quot;&gt;영속 상태&lt;/span&gt;여야만한다.&lt;/i&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Frontend에서 아무리 product의 엔티티 모든 필드를 DTO로 전달하고 이를 '객체'엔티티로 만들었다해도, 이것은 그저 비영속 컨텍스트이다. 주의해야한다. 필연적으로 DB에 em.find(productid,product.class) 와 같이 쿼리를 보내야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;* Cartitem의 생명주기에 따른 관리&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;우선 , 현재 Cart와 CartItem은 '양방향 연관관계'를 가지고 있으며 그 연관관계의 주인은 FK를 가진 CartItem이다.&amp;nbsp;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;CartItem은 Cart가 먼저 생성이되어야 생성될 수 있고, Cart가 삭제되면 마찬가지로 삭제된다. 이로서 CartItem의 생명주기는 Cart에 의존적이다. 여기서 말하는 생명주기는 서비스 로직 내의 생명주기보다는 Cart 와의 의존성의 의미가 더크다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이런 상황에서는 &lt;b&gt;&lt;i&gt;&lt;span style=&quot;color: #1a5490;&quot;&gt;cascade = CascadeType.ALL(영속성전이) , orphanRemoval = true (고아객체)&amp;nbsp; &lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;span style=&quot;color: #000000;&quot;&gt;옵션을 쓰기 시기 적절하다고 판단한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;영속성 전이는 부모 엔티티를 영속화 하거나 삭제하는 등의 행위에 함께 자식 엔티티들도 적용시키는 옵션이다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1612705777542&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/****** 영속성 전이를 사용하지 않을 경우 *********/

// 부모(Cart) 저장
Cart cart = new Cart();
em.persist(cart);

// 1번 자식(CartItem1) 저장
CartItem cartItem1= new CartItem();
cartItem1.setCart(cart);
cart.getCartItem().add(cartItem1);
em.persist(cartItem1);

// 2번 자식(CartItem2) 저장
CartItem cartItem2 = new CartItem();
cartItem2.setCart(cart);
cart.getCartItem().add(cartItem2);
em.persist(cartItem2);

&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1612705786044&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/****** 영속성 전이를 사용하는 경우 *********/

CartItem cartItem1= new CartItem();
CartItem cartItem2 = new CartItem();


Cart cart = new Cart();

cartItem1.setCart(cart);
cartItem2.setCart(cart);

cart.getCartItem().add(cartItem1);
cart.getCartItem().add(cartItem2);

// 부모(Cart) 저장, 연관된 자식들(1,2) 저장
em.persist(cart);

&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;u&gt;영속성 전이는 연관관계를 매핑하는 것과는 아무런 연관이 없으며,&lt;/u&gt; 그저 엔티티를 영속화할 때 연관된 엔티티도 함께 영속화하는 편리함을 제공한다. 그래서 위와 같이&amp;nbsp; cartItem들을 cart에 연관관계를 추가한 후 cart를 영속 상태로 만드는 것을 확인할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;위와 같이 한 트랜잭션에서 카트와 카트아이템들이 생성되는 경우는 없지만(Cart는 선 트랜잭션 상에서 만들어진 후, 다음 트랜잭션에서 cartItem을 추가함) , Cart에서 CartItem들을 '관리하고 책임하는 객체' 라는 객체지향적 관점에서 사용하게 되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;POST&amp;nbsp; /api/cartitem&amp;nbsp; vs&amp;nbsp; &amp;nbsp;POST&amp;nbsp; &amp;nbsp;/api/cart/{id}/cartitem&amp;nbsp;&amp;nbsp;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;첫번째로 API로 쓰게 되면, Cart 객체의 책임이 적어진다. Cart 객체는 CartItem들을 효과적으로 관리하기 위해 존재하는 객체라고 생각하기 때문에 CartItem이 단독적으로 생성되고 수정되고 삭제되는 일은 어울리지 않는다. &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;또한, CartItem의 추가,수정,삭제 등에 따라 Cart의 TotalPrice,Quantity 등 값이 업데이트 되어야하는 점을 고려했을때도 일리가 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;따라서, CartItem 은 항상 Cart에 add를 해주고 영속성 전이로서 Cart가 관리하게 해야한다. 특히 Cart가 삭제되는 경우 빛을 발한다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다음으로 고아 객체는 왜 사용하는게 좋을 것인가에 대한 이야기다. 고아 객체는 참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아 객체로 보고 삭제하는 기능이다. &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;정말 정말 중요한 것은 이 기능은 연관 엔티티를 참조하는 것이 나 하나일 때만 사용해야한다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;예를 들어, CartItem과 Product 가 양방향 연관관계를 가지고 있다면, 이 기능은 절대 사용해선 안된다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;i&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Cart 가 CartItem 을 주도적으로 관리하고 있기 때문에 사용 가능한 것이고, &lt;b&gt;'CartItem'을 낱개 단위로 삭제하는 상황에서 매우 편리&lt;/b&gt;하다.&lt;/span&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1612707454644&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/***** 고아 객체 옵션 사용한 경우 ******/

Cart cart = em.find(Cart.class, cartId);

cart.getCartItems().remove(0);   // 첫번째 아이템 제거됨
em.flush(cart);  // 이 시점에 DB에 Delete 쿼리 날라감. 혹은 transaction끝날때 
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Cart의 첫번째 아이템을 삭제 하는 경우 위와 같이 Cart에서 연관관계를 끊어내면 자동으로 delete쿼리가 날라간다. 다시 이야기하지만, 이 기능은 특정엔티티가 개인 소유하는 엔티티에만 적용하여야한다. 즉 @OneToOne 혹은 @OneToMany 에서 말이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;결론적으로, Cart 객체는 CartItem을 관리하는 유일의 부모의 역할을 해내야하는 객체로 바라보아야한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Cart없는 CartItem은 없고, CartItem없는 Cart는 무의미하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;한가지 더 알아야하는 것은 &lt;b&gt;&lt;i&gt;'fetch = FetchType.LAZY' (지연로딩)&lt;/i&gt; &lt;/b&gt;이다. Cart에서 지연로딩을 사용하지 않으면 매번 CartItem들이 즉시로딩되면 이는 &lt;b&gt;N+1&lt;/b&gt; 문제를 야기한다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;CartItem.getCart().메소드() 를 통해서 CartItem이 주체가 되어 필요에 의해 Cart에 의존관계를 맺어야할지 등에대한 고민이 있다. 분명한건 CartItem이 FK를 들고있고 연관관계의 주인이기때문이다. &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;s&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Cart의 List&amp;lt;CartItem&amp;gt;은 GET /api/cart 같은 상황에선 반드시 필요할 것 같으나, 실질적으로 cartitem 을 관리하는 건 cartitem 본인이 되어야 더 깔끔한 로직이 나올 수도 있을 것같다.&lt;/span&gt;&lt;/s&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;고민을 해결하고자 작성하였으나 결국 고민만 더 늘고 끝났다. JPA는 강력하고 견고하지만 , 이를 잘 써먹으려면 더 많이 깊게 공부를 해야한다는 생각뿐이다&lt;i&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;. 다만 한가지 얻은게 있다면,도메인을 면밀히 분석하면 그 위에 놓여진 객체들의 역할, 책임 분할에 대한 시각이 생기게 되고 그 한끝차이들이 설계를 좌우한다는 것이다. &lt;span style=&quot;color: #000000;&quot;&gt;&lt;u&gt;Cart와 CartItem의 관계 , 그리고 Order와 Payment에 비해 영속성에 대한 부담감이 비교적 적은 데이터들을 품고있다는 것&lt;/u&gt;을 생각하니 수월하게 로직의 방향성을 잡을 수 있었다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;* 유레카. 해결책을 찾았다!&lt;/span&gt;&lt;/span&gt;&lt;/i&gt;&lt;/span&gt;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Screen Shot 2021-02-09 at 10.17.52 PM.png&quot; data-origin-width=&quot;1820&quot; data-origin-height=&quot;1220&quot; width=&quot;508&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CVVNz/btqWzt0F5v5/xbWjXSqdhusMO8EkTFy4y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CVVNz/btqWzt0F5v5/xbWjXSqdhusMO8EkTFy4y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CVVNz/btqWzt0F5v5/xbWjXSqdhusMO8EkTFy4y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCVVNz%2FbtqWzt0F5v5%2FxbWjXSqdhusMO8EkTFy4y1%2Fimg.png&quot; data-filename=&quot;Screen Shot 2021-02-09 at 10.17.52 PM.png&quot; data-origin-width=&quot;1820&quot; data-origin-height=&quot;1220&quot; width=&quot;508&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;Cart가 전적으로 Cartitem을 관리하는 방향으로 결정했다.&amp;nbsp;&lt;/span&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1612876834834&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; @OneToMany(fetch = FetchType.LAZY,mappedBy=&quot;cart&quot;,cascade = CascadeType.ALL, orphanRemoval = true)   
    private List&amp;lt;CartItem&amp;gt; cartItems;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Fetch를 LAZY로 설정해놓으면, 아래와 같이 새로운 cartitem을 add() 할때, 기존에 있던 cartitem들이 프록시엔티티이므로 효율성 문제는 사라진다. 이렇게 Cart만 Save하게 되면, 새로운 Caritem에 대한 INSERT 쿼리 하나만 날라가게된다.&lt;b&gt;&lt;i&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt; (영속성전이)&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1612876879361&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// CartService 

    public long addItem(long cartid,long productid) {
        Cart cart = findOne(cartid);
        cart.getCartItems().add(CartItem.createCartItem(cart,productRepository.findById(productid).get()));

        return save(cart).getCartId();

    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;마찬가지로 Delete도 아래와같이 구현하면, 해당 cartitem 이 삭제된다. &lt;i&gt;&lt;b&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;(고아 객체)&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1612877002709&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public long delete(long cartid, long cartitemid) {
        Cart cart = findOne(cartid);
        cart.getCartItems().remove(cartItemRepository.findById(cartitemid).get());
        return save(cart).getCartId();
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Web</category>
      <category>eCommerce</category>
      <category>JPA</category>
      <category>ORM</category>
      <category>Spring</category>
      <category>SpringBoot</category>
      <category>스프링</category>
      <category>이커머스</category>
      <category>자바</category>
      <author>Megan Kim</author>
      <guid isPermaLink="true">https://mysoftworld.tistory.com/40</guid>
      <comments>https://mysoftworld.tistory.com/40#entry40comment</comments>
      <pubDate>Sun, 7 Feb 2021 23:50:30 +0900</pubDate>
    </item>
    <item>
      <title>[e-commerce] Rest API 설계 디자인</title>
      <link>https://mysoftworld.tistory.com/36</link>
      <description>&lt;p&gt;&lt;i&gt;*RESTful API에 대한 기본 이론 설명은 생략하겠습니다.&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Rest에 대한 기본적인 이해와 그 규칙들을 좀 체계적으로 공부해보고싶은데 영어 원서에 대한 부담이 없다면 , 책살돈이 없다면(?), 아래 pdf를 제본해서 보길 추천한다. 번역본들은 거의 다 사야되는데.. &lt;b&gt;&lt;i&gt;Mark Masse 저자의 'REST API Design Rulebo&lt;/i&gt;&lt;i&gt;ok&lt;/i&gt;'&lt;/b&gt; 이 교재는 꽤 유명해서 서치하면 잘 나오는데 책말고 pdf도 있으니 아래 링크에서 확인하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/indrabasak/Books/blob/master/REST%20API%20Design%20Rulebook.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;github.com/indrabasak/Books/blob/master/REST%20API%20Design%20Rulebook.pdf&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1612586202784&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;object&quot; data-og-title=&quot;indrabasak/Books&quot; data-og-description=&quot;CS Books. Contribute to indrabasak/Books development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/indrabasak/Books/blob/master/REST%20API%20Design%20Rulebook.pdf&quot; data-og-url=&quot;https://github.com/indrabasak/Books&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/o7JOk/hyJbktdVud/AY2JUTNGUKS2nHh3H7u3Z0/img.png?width=285&amp;amp;height=285&amp;amp;face=0_0_285_285&quot;&gt;&lt;a href=&quot;https://github.com/indrabasak/Books/blob/master/REST%20API%20Design%20Rulebook.pdf&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/indrabasak/Books/blob/master/REST%20API%20Design%20Rulebook.pdf&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/o7JOk/hyJbktdVud/AY2JUTNGUKS2nHh3H7u3Z0/img.png?width=285&amp;amp;height=285&amp;amp;face=0_0_285_285');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;indrabasak/Books&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;CS Books. Contribute to indrabasak/Books development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;IMG_1902.JPG&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; width=&quot;372&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Rcmih/btqV36cpLhl/OHVONdehqwn1yao6WkZdF0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Rcmih/btqV36cpLhl/OHVONdehqwn1yao6WkZdF0/img.jpg&quot; data-alt=&quot;매우 짧아서 이틀이면 다볼 수 있다.&amp;amp;amp;nbsp;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Rcmih/btqV36cpLhl/OHVONdehqwn1yao6WkZdF0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRcmih%2FbtqV36cpLhl%2FOHVONdehqwn1yao6WkZdF0%2Fimg.jpg&quot; data-filename=&quot;IMG_1902.JPG&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; width=&quot;372&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;매우 짧아서 이틀이면 다볼 수 있다.&amp;nbsp;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;개인적인 조언이지만.. Rest에 관한 규칙들은 생각보다 저자들마다 하는말이 좀 달라서 이것 저것 레퍼런스 참고하면 머릿속만 복잡해지지 실무에 딱딱 적용시켜보기가 만만치않다. 꼭!! 책을 통해서 전체 큰 숲을 보고 적용시켜보는 과정을 거치면 좋겠다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;먼저, 이 글을 읽는 당신이 &lt;b&gt;이커머스 REST API를 설계&lt;/b&gt;하고자한다면 아래 레퍼런스에서 큰 그림을 먼저 그리며 필요한 요소들이 뭔지 쓱 느낌을 알면 좋을 것같다. 보통 커머셜한 사이트에서 자사 API 지원 도큐멘트들은 차고넘치나 도움이 별로 안되는데! 이건 좀 도움을 많이 봤다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://docs.clover.com/reference&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;docs.clover.com/reference&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1612585927359&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;website&quot; data-og-title=&quot;API Reference Overview&quot; data-og-description=&quot;&quot; data-og-host=&quot;docs.clover.com&quot; data-og-source-url=&quot;https://docs.clover.com/reference&quot; data-og-url=&quot;https://docs.clover.com/reference&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.clover.com/reference&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.clover.com/reference&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;API Reference Overview&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;docs.clover.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;[ 자 이제 진짜 알겠으니깐 이커머스에 필요한 핵심 API들을 다루어보자&amp;nbsp; ]&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;* * 규칙 및 설계 가이드&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;-&amp;nbsp;&lt;/b&gt;&lt;i&gt;CRUD&lt;/i&gt; 에 기반한 기본적인 API 먼저 구현하면서 가이드라이닝을 하는것이 효과적이다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp; : 막상 해보다보면 Account 같은건 GET 이 많고 , Cart 같은건 PUT이 많다. 그래도 기본 CRUD를 다 구현하는 걸 목표로 잡고하자.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;-&amp;nbsp;&lt;i&gt;&amp;nbsp;&lt;/i&gt;&lt;/b&gt;&lt;i&gt;'수정' 은 가급적 꼭 'PUT' 메소드&lt;/i&gt;를 사용할 것. 어떤 이는 'POST'로 작업을 하는데 이는 PUT이 지원안되는 브라우저시절이있었기때문에 통용해서 썼다는데 우리는 경험을 하기위한 것이니 'PUT' 사용하기.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;- 국룰이라고 말하는 '&lt;i&gt; 복수형 명사&lt;/i&gt; ' 사용하기. 예를 들어 회원 삭제를 /account/delete/{id} 이런식으로 동사형은 지양하도록 하겠다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Account&amp;nbsp;&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 114px;&quot; border=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;GET&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;POST&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;PUT&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;DELETE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;회원 추가&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&lt;span&gt;/accounts/&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;회원 정보 조회&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;/accounts/{id}&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;회원 정보 수정&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;/accounts/{id}&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;회원 삭제&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;/accounts/{id}&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Screen Shot 2021-02-06 at 1.48.11 PM.png&quot; data-origin-width=&quot;1410&quot; data-origin-height=&quot;1638&quot; width=&quot;355&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzcN3H/btqV0D987Yj/nDVgP83sQlZLn5XkGOUcs0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzcN3H/btqV0D987Yj/nDVgP83sQlZLn5XkGOUcs0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzcN3H/btqV0D987Yj/nDVgP83sQlZLn5XkGOUcs0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzcN3H%2FbtqV0D987Yj%2FnDVgP83sQlZLn5XkGOUcs0%2Fimg.png&quot; data-filename=&quot;Screen Shot 2021-02-06 at 1.48.11 PM.png&quot; data-origin-width=&quot;1410&quot; data-origin-height=&quot;1638&quot; width=&quot;355&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;회원 추가 (&lt;span style=&quot;color: #333333;&quot;&gt;POST)&lt;/span&gt;, 즉 회원 가입 의 경우엔 상세 항목들은 RequestBody에 JSON 방식으로 아래와 같이 들어간다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Screen Shot 2021-02-06 at 1.52.14 PM.png&quot; data-origin-width=&quot;808&quot; data-origin-height=&quot;368&quot; width=&quot;522&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQfQ5c/btqV35R8l0s/ksMIQWinXki4VQWG3mwjBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQfQ5c/btqV35R8l0s/ksMIQWinXki4VQWG3mwjBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQfQ5c/btqV35R8l0s/ksMIQWinXki4VQWG3mwjBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQfQ5c%2FbtqV35R8l0s%2FksMIQWinXki4VQWG3mwjBK%2Fimg.png&quot; data-filename=&quot;Screen Shot 2021-02-06 at 1.52.14 PM.png&quot; data-origin-width=&quot;808&quot; data-origin-height=&quot;368&quot; width=&quot;522&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;Address&lt;/b&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;GET&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;POST&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;PUT&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;DELETE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;배송지 목록 조회&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;/accounts/{i&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;d}&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;/addresses/{id}&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;배송지 추가&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&lt;span&gt;/accounts/{a&lt;/span&gt;&lt;span&gt;Id}&lt;/span&gt;&lt;span&gt;/addresses/&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;배송지 수정&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;/accounts/{i&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;d}&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;/addresses/{id}&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;기본 배송지 설정&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;/accounts/{i&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;d}&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;/default-address/{id}&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;배송지 삭제&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;/accounts/{i&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;d}&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;/addresses/{id}&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;* 기본 배송지는 어떻게 관리할 것인가?&amp;nbsp;&lt;/p&gt;
&lt;p&gt;보통 이커머스를 살펴보면, 배송지는 개인정보랑 따로 관리를 하고, 기본 배송지도 지정할 수 있다. 예를 들어 아래를 보면, 기본 배송지로 어떤 배송지를 택을 하면 페이지 reloading 없이 ajax 로&amp;nbsp; POST로 패킷이 날라간다. (참고로 마켓컬리는 PUT이 거의 없다.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Screen Shot 2021-02-06 at 1.55.28 PM.png&quot; data-origin-width=&quot;1954&quot; data-origin-height=&quot;1502&quot; width=&quot;617&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bL8NC5/btqVZRt3etU/bsMsLGcPjMbIHSCu8RldsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bL8NC5/btqVZRt3etU/bsMsLGcPjMbIHSCu8RldsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bL8NC5/btqVZRt3etU/bsMsLGcPjMbIHSCu8RldsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbL8NC5%2FbtqVZRt3etU%2FbsMsLGcPjMbIHSCu8RldsK%2Fimg.png&quot; data-filename=&quot;Screen Shot 2021-02-06 at 1.55.28 PM.png&quot; data-origin-width=&quot;1954&quot; data-origin-height=&quot;1502&quot; width=&quot;617&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;나는 default address 를 db 상에서 따로 테이블을 만들어서 관리하고 있고, account-defaultaddress 와 같은 조인테이블을 생성했다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;address 테이블에 default라는 필드를 만들어서 상태를 관리해도 좋을 듯했으나, 여러 고민끝에 조인 테이블을 만들었다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;Product&lt;/p&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 95px;&quot; border=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;GET&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;POST&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;PUT&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;DELETE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;상품 조회&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;/products?category={id}&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;상품 추가 (seller)&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;상품 수정 (seller)&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;상품 삭제 (seller)&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 19px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;상품은 굉장히 많기 때문에 보통 카테고리로 관리가 된다. 또한 상품은 Seller account type을 가진 회원만이 추가,수정,삭제를 할 수 있다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;그리고 Tag 로 상품을 검색할 때나, '추천순','별점순' 등으로 랭킹을 해야하기 때문에 질의문이 굉장히 많이 쓰이게된다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이 부분은 다음에 따로 다룰 예정이다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;Screen Shot 2021-02-06 at 2.21.19 PM.png&quot; data-origin-width=&quot;1876&quot; data-origin-height=&quot;1610&quot; width=&quot;563&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbTRmJ/btqV0EBc00h/mHQCkPVqMF27ke4FykhRWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbTRmJ/btqV0EBc00h/mHQCkPVqMF27ke4FykhRWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbTRmJ/btqV0EBc00h/mHQCkPVqMF27ke4FykhRWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbTRmJ%2FbtqV0EBc00h%2FmHQCkPVqMF27ke4FykhRWk%2Fimg.png&quot; data-filename=&quot;Screen Shot 2021-02-06 at 2.21.19 PM.png&quot; data-origin-width=&quot;1876&quot; data-origin-height=&quot;1610&quot; width=&quot;563&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;lt;진행중&amp;gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Cart&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;GET&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;POST&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;PUT&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;DELETE&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CartItem&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Order&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;OrderItem&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Payment&lt;/li&gt;
&lt;/ul&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Web</category>
      <category>API</category>
      <category>api설계</category>
      <category>JSON</category>
      <category>REST API</category>
      <category>restful</category>
      <category>스프링</category>
      <category>자바</category>
      <author>Megan Kim</author>
      <guid isPermaLink="true">https://mysoftworld.tistory.com/36</guid>
      <comments>https://mysoftworld.tistory.com/36#entry36comment</comments>
      <pubDate>Sun, 7 Feb 2021 10:28:57 +0900</pubDate>
    </item>
  </channel>
</rss>