<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>le r&amp;eacute;cit de ellie</title>
    <link>https://programmer-ririhan.tistory.com/</link>
    <description>ellie의 개발 블로그</description>
    <language>ko</language>
    <pubDate>Fri, 21 Aug 2026 19:20:34 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>ellie.strong</managingEditor>
    <image>
      <title>le r&amp;eacute;cit de ellie</title>
      <url>https://tistory1.daumcdn.net/tistory/3531896/attach/c24a6a14977946c795939e436b3d2251</url>
      <link>https://programmer-ririhan.tistory.com</link>
    </image>
    <item>
      <title>모락  회고를 가장한 우테코 막바지를 맞이한 소감</title>
      <link>https://programmer-ririhan.tistory.com/429</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;두서없이 쓰는 그냥 일기입니다 &lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;(잠깐 홍보) 모락이 어떤 프로젝트인지 궁금하다면?  &lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://sites.google.com/woowahan.com/woowacourse-demo-4th/%ED%94%84%EB%A1%9C%EC%A0%9D%ED%8A%B8/%EB%AA%A8%EB%9D%BD&quot;&gt;모락 데모 페이지로 이동&lt;/a&gt;&lt;/b&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-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2142&quot; data-origin-height=&quot;1318&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceCzG4/btrPhlOH0c6/1nMXvLvnPauUVpfiUGGHU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceCzG4/btrPhlOH0c6/1nMXvLvnPauUVpfiUGGHU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceCzG4/btrPhlOH0c6/1nMXvLvnPauUVpfiUGGHU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceCzG4%2FbtrPhlOH0c6%2F1nMXvLvnPauUVpfiUGGHU0%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;600&quot; height=&quot;369&quot; data-origin-width=&quot;2142&quot; data-origin-height=&quot;1318&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;h2 data-ke-size=&quot;size26&quot;&gt;최종 데모 데이&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;이틀 전 금요일 우아한테크코스 4기 최종 데모데이를 진행했다.&amp;nbsp;팀원들과 함께 약 4개월간 진행했던 프로젝트를 우테코 외부 사람들에게도 공개하는 것이기 때문에 설레면서도 불안했다. '우리 프로젝트를 사람들이 보러 올까?', '사람들이 어떤 반응을 보일까?' 사실 설렘보다는 불안한 마음이 더 컸던 것 같다 &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-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;274&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RaCCp/btrPkqIF7KU/knJvaKbN35duKQiKE23sLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RaCCp/btrPkqIF7KU/knJvaKbN35duKQiKE23sLk/img.png&quot; data-alt=&quot;게더로 진행됐던 최종 데모 데이!&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RaCCp/btrPkqIF7KU/knJvaKbN35duKQiKE23sLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRaCCp%2FbtrPkqIF7KU%2FknJvaKbN35duKQiKE23sLk%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;500&quot; height=&quot;274&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;274&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;게더로 진행됐던 최종 데모 데이!&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;position: absolute;&quot; data-ke-size=&quot;size16&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;/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;/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;여튼 생각했던 것 보다 재미있게, 정신없게 최종 데모 데이를 마쳤다. 뿌듯함이 컸다. 그리고 회식 가기전 이번 스프린트와 관련한 회고를 진행했다. 회고를 하다가 프로젝트 초반의 1, 2차 데모 데이가 생각나서 살짝 울컥했다. 당시에는 미뤄져만 가는 일정에, '우리가 주제를 정말 잘못 잡은건가?'하는 의문이 계속 들었다. 나는 지금까지 프로젝트를 해오는 원동력이 프로젝트에 대한 애정이라고 생각했다. 하지만 이번 프로젝트는 달랐다. 내 원동력은 전적으로 우리 팀원들에게서 나왔다. 이렇게 매일 붙어있으면서도 항상 즐겁게 떠들고 웃을 수 있는 우리 팀원들이 있었기에, 항상 놀랄만큼 열심히 하는 우리 팀원들이 있었기에 이번 프로젝트를 끝까지 완성할 수 있었다고 생각한다. 그리고 포기하지 않으니 결과물도 만족스럽다. 많이 컸다 모락이 &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;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;우테코에서 진행하는 프로젝트는 &quot;진짜 협업&quot;을 경험하게 한다고 생각한다. 학부 과정에서 여러 팀 프로젝트 경험을 했지만 결이 다르다고 생각한다. 당시의 팀 프로젝트는 &quot;분업&quot;에 가까웠다고 생각한다. 프로젝트 기획 회의를 하고 각자 어떤 일을 할지 나누고 하면 끝!이다. 내 코드를 봐주는 사람이 없다. 결과물만 볼 뿐이다.&amp;nbsp;하지만 이번 모락 프로젝트는 진짜 협업이었다. 여기에는 &quot;팀문화&quot;, &quot;데일리 미팅&quot;, &quot;코드 리뷰&quot;, &quot;회고&quot;가 가장 큰 기여를 하지 않았나 싶다.&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;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;일단 프로젝트 초반에 &quot;팀문화&quot;를 정한다는 것에서부터 game over다. 팀 문화는 팀원들간의 약속이다. 이 약속을 지키려고 노력하는 과정에서 팀원들간의 결속력이 생기고 팀 분위기가 좋아지게 한다고 생각한다. 아래는 모락 팀의 팀문화이다. 이건 사실 최종 버전이고, 초기 버전에서 회고를 진행하면서 팀 문화를 계속해서 개선해왔다.&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-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;591&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqsaNb/btrPgCwCzcA/95tadlNec6YhGJyI1VU9A0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqsaNb/btrPgCwCzcA/95tadlNec6YhGJyI1VU9A0/img.png&quot; data-alt=&quot;모락의 팀문화❤️&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqsaNb/btrPgCwCzcA/95tadlNec6YhGJyI1VU9A0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqsaNb%2FbtrPgCwCzcA%2F95tadlNec6YhGJyI1VU9A0%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;700&quot; height=&quot;591&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;591&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;모락의 팀문화❤️&lt;/figcaption&gt;
&lt;/figure&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;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;데일리 미팅이 나는 진짜 진국이라고 생각한다. 아침마다 서로의 컨디션 점수와 그 이유를 공유하면서 웃는 시간을 가진다. 저혈압 때문에 아침 컨디션이 정말 안 좋을 편인데 데일리 미팅을 하다보면서 점점 기분이 좋아지는게 느껴진다. 아침을 시작하는 최고의 방법이다 &lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;15분은 그렇게 서로의 컨디션을 체크하고 이후 15분 동안은 오늘의 TODO를 이야기하면서 같이 계획을 세운다. 나중에는 회고를 통해 전날 어떤 일을 했는지도 공유하는게 좋을 것 같다는 의견이 나와 이 15분 동안은 어제의 DID와 오늘의 TODO를 이야기하는 시간으로 바뀌었다. 항상 머물지 않고 팀문화를 개선해 나가려고 하고 여기에 적극적으로 협조하는 우리 팀원들의 모습이 너무 멋지다 &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;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;코드 리뷰야 말로 내 코드를 다른 사람에게 보여주고 나도 다른사람의 코드를 볼 기회를 제공한다고 생각한다. 레벨1,2에서는 내가 한 미션에 대해 리뷰어에게 코드 리뷰를 받는 입장이었지만, 레벨3부터 프로젝트를 진행하면서 팀원들끼리 서로 코드리뷰를 해주게 됐다. 처음에는 받아 보기만 한 코드 리뷰를 어떻게 남겨야할지 잘 감이 잡히지 않았지만 몇번 하다보니 점점 재미를 붙이게 됐다. 다른 사람의 코드를 보면서 내 의견을 낼 수도 있고, 내 코드에 다른 팀원들이 의견을 내주는게 꽤나 재미있는 과정이라고 생각했다. 특히 처음에는 PR의 files changed만 보면서 리뷰를 남겼었는데, 배카라가 pull 받아서 리뷰를 해달라고 했다. 처음에는 '오잉? 그냥 files changed만 봐도 리뷰할 수 있는데?'라는 생각이 들었었다. 하지만 이렇게 pull 받아서 리뷰를 하다보니 불필요한 import를 찾을 수도 있었고, 실제로 코드를 바꿔보면서 개선해볼 수 있는 부분들을 찾을 수도 있었고, 테스트도 돌려볼 수 있는 등 많이 장점이 있었다. 그래서 그 이후에는 최대한 pull 받아서 리뷰를 하고 있다. 까라 땡큐 &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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;390&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eiC8nR/btrPkrAO0l5/BaoOpIsck0Ge4K0PtmyV6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eiC8nR/btrPkrAO0l5/BaoOpIsck0Ge4K0PtmyV6K/img.png&quot; data-alt=&quot;요즘에 PR에 스누피 짤을 넣는데 재미를 붙였다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eiC8nR/btrPkrAO0l5/BaoOpIsck0Ge4K0PtmyV6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeiC8nR%2FbtrPkrAO0l5%2FBaoOpIsck0Ge4K0PtmyV6K%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;500&quot; height=&quot;390&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;390&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;요즘에 PR에 스누피 짤을 넣는데 재미를 붙였다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;회고&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;회고는 팀 문화를 개선해나갈 수 있는 기회라고 생각한다. 개발 이야기는 평소 일상에서도 많이 하고 관련 회의 시간이 많기 때문에 우리 팀에서는 회고 시간에는 감정 회고나 팀 문화에 대한 회고를 진행하기로 했다. 잘 한 선택이라고 생각한다 &lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;우리는 KPT 방식으로 회고를 진행했다. 좋았던 점, 앞으로도 이어갔으면 좋겠는점(Keep)과 문제라고 생각하는 점(Problem), 문제라고 생각하는 점을 어떻게 개선할 수 있을지(Try)를 이야기한다. 이 과정이 있었기에 팀 문화를 계속해서 개선해나갈 수 있었고, 그러면서 팀 분위기가 더 좋아지지 않았나?싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;우테코에 들어와 &quot;회고&quot;라는 과정을 정말 많이 진행했는데, 스스로 메타 인지를 높일 수 있는 시간이라고 생각한다. 그래도 회고에 회고는 아직 어렵다..ㅋㅎ&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;/h2&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;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;- &quot;왜&quot;의 중요성을 알게 됐다.&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;- command + b로 javadocs와 내부 구현을 보고 이해할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;- 이전보다 intellij 단축키를 많이 알게 됐다.&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;- CI/CD 자동화 환경을 구축할 수 있다.&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;- 스터디를 통해 다른 사람들과 함께 학습하는 법을 배웠다.&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;(더 많을꺼야..)&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;/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;ps. 바쁘다는 이유로 우테코 과정 동안 블로그 포스팅을 많이 못한게 많이 아숩다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Life</category>
      <category>모락</category>
      <category>성장</category>
      <category>우테코</category>
      <category>최종 데모 데이</category>
      <category>취준</category>
      <author>ellie.strong</author>
      <guid isPermaLink="true">https://programmer-ririhan.tistory.com/429</guid>
      <comments>https://programmer-ririhan.tistory.com/429#entry429comment</comments>
      <pubDate>Sun, 23 Oct 2022 17:21:02 +0900</pubDate>
    </item>
    <item>
      <title>[모락] byte[]를 String으로 변환하기</title>
      <link>https://programmer-ririhan.tistory.com/428</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;모락 프로젝트를 진행하는 과정에서의 트러블 슈팅 내용을 다루고 있습니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 byte[]를 String으로 변환하는 방법에 대해 이야기해보려 합니다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 QA 과정에서 발견된 문제 상황에 대해 공유해드리려고해요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희는 로직 상 처리하지 못한 예외에 대해서는 문제 상황을 재현해 볼 수 있도록 요청 정보를 모두 로깅하고 있었습니다. 그런데 로그 파일을 까고 보니 요청 바디(Request Body)가 다음과 같이 이상한 숫자들로 출력되고 있었습니다. 분명 PR을 날리기 전에 요청 바디가 잘 찍히는 것을 확인했었는데.. 이상한 일이었습니다 &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-origin-width=&quot;1280&quot; data-origin-height=&quot;342&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bywxvg/btrKGOHv2B4/ZxWMlMiFKcXbuOKz8qUmA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bywxvg/btrKGOHv2B4/ZxWMlMiFKcXbuOKz8qUmA0/img.png&quot; data-alt=&quot;바보같이 찍히고 있는 요청 바디&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bywxvg/btrKGOHv2B4/ZxWMlMiFKcXbuOKz8qUmA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbywxvg%2FbtrKGOHv2B4%2FZxWMlMiFKcXbuOKz8qUmA0%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;1280&quot; height=&quot;342&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;342&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;이와 관련해 왜 이러한 문제가 발생했는지, 어떻게 해결했는지 이야기해보겠습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;new String()을 사용했던 이유&lt;/h3&gt;
&lt;pre id=&quot;code_1661515764964&quot; class=&quot;processing&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private static StringBuilder getBody(ContentCachingRequestWrapper requestWrapper) {
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.append(&quot;body: &quot;).append(NEWLINE);
    stringBuilder.append(new String(requestWrapper.getContentAsByteArray()));
    return stringBuilder;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희는 ContentCashingRequestWrapper#getContentAsByteArray()를 사용해 요청 바디를 byte[]로 불러와 사용하기 때문에 &lt;span data-token-index=&quot;1&quot; data-reactroot=&quot;&quot;&gt;byte[]를 String으로 변환&lt;/span&gt;해야했습니다. 검색 결과 byte[]를 String으로 변환하기 위해서는 &lt;span data-token-index=&quot;3&quot; data-reactroot=&quot;&quot;&gt;String의 생성자&lt;/span&gt;를 이용해 직접 String으로 만들어주면 된다는 글이 많았습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;398&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bY1s0W/btrKFAXOVuo/stFyyCqAuHe7TCs9wkzJPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bY1s0W/btrKFAXOVuo/stFyyCqAuHe7TCs9wkzJPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bY1s0W/btrKFAXOVuo/stFyyCqAuHe7TCs9wkzJPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbY1s0W%2FbtrKFAXOVuo%2FstFyyCqAuHe7TCs9wkzJPk%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;1280&quot; height=&quot;398&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;398&quot;/&gt;&lt;/span&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;어렴풋한 기억에 Java에서는 String과 Wrapper 클래스에 대해 &lt;b&gt;&lt;span data-token-index=&quot;1&quot; data-reactroot=&quot;&quot;&gt;객체 풀(Object Pool)&lt;/span&gt;&lt;/b&gt;을 이용하기 때문에 생성자를 사용하지 말고, valueOf()를 사용하는 것이 좋다고 했던 것 같아 valueOf()를 사용해보려 했지만 byte[]에 대한 valueOf()는 제공되고 있지 않아 String의 생성자를 직접 사용했었습니다.&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-origin-width=&quot;1114&quot; data-origin-height=&quot;548&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/H2vKw/btrKFPtFA7Z/6D2d0iXfKFlh9Lbh8zKQo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/H2vKw/btrKFPtFA7Z/6D2d0iXfKFlh9Lbh8zKQo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/H2vKw/btrKFPtFA7Z/6D2d0iXfKFlh9Lbh8zKQo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FH2vKw%2FbtrKFPtFA7Z%2F6D2d0iXfKFlh9Lbh8zKQo0%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;1114&quot; height=&quot;548&quot; data-origin-width=&quot;1114&quot; data-origin-height=&quot;548&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Arrays.toString()은 내가 생각한 녀석이 아니었다.&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;389&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lg5jd/btrKFv3xT2r/ZPw2TdpYMQhwNeKgoWOQq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lg5jd/btrKFv3xT2r/ZPw2TdpYMQhwNeKgoWOQq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lg5jd/btrKFv3xT2r/ZPw2TdpYMQhwNeKgoWOQq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Flg5jd%2FbtrKFv3xT2r%2FZPw2TdpYMQhwNeKgoWOQq0%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;1280&quot; height=&quot;389&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;389&quot;/&gt;&lt;/span&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;요청 바디가 잘 출력되는 것을 확인하고 PR을 날렸습니다. 하지만 제 PR의 코드를 정적 분석한 소나큐브가 제 코드에서 냄새가 난다고 하더라구요ㅠㅅㅠ 당시 이 냄새를 없애야 한다는 생각에 이래저래 코드를 바꿔보다가 Arrays.toString()을 발견하고 혼자 좋다면서 테스트도 안 해보고 PR에 커밋을 붙였습니다&lt;span data-token-index=&quot;1&quot; data-reactroot=&quot;&quot;&gt;.&lt;/span&gt; 메서드 이름이 누가봐도 배열을 String으로 출력해준다는 것 같지 않나요?&lt;span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1661515865720&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Arrays.toString(requestWrapper.getContentAsByteArray());&lt;/code&gt;&lt;/pre&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;342&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bywxvg/btrKGOHv2B4/ZxWMlMiFKcXbuOKz8qUmA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bywxvg/btrKGOHv2B4/ZxWMlMiFKcXbuOKz8qUmA0/img.png&quot; data-alt=&quot;바보같이 찍히고 있는 요청 바디&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bywxvg/btrKGOHv2B4/ZxWMlMiFKcXbuOKz8qUmA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbywxvg%2FbtrKGOHv2B4%2FZxWMlMiFKcXbuOKz8qUmA0%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;1280&quot; height=&quot;342&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;342&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;Arrays.toString()의 구현 로직을 보니 단순히 배열의 모든 byte를 출력할 때 사용하는 메서드였습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1661515915963&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static String toString(byte[] a) {
    if (a == null)
        return &quot;null&quot;;
    int iMax = a.length - 1;
    if (iMax == -1)
        return &quot;[]&quot;;

    StringBuilder b = new StringBuilder();
    b.append('[');
    for (int i = 0; ; i++) {
        b.append(a[i]);
        if (i == iMax)
            return b.append(']').toString();
        b.append(&quot;, &quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;new String()의 charset 파라미터&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 다시 String의 생성자를 사용하는 방식으로 회귀했습니다. 이와 관련해서 팀원 차리가 charset 파라미터도 넣어주면 좋을 것 같다는 조언을 해주었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1661515955710&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private static StringBuilder getBody(ContentCachingRequestWrapper requestWrapper) {
    StringBuilder stringBuilder = new StringBuilder();
    stringBuilder.append(&quot;body: &quot;).append(NEWLINE);
    stringBuilder.append(new String(requestWrapper.getContentAsByteArray(), StandardCharsets.UTF_8));
    return stringBuilder;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 charset 파라미터를 통해 String으로 변환하려는 byte[]의 byte가 어떠한 charset(UTF_8, US_ASCII, ISO_8859_1 등)인지 명시해줄 수 있습니다. 디코딩할 때 이를 활용하겠죠?&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;526&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJM7ea/btrKHtJFeOw/wDnnpFRdMmdCftg1eIyEM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJM7ea/btrKHtJFeOw/wDnnpFRdMmdCftg1eIyEM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJM7ea/btrKHtJFeOw/wDnnpFRdMmdCftg1eIyEM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJM7ea%2FbtrKHtJFeOw%2FwDnnpFRdMmdCftg1eIyEM1%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;1280&quot; height=&quot;526&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;526&quot;/&gt;&lt;/span&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;결과적으로 문자열 및 이모지 모두 디코딩이 잘 되었으며, 소나큐브에서도 이를 코드 스멜로 잡아내지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;new String()은 어떤 charset을 사용할까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 그냥 new String()을 사용해도 문자열과 이모지 모두 디코딩이 잘 됩니다. 그래서 궁금해졌습니다. 그럼 new String()에서는 default charset으로 UTF_8을 사용하는건가?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docs를 살펴보면 using the platform's default charset, 즉 플랫폼의 기본 charset을 사용한다고 설명하고 있습니다.&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-origin-width=&quot;1276&quot; data-origin-height=&quot;496&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4w8am/btrKIsjdmjv/RB8Q4ujSBSKJAJGmptJw21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4w8am/btrKIsjdmjv/RB8Q4ujSBSKJAJGmptJw21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4w8am/btrKIsjdmjv/RB8Q4ujSBSKJAJGmptJw21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4w8am%2FbtrKIsjdmjv%2FRB8Q4ujSBSKJAJGmptJw21%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;1276&quot; height=&quot;496&quot; data-origin-width=&quot;1276&quot; data-origin-height=&quot;496&quot;/&gt;&lt;/span&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;관련해서 찾아보니 플랫폼은 JVM을 말한다고 합니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Every instance of the &lt;b&gt;Java virtual machine has a default charset&lt;/b&gt;, which may or may not be one of the standard charsets. The default charset is determined during virtual-machine startup and typically depends upon the locale and charset being used by the underlying operating system. (출처: &lt;a href=&quot;https://docs.oracle.com/javase/7/docs/api/java/nio/charset/Charset.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Oracle 공식 문서&lt;/a&gt;)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JVM의 default charset을 알려주는 Charset.defaultCharset()을 이용해 찍어보니 제 노트북에서는 UTF-8로 적용되어 있었습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;b&gt;실습 환경&lt;/b&gt;&lt;br /&gt;openjdk 11.0.11 2021-04-20 OpenJDK Runtime Environment AdoptOpenJDK-11.0.11+9 (build 11.0.11+9) &lt;br /&gt;OpenJDK 64-Bit Server VM AdoptOpenJDK-11.0.11+9 (build 11.0.11+9, mixed mode)&lt;/blockquote&gt;
&lt;pre id=&quot;code_1661516189160&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;System.out.println(&quot;Charset.defaultCharset() = &quot; + Charset.defaultCharset());&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;912&quot; data-origin-height=&quot;328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VXtLZ/btrKGxF4yvY/Os32rEuzuPKpQsnM2FCWB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VXtLZ/btrKGxF4yvY/Os32rEuzuPKpQsnM2FCWB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VXtLZ/btrKGxF4yvY/Os32rEuzuPKpQsnM2FCWB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVXtLZ%2FbtrKGxF4yvY%2FOs32rEuzuPKpQsnM2FCWB1%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;912&quot; height=&quot;328&quot; data-origin-width=&quot;912&quot; data-origin-height=&quot;328&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- String 생성자를 이용해 byte[]를 String으로 변환(디코딩)할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- new String(), new String(byte[], Charset) 둘 다 사용할 수 있지만 new String(byte[], Charset)이 &amp;ldquo;이 메서드는 디코딩을 하는 애에요&amp;rdquo;라고 명시적으로 말해주는 것 같아 new String(byte[], Charset)을 선택했습니다.&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;h3 data-ke-size=&quot;size23&quot;&gt;참고 자료&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://tech.wheejuni.com/2018/05/21/java-string/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://tech.wheejuni.com/2018/05/21/java-string/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://stackoverflow.com/questions/14402794/object-pool-for-java-wrappers-and-string&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://stackoverflow.com/questions/14402794/object-pool-for-java-wrappers-and-string&lt;/a&gt;&lt;/p&gt;</description>
      <category>Backend/Java</category>
      <category>Byte Array</category>
      <category>Java</category>
      <category>string</category>
      <category>자바</category>
      <author>ellie.strong</author>
      <guid isPermaLink="true">https://programmer-ririhan.tistory.com/428</guid>
      <comments>https://programmer-ririhan.tistory.com/428#entry428comment</comments>
      <pubDate>Fri, 26 Aug 2022 21:20:26 +0900</pubDate>
    </item>
    <item>
      <title>[모락] EC2에서 스왑 메모리(Swap Memory) 설정하기</title>
      <link>https://programmer-ririhan.tistory.com/427</link>
      <description>&lt;blockquote data-ke-style=&quot;style2&quot;&gt;모락 프로젝트를 진행하는 과정에서의 트러블 슈팅 내용을 다루고 있습니다.&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;오늘은 EC2 인스턴스에서 Swap Memory를 설정하는 방법에 대해 이야기해보려 합니다.&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;모락팀에서는 정적 분석 도구로 SonarQube를 사용합니다. SonarQube를 사용하기 위해서는 이를 위한 서버를 하나 띄워야합니다. 24시간 정적 분석이 가능하게 하려면 24시간 가동 중인 컴퓨터에 해당 서버를 띄워야겠죠? 그래서 저희는 SonarQube 용 EC2 인스턴스를 새로 띄우기로 결정했습니다.&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;그런데 이 과정에서 문제가 발생했습니다. SonarQube 설치까지는 정상적으로 완료했는데.. 서버가 띄워지지 않았습니다. 뭐가 문제일까? 고민을 하다 SonarQube 서버를 띄우기 위한 최소 메모리 요구사항이 있었던 것 같아 메모리 사용량을 확인해봤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;top 명령어를 통해 메모리 사용량을 실시간으로 확인할 수 있습니다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/45311765/186837960-4e67eec5-bb27-418f-b118-1127483c45ee.png&quot; alt=&quot;image&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;&lt;a href=&quot;https://docs.sonarqube.org/latest/requirements/requirements/&quot;&gt;SonarQube 공식 문서&lt;/a&gt;를 보면 개인 사용자의 경우 최소 3GB의 메모리가 필요하다고 명시하고 있습니다. (역시 공식문서를 잘 읽어봐야겠죠? )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/45311765/186838082-62ff3337-f9f8-47e7-951b-6d97d9fe7a78.png&quot; alt=&quot;image&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;현재 사용 중인 EC2 인스턴스의 경우 1GB 메모리만을 사용할 수 있는 제약이 있었습니다. 저희는 추가적으로 2GB의 스왑 메모리(Swap Memory)를 설정해 이 문제를 해결하려 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스왑 메모리(Swap Memory)&lt;/h2&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;스왑 메모리란 &lt;b&gt;실제 메모리(RAM)이 가득 찼지만 더 많은 메모리가 필요할 때 디스크 공간을 이용하여 부족한 메모리를 대체할 수 있는 공간(가상 메모리)&lt;/b&gt;을 의미합니다. 따라서 하드 디스크의 일부를 RAM 처럼 사용할 수 있게 하는 기술이라 볼 수 있습니다.&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;/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;/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;(토이 프로젝트 특성상?) 사실 속도적인 측면에서는 크게 체감한 부분은 없는 것 같습니다. 하지만 하드 디스크의 공간이 줄어든 다는 단점은 치명적일 수 있습니다. 저희 팀에서는 모든 EC2 인스턴스의 하드 디스크 크기를 8GB로 생성해주었습니다. 이 중 젠킨스(Jenkins)를 설치한 인스턴스의 경우 스왑 메모리를 설정하니 그 만큰 사용할 수 있는 하드 디스크의 크기가 줄어 젠킨스가 실행되지 않는 문제가 발생했습니다. (이 부분은 어쩔 수 없이 하드 디스크를 늘려 해결했습니다 ) 이 부분에 대해 인지하고 스왑 메모리를 설정하시면 좋겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스왑 메모리 설정하기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 실제로 스왑 메모리를 설정해보려하는데요. 해당 과정은 &lt;a href=&quot;https://aws.amazon.com/ko/premiumsupport/knowledge-center/ec2-memory-swap-file/&quot;&gt;AWS 공식 홈페이지&lt;/a&gt;를 참고하였으며, 실습 환경은 다음과 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;EC2 인스턴스 환경: Ubuntu, 64bit(Arm), t4g.micro(1GB), 8GB Storage&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. dd 명령을 사용하여 루트 파일 시스템에 빈 스왑 파일을 생성합니다.&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;$ sudo dd if=/dev/zero of=/swapfile bs=128M count=16
16+0 records in
16+0 records out
2147483648 bytes (2.1 GB, 2.0 GiB) copied, 14.3367 s, 150 MB/s&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;of 옵션으로 스왑 파일의 위치와 이름을 지정할 수 있습니다. 저는 / 디렉토리에 swapfile이라는 이름으로 생성하였습니다.&lt;/li&gt;
&lt;li&gt;bs 옵션은 블록의 크기, count 옵션은 블록의 개수를 의미하며, 스왑 파일의 총 크기는 bs * count가 됩니다. 저는 총 2.1GB의 스왑 파일을 생성했습니다.&lt;/li&gt;
&lt;li&gt;bs(블록의 크기)는 인스턴스에서 사용 가능한 메모리보다 작아야 합니다. 그렇지 않으면 &quot;memory exhausted&quot; 오류가 발생합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 스왑 파일의 읽기/쓰기 권한을 업데이트합니다.&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;$ sudo chmod 600 /swapfile&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 앞서 만든 빈 파일(&lt;code&gt;/swapfile&lt;/code&gt;)을 스왑파일로 설정합니다.&lt;/h3&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;$ sudo mkswap /swapfile
Setting up swapspace version 1, size = 2 GiB (2147479552 bytes)
no label, UUID=...&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 스왑 공간에 스왑 파일을 추가해 스왑 파일을 즉시 활성화합니다.&lt;/h3&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt;$ sudo swapon /swapfile&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 실제 메모리 1G + 가상 메모리 2G를 가지게 되었습니다 &lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 스왑이 활성화된 파티션과 사이즈를 확인해봅니다.&lt;/h3&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;$ sudo swapon -s
Filename                Type        Size        Used        Priority
/swapfile                               file        2097148        0        -2&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. &lt;code&gt;/etc/fstab&lt;/code&gt; 파일 마지막 줄에 다음 내용을 추가해, 인스턴스가 재부팅되더라도 스왑 파일을 사용하도록 설정해줍니다.&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;/swapfile swap swap defaults 0 0&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 설정을 해주지 않는다면 부득이한 경우로 EC2 인스턴스를 재부팅한 경우 스왑 파일이 적용되어 있지 않을 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 스왑 메모리 2GB가 추가되어 있으며, SonarQube 서버도 정상적으로 띄울 수 있었습니다 &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/45311765/186838427-2b4fa42b-4ad4-447b-be35-6c915785e080.png&quot; alt=&quot;image&quot; /&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;참고 자료&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://aws.amazon.com/ko/premiumsupport/knowledge-center/ec2-memory-swap-file/&quot;&gt;https://aws.amazon.com/ko/premiumsupport/knowledge-center/ec2-memory-swap-file/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;http://www.macnorton.com/csLab/886323&quot;&gt;http://www.macnorton.com/csLab/886323&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jw910911.tistory.com/122&quot;&gt;https://jw910911.tistory.com/122&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://it-serial.tistory.com/entry/Linux-Swap-%ED%8C%8C%ED%8B%B0%EC%85%98%EC%9D%B4%EB%9E%80-CPU-RAM-%ED%95%98%EB%93%9C-%EB%94%94%EC%8A%A4%ED%81%AC-%E2%91%A0&quot;&gt;https://it-serial.tistory.com/entry/Linux-Swap-%ED%8C%8C%ED%8B%B0%EC%85%98%EC%9D%B4%EB%9E%80-CPU-RAM-%ED%95%98%EB%93%9C-%EB%94%94%EC%8A%A4%ED%81%AC-%E2%91%A0&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Backend/Infra</category>
      <category>AWS</category>
      <category>EC2</category>
      <category>Swap memory</category>
      <category>모락</category>
      <category>스왑 메모리</category>
      <author>ellie.strong</author>
      <guid isPermaLink="true">https://programmer-ririhan.tistory.com/427</guid>
      <comments>https://programmer-ririhan.tistory.com/427#entry427comment</comments>
      <pubDate>Fri, 26 Aug 2022 15:43:11 +0900</pubDate>
    </item>
    <item>
      <title>간단히 스프링 부트(Spring Boot)에 대해 알아보자!</title>
      <link>https://programmer-ririhan.tistory.com/426</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;스프링에 대해 학습하다보니 내가 &quot;웹 체스 미션&quot;에서 사용하고 있는 &lt;b&gt;스프링 부트는 그냥 스프링과 어떻게 다른 것인지 궁금해졌다.&lt;/b&gt; 최범균님의 &quot;초보 웹 개발자를 위한 스프링5 프로그래밍 입문&quot;과 &lt;a href=&quot;https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#using.using-the-springbootapplication-annotation&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;스프링 부트 공식 문서&lt;/a&gt;로 학습한 내용을 짧게 정리했다. &lt;span&gt;스프링 부트에 대해 깊게 학습했다기보다는 &lt;b&gt;&quot;스프링 부트의 @SpringBootApplication 어노테이션이 이런 기능을 해주는거구나~&quot;&lt;/b&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;스프링 부트는 최소한의 작업으로 스프링 프로젝트를 시작할 수 있도록 돕는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@SpringBootApplication&lt;/b&gt; 어노테이션을 사용해 이러한 스프링 부트의 핵심 기능을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;@SpringBootApplication&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Many Spring Boot developers like their apps to use &lt;b&gt;auto-configuration, component scan and be able to define extra configuration on their &quot;application class&quot;.&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;필요한 설정을 자동으로 등록한다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SpringApplication.run()&lt;/b&gt;을 이용해서 이 어노테이션을 붙인 클래스를 실행하면 여러 필요한 설정을 자동으로 등록해준다.&lt;/p&gt;
&lt;pre id=&quot;code_1651413263371&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@SpringBootApplication
public class SpringChessApplication {

    public static void main(final String[] args) {
        SpringApplication.run(SpringChessApplication.class, args);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;spring-boot-starter-web&lt;/b&gt; 모듈을 사용하면 웹 관련 자동 설정 기능을 활성화한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;spring-webmvc, JSON, Validator, 내장 톰캣 등 &lt;b&gt;웹 개발에 필요한 의존을 설정&lt;/b&gt;한다.&lt;/li&gt;
&lt;li&gt;DispatcherServlet, 디폴트 서블릿, Jackson 등 &lt;b&gt;스프링 MVC를 위한 다양한 구성 요소에 대한 설정을 자동 생성&lt;/b&gt;한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;spring-boot-starter-jdbc&lt;/b&gt; 모듈을 사용하면 DB관련 자동 설정 기능을 활성화한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;JDBC 연결에 필요한 DataSource, JdbcTemplate, 트랜잭션 관리자 등을 자동으로 설정&lt;/b&gt;한다.&lt;/li&gt;
&lt;li&gt;DataSource를 생성할 때 필요한 JDBC URL 정보는 application.properties 파일에서 읽어온다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;build.gradle 파일&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-jdbc'

		runtimeOnly 'mysql:mysql-connector-java'
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;컴포넌트 스캔 기능을 활성화한다.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Component, @Controller, @RestController, @Service, @Repository 등 컴포넌트 스캔 대상 어노테이션을 붙인 클래스를 빈으로 자동 등록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;application.properties&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;application.properties 파일에 설정 정보를 담을 수 있다. JDBC URL, 웹 캐시 시간과 같은 설정을 변경하고 싶을 때 이 설정 파일에 설정을 추가해주면 된다.&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;/p&gt;</description>
      <category>Backend/Spring</category>
      <category>스프링 부트</category>
      <category>웹</category>
      <category>자바</category>
      <author>ellie.strong</author>
      <guid isPermaLink="true">https://programmer-ririhan.tistory.com/426</guid>
      <comments>https://programmer-ririhan.tistory.com/426#entry426comment</comments>
      <pubDate>Sun, 1 May 2022 22:51:36 +0900</pubDate>
    </item>
    <item>
      <title>LIMIT에는 OFFSET을 지정할 수 있다.</title>
      <link>https://programmer-ririhan.tistory.com/425</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;SQL 스터디에 참여하고 있는데, &quot;총매출이 2번째로 높은 공급자의 id, 이름, 총매출을 조회해 주세요.&quot;의 쿼리문을 작성해야했다. 나는 이 문제를 &quot;총매출이 큰 순서로 정렬하고 2번째 행을 검색한다.&quot;는 관점으로 접근했다. 이는 SELECT 문의 LIMIT 절을 이용해 구현할 수 있었다.&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;a href=&quot;https://dev.mysql.com/doc/refman/8.0/en/select.html&quot;&gt;MySQL 8.0 Documentations&lt;/a&gt; 으로 학습한 내용을 정리했다. Spring을 학습하는 과정에서 공식 문서를 좀 읽다보니 점점 다른 공식 문서도 좀 읽어보게 되는 것 같다 &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;LIMIT&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;The LIMIT clause can be used to &lt;b&gt;constrain the number of rows&lt;/b&gt; returned by the SELECT statement. LIMIT takes one or two numeric arguments, which must both be nonnegative integer constants.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LIMIT 절을 사용하여 SELECT 문이 검색하는 &lt;b&gt;행 수를 제한&lt;/b&gt;할 수 있다. LIMIT절은 하나 또는 두 개의 숫자를 인수로 가지며, 둘 다 음이 아닌 정수여야한다. LIMIT 절은 ORDER BY 절 다음에 사용한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/45311765/165943823-6572dbfb-da79-4bb7-9301-7ec009fff943.png&quot; alt=&quot;image&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;행 수를 2개로 제한한다. &amp;rarr; 1-2행이 검색된다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;SELECT * FROM tbl LIMIT 2;  # Retrieve rows 1-2&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;OFFSET of LIMIT&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;With two arguments, &lt;b&gt;the first argument specifies the offset of the first row to return&lt;/b&gt;, and the second specifies the maximum number of rows to return. The offset of the initial row is 0 (not 1).&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;LIMIT 절에 두 개의 인수를 사용하는 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;첫 번째 인수는 OFFSET을 의미하며 건너뛰는 행의 개수를 지정하고, 두 번째 인수는 제한하는 행의 개수를 지정한다.&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;처음 5개의 행을 건너뛰고, 행 수를 10개로 제한한다. &amp;rarr; 6-15행이 검색된다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;SELECT * FROM tbl LIMIT 5,10;  # Retrieve rows 6-15&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;OFFSET의 기본값은 0&lt;/b&gt;으로, 아래의 두 쿼리문은 같다.&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;SELECT * FROM tbl LIMIT 2;  # Retrieve rows 1-2
SELECT * FROM tbl LIMIT 0, 2;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 OFFSET을 명시적으로 지정해 사용할 수 있다. (순서가 반전됨에 주의하자!)&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;SELECT * FROM tbl LIMIT 10 OFFSET 5;  # Retrieve rows 6-15&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 95개의 행을 건너뛰고, 모든 행을 검색한다. &amp;rarr; 96-끝행이 검색된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LIMIT 절에 두번째 인자로 매우 큰 값을 사용하면 끝행까지 검색된다. (행 수가 제한되지 않는다.)&lt;/p&gt;
&lt;pre class=&quot;n1ql&quot;&gt;&lt;code&gt;SELECT * FROM tbl LIMIT 95,18446744073709551615;&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Backend/SQL</category>
      <category>limit</category>
      <category>MySQL</category>
      <category>offset</category>
      <author>ellie.strong</author>
      <guid isPermaLink="true">https://programmer-ririhan.tistory.com/425</guid>
      <comments>https://programmer-ririhan.tistory.com/425#entry425comment</comments>
      <pubDate>Fri, 29 Apr 2022 21:26:25 +0900</pubDate>
    </item>
    <item>
      <title>Spark Java에서 에러 메시지 전송하기</title>
      <link>https://programmer-ririhan.tistory.com/424</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;체스미션에서 &lt;b&gt;View(Client)에 원하는 에러 메시지를 전송&lt;/b&gt;하기위해 아래와 같이 try-catch문을 남발하여 에러가 발생하는 경우 에러 메시지를 DTO에 담아 보내도록 구현했었다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;367&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Dxonb/btryEuoqqGh/8X4jadli48dpELc0TITfV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Dxonb/btryEuoqqGh/8X4jadli48dpELc0TITfV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Dxonb/btryEuoqqGh/8X4jadli48dpELc0TITfV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDxonb%2FbtryEuoqqGh%2F8X4jadli48dpELc0TITfV1%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;592&quot; height=&quot;367&quot; data-origin-width=&quot;592&quot; data-origin-height=&quot;367&quot;/&gt;&lt;/span&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;알고보니 &lt;b&gt;Spark Java에서는 예외를 처리해주는 exception 메서드를 지원&lt;/b&gt;하고 있었고, 이를 사용하는 방향으로 리팩토링을 진행했다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;467&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGkPvF/btryFoH7uCr/PQVj8yZrgnsDIFfLFCVnRK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGkPvF/btryFoH7uCr/PQVj8yZrgnsDIFfLFCVnRK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGkPvF/btryFoH7uCr/PQVj8yZrgnsDIFfLFCVnRK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGkPvF%2FbtryFoH7uCr%2FPQVj8yZrgnsDIFfLFCVnRK%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;1280&quot; height=&quot;467&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;467&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Controller&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;에러가 발생하면 ErrorResponseDto에 에러 메시지를 담아 보내도록 구현&lt;/b&gt;하였다. 현재 모든 에러를 잡도록(일명 포켓몬 캐치..) 구현하였는데 이는 추후 수정이 필요해보인다.&lt;/p&gt;
&lt;pre id=&quot;code_1649313752898&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;exception(Exception.class, (e, req, res) -&amp;gt; {
    res.body(GSON.toJson(new ErrorResponseDto(e.getMessage())));
});&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 exception 메서드를 사용하면서 중복된 try-catch문 사용을 피할 수 있었다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1649313889521&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private ChessResponseDto load() {
    final ChessGame chessGame = chessService.load();
    return chessService.createChessResponseDto(chessGame);
}

private ChessResponseDto start() {
    ChessGame chessGame = chessService.load();
    chessGame = chessService.start(chessGame);
    return chessService.createChessResponseDto(chessGame);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DTO&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ErrorResponseDto&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러 출력만을 위한 DTO를 만들었다. 이를 만듬으로써 &lt;b&gt;ChessResponseDto, StatusResponseDto에서 불필요한 코드를 제거&lt;/b&gt;할 수 있었다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1649313948755&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ErrorResponseDto {

    private final String status;
    private final String message;

    public ErrorResponseDto(final String message) {
        this.status = &quot;error&quot;;
        this.message = message;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;StatusResponseDto&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Before&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 status 명령어의 응답에 사용하기위한 StatusResponseDto 클래스에는 점수, 승패에 관한 데이터 뿐만아니라 응답 성공 여부를 나타내는 code, 에러 메시지를 담는 message가 추가적으로 필요했으며, &lt;b&gt;이 하나의 클래스로 성공, 실패를 모두 커버하기 위해 불필요한 값(&quot;&quot;, 0 등)을 넣어야했었다. (생성자도 여러개..)&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1649311214504&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class StatusResponseDto {

    private final String code;
    private final String message;
    private final String myTurn;
    private final String opponentTurn;
    private final double myScore;
    private final double opponentScore;
    private final String result;

    public StatusResponseDto(final ChessGame chessGame, final double myScore, final double opponentScore,
                             final String result) {
        this(&quot;success&quot;, &quot;&quot;, chessGame, myScore, opponentScore, result);
    }

    public StatusResponseDto(final String code, final String message, final ChessGame chessGame) {
        this(code, message, chessGame, 0, 0, &quot;&quot;);
    }

    public StatusResponseDto(final String code, final String message, final ChessGame chessGame, final double myScore,
                             final double opponentScore,
                             final String result) {
        this.code = code;
        this.message = message;
        this.myTurn = chessGame.getTurn().getName();
        this.opponentTurn = chessGame.getTurn().getOpposite().getName();
        this.myScore = myScore;
        this.opponentScore = opponentScore;
        this.result = result;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;After&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;성공했을 때만 사용하는 DTO로 변화되었기때문에 생성자가 하나만 있어도 된다!!&lt;/p&gt;
&lt;pre id=&quot;code_1649314139502&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class StatusResponseDto {

    private final String status;
    private final String myTurn;
    private final String opponentTurn;
    private final double myScore;
    private final double opponentScore;
    private final String result;

    public StatusResponseDto(final ChessGame chessGame, final double myScore, final double opponentScore,
                             final String result) {
        this.status = &quot;success&quot;;
        this.myTurn = chessGame.getTurn().getName();
        this.opponentTurn = chessGame.getTurn().getOpposite().getName();
        this.myScore = myScore;
        this.opponentScore = opponentScore;
        this.result = result;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ChessResponseDto&lt;/h3&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;h2 data-ke-size=&quot;size26&quot;&gt;결과&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경 후에도 에러메시지가 잘 출력되는 모습을 확인했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1894&quot; data-origin-height=&quot;612&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cIdffq/btryHG2gCef/FzaWa4wvHthnid5ORIbBD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cIdffq/btryHG2gCef/FzaWa4wvHthnid5ORIbBD1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cIdffq/btryHG2gCef/FzaWa4wvHthnid5ORIbBD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcIdffq%2FbtryHG2gCef%2FFzaWa4wvHthnid5ORIbBD1%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;1894&quot; height=&quot;612&quot; data-origin-width=&quot;1894&quot; data-origin-height=&quot;612&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;To-Be&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 현재 내 맘대로 status에 성공이면 &quot;success&quot;, 실패하면 &quot;error&quot;를 담아서 보내므로, &lt;b&gt;HTTP 상태 코드를 이용한 방법으로 변경&lt;/b&gt;하면 좋을 것 같다. 400(Bad Request)상태를 반환하도록 시도는 해봤는데, JS에서 이를 어떻게 처리해야할 지 모르겠어서 일단 스탑한 상태!! 그리고 이렇게 되면 어떠한 HTTP 상태코드를 보내야할지 잘 모르겠다..ㅎ(&lt;a href=&quot;https://www.whatap.io/ko/blog/40/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;HTTP 상태 코드&lt;/a&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;2. 현재 exception 메서드에서 모든 에러를 잡도록(일명 포켓몬 캐치..) 구현하였는데 이는 추후 내가 지정한 에러만을 잡도록 수정해야겠다. 커스텀 에러 클래스를 하나 만들어보는 것도 좋을 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sparkjava.com/documentation&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://sparkjava.com/documentation&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://self-learning-java-tutorial.blogspot.com/2015/01/spark-java-exception-mapping.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://self-learning-java-tutorial.blogspot.com/2015/01/spark-java-exception-mapping.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend/Java</category>
      <category>exception</category>
      <category>HTTP Status</category>
      <category>Java</category>
      <category>spark</category>
      <author>ellie.strong</author>
      <guid isPermaLink="true">https://programmer-ririhan.tistory.com/424</guid>
      <comments>https://programmer-ririhan.tistory.com/424#entry424comment</comments>
      <pubDate>Thu, 7 Apr 2022 15:57:20 +0900</pubDate>
    </item>
    <item>
      <title>DAO, DTO, Service, Controller (feat. 체스미션)</title>
      <link>https://programmer-ririhan.tistory.com/423</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;DAO, DTO, Service, Controller의 개념을 잘 학습하지 않은 상태에서 &quot;&lt;span&gt;체스 미션 5단계 : DB 적용&quot;을 구현하다보니 코드가 이상해지고 있다는 느낌을 받았다. 오늘은 이 객체들에 대해 알아보고 현재 내 코드를 어떻게 수정할 수 있을지 고민해보려한다. 이후 어떻게 변경되었는지도 추가하면 좋을지도..?&lt;/span&gt;&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 widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;129&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4ltEq/btryCkMuVL3/uow77GhWwFfBbxsgonhd60/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4ltEq/btryCkMuVL3/uow77GhWwFfBbxsgonhd60/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4ltEq/btryCkMuVL3/uow77GhWwFfBbxsgonhd60/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4ltEq%2FbtryCkMuVL3%2Fuow77GhWwFfBbxsgonhd60%2Fimg.webp&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;512&quot; height=&quot;129&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;129&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DAO(Data Access Object)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DB에 접근해 데이터를 조회하거나 조작&lt;/b&gt;하는 객체를 말한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ellie's code&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 에러처리에 소홀히 한 부분이 있다. (단순히 제이슨이 강의해주신 부분을 따라함..ㅎ) 이를 좀 더 유의미한 에러처리로 변경해야겠다!! 그리고 현재 PreparedStatement는 try-resources문을 사용해 자원을 회수하고 있지만 Connection에 대한 회수는 이루어지지 않고 있다. Connectoin, PreparedStatement를 학습하고 이에 대한 처리를 추가해줘야겠다!!&lt;/p&gt;
&lt;pre id=&quot;code_1649253383424&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class GameDao {

    private final Connection connection;

    public GameDao() {
        this(MySqlConnector.getConnection());
    }

    public GameDao(final Connection connection) {
        this.connection = connection;
    }

    public void save(final GameDto game) {
        final String sql = &quot;INSERT INTO game (state, turn) values (?, ?)&quot;;

        try (final PreparedStatement statement = connection.prepareStatement(sql)) {
            statement.setString(1, game.getState());
            statement.setString(2, game.getTurn());
            statement.execute();
        } catch (final SQLException e) {
            e.printStackTrace();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;DAO vs. Repository&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://bperhaps.tistory.com/entry/Repository%EC%99%80-Dao%EC%9D%98-%EC%B0%A8%EC%9D%B4%EC%A0%90&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;관련 글1&lt;/a&gt;, &lt;a href=&quot;https://devlopsquare.tistory.com/106&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;글2&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;몇가지 글을 읽어봤지만 DAO와 Repository의 차이는 아직 모르겠다. 예전에 SpringBoot기반의 프로젝트를 개발할 때는 JPA를 사용하면서 자연스럽게(?) Repository를 사용했었는데, 그 당시에도 Repository의 개념을 몰랐다. 그저 &quot;JPA를 이용해 데이터베이스에 접근하고, 데이터를 조작한다.&quot;라고 생각했던 것 같다. 흠.. 앞으로 알아가야할게 참 많다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DTO(Data Transfer Object)&lt;/h2&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;&lt;b&gt;오직 getter/setter 메서드 만&lt;/b&gt;을 갖는다. (다른 로직을 갖지 않는다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;setter를 제거하고 생성자에서 데이터를 받아 필드에 할당해준다면 전달 과정에서 데이터의 변조를 막을 수 있다. (불변객체)&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;왜 DTO를 쓸까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;DTO는 View, DB의 변화가 비지니스 로직에 영향을 주는 것을 차단한다. View나 DB에 변화가 생기면 DTO의 내부를 바꾸면 되므로 비지니스 로직에 영향을 덜 줄 수 있다.&amp;nbsp;&lt;/s&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;어디서 DTO를 쓸까?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DAO와 Service, Controller 사이의 데이터 교환을 할 때 사용하는 경우와 Controller와 View 사이의 데이터 교환을 할 때 사용하는 경우로 생각해볼 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ellie's code&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 구현한 GameDto를 보면 createState라는 메서드를 가지고 있다. 여기 뿐만아니라 BoardDto에서도 Map&amp;lt;String, String&amp;gt;을 Map&amp;lt;Position, Piece&amp;gt;로 변환하는 메서드를 가지고 있다. &lt;span&gt;이것은 getter/setter외의 다른 로직이라고 볼 수 있다. 그래서 이러한 메서드들이 DTO에&amp;nbsp;&lt;/span&gt; 있는게 맞는가?라는 고민을 갖게 됐다. 어디로 보내야할지 생각해봐야겠다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또 하나의 고민은 필드를 DB 데이터 타입에 따라야하는지, Domain의 데이터 타입에 따라야하는지 잘 모르겠다. 현재는 모두 DB의 데이터 타입을 따르도록 구현했다. (View와의 교환을 위한 DTO에서도 View에서 사용하는 데이터 타입으로 지정했다.)&lt;/p&gt;
&lt;pre id=&quot;code_1649254561593&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class GameDto {

    private final int id;
    private final String state;
    private final String turn;

    public GameDto(final String state, final String turn) {
        this(0, state, turn);
    }

    public GameDto(final int id, final String state, final String turn) {
        this.id = id;
        this.state = state;
        this.turn = turn;
    }

    public static GameDto of(final int id, final State state, final Color turn) {
        return new GameDto(id, createState(state), turn.getName());
    }

    public static GameDto of(final ChessGame chessGame) {
        return new GameDto(0, createState(chessGame.getState()), chessGame.getTurn().getName());
    }

    private static String createState(final State state) {
        if (state instanceof Ready) {
            return &quot;Ready&quot;;
        }
        if (state instanceof Started) {
            return &quot;Started&quot;;
        }
        return &quot;Ended&quot;;
    }

    public Integer getId() {
        return id;
    }

    public String getState() {
        return state;
    }

    public String getTurn() {
        return turn;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;VO(Value Object)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;값 그 자체를 표현&lt;/b&gt;하는 객체를 말한다. (DTO와 혼동하지 말자!!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;setter를 가지면 안 되며, 생성자를 통해서만 값을 초기화&lt;/b&gt;해야한다. (= 불변객체, read-only)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getter 외의 로직을 가질 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;속성 값이 같으면 같은 객체로 간주&lt;/b&gt;해야하기 때문에 equlas, hashCode 메서드를 모두 오버라이딩 해 줘야 한다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ellie's code&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;체스에서는 Piece 클래스가 VO가 되지 않을까 싶다.&lt;/p&gt;
&lt;pre id=&quot;code_1649255608863&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public abstract class Piece {

    private final Color color;
    private final Type type;

    protected Piece(final Color color, final Type type) {
        this.color = color;
        this.type = type;
    }

    public abstract boolean isRightMovement(final Position from, final Position to, final boolean isEmptyTarget);

    public abstract boolean isJumpable();

    public boolean isSame(final Type type) {
        return this.type == type;
    }

    public boolean isSame(final Color color) {
        return this.color.equals(color);
    }

    public double getScore() {
        return type.getScore();
    }

    public Color getColor() {
        return color;
    }

    public Type getType() {
        return type;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }
        if (o == null || getClass() != o.getClass()) {
            return false;
        }
        Piece piece = (Piece) o;
        return color == piece.color &amp;amp;&amp;amp; type == piece.type;
    }

    @Override
    public int hashCode() {
        return Objects.hash(color, type);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Service&lt;/h2&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;DAO 여러개를 묶어서 사용하는 객체를 가리키기도 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 DAO를 통해 데이터에 접근하고, DTO로 전달받은 데이터를 처리하는 데 필요한 로직을 구현한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Controller가 Request를 받으면 적절한 Service에 전달하고, 전달 받은 Service는 비지니스 로직을 처리한다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ellie's code&lt;/h3&gt;
&lt;pre id=&quot;code_1649255737930&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ChessService {

    private final GameDao gameDao;
    private final BoardDao boardDao;

    public ChessService() {
        this.gameDao = new GameDao();
        this.boardDao = new BoardDao();
    }

    public ChessGame load() {
        final GameDto gameDto = gameDao.findByMaxId();
        final BoardDto boardDto = boardDao.findByGameId(gameDto.getId());

        final Board board = boardDto.toBoard();
        final State state = createState(gameDto.getState(), gameDto.getTurn(), board);

        return new ChessGame(state, board);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Controller(Web)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;View(Client)에서 보낸 요청 URL에 따라 적절한 응답&lt;/b&gt;을 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;View의 Response Body에 담긴 데이터(DTO)를 Service에 넘겨 적절하게 처리한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Service에서 처리하고 반환된 데이터(DTO)를 Response Body에 담아 View에 전달한다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ellie's code&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 View에 원하는 에러메시지를 전달하기 위해 try-catch문을 사용해 DTO에 에러메시지를 담아 보낸한다. 하지만 Spark Java에는 이미 &lt;b&gt;에러를 처리해주는 exception 메서드&lt;/b&gt;가 있다고 한다. (후니가 친절하게 알려줌 ) 당시에 Doc를 볼 때 이런게 있구나~ 하고 넘어갔었는데 다시 알아보고 적용해봐야겠다!!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1822&quot; data-origin-height=&quot;666&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yswUn/btryCldyCsX/K1ZHL2LP7qklS9v1EiXVy0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yswUn/btryCldyCsX/K1ZHL2LP7qklS9v1EiXVy0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yswUn/btryCldyCsX/K1ZHL2LP7qklS9v1EiXVy0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyswUn%2FbtryCldyCsX%2FK1ZHL2LP7qklS9v1EiXVy0%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;1822&quot; height=&quot;666&quot; data-origin-width=&quot;1822&quot; data-origin-height=&quot;666&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1649256042245&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ChessWebController {

    private static final Gson GSON = new Gson();

    final ChessService chessService;

    public ChessWebController() {
        chessService = new ChessService();
    }

    public void run() {
        get(&quot;/&quot;, (req, res) -&amp;gt; {
            return render(new HashMap&amp;lt;&amp;gt;(), &quot;index.html&quot;);
        });

        get(&quot;/load&quot;, (req, res) -&amp;gt; {
            return GSON.toJson(load());
        });
    }
    
    private ChessResponseDto load() {
        try {
            final ChessGame chessGame = chessService.load();
            return chessService.createChessResponseDto(chessGame);
        } catch (final Exception e) {
            return chessService.createErrorChessResponseDto(e.getMessage());
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://lazymankook.tistory.com/30&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://lazymankook.tistory.com/30&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=z5fUkck_RZM&quot;&gt;https://www.youtube.com/watch?v=z5fUkck_RZM&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@jyyoun1022/SpringEntityDTODAO-%EA%B0%9C%EB%85%90%EC%A0%95%EB%A6%AC&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@jyyoun1022/SpringEntityDTODAO-%EA%B0%9C%EB%85%90%EC%A0%95%EB%A6%AC&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend/Java</category>
      <category>controller</category>
      <category>dao</category>
      <category>DTO</category>
      <category>service</category>
      <category>Vo</category>
      <category>체스미션</category>
      <author>ellie.strong</author>
      <guid isPermaLink="true">https://programmer-ririhan.tistory.com/423</guid>
      <comments>https://programmer-ririhan.tistory.com/423#entry423comment</comments>
      <pubDate>Wed, 6 Apr 2022 22:37:18 +0900</pubDate>
    </item>
    <item>
      <title>처음 만난 SparkJava</title>
      <link>https://programmer-ririhan.tistory.com/419</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;우아한테크코스의 체스 미션에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;SparkJava를 활용해 웹 UI를 적용&lt;/b&gt;해야한다. 이를 위한 캡틴 포비의 강의가 제공되며, 해당 강의를 들으며 학습한 내용을 정리했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&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&gt;프로젝트 생성 및 세팅&lt;/li&gt;
&lt;li&gt;클라이언트 요청 처리 : GET &amp;amp; POST&lt;/li&gt;
&lt;li&gt;Template Engine : Handlebars&lt;/li&gt;
&lt;li&gt;Java Bean을 이용한 데이터 전송&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로젝트 생성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;IntelliJ에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;SpringBoot 프로젝트 생성&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;기능을 이용해 일단 프로젝트를 생성한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;888&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CRGst/btryiFCvIV2/Q8vKJatGsifq8kKdZpPVMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CRGst/btryiFCvIV2/Q8vKJatGsifq8kKdZpPVMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CRGst/btryiFCvIV2/Q8vKJatGsifq8kKdZpPVMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCRGst%2FbtryiFCvIV2%2FQ8vKJatGsifq8kKdZpPVMK%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;600&quot; height=&quot;583&quot; data-origin-width=&quot;914&quot; data-origin-height=&quot;888&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;build.gradle에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;SpringBoot와 관련된 설정들을 제거&lt;/b&gt;하고, main, test 디렉토리의&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;초기 파일들을 모두 제거&lt;/b&gt;한다.&lt;/p&gt;
&lt;pre id=&quot;code_1648964854500&quot; class=&quot;routeros&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;plugins {
    id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

repositories {
    mavenCentral()
}

dependencies {

}

tasks.named('test') {
    useJUnitPlatform()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;클라이언트 요청 처리(응답)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;build.gradle에 dependency를 추가하면,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;spark를 사용하기 위해 필요한 라이브러리들을 자동으로 다운&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;Gradle 7.0 이상&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1648964854501&quot; class=&quot;isbl&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies {
    implementation(&quot;com.sparkjava:spark-core:2.9.3&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Gradle 7.0 이전&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1648964854501&quot; class=&quot;gradle&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies {
    compile &quot;com.sparkjava:spark-core:2.9.3&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로젝트 실행&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sparkjava.com/documentation#getting-started&quot;&gt;Spark Framework Documentation&lt;/a&gt;에는 spark를 쉽게 시작하는 방법과 제공하는 기능들에 대한 설명을 제공한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;GET&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 코드 작성을 통해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&quot;localhost:4567/hello&quot;로 들어오는 요청을 처리&lt;/b&gt;하는 웹 페이지를 만들어볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅&amp;nbsp; 브라우저는 기본적으로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;get 방식&lt;/b&gt;을 이용해 서버에 요청을 보낸다.&lt;/p&gt;
&lt;pre id=&quot;code_1648964854501&quot; class=&quot;arduino&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class HelloWorld {
    public static void main(String[] args) {
        get(&quot;/hello&quot;, (req, res) -&amp;gt; &quot;Hello World&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;652&quot; data-origin-height=&quot;487&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OvQ48/btryg34Ydh2/yeZ3dJ2i0G9aCapJqHH2H0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OvQ48/btryg34Ydh2/yeZ3dJ2i0G9aCapJqHH2H0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OvQ48/btryg34Ydh2/yeZ3dJ2i0G9aCapJqHH2H0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOvQ48%2Fbtryg34Ydh2%2FyeZ3dJ2i0G9aCapJqHH2H0%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;600&quot; height=&quot;448&quot; data-origin-width=&quot;652&quot; data-origin-height=&quot;487&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot;:name&quot;&lt;/b&gt;과 같은 파라미터의 이름을 이용해&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;동적으로 변경되는 데이터를 받아올 수 있다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1648964854502&quot; class=&quot;livescript&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;get(&quot;/hello/:name&quot;, (req, res) -&amp;gt; {
    return &quot;Hello : &quot; + req.params(&quot;:name&quot;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;669&quot; data-origin-height=&quot;487&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cySx02/btryiHAppO5/I65GExkpeKkR3d5h6tEGb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cySx02/btryiHAppO5/I65GExkpeKkR3d5h6tEGb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cySx02/btryiHAppO5/I65GExkpeKkR3d5h6tEGb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcySx02%2FbtryiHAppO5%2FI65GExkpeKkR3d5h6tEGb0%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;669&quot; height=&quot;487&quot; data-origin-width=&quot;669&quot; data-origin-height=&quot;487&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;query string&lt;/b&gt;을 이용해 데이터를 넘기는 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;queryParams() 메서드&lt;/b&gt;를 이용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅&amp;nbsp; Query String : url 주소에 미리 협의된 형식을 이용해 데이터를 파라미터를 통해 넘기는 것을 말한다.&lt;/p&gt;
&lt;pre id=&quot;code_1648964854502&quot; class=&quot;livescript&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;get(&quot;/hello&quot;, (req, res) -&amp;gt; {
    return &quot;Hello : &quot; + req.queryParams(&quot;age&quot;) + &quot;살 &quot; + req.queryParams(&quot;name&quot;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1538&quot; data-origin-height=&quot;974&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPzcvj/btrye3kioY8/j7lRkdjeS3iRufIgRh7f40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPzcvj/btrye3kioY8/j7lRkdjeS3iRufIgRh7f40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPzcvj/btrye3kioY8/j7lRkdjeS3iRufIgRh7f40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPzcvj%2Fbtrye3kioY8%2Fj7lRkdjeS3iRufIgRh7f40%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;600&quot; height=&quot;380&quot; data-origin-width=&quot;1538&quot; data-origin-height=&quot;974&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;POST&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;post 방식&lt;/b&gt;으로 보낸 요청을 처리할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648964854503&quot; class=&quot;livescript&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;post(&quot;/hello&quot;, (req, res) -&amp;gt; {
    return &quot;POST Hello : &quot; + req.queryParams(&quot;age&quot;) + &quot;살 &quot; + req.queryParams(&quot;name&quot;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1648964854503&quot; class=&quot;stata&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;form action=&quot;/hello&quot; method=&quot;post&quot;&amp;gt;
    이름 : &amp;lt;input type=&quot;text&quot; name=&quot;name&quot;/&amp;gt;
    &amp;lt;br/&amp;gt;
    &amp;lt;br/&amp;gt;
    나이 : &amp;lt;input type=&quot;text&quot; name=&quot;age&quot;/&amp;gt;
    &amp;lt;br/&amp;gt;
    &amp;lt;br/&amp;gt;
    &amp;lt;input type=&quot;submit&quot; value=&quot;회원가입&quot;/&amp;gt;
&amp;lt;/form&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;777&quot; data-origin-height=&quot;487&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ceV5m3/btrye26KQfr/Djfte81TPa7KScTYwTvkxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ceV5m3/btrye26KQfr/Djfte81TPa7KScTYwTvkxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ceV5m3/btrye26KQfr/Djfte81TPa7KScTYwTvkxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FceV5m3%2Fbtrye26KQfr%2FDjfte81TPa7KScTYwTvkxk%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;600&quot; height=&quot;376&quot; data-origin-width=&quot;777&quot; data-origin-height=&quot;487&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅&amp;nbsp; html과 같은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;정적(static) 파일의 저장 경로를 지정&lt;/b&gt;해서 사용할 수 있다. (기본 : main/resources)&lt;/p&gt;
&lt;pre id=&quot;code_1648964854504&quot; class=&quot;abnf&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;staticFiles.location(&quot;/static&quot;);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;293&quot; data-origin-height=&quot;169&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MYq8A/btryf0nm9VD/4XViL5sZLWuFSNpPPEXFtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MYq8A/btryf0nm9VD/4XViL5sZLWuFSNpPPEXFtk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MYq8A/btryf0nm9VD/4XViL5sZLWuFSNpPPEXFtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMYq8A%2Fbtryf0nm9VD%2F4XViL5sZLWuFSNpPPEXFtk%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;293&quot; height=&quot;169&quot; data-origin-width=&quot;293&quot; data-origin-height=&quot;169&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Template Engine&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 SparJava에서 제공하는 template engin들 중 HandleBars를 이용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;nbsp;&amp;nbsp;&lt;a href=&quot;https://sparkjava.com/documentation#views-and-templates&quot;&gt;SparkJava Template Engine Docs&lt;/a&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Handlebars&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dependency를 추가해 Handlebars 라이브러리를 다운받는다.&lt;/p&gt;
&lt;pre id=&quot;code_1648964854504&quot; class=&quot;clean&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dependencies {
    implementation(&quot;com.sparkjava:spark-core:2.9.3&quot;)
    implementation(&quot;com.sparkjava:spark-template-handlebars:2.7.1&quot;) // 의존성 추가
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Map을 이용해 JSON과 비슷한 형식으로 데이터를 저장해 넘길 수 있다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;HandlebarsTemplateEngine은 render() 메서드를 이용해 데이터를 담은 Map과 동적 html을 엮어 브라우저에 렌더링해준다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1648964854505&quot; class=&quot;livescript&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void main(String[] args) {
    post(&quot;/hello&quot;, (req, res) -&amp;gt; {
        Map&amp;lt;String, Object&amp;gt; model = new HashMap&amp;lt;&amp;gt;();
        model.put(&quot;name&quot;, req.queryParams(&quot;name&quot;));
        model.put(&quot;age&quot;, req.queryParams(&quot;age&quot;));

        return render(model, &quot;result.html&quot;);
    });
}

public static String render(Map&amp;lt;String, Object&amp;gt; model, String templatePath) {
    return new HandlebarsTemplateEngine().render(new ModelAndView(model, templatePath));
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;{{name}}, {{age}}&lt;/b&gt;에 우리가 Map에 저장한 데이터를 넣어준다. 이것은 HandleBars의 문법을 따르며, 이는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Mustache의 문법을 기본&lt;/b&gt;으로 만들어졌다. (파이썬의 Template Engine Jinja가 생각난다.. 꽤 편했지..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://github.com/jknack/handlebars.java&quot;&gt;Handlebars 공식 문서&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &amp;nbsp;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://mustache.github.io/mustache.5.html&quot;&gt;Mustache 문법&lt;/a&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1648964854505&quot; class=&quot;django&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;h1&amp;gt;회원 가입 결과&amp;lt;/h1&amp;gt;
이름 : {{name}}
&amp;lt;br/&amp;gt;
&amp;lt;br/&amp;gt;
나이 : {{age}}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의할 점으로 해당 html은 정적 파일이 아니라 동적 파일이 되므로 동적 파일의 기본 경로인&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;resources/templates로 동적 html 파일을 이동&lt;/b&gt;시켜줘야 정삭적으로 동작한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;777&quot; data-origin-height=&quot;487&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BMNJ3/btryhNt1trN/IHEkDr5Tdj24ahrKNqPfyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BMNJ3/btryhNt1trN/IHEkDr5Tdj24ahrKNqPfyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BMNJ3/btryhNt1trN/IHEkDr5Tdj24ahrKNqPfyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBMNJ3%2FbtryhNt1trN%2FIHEkDr5Tdj24ahrKNqPfyK%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;600&quot; height=&quot;376&quot; data-origin-width=&quot;777&quot; data-origin-height=&quot;487&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Java Bean&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;자바빈은 jsp에서 객체를 가져오기 위한 기법으로, 데이터 전달 오브젝트 파일&lt;span&gt;&amp;nbsp;&lt;/span&gt;DTO(Data Transfer Object)라고도 한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서버에서는 자바빈 클래스에 데이터를 담아 클라이언트로 보낼 수 있다. 이럴 경우 html 파일에서는 Handlebars 문법을 이용해 데이터에 접근할 수 있으며,&lt;span&gt;&amp;nbsp;&lt;/span&gt;자바빈 규약을 따르기 때문에&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;클래스에는 getter가 존재해야 정상적으로 동작&lt;/b&gt;한다.&lt;/p&gt;
&lt;pre id=&quot;code_1648964854506&quot; class=&quot;lisp&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;post(&quot;/users&quot;, (req, res) -&amp;gt; {
    User user = new User();
    user.setName(req.queryParams(&quot;name&quot;));
    user.setAge(req.queryParams(&quot;age&quot;));

    Map&amp;lt;String, Object&amp;gt; model = new HashMap&amp;lt;&amp;gt;();
    model.put(&quot;user&quot;, user);

    return render(model, &quot;result.html&quot;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1648964854506&quot; class=&quot;django&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;h1&amp;gt;회원 가입 결과&amp;lt;/h1&amp;gt;
이름 : {{user.name}}
&amp;lt;br/&amp;gt;
&amp;lt;br/&amp;gt;
나이 : {{user.age}}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1648964854507&quot; class=&quot;handlebars&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;h1&amp;gt;회원 가입 결과&amp;lt;/h1&amp;gt;
{{#user}}
이름 : {{name}}
&amp;lt;br/&amp;gt;
&amp;lt;br/&amp;gt;
나이 : {{age}}
{{/user}}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Handlebars 내부에서 for문을 처리하므로 따로 명시할 필요 없이 List를 이용해 데이터를 보낼 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1648964854507&quot; class=&quot;routeros&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;User&amp;gt; users = new ArrayList&amp;lt;&amp;gt;();
post(&quot;/users&quot;, (req, res) -&amp;gt; {
    User user = new User();
    user.setName(req.queryParams(&quot;name&quot;));
    user.setAge(req.queryParams(&quot;age&quot;));
    users.add(user);

    Map&amp;lt;String, Object&amp;gt; model = new HashMap&amp;lt;&amp;gt;();
    model.put(&quot;users&quot;, users);

    return render(model, &quot;result.html&quot;);
});&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1648964854507&quot; class=&quot;handlebars&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;h1&amp;gt;회원 가입 결과&amp;lt;/h1&amp;gt;
{{#users}}
이름 : {{name}}, 나이 : {{age}}
&amp;lt;br/&amp;gt;
{{/users}}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1648964854507&quot; class=&quot;handlebars&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;h1&amp;gt;회원 가입 결과&amp;lt;/h1&amp;gt;
{{#users}}
이름 : {{this.name}}, 나이 : {{this.age}}
&amp;lt;br/&amp;gt;
{{/users}}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;777&quot; data-origin-height=&quot;487&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBAENi/btrye35HECR/3yz6GIBPkJKXyudIRmCT4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBAENi/btrye35HECR/3yz6GIBPkJKXyudIRmCT4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBAENi/btrye35HECR/3yz6GIBPkJKXyudIRmCT4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBAENi%2Fbtrye35HECR%2F3yz6GIBPkJKXyudIRmCT4k%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;600&quot; height=&quot;376&quot; data-origin-width=&quot;777&quot; data-origin-height=&quot;487&quot;/&gt;&lt;/span&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;&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;</description>
      <category>Backend/Java</category>
      <author>ellie.strong</author>
      <guid isPermaLink="true">https://programmer-ririhan.tistory.com/419</guid>
      <comments>https://programmer-ririhan.tistory.com/419#entry419comment</comments>
      <pubDate>Sun, 3 Apr 2022 14:48:24 +0900</pubDate>
    </item>
    <item>
      <title>공유 중인 가변 데이터는 동기화해 사용하라</title>
      <link>https://programmer-ririhan.tistory.com/417</link>
      <description>&lt;h3&gt;11장. 동시성&lt;/h3&gt;
&lt;p&gt;이번 장에서는 동시성 프로그램을 명확하고 정확하게 만들 수 있는 방법을 제시한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;동기화(Synchronized)란 무엇인가?&lt;/li&gt;
&lt;li&gt;동기화에 대한 흔한 오해&lt;/li&gt;
&lt;li&gt;동기화 적용 방법&lt;/li&gt;
&lt;li&gt;volatile 키워드&lt;/li&gt;
&lt;li&gt;Java.util.concurrent.atomic 패키지&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;1. 동기화(Synchronized)란 무엇인가?&lt;/h2&gt;
&lt;p&gt;멀티 스레드 환경에서 하나의 메서드나 블록을 &lt;code&gt;한번에 한 스레드씩 수행&lt;/code&gt;하도록 보장하는 것을 의미한다.&lt;br&gt;자바에서는 &lt;code&gt;synchronized&lt;/code&gt; 키워드를 이용해 동기화를 제공한다. &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public synchronized void increase() {
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;synchronized(number) {
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;동기화의 주요 기능&lt;/h3&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;배타적 실행&lt;/strong&gt;&lt;br&gt;한 스레드가 변경하는 중이라서 상태가 일관되지 않은 순간의 객체를 다른 스레드가 보지 못하게 막아준다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;스레드 사이의 안정적인 통신&lt;/strong&gt; ⭐️&lt;br&gt;동기화 없이는 한 스레드가 만든 변화를 다른 스레드에서 확인하지 못할 수도 있다.&lt;br&gt;등기화된 메서드나 블록에 들어간 스레드가 같은 락의 보호하에 수행된 모든 이전 수정의 최종 결과를 보게 해준다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;동기화에 대한 흔한 오해&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;우리는 1번 기능만을 동기화로 오해하는 경우가 많다. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class StopThread {

    private static boolean stopRequested;

    public static void main(String[] args) throws InterruptedException {

        Thread backgroundThread = new Thread(() -&amp;gt; {
            int i = 0;
            while(!stopRequested) {
                i++;
            }
        });

        backgroundThread.start();

        TimeUnit.SECONDS.sleep(1);
        stopRequested = true;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;자바 언어 명세상 long, double 이외의 변수를 읽고 쓰는 동작은 원자적(atomic)이다.&lt;br&gt;메인 스레드가 1초 후 &lt;code&gt;stopRequested&lt;/code&gt;를 &lt;code&gt;true&lt;/code&gt;로 설정하면 백그라운드 스레드는 반복문을 빠져나올 것처럼 보인다.&lt;br&gt;하지만 &lt;code&gt;응답 불가(liveness failure)&lt;/code&gt; 상태에 빠진다.&lt;/p&gt;
&lt;h3&gt;엥? 왜 안되지?&lt;/h3&gt;
&lt;p&gt;메인 스레드가 수정한 &lt;code&gt;stopRequested&lt;/code&gt; 값을 백그라운드 스레드가 언제 보게 될지 보장할 수 없기 때문이다.&lt;br&gt;배타적 실행은 보장되지만, &lt;strong&gt;스레드 사이의 안정적인 통신이 보장되지 않고 있다.&lt;/strong&gt;    &lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;멀티 스레드 환경에서는 각 스레드 별로 할당 된 CPU Cache로 데이터를 불러와 사용한다.&lt;/li&gt;
&lt;li&gt;각각의 CPU Cache에 저장된 데이터가 다를 수 있기 때문에 &lt;code&gt;값 불일치 문제&lt;/code&gt;가 발생할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/45311765/160759469-fbe7caed-02c4-493b-a060-7f600e66553a.jpeg&quot; alt=&quot;KakaoTalk_Photo_2022-03-30-14-42-02&quot;&gt;&lt;/p&gt;
&lt;p&gt;✅ &lt;code&gt;동기화&lt;/code&gt;를 통해 해결할 수 있다.&lt;/p&gt;
&lt;p&gt;또한 OpenJDK와 같은 일부 JVM은 동기화가 없을 경우 다음과 같이 우리의 의도와 다른 최적화(hoisting)를 할 수도 있다.  &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;//원래 코드
while(!stopRequested) {
    i++;
}

// 최적화한 코드
if(!stopRequested) {
    while(true) {
        i++;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;동기화 적용 방법&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;스레드 사이의 안정적인 통신을 위해 동기화를 적용한다. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class StopThread {

    private static boolean stopRequested;

    private static synchronized void requestStop() {
        stopRequested = true;
    }

    private static synchronized boolean stopRequested() {
        return stopRequested;
    }

    public static void main(String[] args) throws InterruptedException {

        Thread backgroundThread = new Thread(() -&amp;gt; {
            int i = 0;
            while(!stopRequested()) {
                i++;
            }
        });

        backgroundThread.start();

        TimeUnit.SECONDS.sleep(1);
        stopRequested();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;기대한 대로 1초 후 종료된다.&lt;br&gt;쓰기 메서드(requestStop)와 읽기 메서드(stopRequested) 모두를 동기화해야한다.&lt;br&gt;쓰기 메서드만 동기화해서는 정상적인 동작을 보장할 수 없다. &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;반복문에서 매번 동기화하는 비용이 크진 않지만 &lt;code&gt;volatile&lt;/code&gt; 키워드를 이용하면 성능을 개선할 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;volatile 키워드&lt;/h2&gt;
&lt;p&gt;자바 변수를 &lt;code&gt;메인 메모리에 저장&lt;/code&gt;하겠다라고 명시해주는 것이다.&lt;br&gt;CPU Cache가 아닌 메인 메모리의 데이터를 직접적으로 사용(읽기/쓰기)한다.&lt;br&gt;따라서 동기화는 아니지만 &lt;code&gt;항상 가장 최근에 기록된 값을 읽게 됨을 보장&lt;/code&gt;한다.&lt;br&gt;&lt;img src=&quot;https://user-images.githubusercontent.com/45311765/160760385-1f1d57a4-ac6e-4afe-859e-9c5cc01814a9.jpg&quot; alt=&quot;IMG_6323&quot;&gt;&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public class StopThread {

    private static volatile boolean stopRequested;

    public static void main(String[] args) throws InterruptedException {

        Thread backgroundThread = new Thread(() -&amp;gt; {
            int i = 0;
            while(!stopRequested) {
                i++;
            }
        });

        backgroundThread.start();

        TimeUnit.SECONDS.sleep(1);
        stopRequested = true;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;✅ 2번 기능(안정적 통신)만 필요하다면 &lt;code&gt;volatile&lt;/code&gt; 키워드만으로 동기화 효과를 낼 수 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;long, double을 제외한 기본타입은 volitile 키워드를 사용하면 동기화를 생략해도 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;volatile 키워드 주의점&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;private static volatile int nextSerialNumber = 0;

public static int generateSerialNumber() {
    return nextSerialNumber++;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;정상적으로 실행될 것 같지만 잘못된 결과를 계산하게 되는 &lt;code&gt;안전 실패(safety failture)&lt;/code&gt;가 발생한다.&lt;br&gt;증감연산자는 실행 시 다음과 같이 nextSerialNumber에 두번 접근하게 되므로, 원자적으로 실행되지 않는다. &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;nextSerialNumber = nextSerialNumber + 1;&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;동기화로 해결 가능&lt;/h3&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;private static int nextSerialNumber = 0;

public static synchronized int generateSerialNumber() {
    return nextSerialNumber++;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;synchronized 키워드를 붙였다면 &lt;code&gt;volitale 키워드는 제거&lt;/code&gt;해야 한다.&lt;br&gt;이 메서드를 더 견고하게 하려면 int 대신 long을 사용하거나 nextSerialNumber가 최댓값에 도달하면 예외를 던지게 하자.  &lt;/p&gt;
&lt;h2&gt;long, double 동기화를 위해 제공되는 라이브러리&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/RIANAEH/2022-daily-effective-java/blob/main/handbook/09/59.%20%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%AC%EB%A5%BC%20%EC%9D%B5%ED%9E%88%EA%B3%A0%20%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC.md&quot;&gt;아이템 59. 라이브러리를 익히고 사용하라&lt;/a&gt;  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Java.util.concurrent.atomic 패키지는 &lt;code&gt;AtomicLong&lt;/code&gt;, &lt;code&gt;AtomicDouble&lt;/code&gt;을 제공한다.&lt;br&gt;이 패키지에는 락 없이도(lock-free) 스레드 안전한 프로그래밍을 지원하는 클래스들이 담겨 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;private static final AtomicLong nextSerialNum = new AtomicLong();

public static long generateSerialNumber() {
    return nextSerialNum.getAndIncrement();
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul&gt;
&lt;li&gt;syncronized 키워드를 이용한 동기화보다 우수한 성능을 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;결론&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;이러한 문제들을 피하는 가장 좋은 방법은 &lt;code&gt;가변 데이터를 공유하지 않는것이다.&lt;/code&gt;&lt;br&gt;✅ 가변 데이터는 단일 스레드에서만 쓰도록 하자.&lt;br&gt;✅ 가변 데이터를 공유해야한다면 무조건 동기화를 해야 한다.&lt;br&gt;✅ &lt;a href=&quot;https://github.com/RIANAEH/2022-daily-effective-java/blob/main/handbook/04/17.%EB%B3%80%EA%B2%BD%20%EA%B0%80%EB%8A%A5%EC%84%B1%EC%9D%84%20%EC%B5%9C%EC%86%8C%ED%99%94%ED%95%98%EB%9D%BC.%20%EB%B6%88%EB%B3%80%20%EA%B0%9D%EC%B2%B4%EB%A5%BC%20%EB%A7%8C%EB%93%A4%EC%9E%90.md&quot;&gt;불변 데이터(아이템 17)&lt;/a&gt; 만 공유하거나 아무것도 공유하지 말자.  &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;Ref.&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;잘 정리된 블로그 : &lt;a href=&quot;https://jaehun2841.github.io/2019/03/11/effective-java-item78/#%EB%8F%99%EA%B8%B0%ED%99%94%EC%9D%98-%ED%8A%B9%EC%A7%95&quot;&gt;https://jaehun2841.github.io/2019/03/11/effective-java-item78/#%EB%8F%99%EA%B8%B0%ED%99%94%EC%9D%98-%ED%8A%B9%EC%A7%95&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;volatile 참고 : &lt;a href=&quot;https://nesoy.github.io/articles/2018-06/Java-volatile&quot;&gt;https://nesoy.github.io/articles/2018-06/Java-volatile&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Backend/Java</category>
      <category>Java</category>
      <category>syncronized</category>
      <category>volatile</category>
      <category>동기화</category>
      <category>자바</category>
      <author>ellie.strong</author>
      <guid isPermaLink="true">https://programmer-ririhan.tistory.com/417</guid>
      <comments>https://programmer-ririhan.tistory.com/417#entry417comment</comments>
      <pubDate>Wed, 30 Mar 2022 18:27:30 +0900</pubDate>
    </item>
    <item>
      <title>Enum에서 상수를 사용하는 방법</title>
      <link>https://programmer-ririhan.tistory.com/412</link>
      <description>&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;h2 data-ke-size=&quot;size26&quot;&gt;문제 발생&lt;/h2&gt;
&lt;pre id=&quot;code_1647489486544&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public enum Result {

    WIN(&quot;승&quot;, (dealerScore, participantScore) -&amp;gt;
            (dealerScore &amp;lt; participantScore &amp;amp;&amp;amp; participantScore &amp;lt;= 21) || (dealerScore &amp;gt; 21 &amp;amp;&amp;amp; participantScore &amp;lt;= 21)),
    DRAW(&quot;무승부&quot;, (dealerScore, participantScore) -&amp;gt;
            Objects.equals(dealerScore, participantScore) &amp;amp;&amp;amp; participantScore &amp;lt;= 21),
    LOSE(&quot;패&quot;, (dealerScore, participantScore) -&amp;gt;
            (dealerScore &amp;gt; participantScore &amp;amp;&amp;amp; dealerScore &amp;lt;= 21) || participantScore &amp;gt; 21);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 코드를 보시고 리뷰어 토니께서 &lt;b&gt;21에 의미를 부여해주면 좋겠다&lt;/b&gt;는 리뷰를 해주셨다. 당연히 나는 평소처럼 이를 &lt;b&gt;private static final 상수로 만들어주려고 했다.&lt;/b&gt; 하지만 생각지 못한 문제가 발생했다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;첫번째 시도&lt;/h3&gt;
&lt;pre id=&quot;code_1647489846208&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public enum Result {

    WIN(&quot;승&quot;, (dealerScore, participantScore) -&amp;gt; // Illegal forward reference
            (dealerScore &amp;lt; participantScore &amp;amp;&amp;amp; participantScore &amp;lt;= WINNING_SCORE) || (dealerScore &amp;gt; WINNING_SCORE &amp;amp;&amp;amp; participantScore &amp;lt;= WINNING_SCORE)),
    DRAW(&quot;무승부&quot;, (dealerScore, participantScore) -&amp;gt;
            Objects.equals(dealerScore, participantScore) &amp;amp;&amp;amp; participantScore &amp;lt;= WINNING_SCORE),
    LOSE(&quot;패&quot;, (dealerScore, participantScore) -&amp;gt;
            (dealerScore &amp;gt; participantScore &amp;amp;&amp;amp; dealerScore &amp;lt;= WINNING_SCORE) || participantScore &amp;gt; WINNING_SCORE);
            
    private static final int WINNING_SCORE = 21;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이넘에서는 인스턴스를 가장 먼저 선언해주어야한다는 것을 알고있었기 때문에 인스턴스 선언 아래에 상수를 선언해주었다. 여기까지는 문제가 없었지만 &lt;b&gt;선언한 상수를 인스턴스 생성자의 인자로 넘겨주는 방식을 사용하니 Illegal forward reference 컴파일 에러가 발생&lt;/b&gt;했다. 이때가지는 뭐가 문제인지 알지 못했다. 그래서 혹시 선언 순서가 문제인가해서 안 될걸 알면서도 한번 상수 선언을 맨 위로 올려보았다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;두번째 시도&lt;/h3&gt;
&lt;pre id=&quot;code_1647490291805&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public enum Result {

    private static final int WINNING_SCORE = 21;

    //Illegal in enums, first statement has to be an instance
    WIN(&quot;승&quot;, (dealerScore, participantScore) -&amp;gt; 
            (dealerScore &amp;lt; participantScore &amp;amp;&amp;amp; participantScore &amp;lt;= WINNING_SCORE) || (dealerScore &amp;gt; WINNING_SCORE &amp;amp;&amp;amp; participantScore &amp;lt;= WINNING_SCORE)),
    DRAW(&quot;무승부&quot;, (dealerScore, participantScore) -&amp;gt;
            Objects.equals(dealerScore, participantScore) &amp;amp;&amp;amp; participantScore &amp;lt;= WINNING_SCORE),
    LOSE(&quot;패&quot;, (dealerScore, participantScore) -&amp;gt;
            (dealerScore &amp;gt; participantScore &amp;amp;&amp;amp; dealerScore &amp;lt;= WINNING_SCORE) || participantScore &amp;gt; WINNING_SCORE);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 선언하니 컴파일러가 아예 WIN, DRAW, LOSE를 인스터스 선언으로 인식하지 못해 컴파일 에러가 발생했다. 어떻게 해줘야할지 모르겠어서 &lt;b&gt;아예 새로운 Rule 이넘을 생성해 WINNING_SCORE를 새로운 인스턴스로 선언&lt;/b&gt;하고, Rule.WINNING_SCORE를 사용하는 방식으로 적용했다. 그리고 이에 대해 리뷰어께 여쭈어봤다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이넘에서의 static 필드 초기화 순서&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이펙티브 자바에서는 이넘을 이렇게 설명한다.&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;열거 타입 자체는 클래스이며, &lt;b&gt;상수 하나당 자신의 인스턴스를 하나씩 만들어 public static final 필&lt;/b&gt;드로 공개한다.&amp;nbsp;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 &lt;b&gt;이 인스턴스는 static이기 때문에&lt;/b&gt; 생성문에서 다른 상수를 바로 참조할 수 없게된다. 또한 같은 이넘 내부의 값들도 선언되는 순서가 역전되면 참조할 수 없다. 무슨말인고 하면 &lt;b&gt;이넘에서 static 필드는 선언되어있는 순서데로 초기화가 진행된다.&lt;/b&gt; 따라서 WIN, DRAW, LOSE가 초기화되는 시점에 WINNING_SCORE는 초기화되어 있지 않기 때문에 &lt;span&gt;Illegal forward reference&lt;span&gt; 컴파일 에러가 발생하는 것이다. 같은 이유로 DRAW에서는 WIN을 참조할 수 있지만, WIN에서는 DRAW를 참조하지 못한다.&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;&lt;span&gt;&lt;span&gt;Java9부터 JLS에 이와 관련된 내용이 추가되었다고 한다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;Note that&amp;nbsp;static&amp;nbsp;fields that are constant variables are initialized before other&amp;nbsp;static&amp;nbsp;fields. [...]&amp;nbsp;&lt;b&gt;When such fields are referenced by simple name, they will never be observed to have their default initial values.&lt;/b&gt;&lt;/blockquote&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&gt;&lt;span&gt;그렇다면 어떻게 해결해야할까??&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;해결하는 방법으로는 3가지 정도가 있지만 그 중 &lt;b&gt;중첩(nested) 클래스 사용이 가장 일반적&lt;/b&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;span&gt;중첩(Nested) 클래스 사용&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;중접 클래스 중 정적 멤버 클래스를 이용해 상수를 선언해서 사용할 수 있다. 중첩 클래스는 별도로 초기화되므로 참조 가능하다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;멤버 클래스에서 바깥 인스턴스에 접근할 일이 없으므로 &lt;b&gt;정적 멤버 클래스로 사용하기를 권장한다.&lt;/b&gt; (이펙티브 자바 3/E 아이템 24)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1647497115232&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public enum Result {

    WIN(&quot;승&quot;, (dealerScore, participantScore) -&amp;gt; 
            (dealerScore &amp;lt; participantScore &amp;amp;&amp;amp; participantScore &amp;lt;= Constants.WINNING_SCORE) || (dealerScore &amp;gt; Constants.WINNING_SCORE &amp;amp;&amp;amp; participantScore &amp;lt;= Constants.WINNING_SCORE)),
    DRAW(&quot;무승부&quot;, (dealerScore, participantScore) -&amp;gt;
            Objects.equals(dealerScore, participantScore) &amp;amp;&amp;amp; participantScore &amp;lt;= Constants.WINNING_SCORE),
    LOSE(&quot;패&quot;, (dealerScore, participantScore) -&amp;gt;
            (dealerScore &amp;gt; participantScore &amp;amp;&amp;amp; dealerScore &amp;lt;= Constants.WINNING_SCORE) || participantScore &amp;gt; Constants.WINNING_SCORE);
    
    private static class Constants { // 정적 멤버 클래스
        private static final int WINNING_SCORE = 21;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;&lt;span&gt;메서드 사용&lt;/span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;정적 메서드를 선언해 사용할 수 있다. 하지만 메서드 호출 자체는 &lt;a href=&quot;https://docs.oracle.com/javase/specs/jls/se10/html/jls-15.html#jls-15.28&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;상수 표현식(constant expression)&lt;/a&gt;이 아니기 때문에 어노테이션 요소 값으로 넣을 수 없는 등의 &lt;b&gt;사용에 제약&lt;/b&gt;이 있을 수 있다고 한다.&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1647497504021&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public enum Result {

    WIN(&quot;승&quot;, (dealerScore, participantScore) -&amp;gt;
            (dealerScore &amp;lt; participantScore &amp;amp;&amp;amp; participantScore &amp;lt;= getWinningScore()) || (dealerScore &amp;gt; getWinningScore() &amp;amp;&amp;amp; participantScore &amp;lt;= getWinningScore())),
    DRAW(&quot;무승부&quot;, (dealerScore, participantScore) -&amp;gt;
            Objects.equals(dealerScore, participantScore) &amp;amp;&amp;amp; participantScore &amp;lt;= getWinningScore()),
    LOSE(&quot;패&quot;, (dealerScore, participantScore) -&amp;gt;
            (dealerScore &amp;gt; participantScore &amp;amp;&amp;amp; dealerScore &amp;lt;= getWinningScore()) || participantScore &amp;gt; getWinningScore());

    private static int getWinningScore() {
        return 21;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Qualified Name 사용&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;qualified name이란 simple name인 WINNING_SCORE의 계층을 모두 표현한 &lt;b&gt;Result.WINNING_SCORE&lt;/b&gt;를 의미한다. &lt;span&gt;&lt;span&gt;하지만 이 방법은 Java9부터는 &lt;a href=&quot;https://docs.oracle.com/javase/specs/jls/se9/html/index.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;JLS&lt;/a&gt;에서 더 이상 작동을 보장하지 않으므로 &lt;b&gt;사용을 권장하지 않는다고한다.&lt;/b&gt; (일단 Java11을 사용하는 내 코드에서는 컴파일 에러가 나지 않으며, 테스트 결과 잘 작동하는 것으로 확인되어, 아직 무슨말인지 잘 이해가 가지는 않는다.   )&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1647498084097&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public enum Result {

    WIN(&quot;승&quot;, (dealerScore, participantScore) -&amp;gt;
            (dealerScore &amp;lt; participantScore &amp;amp;&amp;amp; participantScore &amp;lt;= Result.WINNING_SCORE) || (dealerScore &amp;gt; Result.WINNING_SCORE &amp;amp;&amp;amp; participantScore &amp;lt;= Result.WINNING_SCORE)),
    DRAW(&quot;무승부&quot;, (dealerScore, participantScore) -&amp;gt;
            Objects.equals(dealerScore, participantScore) &amp;amp;&amp;amp; participantScore &amp;lt;= Result.WINNING_SCORE),
    LOSE(&quot;패&quot;, (dealerScore, participantScore) -&amp;gt;
            (dealerScore &amp;gt; participantScore &amp;amp;&amp;amp; dealerScore &amp;lt;= Result.WINNING_SCORE) || participantScore &amp;gt; Result.WINNING_SCORE);

    private static final int WINNING_SCORE = 21;
}&lt;/code&gt;&lt;/pre&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Ref.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/30169153/illegal-forward-reference-in-enum&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://stackoverflow.com/questions/30169153/illegal-forward-reference-in-enum&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/specs/jls/se9/html/index.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.oracle.com/javase/specs/jls/se9/html/index.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Backend/Java</category>
      <category>enum</category>
      <category>Java</category>
      <category>이넘</category>
      <category>자바</category>
      <author>ellie.strong</author>
      <guid isPermaLink="true">https://programmer-ririhan.tistory.com/412</guid>
      <comments>https://programmer-ririhan.tistory.com/412#entry412comment</comments>
      <pubDate>Thu, 17 Mar 2022 14:14:59 +0900</pubDate>
    </item>
  </channel>
</rss>