<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>얇고 넓은 개발 블로그</title>
    <link>https://sundries-in-myidea.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sun, 26 Apr 2026 13:56:44 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>개발섭</managingEditor>
    <image>
      <title>얇고 넓은 개발 블로그</title>
      <url>https://tistory1.daumcdn.net/tistory/2938283/attach/5898fbd5d2d940a591c1be3ca97ef36d</url>
      <link>https://sundries-in-myidea.tistory.com</link>
    </image>
    <item>
      <title>2025 회고 - 이해의 깊이가 바뀐 한 해</title>
      <link>https://sundries-in-myidea.tistory.com/179</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3018&quot; data-origin-height=&quot;2012&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLjCKK/dJMcafL0E0M/BIZD75euMp1e3FlpPIkg70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLjCKK/dJMcafL0E0M/BIZD75euMp1e3FlpPIkg70/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLjCKK/dJMcafL0E0M/BIZD75euMp1e3FlpPIkg70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLjCKK%2FdJMcafL0E0M%2FBIZD75euMp1e3FlpPIkg70%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;3018&quot; height=&quot;2012&quot; data-origin-width=&quot;3018&quot; data-origin-height=&quot;2012&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;올해 적은 글은 참 적지만, 그래도 회고는 늘하고 살아야지 하는 마음이 있어서 부채의식? 아니면, 비슷한 무엇인가 있었을지도 모르죠? 그래도 회고롤 쭉 해내려간다는건 나든, 누구든간에 다시 한 번 조향타를 조정해내려간다. 요런 부분때문에 고민을 쭉 해내려가면 좋겠다는 생각도 하는중이에요.&lt;/p&gt;
&lt;h2&gt;회사&lt;/h2&gt;
&lt;p&gt;올해의 회사는 여러가지 일들이 많았지만, 곰곰히 생각해보면 절반정도로 나눠서 볼 수 있을거 같아요. 그리고 폭풍이 지나가기도 하고.. 잔잔했다가 다시 가라앉았다가, 여러가지 일들이 많아서 복잡했죠. &lt;/p&gt;
&lt;h2&gt;폭풍과 지옥&lt;/h2&gt;
&lt;p&gt;폭풍 같은 반년이라고 개인적으로는 생각하고 있는게, 작년에는 서비스 종료를 통해서 (작년에 서비스 종료한다고 적어두고, 막상 연말쯤에야 서비스 종료를 한다니 이건 참 뭐랄까... ) 결국 새로운 프로젝트를 시작하게되는데, 그게 진짜 레거시와의 사투였거든요.&lt;br&gt;그러다보니까 레거시의 무시무시함 그러니까 뉴비일때는 레거시 개편이 뭐가 어렵나! 이런식으로 생각해봤던 적이 있었는데, 그게 실제로 폭풍을 마주한 순간 정 말 정신을 잃어버릴것 같은 순간이 한두번이 아니였습니다. 3달간을 야근에 쩔어살았었는데, 그때는 어떻게 지나갔는지도 모르겠고.. 너무 괴로웠던 순간이 한 두가지가 아니더라구요.&lt;br&gt;그리고 여기저기 맡은 부분도 많다보니까 부딪히는 지점도 많았고, 그런 부딪히는 지점들을 스무스하게 넘기려고 노력도 많이했던거 같아요....&lt;/p&gt;
&lt;p&gt;그리고 대망의 런칭은 정말 지옥과 같은 기간이었고, 안그래도 지친 나를 더욱 지치게 했어요. 주말 런칭 + 새벽 런칭 + 연장근무 + 기타 등등 개발일 하면서 겪을 수 있는 모든 경우의 수를 다 겪어본뒤, 그게 뭐.. 어떤식으로 나를 성장시켰는지는 모르겠지만? 적절한 보상을..(기분은 좀 모자른 기분이긴했지만..) 받긴 했는데 정신적으로나 몸이나 너무 피곤했다.&lt;br&gt;그래도 폭풍과 지옥 주간이 지나고 난 다음에는 내가 맡았던 부분에서는 딱히 큰 이슈는 없었기에 관련 이슈들을 점검하는 과정들엔 내가 직접적으로 고생했던 부분은 없었다. 상대적으로 사용을 안해서 이슈도 적었다.&lt;/p&gt;
&lt;h2&gt;평온함과 개선&lt;/h2&gt;
&lt;p&gt;그 이후에는 해당 프로젝트를 또 고도화하는 과정을 만들 생각이긴 했지만, 막상 고도화하는 과정이(?) 회사 내부적인 이슈들과 여러가지 이슈들로 이래저래 만드는 과정까지는 나쁘지않았으나? 좀 더 내가 이슈업할 수 있는 부분들도 흐지부지 되면서 회사에서 막상 할 일이 없어지는 과정이 생겨서 좀 쓸쓸했다. 특히, 내가 이 회사에서 뭘 이룰수 있는지? 에 대해서 고민을 많이 해봤었는데, 사실 별 생각이 없어서.. 좀 슬프기도 했고..&lt;br&gt;가끔 이슈들이 굵직하게 터질때 내가 어떤식으로 문제를 해결해야할지에 대해서 고민을 해보긴 했었고, 내가 못 이겨내는 이슈들도 존재했고, 이겨낼 수 있는 이슈들도 있었다. 그래도 뭐.. 내가 할 수 있는 것들에 대해서는 최선을 다했었기에 아쉬움은 없었다.&lt;br&gt;마지막으로는 최대한 사내 비효율을 개선하는 작업을 많이 했었는데, 그런 비효율을 진짜로 내가 개선했었는지를 고민해봤었다. 특히, 빌드 최적화처럼 꽤나 공이 들고 어려운 일들을 내가 처리하면서 이게 정말로 어렵긴하지만 나에게 도움이 될거 같은 일들을 진행해봤다.&lt;/p&gt;
&lt;h2&gt;나름의 최선&lt;/h2&gt;
&lt;p&gt;컨퍼런스를 올해는 그래도 굵직한 행사인 네이버와 카카오를 참여해서 기분이 좋았다. 하지만 해당 컨퍼런스에서 내가 얻을 게 있을까 했던 부분들은 있었다.&lt;br&gt;너무 AI 기준을 요구하기에 내가 지금 맡은 파트인 &amp;quot;백엔드&amp;quot;파트에는 적용하기 무리가 있다는 점과, 대형 트래픽과 대형 요금에 이정도는 태울 수 있는 그들에게 우리 회사의 기술이 정말로 그 기술을 사용하는게 맞는지에 대한 검토가 필요했다.&lt;br&gt;예전엔 단순히 그냥 기술이 좋으면 그냥 그거면 된거고 우리 회사에서 쓸진 모르겠지만 적용되기만 하면 잘 쓰겠거니 했지만, 실제로 우리 회사에서는 잘 사용하게끔 유도하고 싶었다. 그래서 조금 더 신중해진 거 같기두..&lt;/p&gt;
&lt;h2&gt;마지막으로...&lt;/h2&gt;
&lt;p&gt;AI와 함께한 마무리 회고를 첨부하여 마무리 지어보려한다. &lt;/p&gt;
&lt;p&gt;2025년은 새로운 기술을 많이 쓴 해라기보다,&lt;br&gt;&lt;strong&gt;기존에 알고 있던 것들을 다르게 이해하게 된 해&lt;/strong&gt;였다.&lt;/p&gt;
&lt;p&gt;시스템이 “돌아간다”는 것과&lt;br&gt;그 상태를 &lt;strong&gt;설명할 수 있다&lt;/strong&gt;는 것은 전혀 다른 문제라는 걸 자주 마주했다.&lt;br&gt;트랜잭션, 락, 배치 롤백, 정산 타이밍 같은 것들은&lt;br&gt;이제 더 이상 운이나 예외 케이스로 넘길 수 없는 대상이 되었다.&lt;/p&gt;
&lt;p&gt;결제·정산·구매 확정 흐름을 이해하려다 회계까지 들여다보게 된 것도,&lt;br&gt;결국 서비스가 &lt;strong&gt;언제 진짜로 끝나는지&lt;/strong&gt;를 알고 싶었기 때문이다.&lt;br&gt;기술적으로 완료된 상태와, 비즈니스적으로 확정된 상태는 다를 수 있었다.&lt;/p&gt;
&lt;p&gt;자동화와 관측 도구에 집착한 이유 역시 단순했다.&lt;br&gt;사람이 반복을 처리하지 않고,&lt;br&gt;판단이 필요한 지점에만 집중할 수 있는 구조를 만들고 싶었다.&lt;/p&gt;
&lt;p&gt;올해를 지나며 하나는 분명해졌다.&lt;br&gt;이제 나는 “어디서 일할지”보다&lt;br&gt;&lt;strong&gt;어떤 복잡한 문제를 풀 수 있는 환경인지&lt;/strong&gt;를 더 중요하게 보고 있다.&lt;/p&gt;
&lt;p&gt;2025년은&lt;br&gt;기술적으로 엄청난 도약의 해는 아니었을지 모른다.&lt;/p&gt;
&lt;p&gt;하지만 분명한 건,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;이해의 기준이 높아졌고&lt;/li&gt;
&lt;li&gt;애매한 상태를 그냥 넘기지 않게 됐고&lt;/li&gt;
&lt;li&gt;“왜?”를 묻는 데 주저함이 사라졌다.&lt;br&gt;그리고 이 변화는,&lt;br&gt;다음 해에 더 큰 시스템을 다룰 준비가 되었다는 신호라고 믿고 있다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>잡담/회고</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/179</guid>
      <comments>https://sundries-in-myidea.tistory.com/179#entry179comment</comments>
      <pubDate>Wed, 31 Dec 2025 23:01:17 +0900</pubDate>
    </item>
    <item>
      <title>글또 안녕! - 몇기수의 글또 회고하면서</title>
      <link>https://sundries-in-myidea.tistory.com/178</link>
      <description>&lt;p&gt;글또를 꽤 오랜기간동안 해왔던 내입장에서 글또의 마지막을 같이할 수 있어서 너무 좋았다. 아쉬움도 당연히 크지만, 4개의 기수 동안 여러가지 행사에 참여했다는 것도 생각해보면 참으로 기쁜일이었다. &lt;/p&gt;
&lt;h2&gt;첫시작은 5기&lt;/h2&gt;
&lt;p&gt;내 글또 첫 시작은 5기부터였는데, 그 당시에는 취업전에 여러가지 동아리 참여와 여러가지 프로젝트를 했던걸로 기억한다.&lt;br&gt;&lt;a href=&quot;https://sundries-in-myidea.tistory.com/99&quot;&gt;https://sundries-in-myidea.tistory.com/99&lt;/a&gt;&lt;br&gt;그래서 취업전에 개발블로그에 내가 했던 프로젝트도 정리할겸해서, 2주마다 마감시한이 있는 방식의 압박감을 나한테 주면 블로그를 주기적으로 올릴 수 있지 않을까 싶어서 참여했었다.&lt;br&gt;그 당시에는 꽤나 이런저런 프로젝트를 많이 참여하면서 프로젝트 정리를 많이 했었고, 대학생입장에서 프로젝트를 어떤 배경에서 어떤식으로 진행하려고 했는지를 고민을 많이 하면서 작성했다.&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://sundries-in-myidea.tistory.com/111&quot; title=&quot;2020년 회고 - 뭔가 많이 했지만, 스스로 만족할 만한 성과는 나오지 않은  한 해&quot;&gt;2020년 회고 - 뭔가 많이 했지만, 스스로 만족할 만한 성과는 나오지 않은 한 해&lt;/a&gt;&lt;br&gt;회고를 보면 코로나 시국의 외로움도 있는거 같고, 여러가지 프로젝트를 하면서 성과를 내려고 고군분투하던 모습이 좀 보인다.&lt;br&gt;&lt;a href=&quot;https://sundries-in-myidea.tistory.com/107&quot; title=&quot;온라인 사진전을 개최하며 겪었던 후일담 -1편&quot;&gt;온라인 사진전을 개최하며 겪었던 후일담 -1편&lt;/a&gt;&lt;br&gt;특히 당시에 적극적으로 진행했던 전시회 프로젝트는 일종의 스트리트 코더의 느낌을 많이 살리는 개념의 프로젝트이기도하고, 나의 정체성이 참 많이 녹아있는 프로젝트를 진행했던 가장 열정적인 첫 기수라고 생각한다. 이때는 글쓰기에 좀더 집중해서 참여했다. 스스로가 글쓰고, 프로젝트를 정리하면서 내가 취업을 하기위해 쌓아가는 기간이였다.&lt;/p&gt;
&lt;h2&gt;두번째 6기&lt;/h2&gt;
&lt;p&gt;6기 시작은 막 인턴십과 함께하던 시절이었다. 현 직장에 공채 인턴십 프로그램이 있었고, 마침 합격하여 그 짧지만 합격하기 위한 좀 빡셌던 그 기간내내 성장하기 위한 여러가지 내용들을 적었던 기간이었다.&lt;br&gt;&lt;a href=&quot;https://sundries-in-myidea.tistory.com/128&quot; title=&quot;라라벨 커스텀 이메일 인증&quot;&gt;라라벨 커스텀 이메일 인증&lt;/a&gt;&lt;br&gt;그래서 회사에서 인턴십 프로그램 동안 사용하던, 여러가지 내가 기존에 사용하지 않던 기술들을 정리하는 시간이었다. 그런 시간동안 나는 인턴십에도 합격했고? 첫 팀에 합류하고, 할 수 있는 일들을 하면서 성장해보려고 노력했었다.&lt;br&gt;물론, 폭발적인 성과나 성장을 하기에는 내가 가진 혹은 팀에서 요구하는 상황이 그렇게 높진 않았으나, 그래도 잘 해보고 싶었고? 잘 하려고 노력했었던 기간이었다.&lt;/p&gt;
&lt;p&gt;아마 이때 글또에 처음으로 커피챗이 있었나? 그래서 커피챗도 종종 참여해보고 관련 행사도 참여했던거 같았다. 근데, 막상 생각해보면, 다른 동아리도 들어가서 서비스하느라 사실 글또보다는 여러가지 것들을 열심히 하는 생활이 쭉 이어졌던거 같다. 그러다보니까 그 기수를 마지막으로 다른 기수 까지 더하지 않았었다.&lt;/p&gt;
&lt;h2&gt;세번째 9기&lt;/h2&gt;
&lt;p&gt;9기는 다양한 상황에 성장을 해보고 싶어서 참여한 기간이었다. 당시에 잘 하고 있는지에 대한 고민이 많았는데,&lt;br&gt; &lt;a href=&quot;https://sundries-in-myidea.tistory.com/158&quot; title=&quot;글또 9기를 시작하며...&quot;&gt;글또 9기를 시작하며...&lt;/a&gt; 프로젝트가 좀 더 빡센 프로젝트로 바뀐 지점이라던가, 내가 거기서 어떤 액션을 하면서, 바뀌고 싶다는 점을 알고 싶었던 과정중 하나였다. 9기는 글또로 봤을때는 이런행사 저런행사를 가장 많이 참여한 기수기도 했다. 참여해볼 수 있는 행사는 좀 많이 참여해봤던 기수였달까.. 9기부터는 참여자들끼리 모여서 즐길 수 있는 환경이 많이 갖춰진 깃수여서, 처음으로 음악 감상회 모임을 만들기도 했었고, 그게 잘되서 꽤 여러번 행사가 이뤄졌던 한해기도 했다.&lt;br&gt; 일적으로는 좀 고민이 많았던 시기기도 했었다. 처음 런칭해보는 대규모 프로젝트에 좀 긴장도 많이 했었고, 야근도 많았어서, 엄청 고통 받았던 시기였는데, 이런 저런 고민을 많이 했었던 시기도 했다.&lt;/p&gt;
&lt;h2&gt;마지막 10기&lt;/h2&gt;
&lt;p&gt;이제 마지막 10기는 사실 여러가지 행사참여보다는 내 주변 사람들과 많이 지냈던 시기이였다. 그래서 사실 행사 참여보다는 글쓰기에 집중하려고 노력했던 기간이다. 사실 이때는 글쓰기에 집중했냐하면, 또 그렇지도 않았던거 같기도해서, 사실 마지막 기수라고 했지만 스스로에 대한 고민은 적었던거 같고, 외부 사람과의 만남도 그렇게 많이 있지 않기도 했다.&lt;br&gt;그래서 좀 아쉽기도하다. 그래도 좋은 사람들을 만난 기회가 되긴했어서, 좀 더 이런 관계가 유지되는 것도 참 좋을텐데 라는 생각은 많이 있었다. 이게 글또 막바지가되면 사실 모임도, 번개도 많이 사라지니까 늦게 참여하려며하면 기회가 모두 사라지는 것도 좀 아쉽긴했다. 이건 내가 좀 찾아나섰어야 그런 기회가 생겼을텐데, 내가 10기 시작과 함께 바뻐지는 바람에 못했던 것도 아쉬웠다.&lt;/p&gt;
&lt;h2&gt;안녕 글또&lt;/h2&gt;
&lt;p&gt;글또가 이제 마지막 기수라서 사실 더 진행되지 않는 게 아쉽긴하다. 왜냐하면 개발일을 하는 사람들이 같이 모여서 관련한 글을 쓰고, 관련한 이야기들을 하는게 언제나 어려웠기 때문에, 이런 기회나, 상황이 있는 건 아니니까.. 그래도 꾸준히 적었던 점은 고무적이라고 생각했다. &lt;/p&gt;</description>
      <category>잡담/경험담, 생각들</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/178</guid>
      <comments>https://sundries-in-myidea.tistory.com/178#entry178comment</comments>
      <pubDate>Sun, 30 Mar 2025 21:32:56 +0900</pubDate>
    </item>
    <item>
      <title>Spring Data envers</title>
      <link>https://sundries-in-myidea.tistory.com/177</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Spring Data Enver란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어플리케이션을 만들고, 결국 운영 및 관리를 해야 하는 입장에서는 활동로그성 데이터들이 필요하다. &quot;누가&quot; 그리고 &quot;언제&quot; 무엇을 &quot;어떻게&quot; 했는지가 중요한 데이터가 되기 때문에, 그런 기록을 반드시 필요로 하는 상황이 생긴다.&lt;br /&gt;즉, 이러한 문제를 해결하기 위해서는 Audit(감사) 기능을 필요로 하게 되는데, 일단 데이터의 변경 이력을 관리하기 쉽게 해주는 Library를 찾았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;spring data envers&lt;/code&gt;는 하이버네이트의 enver의 이력 로깅 기능을 가져와 JPA에서도 훨씬 편리한 방향으로 개발할 수 있도록 도와주는 툴이다.&lt;br /&gt;의존성을 Spring Data Enver를 설치한뒤,&lt;/p&gt;
&lt;pre class=&quot;haskell&quot;&gt;&lt;code&gt;
implementation 'org.springframework.data:spring-data-envers:3.4.4'
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순하게 이렇게만 해도...&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Entity
@Audited
public class Sample {&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동적으로 JPA Entity에 대한 Auditing이 가능해진다. 또한 Repository에 대한 큰 변경 혹은 Entity에 대한 생성없이도, 기능적으로는 이 버젼 관리에 집중할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public interface SampleRepository extends JpaRepository&amp;lt;Sample, Long&amp;gt;, RevisionRepository&amp;lt;Sample, Long, Integer&amp;gt; { }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;RevisionRepository만 다중상속해버리면 &amp;rarr; revision 객체를 통해서 내 이전 이력들을 확인할 수 있어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 이런식으로 레포지토리에서 찾아서 사용한다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Revision&amp;lt;Long, Sample&amp;gt; revision = repository.findRevision(sample.getId(), 1); 

Sample entity = revision.getEntity(); // 엔티티 
Long revisionNumber = revision.getRevisionNumber(); // 리비전 
DateTime dateTime = revision.getRevisionDate(); // 변경 날짜&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 기본적으로는 이런형태의 Scheme를 취하지만..&lt;/p&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;
create table Customer (
    id bigint not null,
    created_on timestamp,
    firstName varchar(255),
    lastName varchar(255),
    primary key (id)
)

create table Customer_AUD (
   id bigint not null,
    REV integer not null,
    REVTYPE tinyint,
    REVEND integer,
    created_on timestamp,
    firstName varchar(255),
    lastName varchar(255),
    primary key (id, REV)
)

create table REVINFO (
    REV integer generated by default as identity,
    REVTSTMP bigint,
    primary key (REV)
)

alter table Customer_AUD
    add constraint FK5ecvi1a0ykunrriib7j28vpdj
    foreign key (REV)
    references REVINFO

alter table Customer_AUD
    add constraint FKqd4fy7ww1yy95wi4wtaonre3f
    foreign key (REVEND)
    references REVINFO&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 configuration 좀더 추가하면 디테일한 옵션도 추가 가능해져서 더 구체적으로 값을 변경지점을 확인할 수도 있는데...&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1702&quot; data-origin-height=&quot;200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSp3Sx/btsML3o8Ruq/XPpLBMkOvjnlltIFYj3R2k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSp3Sx/btsML3o8Ruq/XPpLBMkOvjnlltIFYj3R2k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSp3Sx/btsML3o8Ruq/XPpLBMkOvjnlltIFYj3R2k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSp3Sx%2FbtsML3o8Ruq%2FXPpLBMkOvjnlltIFYj3R2k%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;1702&quot; height=&quot;200&quot; data-origin-width=&quot;1702&quot; data-origin-height=&quot;200&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;flag나 old &amp;amp; new와 같은 값을 저장할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아쉽게도 &lt;b&gt;트랜잭션 단위의 통합 Revision 관리 (Snapshot)&lt;/b&gt; 에 가깝기 때문에 이게 로깅으로써의 역할보다는 백업 역할이 더 큰 것 같다는 점이다.&lt;br /&gt;왜냐하면, 일단 기본적으로 Table Column의 전체적인 복사가 이뤄지기 때문에, 사실상 Table을 두개나가지고 있다는 점이며, 그 Table을 두개나 가지고 있다는 것은 사실 로그성이라기보다는... Backup성의 데이터에 가깝다고 생각이 든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론, 이런것이 필요한 케이스들이 있긴 하겠지만, 실제로 지금 개발해야하는것은 Audit보다는 activity Log에 가까워서 그 값에 대해서 굳이 많이 가지고 있을 필요는 없다고 판단해 이건 나중에 적용하는 걸로 되었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;출처&lt;/h3&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&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://www.youtube.com/watch?v=fGPaj-rlN5w&quot;&gt;스프링캠프 2017 [Day2 A5] : 엔티티 히스토리를 편리하게 관리해주는 스프링 데이터 Envers - YouTube&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#envers-revisionlog&quot;&gt;Hibernate ORM 6.1.7.Final User Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://directori.tistory.com/138&quot;&gt;Hibernate ORM 이력관리 모듈 Envers 사용 가이드&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.google.com/search?q=JPA+Auditing+history&amp;amp;ei=gbMSZK2LFsHP2roP1MC7kAs&amp;amp;oq=jpa+auditing+logging&amp;amp;gs_lcp=Cgxnd3Mtd2l6LXNlcnAQARgBMgoIABBHENYEELADMgoIABBHENYEELADMgoIABBHENYEELADMgoIABBHENYEELADMgoIABBHENYEELADMgoIABBHENYEELADMgoIABBHENYEELADMgoIABBHENYEELADMgoIABBHENYEELADMgoIABBHENYEELADSgQIQRgAUABYAGC8EGgDcAF4AIABAIgBAJIBAJgBAMgBCsABAQ&amp;amp;sclient=gws-wiz-serp&quot;&gt;JPA Auditing history - Google 검색&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://escapefromcoding.tistory.com/550&quot;&gt;JPA를 이용해 History 테이블 자동 생성하기&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://jojoldu.tistory.com/156&quot;&gt;Spring Camp 2017 - 2부 (일요일) 참석 후기!&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-data/envers/docs/2.7.9/reference/html/&quot;&gt;Spring Data Envers - Reference Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://gist.github.com/dlxotn216/94c34a2debf848396cf82a7f21a32abe&quot;&gt;Spring data jpa의 Audit 기능과 Spring data envers&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Spring/JPA</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/177</guid>
      <comments>https://sundries-in-myidea.tistory.com/177#entry177comment</comments>
      <pubDate>Sun, 16 Mar 2025 22:17:22 +0900</pubDate>
    </item>
    <item>
      <title>AWS SES Template 메일 발송</title>
      <link>https://sundries-in-myidea.tistory.com/176</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;AWS SES는 메일발송을 쉽게 AWS에서 전송해주는 역할을 하고 있습니다. 그 중 일정한 형태는 고정되어있고 안의 내용만 변경 되야하는 &lt;code&gt;템플릿 메일&lt;/code&gt;에 대해서 사용기와 경험담에 대해서 설명드리겠습니다.&lt;/p&gt;
&lt;h1&gt;TemplateMail은...&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS에서는 일정형식의 HTML, 혹은 Text 형태의 템플릿 만들어서 그 형태에 어떤 값이 들어가는지만 알게되면 대치하는 방식을 통해 동일한 템플릿 사용자별로 다양한 메세지를 발송할 수 있습니다.&lt;br /&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/ses/latest/dg/send-personalized-email-api.html&quot;&gt;https://docs.aws.amazon.com/ko_kr/ses/latest/dg/send-personalized-email-api.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메일 도메인이 처음 접하는 도메인이기도 했고, 어떤 방식으로 개발을 해야 효과적으로 개발 할 수 있는지에 대해서 잘 모르는 상황이라 좌충우돌이 많긴했으나 공유 드리면 좋을 것 같습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 발송 오류는 SNS로 잡는게 좋다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 AWS SES 메일의 경우 메일의 발송 여부를 Event형태로 확인을 할 수 있습니다. 특히, SES 이벤트중 중요하게 생각하는 이벤트는 다음과 같습니다.&lt;br /&gt;&lt;b&gt;전송 및 배달&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Rejects(거부)&lt;/b&gt; - Amazon SES가 이메일을 수락했으나 이메일에 바이러스가 포함된 것으로 판단되어 수신자의 메일 서버로 전송하려고 시도하지 않았습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Deliveries(배달)&lt;/b&gt; &amp;ndash; Amazon SES에서 이메일을 수신자의 메일 서버로 성공적으로 배달했습니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Hard bounces(하드 바운스)&lt;/b&gt; &amp;ndash; 수신자의 메일 서버가 이메일을 영구적으로 거부했습니다. (_Soft bounces(소프트 바운스)_는 Amazon SES가 일정 시간 동안 재시도한 후 이메일을 배달하는 데 실패한 경우에만 포함됩니다.)&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Complaints(수신 거부)&lt;/b&gt; - 이메일이 수신자의 메일 서버로 성공적으로 전송되었지만 수신자가 이를 스팸으로 표시했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;메일 발송과 관련하여, 중요한 이벤트는 이 정도로 잡을 수 있는데, 템플릿 메일 발송시 간혹 메일이 무슨 이유에서인지 발송이 안된 케이스가 있어서 도대체 이것은 무엇인가... 왜 발송이 안되는것인지에 대해서 파악하지 못했는데...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SES에서는 이벤트로 하나 더 다음과 같은 이벤트를 캡쳐하고 있었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;렌더링 오류&lt;/b&gt; &amp;ndash; 템플릿 렌더링 문제로 인해 이메일이 전송되지 않았습니다. 이 이벤트 유형은 템플릿 데이터가 누락되었을 때 또는 템플릿 파라미터와 데이터 사이에 불일치가 있을 때 발생할 수 있습니다. (이 이벤트 유형은 &lt;a href=&quot;https://docs.aws.amazon.com/ses/latest/APIReference/API_SendTemplatedEmail.html&quot;&gt;&lt;code&gt;SendTemplatedEmail&lt;/code&gt;&lt;/a&gt; 또는 &lt;a href=&quot;https://docs.aws.amazon.com/ses/latest/APIReference/API_SendBulkTemplatedEmail.html&quot;&gt;&lt;code&gt;SendBulkTemplatedEmail&lt;/code&gt;&lt;/a&gt; API 작업을 사용하는 이메일을 전송할 때만 발생합니다.)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 렌더링 오류가 발생한 경우 템플릿에는 key가 존재하는데, 끼워넣어야하는 템플릿 데이터에는 해당하는 키가 없는 경우에 발생합니다. 이렇게 봐도 좋습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;템플릿에는 Key 존재 / 템플릿 데이터에는 해당하는 Key 값이 없음. &amp;rarr; 랜더링 오류 ✅&lt;br /&gt;템플릿에는 Key 미존재 / 템플릿 데이터에는 해당하는 Key 값이 있음 &amp;rarr; 랜더링 오류 ❌&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;이렇게 이런 이벤트는 AWS SES의 구성세트(Configuration Set)로 선택해서 SES 발송시에 해당 구성세트를 포함해서 발송시키면, 위의 이벤트를 캡쳐해서 SNS를 통해 받아 처리할 수 있습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 템플릿 메일은 Handlebars라는 템플릿 툴을 이용한다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 템플릿 메일에서 넣어질 데이터를 처리하는 방법은 {{key}} 형태로 구성됩니다. 문제는 반복적인 컨텐츠 즉, For문을 사용해서 List형태로 받아봐야하는 형태를 처리하는 방법이 어려웠다는점입니다.&lt;br /&gt;그래서 초기에는 Table 형태의 파서를 제작해서 반복문이 나오면 그 형태로 테이블 자체를 붙혀주는 형태로 구축하려했으나, Table 형태의 파서는 여러 형태의 디자인에 즉각적인 대응도 힘들고, 각 케이스별로 메소드도 늘어나는 부담이 너무 커서 쉬운 방법이 없을까 고민했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS SES Template 메일은 Handlebars라는 라이브러리 툴을 이용하고 있고, 기본적으로 제공하는 Built-in Helper중에는 For문과 if/else문과 같은 가장 기초적인 구문은 제공하고 있어, 이 형태를 이용하면 좋습니다.&lt;br /&gt;아래와 같은 형태의 반복문을 만들고, 다음처럼 템플릿 데이터를 구성하면 됩니다.&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;// 반목문 
{{#each subscription}} 
    &amp;lt;li&amp;gt;{{interest}}&amp;lt;/li&amp;gt; 
{{/each}}


// 템플릿 데이터 형태
&quot;subscription&quot;:[ { &quot;interest&quot;:&quot;Sports&quot; }, { &quot;interest&quot;:&quot;Travel&quot; }, { &quot;interest&quot;:&quot;Cooking&quot; } ]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 템플릿 데이터 형태는 제목안에 json 형태로 그 이름이 재대로 지정만되어있으면 잘 나옵니다.&lt;br /&gt;또한 중첩인 케이스도 잘 처리할 수 있습니다.&lt;/p&gt;
&lt;pre class=&quot;1c&quot;&gt;&lt;code&gt;//중첩문
&quot;Subscription Preferences for {{contact.firstName}} {{contact.lastName}}&quot;,

//템플릿 데이터 
&quot;contact&quot;:{ &quot;firstName&quot;:&quot;Anaya&quot;, &quot;lastName&quot;:&quot;Iyengar&quot;, &quot;city&quot;:&quot;Bengaluru&quot;, &quot;country&quot;:&quot;India&quot;, &quot;postalCode&quot;:&quot;560052&quot; },&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 이러한 형태는 Map형식 혹은 DTO 형식에서 JacksonMapper를 통해 String 형태로 변환만 해준다면 쉽게 이용이 가능해지므로 Template 형태를 잡을 때 반복, if/else에 대한 케이스를 처리해서 수월하게 메일 템플릿을 만들면 좋습니다..&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 국내 메일 엣지 케이스 대응&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무리 잘 디자인을 해도 메일에서 깨지는 경우가 종종 있습니다. 깨지는 경우 대응법이 명확하지 않아서 공유 드리면~~&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메일의 대부분 템플릿은 Table형태로 구성하는 게 좋습니다. 다른 컴포넌트를 통해 style 형태를 잡을 수 없기 때문입니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;div 태그를 사용하지 않습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;margin을 사용할 수 없습니다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단, 네이버는 예외입니다. 알 수 없는 이유로 align=&quot;center&quot;가 적용되지 않지만 margin: 0 auto; 는 적용되어 가운데 정렬을 위해 해당 코드를 넣어두기&lt;/li&gt;
&lt;li&gt;&lt;b&gt;마진이 먹지 않아 이경우 간혹 버튼이 왼쪽 정렬되는 경우가 왕왕 있었습니다&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;메일 코드는 table 태그의 rowSpan, colSpan 기능은 지원하지 않습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;중첩 테이블 형태로 이것을 한줄로 합치게 구현해야합니다. 아래 형태처럼
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!-- 중첩 테이블을 사용하세요 --&amp;gt;
&amp;lt;table border=&quot;0&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; style=&quot;width: 100%; border: 1px solid black; text-align: center; &quot;&amp;gt;
&amp;lt;tr style=&quot;border-bottom: 1px solid black;&quot;&amp;gt;
&amp;lt;td&amp;gt;
&amp;lt;!-- 중첩 테이블 --&amp;gt;
&amp;lt;table border=&quot;0&quot; cellpadding=&quot;0&quot; cellspacing=&quot;0&quot; style=&quot;width: 100%;&quot;&amp;gt;
  &amp;lt;tr style=&quot;width: 50%;&quot;&amp;gt;
    &amp;lt;td style=&quot;border-right: 1px solid black;&quot;&amp;gt;
      중첩
    &amp;lt;/td&amp;gt;
    &amp;lt;td style=&quot;width: 50%;&quot;&amp;gt;
      테이블
    &amp;lt;/td&amp;gt;
  &amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;tr&amp;gt;
&amp;lt;td&amp;gt;
여기는 상대적으로 colspan=&quot;2&quot;를 적용한 것처럼 보입니다.
&amp;lt;/td&amp;gt;
&amp;lt;/tr&amp;gt;
&amp;lt;/table&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;스타일링은 인라인 스타일링을 가지는게 좋습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;&amp;lt;td style=&quot;text-align: center; font-size: 14px;&quot;&amp;gt;&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;참고를 위한 글 첨부 드립니다.&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://blog.stibee.com/html-ec-83-81-ec-9e-90-eb-a5-bc-ec-82-ac-ec-9a-a9-ed-95-a0-eb-95-8c-ea-b8-b0-ec-96-b5-ed-95-a0-eb-8b-a4-ec-84-af-ea-b0-80-ec-a7-80-2/&quot;&gt;스티비&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://worker-k.tistory.com/60#comment18544883&quot;&gt;티스토리- 블로그&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 알아본 AWS SES 템플릿 메일 발송시 유의점입니다. 차후 SES를 통한 메일 발송시 문제가 있는 경우 한번 체크해보면 좋겠습니다.&lt;/p&gt;</description>
      <category>DevOps/AWS</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/176</guid>
      <comments>https://sundries-in-myidea.tistory.com/176#entry176comment</comments>
      <pubDate>Sun, 2 Mar 2025 21:58:46 +0900</pubDate>
    </item>
    <item>
      <title>실무에서 사용하기 좋은 Stream Collector 기능</title>
      <link>https://sundries-in-myidea.tistory.com/175</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;TL; DR&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stream에서 사용할수 있는 Collect 함수를 알아보자..&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;본론 ㄱ&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭘 할까 고민하다가 List처럼 Stream을 붙였을때 Map을 만든다던가 처럼 사용하기 유용한 Collectors 함수를 알아보는 시간을 가지겠다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본적으로 사용하게될 DTO&lt;/h3&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;class Dto {  
    private Long id;  
    private String name;  
    private Integer value;  

    public Dto(Long id, String name, Integer value) {  
        this.id = id;  
        this.name = name;  
        this.value = value;  
    }  

    public Long getId() {  
        return id;  
    }  

    public String getName() {  
        return name;  
    }  

    public Integer getValue() {  
        return value;  
    }  

    @Override  
    public String toString() {  
        return &quot;Dto{&quot; +  
                &quot;id=&quot; + id +  
                &quot;, name='&quot; + name + '\'' +  
                &quot;, value=&quot; + value +  
                '}';  
    }  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 객체가 있다고 가정해보자&lt;br /&gt;그리고 매번 만들기는 귀찮으니 Random으로 객체를 만들어주는 함수를 만들어보겠다&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;class RandomCollectors {  
    private static final List&amp;lt;String&amp;gt; names = List.of(&quot;서울&quot;, &quot;대구&quot;, &quot;부산&quot;, &quot;울산&quot;, &quot;춘천&quot;, &quot;인천&quot;, &quot;대전&quot;);  

    static List&amp;lt;Dto&amp;gt; makingRandomList(int size) {  
        List&amp;lt;Dto&amp;gt; dtos = new ArrayList&amp;lt;&amp;gt;();  

        for (long i = 0; i &amp;lt;size; i++) {  
            int idx = ThreadLocalRandom.current().nextInt(0, names.size());  
            dtos.add(new Dto(i, names.get(idx),  ThreadLocalRandom.current().nextInt(100)));  
        }        return dtos;  
    }  
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Collectors.toMap()&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 기능은 일단 기본적으로 Map객체를 만든다. 맵 객체를 만들어서 ID값과 같은 객체를 찾아 Mapping하고 싶을때 사용한다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Map&amp;lt;Long, Dto&amp;gt; collect = list.stream().collect(Collectors.toMap(Dto::getId, it -&amp;gt; it));
//Map의 value값을 DTO로 쓰고 싶다는건데.. it-&amp;gt;it은 뭔가 짜친다. (물론 무슨 뜻인지는 이해하겠지만...) 좀 더 명확하게 하고 싶다면?
Map&amp;lt;Long, Dto&amp;gt; collect = list.stream().collect(Collectors.toMap(Dto::getId, Functions.identity()));
//이렇게 Functions.identity를 사용하자&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 다음과 같이 나온다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Test  
@DisplayName(&quot;collectors.toMap을 잘써보자&quot;)  
void test() {  
    List&amp;lt;Dto&amp;gt; list = List.of(new Dto(1L ,&quot;이름&quot;, 1), new Dto(2L, &quot;이름2&quot;, 2));  
    Map&amp;lt;Long, Dto&amp;gt; collect = list.stream().collect(Collectors.toMap(Dto::getId, it -&amp;gt; it));  
    assertThat(collect.size(), is(2));  
    log.info(&quot;map get 1L : {}&quot;, collect.get(1L));  
}

//응답.
map get 1L : Dto{id=1, name='이름', value=1}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;언제 사용하지?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로는 DTO 생성시 &amp;rarr; &lt;b&gt;여러가지 값을 조회해서 넣어야하는 경우&lt;/b&gt; Index Key를 기반으로 찾아서 DTO에 조회해야하는 경우 편한다.&lt;br /&gt;물론 JPA 경우 JOIN Table을 통해서 Response자체에서 값을 다 묶어서 주는 방법도 있겠지만, 꼭 그런 값만 있는게 아니니까 ㅎㅎ; 외부 APi를 통해서 들어오는 경우도 왕왕 있었는데 그런 경우 좋았다&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Collectors.groupingBy&lt;/h3&gt;
&lt;pre class=&quot;xquery&quot;&gt;&lt;code&gt;@Test  
@DisplayName(&quot;collectors.grouping by을 잘써보자&quot;)  
void test1() {  
    Map&amp;lt;String, List&amp;lt;Dto&amp;gt;&amp;gt; group = RandomCollectors.makingRandomList(20).stream().collect(Collectors.groupingBy(Dto::getName));  
    log.info(&quot;map: {}&quot;, group);  
}

//이러면 다음처럼 grouping이 가능해진다.
map: {
울산=[Dto{id=1, name='울산', value=34}, Dto{id=3, name='울산', value=87}, Dto{id=5, name='울산', value=24}, Dto{id=18, name='울산', value=99}],

대전=[Dto{id=7, name='대전', value=36}, Dto{id=8, name='대전', value=16}, Dto{id=12, name='대전', value=78}, Dto{id=15, name='대전', value=40}],

서울=[Dto{id=6, name='서울', value=54}, Dto{id=19, name='서울', value=7}],

부산=[Dto{id=16, name='부산', value=73}],

대구=[Dto{id=11, name='대구', value=7}], 

인천=[Dto{id=2, name='인천', value=80}, Dto{id=4, name='인천', value=68}, Dto{id=9, name='인천', value=28}, Dto{id=10, name='인천', value=96}, Dto{id=13, name='인천', value=22}, Dto{id=14, name='인천', value=26}, Dto{id=17, name='인천', value=47}],

춘천=[Dto{id=0, name='춘천', value=31}]

}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;언제쓰지?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;복잡한 집계&lt;/b&gt;를 만들고 싶을때 괜찮다. SQL 만능으로 집계하느냐.. 아니면 Application 단으로 한칸 올려서 집계할지에 대한 트레이드 오프는 개발자가 고민해야하는 문제이지만, 해당하는 트레이드 오프를 기반으로 고민해보면 된다.&lt;br /&gt;혹은 &lt;b&gt;묶어서 필터링&lt;/b&gt;하고 싶을때도 유용했다. 필터링은 Stream의 필터를 써도 되지만, 해당하는 Grouping 한 값들중에 전부다를 필터링하지 않는 경우도 좋았다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. partitioningBy&lt;/h3&gt;
&lt;pre class=&quot;xquery&quot;&gt;&lt;code&gt;@Test  
@DisplayName(&quot;collectors.partitioningBy 을 잘써보자&quot;)  
void test3() {  
    Map&amp;lt;Boolean, List&amp;lt;Dto&amp;gt;&amp;gt; partitioningBy = RandomCollectors.makingRandomList(20).stream()  
            .collect(Collectors.partitioningBy(it -&amp;gt; it.getId() &amp;lt; 10));  
    assertThat(partitioningBy.get(true), hasSize(10));  
    log.info(&quot;map true: {}&quot;, partitioningBy.get(true));  
    log.info(&quot;map false: {}&quot;, partitioningBy.get(false));  
}

/// 결과
map true: [Dto{id=0, name='춘천', value=90}, Dto{id=1, name='대전', value=22}, Dto{id=2, name='대전', value=56}, Dto{id=3, name='대구', value=1}, Dto{id=4, name='울산', value=85}, Dto{id=5, name='울산', value=76}, Dto{id=6, name='인천', value=67}, Dto{id=7, name='울산', value=60}, Dto{id=8, name='울산', value=91}, Dto{id=9, name='대구', value=82}] //9까지만 있음...

map false: [Dto{id=10, name='울산', value=47}, Dto{id=11, name='부산', value=35}, Dto{id=12, name='울산', value=64}, Dto{id=13, name='서울', value=8}, Dto{id=14, name='춘천', value=82}, Dto{id=15, name='서울', value=38}, Dto{id=16, name='부산', value=14}, Dto{id=17, name='대전', value=49}, Dto{id=18, name='대전', value=39}, Dto{id=19, name='대전', value=63}] // 그외 나머지..&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;언제 쓰지?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GroupingBy보다는 &lt;b&gt;단순하게 조건&lt;/b&gt;을 가져갈때 유용하다 Api Response안에 Detail응답처럼, Response -detail이 있으면 true에 detaildl 없을때는 false로 넣어서 사용하는 식으로 처리해보면 꽤 유용하다. detail이 있으면, 해당하는 값을 true로 처리해서 만들면 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Stream을 사용할 일이 꽤 많은데 다음과 같은 예제로 좀 더 사용해 보면, Application의 여러 가지 정보를 조회해서 뭉칠 수 있다는 점이 매력적이다. 잘 사용하여 효율적인 서비스를 만들어 보자!&lt;/p&gt;</description>
      <category>Java</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/175</guid>
      <comments>https://sundries-in-myidea.tistory.com/175#entry175comment</comments>
      <pubDate>Sun, 16 Feb 2025 19:04:01 +0900</pubDate>
    </item>
    <item>
      <title>코드트리 사용해보면서 느꼈던 점들!</title>
      <link>https://sundries-in-myidea.tistory.com/174</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #1d1c1d; text-align: left;&quot;&gt;이 포스팅은 코드트리 x 글또 블로그 챌린지 2기를 통해 코드트리 체험권을 받아 작성한 후기입니다&lt;/span&gt;&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;&lt;span style=&quot;color: #1d1c1d; text-align: left;&quot;&gt;코딩테스트의 필요성&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #1d1c1d; text-align: left;&quot;&gt;간만에, 코딩테스트 준비를 해야할 필요성을 좀 느꼈다. 문제를 푸는 실력이 많이 죽어서, 문제를 푸는 실력을 다시 한번 살려보고자. 글또에서 관련한 체험권 나눔 행사에 참가했다. 아무래도 코드트리라는 플랫폼을 몰랐던 나로써는 매번 풀었던 백준이나 프로그래머스와 같은 사이트외에 다른 곳에서 도움이 될까라는 생각을 많이 했었다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;text-align: center;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;코드 트리 사이트에서 제공하는 형태&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;타 사이트와의 다른 점이라면, 개인적으로 Codetrails 라는 방식이 가장 특이했는데, 일종의 이 방식대로 따라해보고 열심히 해봅시다~ 하는 형태의 문제집형태의 커리큘럼이 개인적으로 마음에 제일 들었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-02 오후 1.56.38.png&quot; data-origin-width=&quot;2216&quot; data-origin-height=&quot;1008&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgsz4A/btsL4WDtQRa/4W2TPIKUsWfN6okqlfGZw1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgsz4A/btsL4WDtQRa/4W2TPIKUsWfN6okqlfGZw1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgsz4A/btsL4WDtQRa/4W2TPIKUsWfN6okqlfGZw1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcgsz4A%2FbtsL4WDtQRa%2F4W2TPIKUsWfN6okqlfGZw1%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;2216&quot; height=&quot;1008&quot; data-filename=&quot;스크린샷 2025-02-02 오후 1.56.38.png&quot; data-origin-width=&quot;2216&quot; data-origin-height=&quot;1008&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;ul style=&quot;list-style-type: disc; color: #333333; text-align: start;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;color: #000000;&quot;&gt;Novice Low (Trail 1) : 다양한 언어의 기초 문법을 학습할 수 있어요.&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot;&gt;Novice Mid (Trail 2) : 기초 구현력과 디버깅 능력을 얻어가실 수 있어요.&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot;&gt;Novice High (Trail 3) : 문제 해결에 사용되는 알고리즘과 자료구조들을 객관식 유형으로 학습할 수 있어요.&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Intermediate Low (Trail 4) : BFS, DFS, DP 등 간단한 알고리즘을 학습할 수 있어요.&lt;/b&gt;&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot;&gt;Intermediate Mid (Trail 5) : Two Pointer, 이분 탐색 등 시간 최적화에 사용되는 알고리즘과 고급 자료구조들을 학습할 수 있어요.&lt;/li&gt;
&lt;li style=&quot;color: #000000;&quot;&gt;Intermediate High (Trail 6) : Tree, 위상 정렬 등 심화 알고리즘에 대해 학습할 수 있어요.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런형태중.. Trail 4정도에서 내가 필요한 알고리즘을 학습할 수 있다. 아무래도, 내가 BFS나 DFS같은 부분들은 어려움을 겪지 않았지만, 이외의 알고리즘형태에서 꽤나 많은 시행착오를 겪는걸 알고 있어서, 해당하는 지점을 타겟으로 공부를 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론, 벽 같은 부분을 많이 느끼긴했지만, 시험장에서 꽤나많이 봤던 문제 유형들이 문제지로 되어있다는 것도 신기했다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코드트리의 장점&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 풀이 및 개념 설명&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드 트리의 강점을 굳이 요약하자면 &quot;&lt;b&gt;쎈 수학&lt;/b&gt;&quot; 같은 느낌이 든다는 점이다. 일단 같은 유형을 지속적으로 풀고, 이 유형의 문제중 어려울만한 문제들을 조금씩 난도를 높여가면서 풀리는 경향이 있다는 점이다. 특히, 가장 좋았던 지점은 개념 설명하는 부분이였다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-02 오후 2.05.03.png&quot; data-origin-width=&quot;2372&quot; data-origin-height=&quot;1940&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rFnKW/btsL3Q5o7di/6WeWKRqCSgKI3IwisOMvUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rFnKW/btsL3Q5o7di/6WeWKRqCSgKI3IwisOMvUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rFnKW/btsL3Q5o7di/6WeWKRqCSgKI3IwisOMvUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrFnKW%2FbtsL3Q5o7di%2F6WeWKRqCSgKI3IwisOMvUK%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;2372&quot; height=&quot;1940&quot; data-filename=&quot;스크린샷 2025-02-02 오후 2.05.03.png&quot; data-origin-width=&quot;2372&quot; data-origin-height=&quot;1940&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&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-filename=&quot;2025-02-02 14.10.00.gif&quot; data-origin-width=&quot;2352&quot; data-origin-height=&quot;1378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwEjOw/btsL26AWrCw/VOEIhSbpbwUBnI4YKr3ggk/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwEjOw/btsL26AWrCw/VOEIhSbpbwUBnI4YKr3ggk/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwEjOw/btsL26AWrCw/VOEIhSbpbwUBnI4YKr3ggk/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/bwEjOw/btsL26AWrCw/VOEIhSbpbwUBnI4YKr3ggk/img.gif&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;2352&quot; height=&quot;1378&quot; data-filename=&quot;2025-02-02 14.10.00.gif&quot; data-origin-width=&quot;2352&quot; data-origin-height=&quot;1378&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;다음으로 해설도 좋았다. 일단 다른 사이틀에서 공식 해설이랄게 존재하진 않았고, 구글링으로 문제를 확인해보는 방법을 많이 썼다. 하지만 코드트리는 해설을 자세하게 다뤄줘는것도 좋았는데, 아래처럼 세세한 해설을 작성해준점 도 좋았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2025-02-02 오후 2.18.18.png&quot; data-origin-width=&quot;2266&quot; data-origin-height=&quot;1906&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c03Nz4/btsL4XvAQtu/JCOEAwMpRVSB4O3Jp0DZEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c03Nz4/btsL4XvAQtu/JCOEAwMpRVSB4O3Jp0DZEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c03Nz4/btsL4XvAQtu/JCOEAwMpRVSB4O3Jp0DZEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc03Nz4%2FbtsL4XvAQtu%2FJCOEAwMpRVSB4O3Jp0DZEk%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;2266&quot; height=&quot;1906&quot; data-filename=&quot;스크린샷 2025-02-02 오후 2.18.18.png&quot; data-origin-width=&quot;2266&quot; data-origin-height=&quot;1906&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;TC 공유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;틀린 문제가 있는 경우 테스트 제출 답안이 노출되는 점도 좋았다. 체점 중간에 틀린 지점이 나오면 해당하는 테스트 TC input output이 나와서 해당하는 아웃풋을 보고 내가 이 문제를 어떤식으로 수정해야하겠다는 지점을 체크해볼 수 있던 것도 좋았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이게.. 어디 있긴했는데, 스크린샷을 찾지 못해서 글로만 남기는 점 양해 바란다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;깃허브 연동&lt;/h3&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-filename=&quot;edited_스크린샷 2025-02-02 오후 2.13.17.png&quot; data-origin-width=&quot;1548&quot; data-origin-height=&quot;1180&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VC6fZ/btsL4c743fc/8ATKW7kVWHBVYjUim9KJV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VC6fZ/btsL4c743fc/8ATKW7kVWHBVYjUim9KJV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VC6fZ/btsL4c743fc/8ATKW7kVWHBVYjUim9KJV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVC6fZ%2FbtsL4c743fc%2F8ATKW7kVWHBVYjUim9KJV0%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;1548&quot; height=&quot;1180&quot; data-filename=&quot;edited_스크린샷 2025-02-02 오후 2.13.17.png&quot; data-origin-width=&quot;1548&quot; data-origin-height=&quot;1180&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깃허브 연동은 특히 계정연동을 통해서 내 코드를 직접 깃허브에 올려주는 것이 꽤 편리했다. 물론, 뭐... 인증을 하기위해서 공부를 하는 건 아니다만, 내가 문제 풀었던 문제들과 함께 정리되어서 깃허브에 올라감으로써, 마치 1일 1커밋 하는 느낌처럼 열심히 공부해나가는 느낌이 들었던 것도 사실이다.&lt;/p&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-filename=&quot;스크린샷 2025-02-02 오후 2.17.43.png&quot; data-origin-width=&quot;1040&quot; data-origin-height=&quot;572&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bx6OIe/btsL4L3fFBs/RsarcqjxKgu4KbB4wioCf0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bx6OIe/btsL4L3fFBs/RsarcqjxKgu4KbB4wioCf0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bx6OIe/btsL4L3fFBs/RsarcqjxKgu4KbB4wioCf0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbx6OIe%2FbtsL4L3fFBs%2FRsarcqjxKgu4KbB4wioCf0%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;1040&quot; height=&quot;572&quot; data-filename=&quot;스크린샷 2025-02-02 오후 2.17.43.png&quot; data-origin-width=&quot;1040&quot; data-origin-height=&quot;572&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;마치면서..&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;</description>
      <category>알고리즘</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/174</guid>
      <comments>https://sundries-in-myidea.tistory.com/174#entry174comment</comments>
      <pubDate>Sun, 2 Feb 2025 14:30:55 +0900</pubDate>
    </item>
    <item>
      <title>Spring Boot + FireBase Auth 기능을 활용한 간편 회원체계를 만들기</title>
      <link>https://sundries-in-myidea.tistory.com/173</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;TL; DR&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회원 체계는 초기 사이드 프로젝트에서 은근한 발목을 잡는다. 예상보다 회원 체계를 만드는 공이 크지만, 그렇다고 안 할 수도 없고 애매한 상황이다. 필요한 부분은 FireBase로 넘기고 간단한 부분들만 받아서 쓰면 되지 않을까?라는 스스로에 생각에 기반한 프로젝트&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;계기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 초창기 사이드 프로젝트에서 회원 체계를 현재 DB에서 관리하는 것은 은근히 귀찮은 작업이다. 예를 들면 패스워드 암복호화 같은 작업이나, 소셜 로그인을 붙이는 경우처럼 회원 인증 체계는 우리 서비스로 가져오는 순간부터 이상하게 잡무가 많아진다.&lt;br /&gt;그리고 사이드 프로젝트의 성패를 가르는 지점은 결국 해당 도메인의 성격을 얼마나 잘 녹이면서도, 빠르게 프로젝트를 완료하는가에 달려있다 본다. 예상보다 사이드 프로젝트는 공수가 늘고, 투자할 수 있는 시간이 많아지면 많아질수록 탠션이 엄청 죽기때문에 절대적으로 실제 들어가는 시간이 중요하다고 생각한다.&lt;br /&gt;즉, 이런 공수만 줄여도 비즈니스 도메인적인 로직에 상대적으로 집중할 수 있는데, 그런 공수를 줄여줄 수 있는 서드파티용 로그인 체계를 사용하는 것이 꽤나 도움이 될거라고 생각했다.&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;1144&quot; data-origin-height=&quot;475&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LlbGC/btsLSsps7zQ/vgxdiMAHaE6Qf63qFPX5gk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LlbGC/btsLSsps7zQ/vgxdiMAHaE6Qf63qFPX5gk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LlbGC/btsLSsps7zQ/vgxdiMAHaE6Qf63qFPX5gk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLlbGC%2FbtsLSsps7zQ%2FvgxdiMAHaE6Qf63qFPX5gk%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;1144&quot; height=&quot;475&quot; data-origin-width=&quot;1144&quot; data-origin-height=&quot;475&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엑세스토큰, 리프레쉬 토큰 생성, 세션 체크 &amp;rarr; Firebase&lt;/li&gt;
&lt;li&gt;이관 받은 세션의 체크, 비즈니스 로직 &amp;rarr; Spring Boot&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;화면을 보면?&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1150&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dt8q3j/btsLS7SLwJA/BkqGC8DOd9NpOtDQkXCe9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dt8q3j/btsLS7SLwJA/BkqGC8DOd9NpOtDQkXCe9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dt8q3j/btsLS7SLwJA/BkqGC8DOd9NpOtDQkXCe9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdt8q3j%2FbtsLS7SLwJA%2FBkqGC8DOd9NpOtDQkXCe9K%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;1150&quot; height=&quot;286&quot; data-origin-width=&quot;1150&quot; data-origin-height=&quot;286&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 로그인 하려는 페이지가 뜨고 &amp;rarr; 자체적으로 회원인지 여부 검증, 회원이면 로그인 / 회원이 아닌경우 회원가입 로직을 태워 해당하는 기능을 간단하게 빨리 구현할 수 있다.&lt;br /&gt;물론, 현재는 FE보다는 BE 로직을 더 중시했으므로, 간단한 UI 툴로서 처리했다. 그래서 아래 로직은 단순하게 이 UI Framework에 올바르게 작성된 코드일뿐 FE가 새로 짜여하는 경우라면, 그런 형태로 구축하면된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코드로 알아보자&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 해당 경우는 SPA 형태의 FE BE 분리 형태일때 좀 더 유리한 로직이였다. 일단 기본적으로 BE 로직은 세션 체크 이외에 값은 처리하지 않는 형태로 구현하는 식으로 처리했다.&lt;br /&gt;IDP는 firebase가 되기때문에 해당하는 엑세스 로직은 다음처럼 처리한다. 간단한 테스트를 위한 코드이므로 html과 script로만 작성했다.&lt;br /&gt;React면 React스럽게, Vue면 Vue스럽게 작성하면된다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;html lang=&quot;ko&quot;&amp;gt;  
&amp;lt;head&amp;gt;  
    &amp;lt;meta charset=&quot;UTF-8&quot;&amp;gt;  
    &amp;lt;title&amp;gt;Sample FirebaseUI App&amp;lt;/title&amp;gt;  
    &amp;lt;script src=&quot;https://www.gstatic.com/firebasejs/10.0.0/firebase-app-compat.js&quot;&amp;gt;&amp;lt;/script&amp;gt;  
    &amp;lt;script src=&quot;https://www.gstatic.com/firebasejs/10.0.0/firebase-auth-compat.js&quot;&amp;gt;&amp;lt;/script&amp;gt;  
    &amp;lt;script&amp;gt;        
    //관련 키들은 여기서 관리하겠지만? 실제로 PRD 환경에서는 이렇게 사용하진 않을테니...
    const firebaseConfig = {  
          apiKey: &quot;-&quot;,  
          authDomain: &quot;-&quot;,  
          projectId: &quot;-&quot;,  
          storageBucket: &quot;-&quot;,  
          messagingSenderId: &quot;-&quot;,  
          appId: &quot;-&quot;  
        };  

        // Initialize Firebase  
        firebase.initializeApp(firebaseConfig); //순서는 앱 초기화를 먼저하고 아래 로직을 돌아가야한다.
    &amp;lt;/script&amp;gt;  
    &amp;lt;script src=&quot;https://www.gstatic.com/firebasejs/ui/6.1.0/firebase-ui-auth.js&quot;&amp;gt;&amp;lt;/script&amp;gt;  
    &amp;lt;link type=&quot;text/css&quot; rel=&quot;stylesheet&quot; href=&quot;https://www.gstatic.com/firebasejs/ui/6.1.0/firebase-ui-auth.css&quot;/&amp;gt;  
    &amp;lt;script type=&quot;text/javascript&quot;&amp;gt; 
        var uiConfig = {  
            callbacks: {  
              signInSuccessWithAuthResult: function(authResult, redirectUrl) {  
                var user = authResult.user;  
                var credential = authResult.credential;  
                var isNewUser = authResult.additionalUserInfo.isNewUser;  
                var providerId = authResult.additionalUserInfo.providerId;  
                var operationType = authResult.operationType;  
                // 실제 토큰은 user._delegate에 존재하니, 꺼내쓰면 된다.
                const token = user._delegate.accessToken  
                document.cookie = `token=${token}; path=/; secure;  SameSite=Strict`;  
                // 리다이렉트 URL로 방지했다. 이경우는 FE 이동하는 경우 사용하면 된다
                return false;  
           }  
          },  
          // 그리고 
          signInSuccessUrl: 'http://localhost:8080/test',  
          signInOptions: [  
            {  
              provider: firebase.auth.EmailAuthProvider.PROVIDER_ID,  
              forceSameDevice: false,  
            },  
            firebase.auth.PhoneAuthProvider.PROVIDER_ID,  
            firebaseui.auth.AnonymousAuthProvider.PROVIDER_ID  
          ],  
          // Terms of service url/callback.  
          tosUrl: '&amp;lt;your-tos-url&amp;gt;',  
          // Privacy policy url/callback.  
          privacyPolicyUrl: function() {  
            window.location.assign('&amp;lt;your-privacy-policy-url&amp;gt;');  
          }  
        }
        var ui = new firebaseui.auth.AuthUI(firebase.auth());  
        ui.start('#firebaseui-auth-container', uiConfig);  
    &amp;lt;/script&amp;gt;  
&amp;lt;/head&amp;gt;  
&amp;lt;body&amp;gt;  
    &amp;lt;h1&amp;gt;Welcome to My Awesome App&amp;lt;/h1&amp;gt;  
    &amp;lt;div id=&quot;firebaseui-auth-container&quot;&amp;gt;&amp;lt;/div&amp;gt;  
&amp;lt;/body&amp;gt;  
&amp;lt;/html&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;firebase-ui 꿀팁&lt;br /&gt;일단 firebase 등록 먼저하고 토큰 생성에 대한 로직을 작성해야한다.&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;단, 이 로직은 React, Vue의 경우 다른 로직으로 구성해야 할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 해당하는 로그인이나 회원가입이 완료되면, 해당하는 사이트에서 토큰을 쿠키로 브라우저에 등록해서 처리하고 백엔드 로직은 오로지 그 토큰이 유효한지만 판단한다.&lt;br /&gt;물론, Refresh Token을 재처리하는 기능을 백에서 처리해도 되며, 401오류 발생시 FE에서 처리하는 방법도 존재한다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Configuration  
public class FirebaseConfig {  

    @Value(&quot;classpath:data/firebase.json&quot;)  
    private Resource firebase;  

    @Bean  
    public FirebaseApp firebaseApp() throws IOException {  
        InputStream credentials = new ByteArrayInputStream(firebase.getContentAsByteArray());  
        FirebaseOptions firebaseOptions = FirebaseOptions.builder()  
                .setCredentials(GoogleCredentials.fromStream(credentials))  
                .build();  
        return FirebaseApp.initializeApp(firebaseOptions);  
    }  

    @Bean  
    public FirebaseAuth firebaseAuth(FirebaseApp firebaseApp) {  
        return FirebaseAuth.getInstance(firebaseApp);  
    }  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 Spring Boot Code의 Security 로직은 기존에 많이 봐왔던 JWT filter와 거의 비슷하다. 왜냐하면 Firebase 토큰이 JWT 토큰형식이기 때문이다. 그래서 기존 로직에 JWT이 올바른지에 대한 여부는 Firebase에서 판단한다. 그렇기에 JWT filter보다는 좀 더 간략화 되어있다.&lt;/p&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;@Component  
@RequiredArgsConstructor  
public class TokenAuthenticationFilter extends OncePerRequestFilter {  

    private static final String BEARER_PREFIX = &quot;Bearer &quot;;  
    private static final String USER_ID_CLAIM = &quot;user_id&quot;;  
    private static final String AUTHORIZATION_HEADER = &quot;Authorization&quot;;  

    private final FirebaseAuth firebaseAuth;  
    private final ObjectMapper objectMapper;  

    @Override  
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,  
                                    FilterChain filterChain) throws IOException, ServletException {  
        String authorizationHeader = request.getHeader(AUTHORIZATION_HEADER);  

        if (authorizationHeader != null &amp;amp;&amp;amp; authorizationHeader.startsWith(BEARER_PREFIX)) {  
            String token = authorizationHeader.replace(BEARER_PREFIX, &quot;&quot;);  
            Optional&amp;lt;String&amp;gt; userId = extractUserIdFromToken(token);  

            if (userId.isPresent()) {  
                var authentication = new UsernamePasswordAuthenticationToken(userId.get(), null, null);  
                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));  
                SecurityContextHolder.getContext().setAuthentication(authentication);  
            } else {  
                setAuthErrorDetails(response);  
                return;  
            }  
        }  
        filterChain.doFilter(request, response);  
    }  

    private Optional&amp;lt;String&amp;gt; extractUserIdFromToken(String token) {  
        try {  
            FirebaseToken firebaseToken = firebaseAuth.verifyIdToken(token, true);  
            String userId = String.valueOf(firebaseToken.getClaims().get(USER_ID_CLAIM));  
            return Optional.of(userId);  
        } catch (FirebaseAuthException exception) {  
            return Optional.empty();  
        }    }  
    private void setAuthErrorDetails(HttpServletResponse response) throws IOException {  
        HttpStatus unauthorized = HttpStatus.UNAUTHORIZED;  
        response.setStatus(unauthorized.value());  
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);  
        ProblemDetail problemDetail = ProblemDetail.forStatusAndDetail(unauthorized,  
          &quot;Authentication failure: Token missing, invalid or expired&quot;);  
        response.getWriter().write(objectMapper.writeValueAsString(problemDetail));  
    }  

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 로직은 다음처럼 표현할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Bearer Token이 유효한가?&lt;/li&gt;
&lt;li&gt;FireBase에 해당 토큰이 유효한 토큰인지 검사&lt;/li&gt;
&lt;li&gt;세션 처리&lt;br /&gt;또한 credentials 처럼 구체적인 값은 유효성 검증이 아닌 그 토큰을 통한 회원 정보를 구축하는 방법도 존재한다. 그건 구현하는 개발자 입맛에 맞춰 처리하면 된다.&lt;br /&gt;회원 정보를 직접 세션에 담아서 처리할 수도 있다. 개인정보중 필요한 값들은 별도로 세션에 저장할 수도 있기 때문이다.&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;구축하면서 &lt;b&gt;Spring Boot 로직이 엄청나게 간단해진다는 점은 꽤나 좋았다&lt;/b&gt;. 왜냐하면 JWT를 사용하되, 모든 로직이 Firebase로 빨려들어가면서 실제로 세션 체크하는 부분은 꼬작&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;            FirebaseToken firebaseToken = firebaseAuth.verifyIdToken(token, true);  
            String userId = String.valueOf(firebaseToken.getClaims().get(USER_ID_CLAIM));  
            return Optional.of(userId);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이정도기 때문이다. 인증과 관련한 부분을 FIrebase가 모든 로직을 가져가면서 내가 해야할 일이 많이 준다는 점이 몹시 편했다.&lt;br /&gt;그래서 회원로직을 단순화할 수 있다는 점은 장점이라고 느껴졌다. &lt;b&gt;이메일 SMS와 같은 기능도 모두 Firebase에서 제공&lt;/b&gt;하기 때문에 굳이 우리가 들고 있을 필요도 없으며, 회원관련한 정보도 Firebase가 들고 있으므로 사실 문제가 될만한 요소들이 적긴하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, &lt;b&gt;상세 회원 정보들을 가져가는것은 어려워보이긴했다.&lt;/b&gt; 즉, 상세한 회원의 정보들을 가져가려면 Join Table을 해야한다는점? (나이, 지역, 성별등..) 은 좀 귀찮을 수 있다.&lt;br /&gt;또한 가격도 월간 5만명까지만 무료인데, 사이드 프로젝트가 월간 5만을 넘으면 사실 사업적 성공을 했다고 봐도 되므로, 회원체계의 다변화를 택해보자. 꼭 FireBase가 아니여야하지 않는가에 대한 고민을 좀 해보고 판단해보는 게 프로젝트 성장과 꽤나 밀접한 고민이 될거라고 본다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2202&quot; data-origin-height=&quot;810&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGKypH/btsLRhvJGLj/PWflZh3NM0uUXLgR6c1qX1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGKypH/btsLRhvJGLj/PWflZh3NM0uUXLgR6c1qX1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGKypH/btsLRhvJGLj/PWflZh3NM0uUXLgR6c1qX1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGKypH%2FbtsLRhvJGLj%2FPWflZh3NM0uUXLgR6c1qX1%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;2202&quot; height=&quot;810&quot; data-origin-width=&quot;2202&quot; data-origin-height=&quot;810&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;총평을 내보자면, 개인적으로 2달~3달안에 프로젝트 완수하고 매주 6시간 정도의 분량의 공수를 들여야하는 상황이라면 &lt;b&gt;난 이 방법이 맞다고 생각한다&lt;/b&gt;. 너무 편리하고 FE와의 공수도 대부분 FE &amp;amp; Client로 인증 로직이 들어가 BE 공수는 세션 확인 절차만 거치면 되는 점 역시 좋았다.&lt;br /&gt;물론, 확장성 및 커스텀화라는 측면에서는 부족한 점이 Firebase에는 존재했다. 한국에서 사용할 소셜 로그인을 포함하기 위해서는 좀 더 다양한 옵션이 존재해야하지만, 그렇지 않다. 이것은 비롯 Firebase이기 때문이고, 이러한 인증 대행을 하는 SaaS서비스는 꽤 많아졌다.&lt;br /&gt;즉, 그런 해당 서비스를 사용하여 가격 대비 실익을 따져서 개발하면 된다고 생각한다. 그렇기에 나는 사이드 프로젝트를 개발하게된다면 무조건 사용할거 같다.&lt;/p&gt;</description>
      <category>Spring</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/173</guid>
      <comments>https://sundries-in-myidea.tistory.com/173#entry173comment</comments>
      <pubDate>Sun, 19 Jan 2025 20:33:41 +0900</pubDate>
    </item>
    <item>
      <title>2024 회고 - 고생은 좀 했지만, 행복함이 있는 한 해</title>
      <link>https://sundries-in-myidea.tistory.com/172</link>
      <description>&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;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgc1Wv/btsLt3WXcuw/0obzNreGN2B1k1h7dInPY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgc1Wv/btsLt3WXcuw/0obzNreGN2B1k1h7dInPY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgc1Wv/btsLt3WXcuw/0obzNreGN2B1k1h7dInPY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdgc1Wv%2FbtsLt3WXcuw%2F0obzNreGN2B1k1h7dInPY0%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;720&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해도 벌써 마무리가 다 되어버렸네요. 정말 많은 일이 있었는데 다 말하기 힘들정도로 여러가지 일들이 있었어요. 막상 사진으로 찍어둔 것을 보니까 사실상 회사에서의 일보다는 개인적인 여러가지 일들이 많았던거 같은데, 그래도 회고 한번 쭉 적어보면서 다양한 일에 대해서 생각해보는걸로 해보려구요. 올해는 회사에서의 성장이 스스로가 느끼기에도 좀 더 좋은 성장이 있었던거 같았어요. 그리고 개인적으로도 많은 시도와 여러가지 일들이 많았기 때문에, 성장했다고 생각해요. 사실 회사보다는 개인적인 측면에서의 성장이 스스로가 생각하기엔 인상 깊어요. 나중에 풀겠지만...!&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;h3 data-ke-size=&quot;size23&quot;&gt;격변의 시기&lt;/h3&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;예를 들자면, &quot;통합 회원을 어떤식으로 각 버티컬 서비스에서 받아들여야하는거지?&quot; 라던가, &quot;정책이 엄청 꼬여있는데 얼만큼 우리가 수용할수 있고, 그로 인해서 놓치는 지점이 얼마나 있을까?&quot;처럼 복잡한 이데올로기가 좀 섞였던거 같아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론, 쉽지는 않았지만, 스스로에게 큰 성장을 가져다주긴 했거든요. 특히나 &lt;b&gt;나 답게 일하기에 대해서 한번 다시 고민해보게 된 지점&lt;/b&gt;이었어요. 왜냐하면 &lt;b&gt;누군가에게 잘 보이고 싶은 일 하기&lt;/b&gt; 그러니까 이렇게 하면 니가 좋아하겠지? 에 대한 지점으로 개발을 했던거 같기도했거든요. 복잡한 문제일 수록 답은 없는데 자꾸만 답을 찾으려 하다보니까 스스로가 너무 힘들었죠. 그래서 나답게 일하기에 대해서 좀 고민해봤는데. 스스로가 잘 하는게 뭘까 생각해보니까 제 스스로에게는 &lt;b&gt;도식화라는 방법&lt;/b&gt;을 쓰기로 했어요&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;br /&gt;특히 통합 회원의 경우는 특별한 화면이 없었던것도 컸으며, 해당하는 화면이 &lt;b&gt;굉장히 간소화 추상화&lt;/b&gt;되어있는 점도 문제였어요. BE 개발자로써 내가 이 화면에 뭘뿌려줘야하며, 어떤 정보를 들고 있어서 그 정보를 어떤식으로 전달해야하는지에 대한 고민을 하기가 굉장히 힘들었죠.&lt;br /&gt;그런 정보를 스스로가 이해하기가 너무 힘들어서 이 화면에서는 이렇게 넘어가야하고.. 이렇게 잘 지나가야하고... 이런 화면이 떴을때는 이렇게 처리되야지?를 그림으로 그려보니까 스스로가 이해하기 편하더라구요. 그리고 화면에서 화면으로 넘어가는 부분을 API화 하니까 스스로가 너무 편리함을 느끼기도 했구요. 그래서 내가 잘 일하는 방법에 대해 다시 고민하게 됐어요.. 그리고 누군가와 소통하기 위해서 내가 표현할 수 있는 방법이라고 느껴서 해당하는 방식으로 일해보려고 좀 마음을 먹었어요.&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://minieetea.com/20241021-initiative-activeness/&quot;&gt;https://minieetea.com/20241021-initiative-activeness/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1734874673708&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;적극적이기만 한 사람&quot; data-og-description=&quot;엄청 적극적인 사람과 함께하지만 뭔가 잘 안 되는 느낌, 조용하고 소극적인 것 같은 사람인데 뭔가 잘 되는 느낌을 받았던 경험이 있나요? 저도 그런 것들을 느낄 때마다 '이게 뭔지 모르겠는데&quot; data-og-host=&quot;minieetea.com&quot; data-og-source-url=&quot;https://minieetea.com/20241021-initiative-activeness/&quot; data-og-url=&quot;https://minieetea.com/20241021-initiative-activeness/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/CYuwp/hyXSsC7253/chSi0LnoNvVH7wB84v1u90/img.jpg?width=2000&amp;amp;height=1333&amp;amp;face=1468_334_1539_412,https://scrap.kakaocdn.net/dn/do00k1/hyXStIMYbL/YKYnw1C12PihySikb9Gwk0/img.jpg?width=2000&amp;amp;height=1333&amp;amp;face=1468_334_1539_412,https://scrap.kakaocdn.net/dn/bBIsHA/hyXOd8HCJW/KdTODleJlhcZqYfViZgQQ0/img.png?width=2000&amp;amp;height=1133&amp;amp;face=0_0_2000_1133&quot;&gt;&lt;a href=&quot;https://minieetea.com/20241021-initiative-activeness/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://minieetea.com/20241021-initiative-activeness/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/CYuwp/hyXSsC7253/chSi0LnoNvVH7wB84v1u90/img.jpg?width=2000&amp;amp;height=1333&amp;amp;face=1468_334_1539_412,https://scrap.kakaocdn.net/dn/do00k1/hyXStIMYbL/YKYnw1C12PihySikb9Gwk0/img.jpg?width=2000&amp;amp;height=1333&amp;amp;face=1468_334_1539_412,https://scrap.kakaocdn.net/dn/bBIsHA/hyXOd8HCJW/KdTODleJlhcZqYfViZgQQ0/img.png?width=2000&amp;amp;height=1133&amp;amp;face=0_0_2000_1133');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;적극적이기만 한 사람&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;엄청 적극적인 사람과 함께하지만 뭔가 잘 안 되는 느낌, 조용하고 소극적인 것 같은 사람인데 뭔가 잘 되는 느낌을 받았던 경험이 있나요? 저도 그런 것들을 느낄 때마다 '이게 뭔지 모르겠는데&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;minieetea.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 글을 참고하면, 수동인의 특징에 대해서 이야기해줘요.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;같은 수동인에겐 절대 묻지 않는다&lt;/li&gt;
&lt;li&gt;언제나 확인받고 싶어하는 습성이 있는 수동인들&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이런 수동인에 대한 특징이 스스로의 내 모습과 너무 겹치는 지점이 많이 나와서.. 얼레? 이거 완전 나잖아?! 라는 생각이 들었어요. 그럼 이런 수동인인 내가 어떤식으로 일을 해야지를 고민해본 지점도 있었죠. 스스로가 누군가에게 책임을 떠 넘기고 싶었구나라는 생각이 들었어요. 이게 스스로에 대한 결정 의식이 부족해서 그런거라는 생각이 들었고, 그런 결정을 하면 내가 그것에 대해서 책임을 져야하는 상황을 버티고 싶지 않아서 그럴 수도 있다는 생각도 들었어요.&lt;br /&gt;최근에는 핑거 프린세스 즉, &lt;b&gt;핑프짓도 많이 하는 거 같아서 스스로가 반성하게 되었는데요.&lt;/b&gt; 수동인과 비슷한 맥락 아닐까 싶어요. 잘 모르는 도메인이다 보니까 빠르게 끝내야한다는 압박때문에 남에게 자꾸 물어보게 되고, 그러면 자꾸 내가 생각하고 판단하지 않고, 남이 맞다.. 남이 옳다라는 일에 너무 자주 물어보는거 같아서 많이 미안해졌어요. 하지만 &lt;b&gt;이게 더 효율적이라고 생각하니까&lt;/b&gt; 남을 괴롭히는 상황(물어보니까 당연히 도와주려다보니까 자신의 업무가 밀리는 상황)이 많이 나오고 있어, &lt;b&gt;자제해야겠어요.&lt;/b&gt;&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;br /&gt;스스로가 아쉬운 지점은 &quot;세상은 변한다는 점&quot;이었어요. 왜냐하면 그런 컨퍼런스를 통해서 이전에 내가 변화해보고자 했던 상황을 인식할 수 있었거든요. 다른 회사들은 이런걸 쓰니까... 우리 회사도 이런걸 쓰면 좋겠지? 라는 생각을 하게되는 지점들이이요. 그리고 이런 지점을 통해서 더 적극적으로 회사에 기여하는 법도 좀 깨닿게 된거 같아요. 그래서 적극적으로 새로운 기술을 도입하고 해당하는 서비스를 분석하고 우리 서비스에 대해서 얼마나 개선할 수 있는지에 대해서 생각했어요.&lt;br /&gt;다음해에는 적극적으로 컨퍼런스를 많이 다닐거에요. 그만큼 스스로가 성장하고 다른 회사에서 가져온 기술적인 성취를 우리회사에 적용해보는 작업을 해볼거 같아요.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이런 저런 도전&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 부분들은 좀 추상적으로 표현하겠지만. 시장에 제가 어느정도 가치가 있는 사람인지를 확인 받고 싶었어요. 이유가 여러가지가 있겠지만, 어쨌든 성장하고 싶었거든요. 그런데 일단 스스로가 이런 저런것에 확신을 가지지 못한 것도 있고, 기술적인 부분에서 기초가 많이 부족해 보이기도 했어요. 깊게 파고 들어갔을때 내가 시원하게 대답할 수 있는 것들이 없었거든요. 이런 부분들이 부족하구나 다음번에는 채워서 말해야지 하는 것들을 다시 생각해보려구요. 내년에는 K8S 자격증 공부랑 기초 CS 공부를 좀 더 다지는 작업을 해보는 것도 스스로에게 도움이 될 거 같네요.&lt;/p&gt;</description>
      <category>잡담/회고</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/172</guid>
      <comments>https://sundries-in-myidea.tistory.com/172#entry172comment</comments>
      <pubDate>Sun, 22 Dec 2024 23:09:17 +0900</pubDate>
    </item>
    <item>
      <title>Custom Deserializer를 Json Module방식으로 등록해보자</title>
      <link>https://sundries-in-myidea.tistory.com/171</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;TL; DR&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등록기라서 사실 요약할 내용은 없다. D2 추천 방식으로 해당하는 Enum Type을 좀 더 쉽게 Deserializing하는 방식을 체크해보고 싶었다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;도입 이유&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Enum Type의 사용 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 개발하다보면, 100% 확률로 Enum Type을 사용할 수 밖에 없기때문에 해당하는 타입을 Enum화 해서 관리하는게 좋다. 실제로 실무에서는 DB에 저장되는 형태랑, 실제로 Json 타입으로 주는 형태랑 다를 경우가 많다. 즉, 해당하는 걸 매번 Enum의 키값으로 지정하기에 부담스러울때가 종종 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 Enum 타입을 만들고, 해당하지 않은 타입으로 전달하게 되면 대략 이런 오류가 발생하기 마련이다.&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;import lombok.Getter;  
import lombok.RequiredArgsConstructor;  
import lombok.ToString;  

@ToString  
@RequiredArgsConstructor  
@Getter  
public enum BerryType {  

    CANDY(&quot;c01&quot;, &quot;캔디 타입&quot;),  
    PILL(&quot;c02&quot;, &quot;알약 타입&quot;),  
    SWEETY(&quot;c03&quot;, &quot;달달한 타입&quot;);  

    private final String code;  
    private final String description;  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type ENUM from String &quot;c01&quot;: not one of the values accepted for Enum class: [SWEETY, CANDY, PILL]]&lt;/code&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Enum 타입의 Type String만 사용시 문제점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 이제 전달은 Code로 전달될수 있기 때문에 해당하는 Deserialize를 해주려면... 어노테이션을 포함해서 처리하는 방법도 있다, 하지만, 이러한 방식은 결국 놓치지는 지점이 생길 수 밖에 없다. 결과적으로 팀내 공유를 아무리 잘한다고 해도 실제로 사내 개발자가 그게 있는지를 모르는 경우가 있기 때문이다.&lt;br /&gt;각 개발자마다의 성향이 있고, 그 스타일대로 움직이기 때문에 해당하는 코드스타일을 완전하게 따라할 수 없을 수도 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면, 이런방식을 탈피하기 위해서는 어떤게 좋을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단한 Sample Type Enum을 만들어서 구성해보았다. 그 예제를 보자&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;import lombok.Getter;  
import lombok.RequiredArgsConstructor;  
import lombok.ToString;  

@ToString  
@RequiredArgsConstructor  
@Getter  
public enum BerryType {  

    CANDY(&quot;c01&quot;, &quot;캔디 타입&quot;),  
    PILL(&quot;c02&quot;, &quot;알약 타입&quot;),  
    SWEETY(&quot;c03&quot;, &quot;달달한 타입&quot;);  

    private final String code;  
    private final String description;  


    public static BerryType by(String code) {  
        for (BerryType value : BerryType.values()) {  
            if (value.getCode().equalsIgnoreCase(code)) {  
                return value;  
            }  
        }  
        return null;  
    }  
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Module을 써보자&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모듈을 Bean으로만 등록해서 사용하면, SpringBoot의 자동 옵션 설정중 Jackson관련 옵션인 &lt;code&gt;JacksonAutoConfiguration&lt;/code&gt;를 활용하여, 해당하는 ObjectMapper를 자동으로 등록하는 방식으로 사용한다. 대신 CustomSerializer가 등록되는 방식을 Module로 등록하면 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 Module을 등록하는 방법은 매우 쉽다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 모듈을 구성하고 Bean으로 등록하기&lt;/h3&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Bean  
public Module berryTypeModule() {  
    return new Module() {  
        @Override  
        public String getModuleName() {  
            return &quot;berryTypeModule&quot;;  
        }  

        @Override        public Version version() {  
            return Version.unknownVersion();  
        }  

        @Override        public void setupModule(SetupContext setupContext) {  
            log.info(&quot;setUp modules!!!&quot;);  
            setupContext.addDeserializers(new Deserializers.Base() {  
                final Map&amp;lt;ClassKey, JsonDeserializer&amp;lt;?&amp;gt;&amp;gt; cache = new ConcurrentHashMap&amp;lt;&amp;gt;();  

                @Override  
                public JsonDeserializer&amp;lt;?&amp;gt; findEnumDeserializer(Class&amp;lt;?&amp;gt; type, DeserializationConfig config, BeanDescription beanDesc) throws JsonMappingException {  
                    if (BerryType.class.isAssignableFrom(type)) {  
                        JsonDeserializer&amp;lt;?&amp;gt; deserializer = new BerryTypeDeserializer();  
                        addDeserializer(type, deserializer);  
                        return deserializer;
                    }  
                    return null;  
                }  

                @Override  
                public boolean hasDeserializerFor(DeserializationConfig config, Class&amp;lt;?&amp;gt; valueType) {  
                    return cache.containsKey(new ClassKey(valueType));  
                }  


                public void addDeserializer(Class&amp;lt;?&amp;gt; forClass, JsonDeserializer&amp;lt;?&amp;gt; deserializer) {  
                    ClassKey key = new ClassKey(forClass);  
                    cache.put(key, deserializer);  
                }  
              });  
        }  
    };  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드를 요약하면 대략 다음과 같다. 해당하는 Jackson의 모듈의 Serializer를 찾는 모듈을 통해 Enum Deserializer를 찾게된다.&lt;br /&gt;그래서 해당하는 타입과 같은 타입이 있는 경우 &amp;rarr; 해당 타입을 등록해서 사용해줄 수 있는데, Naver D2 예제의 경우는 해당하는 코드를 Cache와 같은 Map을 활용해서 해당하는 Deserializer가 중복 등록되는 경우를 방지하고, 처리할 수 있게 했다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. DeSerializer를 등록하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;순서가 살짝 바뀐거 같긴하지만 해당하는 &lt;code&gt;JsonDeserializer&amp;lt;?&amp;gt; deserializer = new BerryTypeDeserializer();&lt;/code&gt; 와 같이 해당하는 Enum Type을 정상적으로 변경할 수 있는 Deserializer를 구축해야한다.&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;import com.fasterxml.jackson.core.JacksonException;  
import com.fasterxml.jackson.core.JsonParser;  
import com.fasterxml.jackson.databind.DeserializationContext;  
import com.fasterxml.jackson.databind.JsonDeserializer;  
import com.fasterxml.jackson.databind.JsonNode;  
import com.kakaohealthcare.cslee.domain.BerryType;  
import lombok.extern.slf4j.Slf4j;  

import java.io.IOException;  

@Slf4j  
public class BerryTypeDeserializer extends JsonDeserializer&amp;lt;BerryType&amp;gt; {  

    @Override  
    public BerryType deserialize(JsonParser jp, DeserializationContext dctx) throws IOException, JacksonException {  
        JsonNode jsonNode = jp.getCodec().readTree(jp);  
        String text = jsonNode.asText();  
        log.info(&quot;text text::: {}&quot;, text);  
        return BerryType.by(text);  
    }  

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따른건 없고, 그냥 Text로 변환되어진, 해당하는 값을 Enum Type으로 치환해주는 방식으로 구현하면 된다.&lt;br /&gt;즉 Deserializer는 BerryType으로 전달된 String값 &amp;rarr; c01이라는 값을 받아서 &amp;rarr; Enum을 찾아서 by라는 스태틱메소드를 통해서 Enum을 찾는 방식을 취한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당하는 방식이 된다면, Controller나 Response에 &lt;code&gt;@JsonDeserialize(using = BerryTypeDeserializer.class)&lt;/code&gt;라는 어노테이션 없어도 동일하게 Enum Type으로 변환되어진다.&lt;br /&gt;어노테이션 있을때와 완전하게 동일한 방식으로 처리되기 때문에 실수할 수 있는 여지가 적어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 이런 방식을 취하면 Enum Type이 늘어날 수록 Custom Module만 여러개 등록해주면 되는 방식으로 형태로 Deserializer를 등록하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 해당하는 내용을 Module이 어떤 방식으로 등록되어지는지를 확인해보자.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Module이 등록되는 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 기본적으로 Bean으로만 등록될뿐 Serializer가 자동으로 등록되지 않는다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;//JacksonAutoConfiguration#L190

StandardJackson2ObjectMapperBuilderCustomizer(JacksonProperties jacksonProperties,  
       Collection&amp;lt;Module&amp;gt; modules) {  
    this.jacksonProperties = jacksonProperties;  
    this.modules = modules;  //모듈을 처음에 해당하는 곳에서 잡아주는데...
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20241124214350.png&quot; data-origin-width=&quot;443&quot; data-origin-height=&quot;282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnR4va/btsKUFwBEQK/T9dKsxl2q9YS205k1xOekK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnR4va/btsKUFwBEQK/T9dKsxl2q9YS205k1xOekK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnR4va/btsKUFwBEQK/T9dKsxl2q9YS205k1xOekK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnR4va%2FbtsKUFwBEQK%2FT9dKsxl2q9YS205k1xOekK%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;443&quot; height=&quot;282&quot; data-filename=&quot;Pasted image 20241124214350.png&quot; data-origin-width=&quot;443&quot; data-origin-height=&quot;282&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;모듈을 까보면 막상 6개밖에 없다. (Bean으로 등록된 커스텀 Deserializer Module은 없음)&lt;br /&gt;사용하는 순간에만 쓰기때문에 메모리를 사용하는 케이스가 없어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 어떤 순간에 우리는 해당하는 Module이 등록될까?&lt;br /&gt;간단한 테스트 코드를 구현해봤다&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;@Test
void tes2() throws JsonProcessingException {  
        ObjectMapper mapper = new ObjectMapper();  
        mapper.registerModule(berryTypeModule);  
        String json  = &quot;&quot;&quot;  
        {                  &quot;berryType&quot;: &quot;c01&quot;,                  
                            &quot;test&quot;: &quot;&quot;,
                            &quot;abc&quot;: &quot;&quot;                
        }
        &quot;&quot;&quot;;  
        DeserializeBody body = mapper.readValue(json, DeserializeBody.class);  
        log.info(&quot;{}&quot;, body);  
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당하는 모듈을 따로 등록하지 않아도, 자동으로 mapper.registerModule처럼 동작하는 상황은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BasicDeserializerFactory에서 Type중에 변환가능한 타입이 있는 경우 Deserializer를 찾아보면서, 해당하는 모듈의 커스텀 Serializer가 있는지를 찾아본다.&lt;br /&gt;모듈에서 오버라이드한 &lt;code&gt;findEnumDeserializer&lt;/code&gt;를 통해서 해당하는 Enum타입의 Deseriailzer를 찾게되는데..&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;/// BasicDeserializerFactory.java#L1672

public JsonDeserializer&amp;lt;?&amp;gt; createEnumDeserializer(DeserializationContext ctxt,  
        JavaType type, BeanDescription beanDesc)  
    throws JsonMappingException  
{  
    final DeserializationConfig config = ctxt.getConfig();  //시리얼라이즈 옵션과
    final Class&amp;lt;?&amp;gt; enumClass = type.getRawClass();  //Enum타입의 로우클래스를 통해서 커스텀 이넘타입을 찾아보려한다.
    // 23-Nov-2010, tatu: Custom deserializer?  
    JsonDeserializer&amp;lt;?&amp;gt; deser = _findCustomEnumDeserializer(enumClass, config, beanDesc);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 찾아볼때 다음과 같이 작동하는데..&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;/// BasicDeserializerFactory.java#L2256
protected JsonDeserializer&amp;lt;?&amp;gt; _findCustomEnumDeserializer(Class&amp;lt;?&amp;gt; type,  
        DeserializationConfig config, BeanDescription beanDesc)  
    throws JsonMappingException  
{  
    for (Deserializers d  : _factoryConfig.deserializers()) {  
        JsonDeserializer&amp;lt;?&amp;gt; deser = d.findEnumDeserializer(type, config, beanDesc);  //여기서 해당하는 Enum 타입을 시리얼 라이저를 찾게된다. 
        //당연하게되 Type이 해당 Enum 타입이므로..
        if (deser != null) {  
                return deser;   // 여기서 Deserializer가 등록됨
        }    }    return null;  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당하는 Deserializer가 자동으로 등록된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해당 방식의 장점. LAZY Loading&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모듈 자체는 처음 initializing할때는 등록되진 않지만 이후 Controller단에서 ObjectMapper를 확인해보면 해당하는 Module이 등록되어있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20241124224145.png&quot; data-origin-width=&quot;635&quot; data-origin-height=&quot;264&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRqFqC/btsKTVtjRHQ/lNEukLHjbi2k70g8uI2gSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRqFqC/btsKTVtjRHQ/lNEukLHjbi2k70g8uI2gSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRqFqC/btsKTVtjRHQ/lNEukLHjbi2k70g8uI2gSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRqFqC%2FbtsKTVtjRHQ%2FlNEukLHjbi2k70g8uI2gSk%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;635&quot; height=&quot;264&quot; data-filename=&quot;Pasted image 20241124224145.png&quot; data-origin-width=&quot;635&quot; data-origin-height=&quot;264&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;또한 Deserializer도 이렇게 해당하는 타입이 없는 Path를 먼저 찌르게된다면?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20241124224242.png&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;396&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbom4C/btsKT3LmIhV/BBSkVDbwbDtlcmV5HbSu0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbom4C/btsKT3LmIhV/BBSkVDbwbDtlcmV5HbSu0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbom4C/btsKT3LmIhV/BBSkVDbwbDtlcmV5HbSu0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbbom4C%2FbtsKT3LmIhV%2FBBSkVDbwbDtlcmV5HbSu0K%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;902&quot; height=&quot;396&quot; data-filename=&quot;Pasted image 20241124224242.png&quot; data-origin-width=&quot;902&quot; data-origin-height=&quot;396&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;code&gt;error&lt;/code&gt;를 먼저 찌르고? &amp;rarr; Deserializer를 찾아보면 다음처럼 나오게된다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20241124224444.png&quot; data-origin-width=&quot;486&quot; data-origin-height=&quot;221&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bFiwKK/btsKTz5dKoc/ipUU4qMq3m6OC7MMITG8nK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bFiwKK/btsKTz5dKoc/ipUU4qMq3m6OC7MMITG8nK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bFiwKK/btsKTz5dKoc/ipUU4qMq3m6OC7MMITG8nK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbFiwKK%2FbtsKTz5dKoc%2FipUU4qMq3m6OC7MMITG8nK%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;486&quot; height=&quot;221&quot; data-filename=&quot;Pasted image 20241124224444.png&quot; data-origin-width=&quot;486&quot; data-origin-height=&quot;221&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Moudle빈으로 등록된 Deserializer의 값이 없는데&lt;br /&gt;이걸 &lt;code&gt;/deserialize&lt;/code&gt;를 다시 접근하면 해당하는 Deserializer가 있는것이 보인다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20241124224649.png&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;235&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NqwUf/btsKVKjvgbF/LkWlMKc9UmGAMBaZYCUpNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NqwUf/btsKVKjvgbF/LkWlMKc9UmGAMBaZYCUpNk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NqwUf/btsKVKjvgbF/LkWlMKc9UmGAMBaZYCUpNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNqwUf%2FbtsKVKjvgbF%2FLkWlMKc9UmGAMBaZYCUpNk%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;875&quot; height=&quot;235&quot; data-filename=&quot;Pasted image 20241124224649.png&quot; data-origin-width=&quot;875&quot; data-origin-height=&quot;235&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그리고 해당하는 캐시에 올라간 Deserializer는 이제 다시 &lt;code&gt;/error&lt;/code&gt; path를 접근해도 사라지지 않는다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20241124224801.png&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;184&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dQL876/btsKUmDUoxC/66HHGZXDpPsEAjL3pbhby0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dQL876/btsKUmDUoxC/66HHGZXDpPsEAjL3pbhby0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dQL876/btsKUmDUoxC/66HHGZXDpPsEAjL3pbhby0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdQL876%2FbtsKUmDUoxC%2F66HHGZXDpPsEAjL3pbhby0%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;522&quot; height=&quot;184&quot; data-filename=&quot;Pasted image 20241124224801.png&quot; data-origin-width=&quot;522&quot; data-origin-height=&quot;184&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;즉, 해당하는 Path를 직접적으로 쓰지 않는 경우 해당 deserializer를 아예 등록하지 않는 방식으로 처리되어지기때문에 메모리 효율성이 좋고, 코드적으로 구성이 좋아서 추천할 방법으로 보여진다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;TMI&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;찾다보니까 재밌는것도 찾았는데, 결국 우리가 여러타입의 Deserializer가 기본 값이 여기에 적용되어있는데&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Pasted image 20241124224937.png&quot; data-origin-width=&quot;663&quot; data-origin-height=&quot;445&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lSq4m/btsKT5bm2ww/gJTOIMkFhTp6EyRZq6htlk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lSq4m/btsKT5bm2ww/gJTOIMkFhTp6EyRZq6htlk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lSq4m/btsKT5bm2ww/gJTOIMkFhTp6EyRZq6htlk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlSq4m%2FbtsKT5bm2ww%2FgJTOIMkFhTp6EyRZq6htlk%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;663&quot; height=&quot;445&quot; data-filename=&quot;Pasted image 20241124224937.png&quot; data-origin-width=&quot;663&quot; data-origin-height=&quot;445&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;골머리를 썩이는 LocalDateTime처럼 시간 날짜 타입의 키값들이 많이 들어있다는 점도 신기했다&lt;/p&gt;</description>
      <category>Spring</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/171</guid>
      <comments>https://sundries-in-myidea.tistory.com/171#entry171comment</comments>
      <pubDate>Sun, 24 Nov 2024 23:11:04 +0900</pubDate>
    </item>
    <item>
      <title>에러에 상태에 따라서 로깅 레벨을 조절해보자</title>
      <link>https://sundries-in-myidea.tistory.com/170</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;TL; DR&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선호하진 않을 방식일순 있으나, 에러에 대한 익셉션이 하나로 결정되어진 경우 ControllerAdvicer의 로그레벨을 무조건 한 가지로 고정해버리면 간혹 이 에러에 대한 오류를 지정하기가 힘들어진다. 차라리 이럴때는 Exception에 로그레벨을 넣고, Sl4fj에서 제공하는 makerLoggingEventBuilder기능을 활용하면 좋다. 익셉션에 로그레벨을 프로퍼티로 넣고, 꺼내서 해당하는 어드바이저의 레벨과 상관없이 자동으로 출력하자&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 이런 상황이 만들어지게된 배경&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러 로그를 Exception별로 만들게 되면, 로깅을 우리팀에서는 ControllerAdvicer에서 처리하는 방식으로 구현했었다. 근데 이렇게 하면 문제가 되는 지점이 공통분모로 묶어둔 익셉션이 자주 에러를 발생해서 가끔 에러가 과하게 모니터링 되는 경우가 있다.&lt;br /&gt;관제상 오류를 우리는 error로 레벨링을 하고 있는데, 단순 오류로 판단하던 것들이 간혹 사용자의 오류를 통해서 많이 발생하는 경우가 생길 수 있다. 그럴때 이걸 또 Exception을 쪼개서 나누면 ControllerAdvicer의 코드가 늘어나고 관리 포인트가 늘어난다고 생각해서, 해당하는 관리 포인트를 내가 익셉션 자체에 Logging Level을 추가하고 그 로깅레벨에 따라서 컨트롤러 어드바이저에서 셋업을 하면 좋겠다 생각했다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 예제 상황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 처럼 Exception과 Advicer를 구성하면 화면처럼 나오게 된다.&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;@Slf4j  
@ControllerAdvice  
public class ControllerExceptionHandler {  

    @ExceptionHandler(BaseException.class)  
    public ResponseEntity&amp;lt;String&amp;gt; handlerBaseException(BaseException be) {  
        log.error(&quot;error {}&quot;, be.getMessage());  
        return ResponseEntity.ok(&quot;오류&quot;);  
    }  

}

public class BaseException extends RuntimeException {  

    public BaseException(String number) {  
        super(number);  
    }  
}&lt;/code&gt;&lt;/pre&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;1433&quot; data-origin-height=&quot;95&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eLAGt8/btsKCnQ8EdM/6nIVAZqHyVxtKcLGYKOcM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eLAGt8/btsKCnQ8EdM/6nIVAZqHyVxtKcLGYKOcM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eLAGt8/btsKCnQ8EdM/6nIVAZqHyVxtKcLGYKOcM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeLAGt8%2FbtsKCnQ8EdM%2F6nIVAZqHyVxtKcLGYKOcM0%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;1433&quot; height=&quot;95&quot; data-origin-width=&quot;1433&quot; data-origin-height=&quot;95&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;물론 당연히 케이스바이케이스겠지만, 해당하는 상황이 너무 잦아서 혹시 오류가 많이 난다면 오류의 빈번함은 가끔 관제를 너무 쉽게 본다는 지점이 생기는게 문제다.&lt;br /&gt;즉, 이러면 해당하는 Exception을 분리하는것도 방법이겠지만, Slf4j에서 제공하는 함수를 통해서 해결하는 것도 좋은 방법이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. log.makeLoggingEventBuilder를 사용해보자&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;@Sl4fj&lt;/code&gt;의 기능에는 makerLoggingEventBuilder기능이 존재하는데, 해당하는 빌더는 Builder params에는 로그레벨이... 뒤에는 어떤 로깅을 할지에 대해서 적혀있는 터라, 해당하는 로깅 레벨을 우리가 어플단에서 조정이 가능하다.&lt;br /&gt;즉, 이렇게 수정이 가능하면 Exception에 가볍게 logging level만 넣어준다면 해당하는 오류 조정을 어플리케이션단에서만 할 수 있기 때문에 굳이 불필요한 Exception을 구성하지 않아도 된다는점이 편하다.&lt;br /&gt;물론 혹자는 Exception을 만드는 편이 나을 수도 있다고 보지만, 사실 우리가 전반적인 시스템에서 나눌 수 없는 혹은 너무 평범해서 이걸 쪼개 놓는게 맞는지 헛갈리는 순가이 있을 수도 있어서 해당하는 방식을 한번 고려해볼만한 대상이라고도 본다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예제처럼 builder를 만들어주면&lt;br /&gt;&lt;code&gt;log.makeLoggingEventBuilder(Level.DEBUG).log(&quot;error level control :: {}&quot;, be.getMessage());&lt;/code&gt;&lt;br /&gt;아래처럼 Debug 레벨의 로깅이 찍힌것을 볼 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1429&quot; data-origin-height=&quot;111&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DtXJX/btsKDyYnQYP/uK9PvwpBh7Sy29b0kS4Gh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DtXJX/btsKDyYnQYP/uK9PvwpBh7Sy29b0kS4Gh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DtXJX/btsKDyYnQYP/uK9PvwpBh7Sy29b0kS4Gh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDtXJX%2FbtsKDyYnQYP%2FuK9PvwpBh7Sy29b0kS4Gh1%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;1429&quot; height=&quot;111&quot; data-origin-width=&quot;1429&quot; data-origin-height=&quot;111&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 결론?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 BaseException과 같은 Custom Exception Class에 Logging Level을 넣어주기만 하면되는데, 해당하는 &lt;code&gt;makerLoggingEventBuilder&lt;/code&gt;의 경우는 Level Class를 사용하면 된다. 물론 이경우 SL4FJ에 너무 종속된다는 점이 있긴하지만, 기본적으로 로거 클래스를 바꿀일은 크게 없다고 본다. (물론 엄청 엄근진 하게 보자면 꼭 좋은건 아니지만...)&lt;br /&gt;다음처럼 코드를 구성해보자!&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;//실제 오류가 나는 부분
public void makeException() {  
    throw new BaseException(&quot;1134&quot;, Level.TRACE);  
}

@Getter  
public class BaseException extends RuntimeException {  

    private Level level = Level.ERROR;  

    public BaseException(String number) {  
        super(number);  
    }  

    public BaseException(String number, Level level) {  
        super(number);  
        this.level = level;  
    }
}

/// 오류가 나는 부분을 이렇게 수정하면?
@ExceptionHandler(BaseException.class)  
public ResponseEntity&amp;lt;String&amp;gt; handlerBaseException(BaseException be) {  
    log.makeLoggingEventBuilder(be.getLevel()).log(&quot;error level control :: {}&quot;, be.getMessage());  
    return ResponseEntity.ok(&quot;오류&quot;);  
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;짜잔 다음과 같이 오류가 내가 설정한 Exception별로 오류가 나오게 됩니다~&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1416&quot; data-origin-height=&quot;48&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dqDlo7/btsKDzXgNUP/mCTLZU9WELReHvLcXkUelK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dqDlo7/btsKDzXgNUP/mCTLZU9WELReHvLcXkUelK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dqDlo7/btsKDzXgNUP/mCTLZU9WELReHvLcXkUelK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdqDlo7%2FbtsKDzXgNUP%2FmCTLZU9WELReHvLcXkUelK%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;1416&quot; height=&quot;48&quot; data-origin-width=&quot;1416&quot; data-origin-height=&quot;48&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;또한 기본값으로 Error를 정해두고, 해당하는 에러값이 아닌 경우 생성자를 통해서 넣어주기만 하면 되므로 해당하는 로깅을 강제할 수도 있습니다.&lt;/p&gt;</description>
      <category>Spring</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/170</guid>
      <comments>https://sundries-in-myidea.tistory.com/170#entry170comment</comments>
      <pubDate>Sun, 10 Nov 2024 03:18:23 +0900</pubDate>
    </item>
    <item>
      <title>@AuthenticationPrincipal과 getAuthentication()에서 가져온 principal의 다른점은 있을까?</title>
      <link>https://sundries-in-myidea.tistory.com/169</link>
      <description>&lt;h1&gt;TL; DR&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;없다 같은것이기 때문에 크게 신경쓰지 말자 타입캐스팅도 자동으로 되기때문에 굳이 서비스 로직이아니라면 Parameter화시키는게 더 편하다.&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;code&gt;@CreatedBy&lt;/code&gt; 나 &lt;code&gt;@LastModifiedBy&lt;/code&gt;를 사용하는 경우 Auditor를 구성하기위해 &lt;code&gt;implements AuditorAware&amp;lt;User&amp;gt;&lt;/code&gt; 과 같은 Aware를 이용하게되는데, 개발시 스프링에서 제공하는 예시를 보더라도...&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;class SpringSecurityAuditorAware implements AuditorAware&amp;lt;User&amp;gt; {

  public User getCurrentAuditor() {

    Authentication authentication = SecurityContextHolder.getContext().getAuthentication();

    if (authentication == null || !authentication.isAuthenticated()) {
      return null;
    }

    return ((MyUserDetails) authentication.getPrincipal()).getUser();
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;getPrincipal을 빼서 쓰는데 사실 &lt;code&gt;@AuthenticationPrincipal&lt;/code&gt;를 사용하는 방식 자체가 대략 비슷해보이긴 했었다. 그 둘의 차이가 있는지 싶어서 &lt;code&gt;@AuthenticationPrincipal&lt;/code&gt;의 원리를 파악해서 같은건지 아니면 다른 영역의 것인지 알아 보려고 했다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;원리를 파고 들어가보자&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 어노테이션이기 때문에, 해당하는 어노테이션이 어떤식으로 쓰이는지를 먼저 확인해야한다. &amp;rarr; &lt;code&gt;@AuthenticationPrincipal&lt;/code&gt;를 먼저 찾아보자&lt;br /&gt;![[스크린샷 2024-10-25 오후 2.56.17.png]]&lt;br /&gt;를 확인해보면 &lt;code&gt;AuthenticationPrincipalArgumentResolver&lt;/code&gt;를 확인해볼 수 있다. 결국 어노테이션 밖에서 사용하기 위해서는 Controller의 argument를 인식하는 Resolver가 필요하기때문에...&lt;br /&gt;그 해당하는 Resolver를 파악해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당하는 코드의 105번째줄에 다음과 같은 응답이 있다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Override  
public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer,  
       NativeWebRequest webRequest, WebDataBinderFactory binderFactory) {  
    //우리가 유의해서 봐야할 부분은 다음이다. 
    Authentication authentication = this.securityContextHolderStrategy.getContext().getAuthentication();  
    if (authentication == null) {  
       return null;  
    }
    //그리고 이걸 바로 가져다 쓰는 구조다  
    Object principal = authentication.getPrincipal();  
    AuthenticationPrincipal annotation = findMethodAnnotation(AuthenticationPrincipal.class, parameter);  
    String expressionToParse = annotation.expression();  
    if (StringUtils.hasLength(expressionToParse)) {  
       StandardEvaluationContext context = new StandardEvaluationContext();  
       context.setRootObject(principal);  
       context.setVariable(&quot;this&quot;, principal);  
       context.setBeanResolver(this.beanResolver);  
       Expression expression = this.parser.parseExpression(expressionToParse);  
       principal = expression.getValue(context);  
    }  
    if (principal != null &amp;amp;&amp;amp; !ClassUtils.isAssignable(parameter.getParameterType(), principal.getClass())) {  
       if (annotation.errorOnInvalidType()) {  
          throw new ClassCastException(principal + &quot; is not assignable to &quot; + parameter.getParameterType());  
       }  
       return null;  
    }  
    return principal;  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드에서도 보이지만, 실제로 &lt;code&gt;getAuthentication()&lt;/code&gt;하는 부분과 완전히 동일하다. 혹시 다른부분이 있을까 했지만 딱히 그런 부분은 보이지 않았다. 물론, 이 아규먼트에서도 재밌는 부분은 있었다. 주석에 예시로 달려있는 부분들은 꽤 코드짤때 도움이 되보였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;@AuthenticationPrincipal&lt;/code&gt;를 커스터마이징시 어노테이션 자체에 붙여서 써버리면 CurrentUser 아님을 확인하는 과정이 없어도 문제는 없어보였다&lt;br /&gt;다음처럼 사용하면&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;  @Target({ ElementType. PARAMETER })
  @Retention(RetentionPolicy. RUNTIME)
  @AuthenticationPrincipal
  public @interface CurrentUser {
  }
/// (@CurrentUser CustomUser customUser) 이렇게 표현하기 좋다&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런형태로 쓰기 좋기 때문에 결론적으로는 해당하는 어노테이션을 만들어도 좋을거 같았다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;완전 동일함을 확인했고, Holder를 뽑아서 사용하는 케이스가 아니라면 파라미터로 전달받아서 사용하는 게 좋아 보였다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;출처&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://wildeveloperetrain.tistory.com/324&quot;&gt;https://wildeveloperetrain.tistory.com/324&lt;/a&gt; &amp;rarr; 쓰려는 내용보다 더 깊게 들어갔다. 더 자세한 원리를 파고 싶다면 확인해보자&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://codevang.tistory.com/273&quot;&gt;https://codevang.tistory.com/273&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Spring</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/169</guid>
      <comments>https://sundries-in-myidea.tistory.com/169#entry169comment</comments>
      <pubDate>Sun, 27 Oct 2024 21:30:10 +0900</pubDate>
    </item>
    <item>
      <title>글또 10기를 시작하면서...</title>
      <link>https://sundries-in-myidea.tistory.com/168</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwUAUe/btsJ3srBR24/AeQx5nGtwc5hbl0lzrRcF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwUAUe/btsJ3srBR24/AeQx5nGtwc5hbl0lzrRcF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwUAUe/btsJ3srBR24/AeQx5nGtwc5hbl0lzrRcF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwUAUe%2FbtsJ3srBR24%2FAeQx5nGtwc5hbl0lzrRcF1%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;1080&quot; height=&quot;768&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;768&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;글또 첫주차는 늘 뭘 적을까에 대한 고민을 많이 하곤 하는데, 결국에는 다짐글을 적게되는게 굳혀졌다 아래 글만 봐도 벌써 4번째기 때문&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 글또는 마지막 기수기도하고 이 블로그의 대한 방향성도 좀 미묘해진 상황이다.&lt;/p&gt;
&lt;figure id=&quot;og_1728802661530&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;글또 5기를 시작하면서...&quot; data-og-description=&quot;글또를 시작해버렸다. 글쓰는 또라이라는 이 모임은 개발 블로그 글쓰기 모임과 같다. 사실 개발 블로그는 내가 배운 내용이 아니면 정기적으로 포스팅하기가 쉽지는 않다. 아무래도 내가 순간&quot; data-og-host=&quot;sundries-in-myidea.tistory.com&quot; data-og-source-url=&quot;https://sundries-in-myidea.tistory.com/99&quot; data-og-url=&quot;https://sundries-in-myidea.tistory.com/99&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/rzjwS/hyXhPeZkbW/mdHfemDevcNjkS31RcVGDK/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/GcTtY/hyXd5wZ4Gu/aCvYDUOKBWkVCVgmgD3Kf1/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/0tSfa/hyXhNuGM84/N9cgR9DFCOd7cklsYTYgA0/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot;&gt;&lt;a href=&quot;https://sundries-in-myidea.tistory.com/99&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sundries-in-myidea.tistory.com/99&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/rzjwS/hyXhPeZkbW/mdHfemDevcNjkS31RcVGDK/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/GcTtY/hyXd5wZ4Gu/aCvYDUOKBWkVCVgmgD3Kf1/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/0tSfa/hyXhNuGM84/N9cgR9DFCOd7cklsYTYgA0/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;글또 5기를 시작하면서...&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;글또를 시작해버렸다. 글쓰는 또라이라는 이 모임은 개발 블로그 글쓰기 모임과 같다. 사실 개발 블로그는 내가 배운 내용이 아니면 정기적으로 포스팅하기가 쉽지는 않다. 아무래도 내가 순간&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sundries-in-myidea.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1728802671754&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;글또 6기를 시작하면서 - 올해의 마무리도 글또와 함께&quot; data-og-description=&quot;글또를 또 하게되었다. 이번에도 글또를 시작했다. 뭐 지난번에 이야기를 주절주절했으니까.. 글또에 대해서 무엇이고 어떤 비전을 가지고 있는지에 대해서는 딱히 언급하지 않고, 링크를 남긴&quot; data-og-host=&quot;sundries-in-myidea.tistory.com&quot; data-og-source-url=&quot;https://sundries-in-myidea.tistory.com/127&quot; data-og-url=&quot;https://sundries-in-myidea.tistory.com/127&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bCcos7/hyXhYbVlRP/VqNKpr4R5zVckPayfAJKE0/img.png?width=720&amp;amp;height=640&amp;amp;face=0_0_720_640,https://scrap.kakaocdn.net/dn/mDp2G/hyXhP0lOdW/qyikJRVHzCgZ2VWg6Fmz6k/img.png?width=720&amp;amp;height=640&amp;amp;face=0_0_720_640,https://scrap.kakaocdn.net/dn/bkRgeC/hyXhVlYwSa/wJ6HlYENe2JH3ptkITqTbK/img.png?width=720&amp;amp;height=640&amp;amp;face=0_0_720_640&quot;&gt;&lt;a href=&quot;https://sundries-in-myidea.tistory.com/127&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sundries-in-myidea.tistory.com/127&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bCcos7/hyXhYbVlRP/VqNKpr4R5zVckPayfAJKE0/img.png?width=720&amp;amp;height=640&amp;amp;face=0_0_720_640,https://scrap.kakaocdn.net/dn/mDp2G/hyXhP0lOdW/qyikJRVHzCgZ2VWg6Fmz6k/img.png?width=720&amp;amp;height=640&amp;amp;face=0_0_720_640,https://scrap.kakaocdn.net/dn/bkRgeC/hyXhVlYwSa/wJ6HlYENe2JH3ptkITqTbK/img.png?width=720&amp;amp;height=640&amp;amp;face=0_0_720_640');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;글또 6기를 시작하면서 - 올해의 마무리도 글또와 함께&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;글또를 또 하게되었다. 이번에도 글또를 시작했다. 뭐 지난번에 이야기를 주절주절했으니까.. 글또에 대해서 무엇이고 어떤 비전을 가지고 있는지에 대해서는 딱히 언급하지 않고, 링크를 남긴&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sundries-in-myidea.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1728802689303&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;글또 9기를 시작하며...&quot; data-og-description=&quot;글또 첫주차는 늘 뭘 적을까에 대한 고민을 많이 하곤 하는데, 결국에는 다짐글을 적게되는게 굳혀졌다. ㅋㅋ 왜냐하면 이미 내 글들을 살펴보면, 글또 글들이 1주차에는 무조건 9기를 시작하며&quot; data-og-host=&quot;sundries-in-myidea.tistory.com&quot; data-og-source-url=&quot;https://sundries-in-myidea.tistory.com/158&quot; data-og-url=&quot;https://sundries-in-myidea.tistory.com/158&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/chFXOu/hyXhMCyAtq/nZBwmHmisAZwfwKy8aoKF0/img.png?width=800&amp;amp;height=568&amp;amp;face=0_0_800_568,https://scrap.kakaocdn.net/dn/E3KWl/hyXd3MLRD3/HmYOJGFTwkOmmThoLMHchk/img.png?width=800&amp;amp;height=568&amp;amp;face=0_0_800_568,https://scrap.kakaocdn.net/dn/bPFPn8/hyXedaLCy1/UAB2xvtjWIx1ThRD6YfwDk/img.png?width=1080&amp;amp;height=768&amp;amp;face=0_0_1080_768&quot;&gt;&lt;a href=&quot;https://sundries-in-myidea.tistory.com/158&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sundries-in-myidea.tistory.com/158&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/chFXOu/hyXhMCyAtq/nZBwmHmisAZwfwKy8aoKF0/img.png?width=800&amp;amp;height=568&amp;amp;face=0_0_800_568,https://scrap.kakaocdn.net/dn/E3KWl/hyXd3MLRD3/HmYOJGFTwkOmmThoLMHchk/img.png?width=800&amp;amp;height=568&amp;amp;face=0_0_800_568,https://scrap.kakaocdn.net/dn/bPFPn8/hyXedaLCy1/UAB2xvtjWIx1ThRD6YfwDk/img.png?width=1080&amp;amp;height=768&amp;amp;face=0_0_1080_768');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;글또 9기를 시작하며...&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;글또 첫주차는 늘 뭘 적을까에 대한 고민을 많이 하곤 하는데, 결국에는 다짐글을 적게되는게 굳혀졌다. ㅋㅋ 왜냐하면 이미 내 글들을 살펴보면, 글또 글들이 1주차에는 무조건 9기를 시작하며&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sundries-in-myidea.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근에 느낀 점들을 적기가 좀 쉽지는 않았다. 년차가 쌓이면 쌓일수록 오히려 내가 생각하는 업무풀은 정책적이나, 개발적인 것을 어떤식으로 풀어내는가에 대한 점들로 바뀌었기 때문이다. 막상 기술적인 딥다이브보다는, 급한 업무들이 더 많이 정해졌기도하고 기술적 도전보다는 오히려 탑다운방식의 결정에 맞춰서 내가 그에 맞춰서 대응해야하는 일이 늘다보니까 오히려 엔지니어링보다는 마감에 맞춰 일을 하는 기분에 가까웠다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그리고 더 나아가서는 기술적인 딥다이브가 회사 일에 크게 도움이 되나싶은 생각도 들었다. 물론, 그런일이 필요한 순간이 많았지만, 여러 상황들에 의해서 자꾸 기술적 고민이 아닌 다른 고민을 하고 있었던 내 모습을 보니까 현 블로그가 전반적으로 난잡해지는 생각은 들었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;물론 이번기회에 좀 더 고민을 해보면 되겠지만..&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;어떤 액션 아이템을 가져와볼까?&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;구체적으론 무슨 액션 아이템이 필요할까? 글또를 하면서 목표가 좁아지고, 좀더 명쾌한 목표를 두는게 더 좋다고 생각한다. 특히 추상적인 목표들을 짜본결과 막상 그런 목표가 내 글쓰기에 도움을 주지도 못했고, 바쁘다는 핑계만들긴 참 좋았다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그래서 항상하던 커뮤니티성과 눈에 보이는 목표를 두는게 좋을 것 같다고 생각했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 큐레이션에 담길 글을 작성해보기&lt;/b&gt;&lt;br /&gt;&lt;b&gt;2. 커피챗 그리고 음감회 주최 잘하기&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;큐레이션에 담길 글을 작성&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;큐레이션은 항상 좋은글을 담는 사람들이 많이 올라오는 공간으로 많은 글들을 작성한뒤에 내 글이 큐레이션에 올라올 수 있는 정도인가를 확인하는 자리가 된다. 특히, 큐레이션에는 기술적으로 꽤 좋은 글들이 많이 올라오고 글을 읽다보면 잘썼고 공들여서 썼다는것을 느낄 수 있는데 막상 내가 9기때쓴글을 보면 그렇게 딥다이브하는 글을 썼었나 생각해보면 아니였던거 같기도했다. 특히나, 기술적인 딥다이브를 하면서 그리고 그걸 내가 글로 풀어씀으로서, 내 스스로도 성장하고 기술적인 공유회처럼 되는 것도 모두에게 좋은 것인데 자꾸 그런 일들을 좀 많이 미뤘던거 같다. 바쁘다, 아니면 뭐 피곤하다와 같은 일들로 좀 많이 미뤘지만, 물론 2주만에 낼 수 있다면야 훨씬 좋겠지만 2주안에 못내고 패스를 쓰는 한이 있더라도, 좋은 글로 한번 큐레이션에 담겨 내글이 올라가는 상황이 있으면 좋겠다. 그리고 그런 글을 써보도록 치밀하게 준비해볼것이다.&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;커피챗 그리고 음감회&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;글또의 개인적으로 큰 선호를 느끼는 이유중 하나는 커피챗이다. 커피챗은 나말고 다양한 사람들을 보는 상황을 만들어서 좋았는데, 그 분야도 다들 특이하고 독특한 분야에서 일하는 분들을 많이 만날때가 있어서 좋았다. 대기업부터 스타트업 대학생까지 그 폭이 다양해서 어떤 일들이 있고, 그런 일들이 어떤역할을 하는지를 알 수 있는 좋은 기회가 되어서 좋았다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; 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-filename=&quot;blob&quot; data-origin-width=&quot;2250&quot; data-origin-height=&quot;3000&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Nknj7/btsJ3mZGCZI/hRcnT2Cmziz4dyIRQkK2ok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Nknj7/btsJ3mZGCZI/hRcnT2Cmziz4dyIRQkK2ok/img.png&quot; data-alt=&quot;노래들으면서 밥먹으면서, 수다 떠는 모임&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Nknj7/btsJ3mZGCZI/hRcnT2Cmziz4dyIRQkK2ok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNknj7%2FbtsJ3mZGCZI%2FhRcnT2Cmziz4dyIRQkK2ok%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;657&quot; height=&quot;876&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;2250&quot; data-origin-height=&quot;3000&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;노래들으면서 밥먹으면서, 수다 떠는 모임&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;근데 예상보다 그 모임이 꽤 잘 진행되어서 벌써 한 3번정도 진행되었는데(이전기수에), 내가 이제는 리딩까진 안해도 주최위원회처럼 도와주지 않을까 싶다. 그리고 좀 더 음감회의 형태를 당연히 좀더 더 잘꾸며봐야겠지만 ㅎㅎ 항상 포멧이나 모두 즐거울 수 있는 커뮤니케이션의 장으로 만드는게 참으로 어렵다. 그래도 모두가 좀 즐길 수 있는 장을 만들어보는게 좋은 방향으로 해보고 싶다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;마지막으로&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;내 목표도 달성하고, 커뮤니티 활동도 잘해보고 싶으니 남은 기간동안 모두 화이팅했으면 좋겠다.&amp;nbsp;&lt;/p&gt;</description>
      <category>잡담/경험담, 생각들</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/168</guid>
      <comments>https://sundries-in-myidea.tistory.com/168#entry168comment</comments>
      <pubDate>Sun, 13 Oct 2024 16:34:05 +0900</pubDate>
    </item>
    <item>
      <title>Transactional의 Self Injection이 올바른가</title>
      <link>https://sundries-in-myidea.tistory.com/167</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;TL; DR&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;올바르지 않고, 쓰지마. Self Injection은 기본적으로 Spring이랑 Spring Boot에서 기본 논점으로 막고 있음. 왜냐하면 무한 참조에 위험성을 가지고 있고 @Lazy를 위한 방식으로 처리할 수는 있는데.. 굳이 그렇게 쓰는거 자체가 전자에 발생할 위험성을 포함하고 있음. 스프링 부트의 경우는 아예 기본으로 순환 참조시 아예 Application 켜지지 않는 이슈도 있음. 아님 허용하고 Spring boot 전체가 전부 Self Injection이 가능해져야하기 때문에 비선호&lt;/b&gt;&lt;br /&gt;기본적으로 Spring에서 권고하지 않는 사항이면, 안하는게 좋다고 생각함. 프레임워크기 때문에 굳이 벗어나는 행동을 하는건 좋지 않다고 생각함&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;br /&gt;&lt;a href=&quot;https://cloud-ci.sgs.com/sonar/coding_rules?languages=java&amp;amp;q=%40Transactional&amp;amp;open=java%3AS6809&quot;&gt;https://cloud-ci.sgs.com/sonar/coding_rules?languages=java&amp;amp;q=%40Transactional&amp;amp;open=java%3AS6809&lt;/a&gt;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;Methods with Spring proxy should not be called via &quot;this&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뭐 Spring Proxy 쓸때 당연한거긴하지만.. 클래스내 트랜잭션을 겹처서 쓰거나.. 아니면 트랜젝션이 없는 메소드 안에 트랜젝션이 있는 걸 불러와야하는 경우... this.000method() 해봐야 동작을 하지 않음.&lt;br /&gt;위에서 말했던 것처럼 이런 건 내 기억에서는 트랜젝션이 중첩되는 경우가 아닌 이상 그냥 트랜젝션 안에서 최대한 온몸 비틀기를 했다.&lt;br /&gt;문제는 트랜젝션의 중첩과 같은 경우인데.. 요약하면 부모 메소드는 &lt;code&gt;@Transactional&lt;/code&gt; readonly 상태 &amp;rarr; 자식 메소드는 &lt;code&gt;@Transactional&lt;/code&gt; 한 상황. 그렇다면 Class 분리를 통해서 자식 메소드를 분리하고 부모메소드에 Class 주입을 해서 부모는 readonly 상태 &amp;rarr; 클래스 주입된 자식 메소드는 트랜젝션이 정상 동작한다고 생각했고 실제로도 그렇게 개발을 종종 하기 때문에 난 항상 이렇게 쪼개왔었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 항상 어느정도의 의문이 있었는데.. 그러면 이런 중첩 트랜젝션의 경우 매번 클래스를 분리하라고? 예를들어서 단순하게 이 엔티티자체의 서비스와 크게 다르지도 않는데 중첩 트랜젝션할때마다 클래스를 쪼개서 하려면 트랜젝션을 위한 트랜잭션 전용 class가 하나 더생기는게 아닌가...? 뭐 이정도로 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 위에서 봤던 링크는 이런 의견을 정면으로 반박하는거 같았다.&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;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Compliant solution&lt;/h4&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Service
public class AsyncNotificationProcessor implements NotificationProcessor {

  @Resource
  private AsyncNotificationProcessor

  @Override
  public void process(Notification notification) {

    asyncNotificationProcessor.processAsync(notification); // Compliant, call via injected proxy

  }

  @Async
  public processAsync(Notification notification) {
    // ...
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 자 그럼 다시 생각해보자 위에서 발생한 동일한 이슈를 다시 치환해서 보자면? Self Injection을 해주면 프록시 문제가 풀리고 자동으로 중첩 트랜젝션도 엄밀히 따지면 한 트랜잭션 내부에서 처리 가능하다는 거 아님? 사고가 흘러갔다.&lt;br /&gt;결국 우리가 제일 문제가 된건...?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프록시 객체가 있음. &amp;rarr; 근데 중첩 메소드를 사용하면 프록시 객체 내 프록시 한겹으로 더 감싸는 상태가 될수 없음 &amp;rarr; injection 불가 AOP 형태에서는 중첩 트랜젝션이 불가능한데...&lt;br /&gt;이게... self Injection을 통해서는 가능할 수도 있다는 것? 처럼 느껴졌다.&lt;br /&gt;왜냐하면 어쨋든 자기 자신도 Bean 자체에 Class가 등록되있기 때문에 사실 그 자기 자신 클래스를 그대로 받아서 처리하면 되는거 아냐?? 라고 나름 근들갑 떨고 있는 셈이다.&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;br /&gt;일단 일종의 upsert 기능을 구현해보면 중첩 트랜젝션처럼 구현해볼 수 있는데, 일종의 readonly transactional를 작성해보려했는데...&lt;br /&gt;굵직한 예시만 굳이 표현하자면 대략 아래와 같은 코드로 중첩 트랜젝션을 구성하려 했다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;package com.example.self_injection;  


import jakarta.annotation.Resource;  
import lombok.RequiredArgsConstructor;  
import lombok.extern.slf4j.Slf4j;  
import org.springframework.beans.factory.annotation.Autowired;  
import org.springframework.stereotype.Service;  
import org.springframework.transaction.annotation.Propagation;  
import org.springframework.transaction.annotation.Transactional;  

import java.util.Map;  

@Service  
@RequiredArgsConstructor  
@Transactional(readOnly = true)  
@Slf4j  
public class TestService {  

    private final TestEntityRepository testEntityRepository;  
    private final TestLoggingEntityRepository testLoggingEntityRepository;  
    private final TransactionalService transactionalService;  
    private TestService testService;  

    @Autowired  
    public void setTestService(TestService testService) {  
        this.testService = testService;  
    }  

    @Transactional  
    public void save(Map&amp;lt;String, String&amp;gt; map) {  
        testEntityRepository.save(TestEntity.builder().name(map.get(&quot;name&quot;)).build());  
    }  


    public void getReadOnlyMethod(Long id){  
        log.info(&quot;Transactional ReadOnly Start&quot;);  
        TestEntity te = testEntityRepository.findById(id).orElse(null);  
        if(te == null) {  
            log.info(&quot;해당 번호는 없다. 해당 메소드 종료&quot;);  
            return;  
        }  
        TestLoggingEntity testLoggingEntity = TestLoggingEntity.builder().id(te.getId())  
                .name(&quot;123&quot;).actionName(&quot;action&quot;).build();  
        testService.upsertMethod(testLoggingEntity);  
        log.info(&quot;Transactional ReadOnly End&quot;);  
    }  


    @Transactional(propagation = Propagation.REQUIRES_NEW)  
    public void upsertMethod(TestLoggingEntity testLoggingEntity) {  
        log.info(&quot;Transactional Start&quot;);  
        testLoggingEntityRepository.findById(testLoggingEntity.getId()).ifPresentOrElse(  
                entity -&amp;gt; {  
                    entity.updateAction(&quot;action1&quot;);  
                    testLoggingEntityRepository.save(entity);  
                },  
                () -&amp;gt; testLoggingEntityRepository.save(testLoggingEntity)  
        );  
        log.info(&quot;Transactional end&quot;);  
    }  

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 해당 코드 실행은 실패한다. 에러로그는 다음과 같다.&lt;/p&gt;
&lt;pre class=&quot;applescript&quot;&gt;&lt;code&gt;┌─────┐
|  testService
└─────┘


Action:

Relying upon circular references is discouraged and they are prohibited by default. Update your application to remove the dependency cycle between beans. As a last resort, it may be possible to break the cycle automatically by setting spring.main.allow-circular-references to true.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring을 좀 해봤다 싶으면 당연하게도 이 문제가 있다는걸 인지할 수 밖에 없다. 순환 참조 오류는 당연하니 뭐 그렇다 쳐도... 왜 이걸 Spring Boot나 Spring에서 막았는지 궁금했다. 그리고 명확한 의도가 뭔지를 찾아보고 싶어졌다.&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;br /&gt;나처럼 생각한 개발자들이 꽤 많았는데, 해당하는 걸 막는 거 까지는 아니여도 해당하는 이슈들을 찾았다. 아래 링크는 해당하는 circular reference를 금지하는 옵션에 대해서 만든 이슈이다.&lt;br /&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-boot/issues/27652&quot;&gt;https://github.com/spring-projects/spring-boot/issues/27652&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1719055260772&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Prohibit circular references by default &amp;middot; Issue #27652 &amp;middot; spring-projects/spring-boot&quot; data-og-description=&quot;We believe that circular references between beans should be avoided if at all possible. To help users avoid accidentally creating such dependency cycles, we should configure the BeanFactory to proh...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/spring-projects/spring-boot/issues/27652&quot; data-og-url=&quot;https://github.com/spring-projects/spring-boot/issues/27652&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/b6624Z/hyWoIIUcFu/X4fQtyxSZ2M0mr2kVeYM21/img.png?width=1200&amp;amp;height=600&amp;amp;face=995_120_1055_185&quot;&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-boot/issues/27652&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/spring-projects/spring-boot/issues/27652&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/b6624Z/hyWoIIUcFu/X4fQtyxSZ2M0mr2kVeYM21/img.png?width=1200&amp;amp;height=600&amp;amp;face=995_120_1055_185');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Prohibit circular references by default &amp;middot; Issue #27652 &amp;middot; spring-projects/spring-boot&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;We believe that circular references between beans should be avoided if at all possible. To help users avoid accidentally creating such dependency cycles, we should configure the BeanFactory to proh...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 링크는 Spring Boot에서 기본적으로 Circular Reference를 막는 정책을 쓰고 있는데... 문제는.. 해당하는 이슈 댓글이 엄청 달렸다. 댓글의 요지도 위에처럼 굳이.. 한 Layer를 더 만들어내는 방식이 아닌 자기 참조를 통한 proxy 방식이 &lt;b&gt;&quot;왜 틀린지?&quot;&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;970&quot; data-origin-height=&quot;386&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbid4m/btsH8U4oVwX/QdSP6xCb5Izft6b5uqsNl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbid4m/btsH8U4oVwX/QdSP6xCb5Izft6b5uqsNl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbid4m/btsH8U4oVwX/QdSP6xCb5Izft6b5uqsNl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcbid4m%2FbtsH8U4oVwX%2FQdSP6xCb5Izft6b5uqsNl0%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;970&quot; height=&quot;386&quot; data-origin-width=&quot;970&quot; data-origin-height=&quot;386&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;흠.. 전 Spring Boot 대용? Spring 가서 알아보고 오쇼처럼 들렸기 때문에 뭐 다시 Spring 가서 확인하는 수 밖에 없다고 생각했다.&lt;br /&gt;직접적으로 Spring에서 뭐 Circular Reference가 뭐가 문제고.. 지양하는 바가 뭔가 뚜렸해보이진 않았는데... 이슈 몇개들로 대략적인 추론은 가능해보였다.&lt;br /&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-framework/issues/28299&quot;&gt;https://github.com/spring-projects/spring-framework/issues/28299&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1719055350527&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Clarify recommendation on self-injection for legitimate use cases (e.g. @Transactional re-entrant calls) &amp;middot; Issue #28299 &amp;middot; spri&quot; data-og-description=&quot;Hi, It is becoming harder and harder to use self-injection: Spring boot 2.6 forbids it by default: spring-projects/spring-boot#27652 spring boot 2.6.4 broke @SpyBean: spring-projects/spring-boot#30...&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/spring-projects/spring-framework/issues/28299&quot; data-og-url=&quot;https://github.com/spring-projects/spring-framework/issues/28299&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bNtp3U/hyWoIozmAc/OfEKnqONDKACkQmOyRd6Ik/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-framework/issues/28299&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/spring-projects/spring-framework/issues/28299&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bNtp3U/hyWoIozmAc/OfEKnqONDKACkQmOyRd6Ik/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Clarify recommendation on self-injection for legitimate use cases (e.g. @Transactional re-entrant calls) &amp;middot; Issue #28299 &amp;middot; spri&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Hi, It is becoming harder and harder to use self-injection: Spring boot 2.6 forbids it by default: spring-projects/spring-boot#27652 spring boot 2.6.4 broke @SpyBean: spring-projects/spring-boot#30...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ㄴ 이 이슈같은 경우는 결국 이 Self Injection의 합법적인 Use Case를 알려달라는 이슈였고 해당하는 이슈는 .... 황당하게도 주석하나정도로 처리되긴했었다.&lt;br /&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-framework/commit/22b6d66a286256556a21d83a16e82b756b732da8&quot;&gt;https://github.com/spring-projects/spring-framework/commit/22b6d66a286256556a21d83a16e82b756b732da8&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1719055362486&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;Document @Order behavior on @Configuration classes versus @Bean methods &amp;middot; spring-projects/spring-framework@22b6d66&quot; data-og-description=&quot;Includes brief note on self injection (extracted from qualifiers section). Closes gh-30177 Closes gh-28299&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/spring-projects/spring-framework/commit/22b6d66a286256556a21d83a16e82b756b732da8&quot; data-og-url=&quot;https://github.com/spring-projects/spring-framework/commit/22b6d66a286256556a21d83a16e82b756b732da8&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/X1yRK/hyWoIIUbXg/DVud4bKS7kt9XKCoTlJrvK/img.png?width=1200&amp;amp;height=600&amp;amp;face=1001_131_1053_189&quot;&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-framework/commit/22b6d66a286256556a21d83a16e82b756b732da8&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/spring-projects/spring-framework/commit/22b6d66a286256556a21d83a16e82b756b732da8&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/X1yRK/hyWoIIUbXg/DVud4bKS7kt9XKCoTlJrvK/img.png?width=1200&amp;amp;height=600&amp;amp;face=1001_131_1053_189');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Document @Order behavior on @Configuration classes versus @Bean methods &amp;middot; spring-projects/spring-framework@22b6d66&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Includes brief note on self injection (extracted from qualifiers section). Closes gh-30177 Closes gh-28299&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주석에 이런식으로 작성이 되어있었는데&lt;/p&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;자체 주입(self-injection)은 대체 수단이라는 점에 유의하세요. 실제로는 최후의 수단으로만 자체 참조를 사용해야 합니다(예: 동일한 인스턴스에서 빈의 트랜잭션 프록시를 통해 동일한 인스턴스에서 다른 메서드를 호출하는 경우).
이러한 시나리오에서는 영향을 받는 메서드를 별도의 델리게이트 빈으로 분리하는 것을 고려하세요.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 이러한 문제를 자체 참조자체가 최대한 미루고 미루는 존자라는 것이고... 권고안 자체도 별로 Class 분리 방안을 찾는게 좋다는 뜻으로 보였다.&lt;br /&gt;Self Injection 이러한 이슈가 생길때마다 위의 이슈로 계속 이슈로 연결해주셨던 @snicoll이 지속적으로 Self Injection에 대한 몇개 없지만, 그 적은 근거로.. &quot;무한루프가 돌기 쉽다.&quot; 정도를 들긴했다. 그리고 해당하는 답변에서 계속 말하는건 쓰지마라! 이긴 했기 때문에 쓰진 않겠다만...&lt;br /&gt;결국 스프링 자체적으로 금지 시켜놓은 가이드라고 보면 되지만... 호용성은... ? 긴 하다. 굳이.. infinite loop를 돈다고 해서 순환참조 방식을 막을 필요가 있으려나...?&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;br /&gt;내 입장은 글 적기 전까지만 해도 그냥 써도 괜찮지 않겠냐쪽이긴 했지만 + 소나큐브의 코드 수정에 대한 권위적 위치를 보고 그렇게 수정해야겠다싶어 수정하려 했지만, 글을 다 작성한 지금의 입장은 많이 달라졌다. &lt;b&gt;스프링쪽에서 굳이 권고하지않는 행동을 할 필요 없다는 글들을 좀 읽고 나니까 안하는게 낫겠거니&lt;/b&gt;하면서 방향을 틀었다.&lt;br /&gt;글을 쓴 이유도 어차피 전자에 가까운 마인드로 적었는데 오히려 찾고나니 후자로 좀 더 이야기가 변경되었다. 그리고 또한, 이렇게 되면 SonarQube의 룰이 이상한 것도 찾았기에 SonarQube쪽에도 해당하는 룰 변경 요청을 해볼까 생각중이다.&lt;br /&gt;그리고 SonarQube의 룰 변경에 대해서 해당 이슈의 논의가 진행된다면, 블로그에서 조금 더 이야기를 풀어 보겠다.&lt;/p&gt;</description>
      <category>Spring</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/167</guid>
      <comments>https://sundries-in-myidea.tistory.com/167#entry167comment</comments>
      <pubDate>Sat, 22 Jun 2024 20:23:07 +0900</pubDate>
    </item>
    <item>
      <title>Slack App Directory를 활용하여 Webhook보다 퀄 좋은 메세지 발송하기</title>
      <link>https://sundries-in-myidea.tistory.com/166</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;Legacy Integration....&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Slack 웹훅을 발송하면 구) 버젼을 이용하다보면 문제가 발생할 수 있는데, 해당하는 문제는 다음과 같다.&lt;br /&gt;&amp;rarr; 웹훅 만든사람이 나가면 그 웹훅이 비활성화된다는 것!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Incoming webhook 들어가면 경고 문구가 계속 뜨기 때문에 개발자인 이상 눈길이 갈 수 밖에 없다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2058&quot; data-origin-height=&quot;1168&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beetUJ/btsHnIQBErv/O9vwkNHKrOX0lbkqyWzgXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beetUJ/btsHnIQBErv/O9vwkNHKrOX0lbkqyWzgXk/img.png&quot; data-alt=&quot;아 ㅋㅋ 쓰지말라고 ㅋㅋ (이러면 개발자인 나는 다른걸 찾을 수 밖에 없어)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beetUJ/btsHnIQBErv/O9vwkNHKrOX0lbkqyWzgXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeetUJ%2FbtsHnIQBErv%2FO9vwkNHKrOX0lbkqyWzgXk%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;2058&quot; height=&quot;1168&quot; data-origin-width=&quot;2058&quot; data-origin-height=&quot;1168&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;그러면 App Directory로 이전하기 위해서는 어떤 기능을 활용해야하고, 어떻게 하면 퀄 좋은 메세지 발송해야할 수 있는지 확인해봅시다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;물론 Webhook도 쓸수 있다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹훅도 당연히 쓸 수 있다. 문제는 Webhook은 SaaS 서비스에서 양식을 제공하면 사실 필요없지만, 양식을 만들려면, Blockit을 활용해서 해당하는 양식을 만들 수 있는데, 단점이 하나 있다.&lt;br /&gt;웹훅의 메세지의 타이틀명이 App의 이름을 그대로 따라간다는 점이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1261&quot; data-origin-height=&quot;211&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IHESl/btsHmulYDYz/rWL2hMmk7IgVfLnocpmNNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IHESl/btsHmulYDYz/rWL2hMmk7IgVfLnocpmNNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IHESl/btsHmulYDYz/rWL2hMmk7IgVfLnocpmNNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIHESl%2FbtsHmulYDYz%2FrWL2hMmk7IgVfLnocpmNNK%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;1261&quot; height=&quot;211&quot; data-origin-width=&quot;1261&quot; data-origin-height=&quot;211&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;뭐 나쁘진 않다. 문제는 이러면 좀 짜쳐지는게 뭐냐면.. 뭔가 명칭을 위해서 Webhook용 App을 한 몇개씩 만들어야한다는 점이다. 내부용 앱을 몇개씩 쓰는건 사실 좀 별로다.&lt;br /&gt;그럼 이러한 문제를 해결할 방법은 뭘까?&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;전용 Web API를 쓰자&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Slack Web Api중 메세지를 발송하는 API가 존재한다. 해당하는 API는 chat.postMessage라는 API로, 해당하는 API는 해당 App에서 메세지를 발송하는데 도움을 준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1203&quot; data-origin-height=&quot;608&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSIOnm/btsHoGkmmTG/M4OQJ8nIONKsib3HUOypZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSIOnm/btsHoGkmmTG/M4OQJ8nIONKsib3HUOypZk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSIOnm/btsHoGkmmTG/M4OQJ8nIONKsib3HUOypZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSIOnm%2FbtsHoGkmmTG%2FM4OQJ8nIONKsib3HUOypZk%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;1203&quot; height=&quot;608&quot; data-origin-width=&quot;1203&quot; data-origin-height=&quot;608&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;문제는 해당하는 기능을 활용하기 위해서는 OAuth &amp;amp; Permissions의 Scope 기능을 활용해야한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;680&quot; data-origin-height=&quot;492&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LwJbH/btsHnheU8yB/dvaT09K5mBMJ6Ayknwcrc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LwJbH/btsHnheU8yB/dvaT09K5mBMJ6Ayknwcrc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LwJbH/btsHnheU8yB/dvaT09K5mBMJ6Ayknwcrc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLwJbH%2FbtsHnheU8yB%2FdvaT09K5mBMJ6Ayknwcrc1%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;680&quot; height=&quot;492&quot; data-origin-width=&quot;680&quot; data-origin-height=&quot;492&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;해당하는 기능에 맞는 API Scope를 등록하여, 해당 기능이 동작하는지 확인하면되는데, 해당 Scope를 등록하고 난뒤에는 사실 첫동작이 재대로 동작하지 않을수 있다. 이전 등록했던(첫 등록이면 상관 없겠지만) Scope의 상태가 그대로 유지되고 있고, 해당 앱이 설치되어 내 사용하고있는 Workspace에서 지속적으로 사용되니, ReInstall을 해줘야한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;990&quot; data-origin-height=&quot;84&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8zTQv/btsHnXGR4UM/5l5KWwYakZHKcIeLFsJ1zK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8zTQv/btsHnXGR4UM/5l5KWwYakZHKcIeLFsJ1zK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8zTQv/btsHnXGR4UM/5l5KWwYakZHKcIeLFsJ1zK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8zTQv%2FbtsHnXGR4UM%2F5l5KWwYakZHKcIeLFsJ1zK%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;990&quot; height=&quot;84&quot; data-origin-width=&quot;990&quot; data-origin-height=&quot;84&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당하는 API를 사용하게되면 기본 옵션으로는 이렇게 등록되어있지만 이름, 프로필 이미지를 바꿔치기 하고 싶다면?&lt;br /&gt;API의 옵션 값 중 &amp;rarr; &lt;b&gt;'username'&lt;/b&gt; 과 &lt;b&gt;icon_url&lt;/b&gt;을 변경을 통해서 해당하는 Message 수정 및 변경이 가능합니다. 참고로 Scope중 &lt;b&gt;chat:write.customize&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;246&quot; data-origin-height=&quot;164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wu3QM/btsHoeVU7cm/Tg0AE0OQxvd2bWP4i103Ak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wu3QM/btsHoeVU7cm/Tg0AE0OQxvd2bWP4i103Ak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wu3QM/btsHoeVU7cm/Tg0AE0OQxvd2bWP4i103Ak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fwu3QM%2FbtsHoeVU7cm%2FTg0AE0OQxvd2bWP4i103Ak%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;246&quot; height=&quot;164&quot; data-origin-width=&quot;246&quot; data-origin-height=&quot;164&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;P.S 메세지를 좋은 퀄리티로 만들기 위한 전략들&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 기본적으로 App Directory를 사용한다면, 구체적으로 메세지를 만드는 방법을 공유하고 있습니다.&lt;br /&gt;&lt;a href=&quot;https://app.slack.com/block-kit-builder&quot;&gt;https://app.slack.com/block-kit-builder&lt;/a&gt;&lt;br /&gt;에 들어가면 해당하는 메세지를 직접 작성할 수 있으며, 해당하는 형태를 바탕으로 Java Bolt를 바탕으로 객체를 만들어 내는 방식으로 구현하게 되면 상당히 편해집니다.&lt;/p&gt;
&lt;pre class=&quot;xl&quot;&gt;&lt;code&gt;import static com.slack.api.model.block.Blocks.*;
import static com.slack.api.model.block.composition.BlockCompositions.*;
import static com.slack.api.model.block.element.BlockElements.*;

Slack slack = Slack.getInstance();
String token = System.getenv(&quot;SLACK_TOKEN&quot;);

ChatPostMessageResponse response = slack.methods(token).chatPostMessage(req -&amp;gt; req
  .channel(&quot;C1234567&quot;)
  .blocks(asBlocks(
    section(section -&amp;gt; section.text(markdownText(&quot;*Please select a restaurant:*&quot;))),
    divider(),
    actions(actions -&amp;gt; actions
      .elements(asElements(
        button(b -&amp;gt; b.text(plainText(pt -&amp;gt; pt.emoji(true).text(&quot;Farmhouse&quot;))).value(&quot;v1&quot;)),
        button(b -&amp;gt; b.text(plainText(pt -&amp;gt; pt.emoji(true).text(&quot;Kin Khao&quot;))).value(&quot;v2&quot;))
      ))
    )
  ))
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Section에서 해당하는 메세지 형태를 계속 바꿔가는 방식으로 구현하면 해당하는 메세지를 구축하는데 큰 도움이 됩니다.&lt;/p&gt;</description>
      <category>Java</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/166</guid>
      <comments>https://sundries-in-myidea.tistory.com/166#entry166comment</comments>
      <pubDate>Sun, 12 May 2024 23:06:00 +0900</pubDate>
    </item>
    <item>
      <title>Hibernate 신기능 @SoftDelete 기능</title>
      <link>https://sundries-in-myidea.tistory.com/165</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot도 벌써 3.2.X버전대가 릴리즈 되었고, 그에 따라서 자동으로 하이버네이트도 마이너 버젼 업데이트가 되었습니다. 6.4 버젼부터 해당하는 기능을 사용할 수 있습니다. 해당하는 기능을 한번 사용해보고 싶어서 테스트 해봤습니다.&lt;br /&gt;일단 기본적으로 &lt;b&gt;스프링 부트는 3.2.1 부터 사용 가능&lt;/b&gt;할 수 있으므로 참고 부탁드립니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;기존에 사용하던 Soft Delete 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 Soft Delete기능을 사용하기 위해서는 개념적으로 사용해야하는 @Where절과 같은 기능을 사용하여, 삭제 컬럼에 대한 flag에 대한 조건을 엔티티 자체에 붙여서 사용해야했다.&lt;br /&gt;그리고 삭제조건을 자동으로 update절로 바꿔서 처리할 수 있는 @SqlDelete문 과 같은 어노테이션이 필요했다.&lt;br /&gt;즉, 개념적인 Soft Delete를 구현하기 위한 좀 귀찮은 작업이 많아지는 문제가 있었다. 일단 어노테이션을 두개 반드시 사용해야했고, 아니면 Soft Delete란 함수를 만들어서 직접 강제로 조작하는 방식을 취하는 것도 있긴하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;@Soft Delete가 생기고 달라진 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러개의 어노테이션을 등록하고 해당하는 방식이 Soft Delete인지 명확하지 않았던 기존 방식에서, 현재 방식은 &lt;code&gt;@SoftDelete&lt;/code&gt;라는 어노테이션 하나로 땡칠 수 있다.&lt;br /&gt;Class의 위에 떨렁 &lt;code&gt;@SoftDelete&lt;/code&gt;만 있으면 아래와 같은 작업이 자동으로 처리되어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컬럼이나, 클래스의 변수없이도 자동으로 클래스의 deleted 컬럼이 생성된다. 즉, 해당하는 값은 boolean과 같은 값으로 처리된다. 기본적으로는 false처리가 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1373&quot; data-origin-height=&quot;246&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5hcUe/btsGx22V2a7/lGisLWlpJplMsuh3YbYSc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5hcUe/btsGx22V2a7/lGisLWlpJplMsuh3YbYSc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5hcUe/btsGx22V2a7/lGisLWlpJplMsuh3YbYSc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5hcUe%2FbtsGx22V2a7%2FlGisLWlpJplMsuh3YbYSc1%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;1373&quot; height=&quot;246&quot; data-origin-width=&quot;1373&quot; data-origin-height=&quot;246&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;부가 기능&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 컬럼명 지정, Converter 사용&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 그렇겠지만 deleted라는 컬럼명을 사용한다면, 팀별로는 다르겠지만 &quot;DB 컬럼명 표준화&quot;를 제공하는 내부 룰이 있으면 해당하는 컬럼명을 사용할 수 없기 때문에, 컬럼명을 지정할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@SoftDelete(columnName = &quot;DEL_YN&quot;)

//이렇게 쓰자
@Column(name = &quot;DEL_YN&quot;, insertable = false, updatable = false)  
private boolean isDelete;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;hibernate에서 제공하는 기능이므로, 해당하는 컬럼은 직접 삽입 불가능하도록 구현해야한다.&lt;br /&gt;위 코드처럼 컬럼자체의 updatable, insertable을 이용하는 방법으로 컬럼 자체를 읽어올 수야는 있지만, 해당하는 컬럼이 유용하기 위해서는 true도 검색이 되야하는데 조회를 하면 deleted를 false인 컬럼만 조회하므로 당하는 컬럼의 유용성 자체는 없다고 본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 아래에서도 이야기 하겠지만, NativeQuery가 아닌 이상 거의 이컬럼은 비어있고, 실제로 객체적으로 채워지는 값이 아니다. 그래서 만약 내가 이 컬럼을 통해서 조회를 논리 로직을 만들때는 주의가 필요해보인다.&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;rarr; 자동으로 update 구문이 나간다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;897&quot; data-origin-height=&quot;78&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bp5BL6/btsGvUysI6s/XJwayiPXzIknEsu9MtvSZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bp5BL6/btsGvUysI6s/XJwayiPXzIknEsu9MtvSZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bp5BL6/btsGvUysI6s/XJwayiPXzIknEsu9MtvSZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbp5BL6%2FbtsGvUysI6s%2FXJwayiPXzIknEsu9MtvSZ1%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;897&quot; height=&quot;78&quot; data-origin-width=&quot;897&quot; data-origin-height=&quot;78&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 꼭 boolean으로 처리할 필요도 없다. 예를 들면 Varchar타입의 Y,N의 문자로 처리하고 있다면, 해당하는 컨버터를 이용해 해당하는 값을 변환하여 넣을 수 있다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@SoftDelete(converter = YesNoConverter.class, columnName = &quot;DEL_YN&quot;)  
//...
public class MemberYN implements Serializable {  

    //....

    @Column(name = &quot;DEL_YN&quot;, insertable = false, updatable = false)  
    private String delYn;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래를 보면 yn이 varchar type으로 생성되었다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2156&quot; data-origin-height=&quot;160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BZ7bD/btsGxDIZg5G/b4ooUmnlvk8Go8HrZEdwV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BZ7bD/btsGxDIZg5G/b4ooUmnlvk8Go8HrZEdwV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BZ7bD/btsGxDIZg5G/b4ooUmnlvk8Go8HrZEdwV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBZ7bD%2FbtsGxDIZg5G%2Fb4ooUmnlvk8Go8HrZEdwV1%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;2156&quot; height=&quot;160&quot; data-origin-width=&quot;2156&quot; data-origin-height=&quot;160&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그리고 삭제나, 생성시 Y, N으로 들어가게 된다. 생성은 N이고, 삭제는 Y로 변환해준다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1180&quot; data-origin-height=&quot;106&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6aE5L/btsGwtmIhDF/J15YaA3LMEtKQ7GE0UYyak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6aE5L/btsGwtmIhDF/J15YaA3LMEtKQ7GE0UYyak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6aE5L/btsGwtmIhDF/J15YaA3LMEtKQ7GE0UYyak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6aE5L%2FbtsGwtmIhDF%2FJ15YaA3LMEtKQ7GE0UYyak%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;1180&quot; height=&quot;106&quot; data-origin-width=&quot;1180&quot; data-origin-height=&quot;106&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 자식도 Soft Delete 처리 할 수 있음&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 자식도 soft Delete처리를 할 수 있는지 확인해보겠다. 일단 양방향으로 걸어서 해당하는 값이 삭제되는지 확인해보면?&lt;/p&gt;
&lt;pre class=&quot;groovy&quot;&gt;&lt;code&gt;public class MemberYN implements Serializable { 

@OneToMany(mappedBy = &quot;memberYN&quot;, cascade = CascadeType.ALL, orphanRemoval = true)  
private List&amp;lt;MembershipAddress&amp;gt; addresses = new ArrayList&amp;lt;&amp;gt;();&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;public class MembershipAddress {  

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

    private String addressName;  

    private String addressDetail;  

    @ManyToOne(fetch = FetchType.LAZY)  
    private MemberYN memberYN;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 테스트코드를 돌리면 sql에 자식 관계의 경우 hard delete처리 되버린다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Test
void yn컬럼이_삭제할때_Y로_들어갈것인지() {
    MemberYN memberYN = new MemberYN(&quot;test&quot;);
    em.persist(memberYN);
    em.remove(memberYN);
    em.flush();

    MemberYN find = em.find(MemberYN.class, memberYN.getTestNo());
    Object o = em.createNativeQuery(&quot;SELECT * FROM TEST_MEMYN WHERE test_no = 1&quot;, MemberYN.class).getSingleResult();
    assertThat(((MemberYN)o).getDelYn(), is(&quot;Y&quot;));
    List&amp;lt;MemberYN&amp;gt; delfind = em.createQuery(&quot;Select my from MemberYN my where my.testNo = 1&quot;, MemberYN.class).getResultList();
    assertThat(delfind.size(), is(0)); //JPQL은 soft delete조건이 먹는다. singleResult하믄 오류 나서 List형으로 수정.
    assertThat(find, nullValue());
}&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;1806&quot; data-origin-height=&quot;184&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5PGv4/btsGw7QXMXa/nDwpsTWdsPKeUHWla0K4t0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5PGv4/btsGw7QXMXa/nDwpsTWdsPKeUHWla0K4t0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5PGv4/btsGw7QXMXa/nDwpsTWdsPKeUHWla0K4t0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5PGv4%2FbtsGw7QXMXa%2FnDwpsTWdsPKeUHWla0K4t0%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;1806&quot; height=&quot;184&quot; data-origin-width=&quot;1806&quot; data-origin-height=&quot;184&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;자식도 그러면 Soft Delete처리하고 싶으면?&lt;br /&gt;동일하게 자식 entity에 softDelete 어노테이션을 입히면 동작한다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@SoftDelete
public class MembershipAddress {  

    @Id  
    @GeneratedValue(strategy = GenerationType.IDENTITY)  
    private Long addressNo;  &lt;/code&gt;&lt;/pre&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;1964&quot; data-origin-height=&quot;194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bxy8R8/btsGu0Mjcn8/ouwSgCXNYaM38aplJkzESk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bxy8R8/btsGu0Mjcn8/ouwSgCXNYaM38aplJkzESk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bxy8R8/btsGu0Mjcn8/ouwSgCXNYaM38aplJkzESk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbxy8R8%2FbtsGu0Mjcn8%2FouwSgCXNYaM38aplJkzESk%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;1964&quot; height=&quot;194&quot; data-origin-width=&quot;1964&quot; data-origin-height=&quot;194&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;둘 다 업데이트 처리가 된다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필드 &lt;code&gt;@softDelete&lt;/code&gt;처리도 가능은 한데... 되는 타입이 두개밖에 없다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;FIELD / METHOD, where it applies to the rows of an jakarta.persistence.&lt;b&gt;ElementCollection&lt;/b&gt; or jakarta.persistence.&lt;b&gt;ManyToMany&lt;/b&gt; table.&lt;br /&gt;두 타입은 활용도가 없었어가지고, 해당하는 값을 처리하는건 아래의 링크 예제를 참고 해보는 것도 도움이 될거라 본다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 긍정적인 지점들 몇개를 찾았다.&lt;br /&gt;&lt;b&gt;JPQL의 경우도 해당 JPQL절에 Where에 delete 조건 없이 걸어도 자동으로 delete값이 처리되어, @Query를 통해 값이 처리 된경우 해당하는 값을 매번처리 해주지 않아도 될것으로 보인다.&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;2064&quot; data-origin-height=&quot;422&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/caSBsk/btsGvUysLOM/2OWykqHyLV8YnmlDyBiLH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/caSBsk/btsGvUysLOM/2OWykqHyLV8YnmlDyBiLH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/caSBsk/btsGvUysLOM/2OWykqHyLV8YnmlDyBiLH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcaSBsk%2FbtsGvUysLOM%2F2OWykqHyLV8YnmlDyBiLH1%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;2064&quot; height=&quot;422&quot; data-origin-width=&quot;2064&quot; data-origin-height=&quot;422&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;delfind을 자세히보면 난 where절에 testNo = 1만 걸었지만...? 실제 쿼리는?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1760&quot; data-origin-height=&quot;200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k3Rln/btsGvr4jsav/vjRk9mQtu8VjHDjjwWYHC0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k3Rln/btsGvr4jsav/vjRk9mQtu8VjHDjjwWYHC0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k3Rln/btsGvr4jsav/vjRk9mQtu8VjHDjjwWYHC0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk3Rln%2FbtsGvr4jsav%2FvjRk9mQtu8VjHDjjwWYHC0%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;1760&quot; height=&quot;200&quot; data-origin-width=&quot;1760&quot; data-origin-height=&quot;200&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;and절에 yn절이 자동으로 먹어저 들어간다.&lt;br /&gt;물론 확인은 해봐야하는데... @SoftDelete만의 특성인건지 @Where절때문에 자동으로 And 조건이 들어가는지는 확인해봐야하겠다.&lt;br /&gt;연계글이 생기면, 여기에 남기겠다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제점 혹은 개선점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 Soft Delete는 boolean으로 하는것에 큰 의의는 없다만, &lt;b&gt;운영적으로 봤을때는 LocalDateTime으로 처리하는게 좀더 좋지 않나 싶다&lt;/b&gt;. false 플래그를 세우면, 해당하는 Soft delete의 완전 삭제 조건을 반드시 &quot;수정시간&quot;기준으로 잡아야하는데, 그게 진짜로 flag에 의해서 update이후 수정 안됬다는 보장을 하기가 어렵다고 본다. 만약 정책적으로 해당 엔티티의 정책으로 어떤 변수가 전체 수정되어서 해당하는 값이 수정되었다면? 처럼 수정일시는 안의 값이 하나라도 바뀌면 무조건 수정시간이 바뀌므로 강제하기가 어렵다.&lt;br /&gt;운영적으로 3년의 유예기간을 두고 실제로 삭제는 못하게 만들고, 배치로 소프트 삭제후 3년 이후의 값을 삭제한다던가와 같은 방식으로 처리할 수 있으므로, 운영적인 서비스 정책 그리고 외부 노출될 정책 문서에도 해당하는 값을 처리할 수 있으니 명확한 일시로 처리하는게 더 좋다고 본다. 문제는....&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 @SoftDelete는 LocalDateTime을 지원할 수 없는 상태이다. &lt;b&gt;아니 엄밀히 말하면, 컬럼이 Null을 통해서 처리해야하는 경우가 불가능하다.&lt;/b&gt;&lt;br /&gt;삭제일시가 필요한 경우 삭제 여부를 IS NULL을 통해 삭제 여부를 따져야하는데, 컬럼 컨버터가 Null을 허용하지 않는다!&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;686&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzQ4zz/btsGwd5tH8p/8d6hls2Zsjj0npmTkdwSkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzQ4zz/btsGwd5tH8p/8d6hls2Zsjj0npmTkdwSkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzQ4zz/btsGwd5tH8p/8d6hls2Zsjj0npmTkdwSkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzQ4zz%2FbtsGwd5tH8p%2F8d6hls2Zsjj0npmTkdwSkK%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;686&quot; data-origin-width=&quot;1894&quot; data-origin-height=&quot;686&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그렇다면, 이렇게 null을 통해 일시처리는 불가능하다고 봐야할듯 싶다. 아쉬운 지점이다. hibernate에 한번 개선 문의를 남겨보고 싶긴하다.&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://www.baeldung.com/java-hibernate-softdelete-annotation&quot;&gt;https://www.baeldung.com/java-hibernate-softdelete-annotation&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://docs.jboss.org/hibernate/orm/6.5/javadocs/org/hibernate/annotations/SoftDelete.html#strategy()&quot;&gt;https://docs.jboss.org/hibernate/orm/6.5/javadocs/org/hibernate/annotations/SoftDelete.html#strategy()&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://ohksj77.tistory.com/249&quot;&gt;https://ohksj77.tistory.com/249&lt;/a&gt;&lt;br /&gt;ㄴlocalDateTime에 대한 이야기를 좀 나눴고, 글쓰는데 도움을 받았습니다.&lt;/p&gt;
&lt;figure id=&quot;og_1712738602433&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;SoftDelete (Hibernate Javadocs)&quot; data-og-description=&quot;(Optional) Conversion to apply to determine the appropriate value to store in the database. The &amp;quot;domain representation&amp;quot; can be: true Indicates that the row is considered deleted false Indicates that the row is considered NOT deleted By default, values are &quot; data-og-host=&quot;docs.jboss.org&quot; data-og-source-url=&quot;https://docs.jboss.org/hibernate/orm/6.5/javadocs/org/hibernate/annotations/SoftDelete.html#strategy()&quot; data-og-url=&quot;https://docs.jboss.org/hibernate/orm/6.5/javadocs/org/hibernate/annotations/SoftDelete.html#strategy()&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.jboss.org/hibernate/orm/6.5/javadocs/org/hibernate/annotations/SoftDelete.html#strategy()&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.jboss.org/hibernate/orm/6.5/javadocs/org/hibernate/annotations/SoftDelete.html#strategy()&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;SoftDelete (Hibernate Javadocs)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;(Optional) Conversion to apply to determine the appropriate value to store in the database. The &quot;domain representation&quot; can be: true Indicates that the row is considered deleted false Indicates that the row is considered NOT deleted By default, values are&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.jboss.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1712738600675&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Hibernate 6.4의 @SoftDelete 사용법 탐구&quot; data-og-description=&quot;soft-delete-hibernate hibernate가 제공하는 @SoftDelete 어노테이션을 알아보자 [hibernate 6.4.4 버전 기준으로 MySQL과 함께 테스트 했으며 6.4 버전부터 도입된 어노테이션이다.] @SoftDelete JavaDoc 다음 어노테이&quot; data-og-host=&quot;ohksj77.tistory.com&quot; data-og-source-url=&quot;https://ohksj77.tistory.com/249&quot; data-og-url=&quot;https://ohksj77.tistory.com/249&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bpciKI/hyVMZRGMG4/plPu4dGfxO9CB2m7gmMFxk/img.png?width=597&amp;amp;height=194&amp;amp;face=0_0_597_194,https://scrap.kakaocdn.net/dn/ekvatx/hyVMP9nnvP/DRxRNa49QNXq5CYMCj9tX1/img.png?width=597&amp;amp;height=194&amp;amp;face=0_0_597_194&quot;&gt;&lt;a href=&quot;https://ohksj77.tistory.com/249&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ohksj77.tistory.com/249&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bpciKI/hyVMZRGMG4/plPu4dGfxO9CB2m7gmMFxk/img.png?width=597&amp;amp;height=194&amp;amp;face=0_0_597_194,https://scrap.kakaocdn.net/dn/ekvatx/hyVMP9nnvP/DRxRNa49QNXq5CYMCj9tX1/img.png?width=597&amp;amp;height=194&amp;amp;face=0_0_597_194');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Hibernate 6.4의 @SoftDelete 사용법 탐구&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;soft-delete-hibernate hibernate가 제공하는 @SoftDelete 어노테이션을 알아보자 [hibernate 6.4.4 버전 기준으로 MySQL과 함께 테스트 했으며 6.4 버전부터 도입된 어노테이션이다.] @SoftDelete JavaDoc 다음 어노테이&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ohksj77.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Test코드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/ventulus95/softdelete-test/&quot;&gt;https://github.com/ventulus95/softdelete-test&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1712738601766&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - ventulus95/softdelete-test: hibernate 6.4 @softdelete 값 처리&quot; data-og-description=&quot;hibernate 6.4 @softdelete 값 처리. Contribute to ventulus95/softdelete-test development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/ventulus95/softdelete-test/&quot; data-og-url=&quot;https://github.com/ventulus95/softdelete-test&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bGxItj/hyVMP9nnxu/0jWSQQzlPW8mdQPZXspwBK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/ventulus95/softdelete-test/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/ventulus95/softdelete-test/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bGxItj/hyVMP9nnxu/0jWSQQzlPW8mdQPZXspwBK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - ventulus95/softdelete-test: hibernate 6.4 @softdelete 값 처리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;hibernate 6.4 @softdelete 값 처리. Contribute to ventulus95/softdelete-test development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring/JPA</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/165</guid>
      <comments>https://sundries-in-myidea.tistory.com/165#entry165comment</comments>
      <pubDate>Wed, 10 Apr 2024 17:45:54 +0900</pubDate>
    </item>
    <item>
      <title>클린 코드 - 1. 객체 지향 생활 체조</title>
      <link>https://sundries-in-myidea.tistory.com/164</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;클린코드 with java 17기를 들었던 경험 및 거기서 피드백 받았던 내용을 공유를 하려합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 10월 30일부터 12월 31일까지 클린코드 강의를 코드리뷰, 강의를 끝까지 완료를 했습니다. 실제 강의 일자는 6주 과정이였지만, 업무 + 과제 까지 다해내게기엔 굉장히 빡센 과정이였지만... 배운점은 있었고, 다 아는 내용일지언정, 공유는 드리는 것이 좋아보여 전달을 드립니다.&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;b&gt;겍체지향 생활체조를 지켜 보는것이 왜 중요한가?&lt;/b&gt; (저도 못지키지만...)&lt;br /&gt;생활체조 원칙은 좋은 품질의 소프트웨어를 만들기 위한 &lt;b&gt;응집도(cohension), 느슨한 결합(loose coupling), 무중복(zero duplication), 캡슐화(encapsulation), 테스트 가능성(testability), 가독성(readability), 초점(focus)&lt;/b&gt;을 실현하기 위한 훈련 지침이다.&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;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;한 메서드에 오직 한 단계의 들여쓰기만 한다.&lt;/li&gt;
&lt;li&gt;else 예약어(keyword)를 쓰지 않는다.&lt;/li&gt;
&lt;li&gt;모든 원시값과 문자열을 포장(wrap)한다.&lt;/li&gt;
&lt;li&gt;한 줄에 점을 하나만 찍는다.&lt;/li&gt;
&lt;li&gt;줄여쓰지 않는다(축약 금지).&lt;/li&gt;
&lt;li&gt;모든 엔티티(entity)를 작게 유지한다.&lt;/li&gt;
&lt;li&gt;2개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다.&lt;/li&gt;
&lt;li&gt;제일 클래스(first-class) 콜렉션을 쓴다.&lt;/li&gt;
&lt;li&gt;게터(getter)/세터(setter)/프로퍼티(property)를 쓰지 않는다.&lt;/li&gt;
&lt;/ol&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;br /&gt;중요한건 묶음 단위로 처리가 가능한점이며, 해당하는 내용들은 100% 적용하기 힘든 점도 있다. 한점만 찍는다던가. getter setter를 무조건 쓰지말고 코딩하는것은 원칙적으로 고수하는 방법은 어렵습니다.&lt;br /&gt;완전히 지키는 건 어렵더라도, 해당하는 것들을 지키면 좋았던 것들에 대해서 이야기 해보려 합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;당장 지키기 좋은 클린 코드 전략들&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;Early Return / else 사용하지 않기. &amp;rarr; indent를 줄여내려가자 (1,2)&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제일 쉬운 방법입니다. &lt;b&gt;일단 기본적으로 Else를 사용하지 않는 것입니다&lt;/b&gt;. if를 통해 조건을 분기 처리하며, 해당하는 조건을 만족한 경우 분기 처리 이후 return을 통해 다른 분기를 삭제하는 방향으로 처리합니다.&lt;br /&gt;해당하는 전략을 Early Return 이라 부르며, 해당하는 방식으로 처리하는 경우 Else가 없어지는 형태로 구현하기 쉬워집니다.&lt;br /&gt;장점: 체조 1장,2장에 동시에 처리가 가능한 부분으로써 if절이 줄면 줄수록 함수로 빼면 뺄수록 indent가 줄어들어 로직 이해해 도움을 줄 수 있습니다.&lt;br /&gt;단점: 함수로 모두 다 빼게되면 모든 indent가 사라지는 장점은 있으나, 코드의 가독성이 떨어집니다. 간단한 조건도 모두 함수로 만들게되면 각 함수에 대한 조건들을 10라인 밑에서 찾는 경우가 있으니, 뭐 적절하게 섞어서 사용하는걸 추천합니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;2&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;상수화 처리하기. (Magic Number를 없에자.)&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 제가 많이 하는 것으로 조건중 숫자가 있는 경우 그 숫자를 상수 취급하여, 해당하는 숫자를 java로는 static final를 통해 상수처리를 하게됩니다.&lt;br /&gt;하드코딩으로 만들어진 숫자인 Magic Number는 코드를 작성한 당사자는 알수 있겠으나, 그외의 개발자에게는 그 숫자가 무엇을 뜻하는지에 대해서는 알 수 없게됩니다. 해당 코드에 대한 로직의 이해도가 떨어지게되며, 백로그를 찾으러 다녀야하는 문제 역시 발생합니다.&lt;br /&gt;아래의 예제를 보면서 확인해봅시다. 상수화는 magic Number를 없에는데 큰 도움을 줍니다. 이런 코드의 이런 숫자가 뭘 뜻하는지... 알 수있을까? 그리고 만약에 바꿔야한다면? 바꾸더라도 다른곳에서는 그 값이 안 바뀌었다면?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;elapsedTime &amp;gt;= 10000&lt;/b&gt; 혹은 &lt;b&gt;threadTitle.length &amp;lt;= 100&lt;/b&gt; 당장은 이해하기 쉽긴하겠지만... 상수로 되있으면 코드를 바꿀일은 없다. 이렇게 처리해도됨. ELAPSED__MAX_TIME = 10_000일때 20_000으로만 바꿔도 실제 코드는 변경되지 않기때문에 훨씬 수정이 용이하다.&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;3. 리턴하는 함수를 클래스로 만들자.&lt;/b&gt;&lt;br /&gt;코드 리뷰에서 종종 이야기들었던 내용중 하나입니다. 아래의 일급 컬렉션 적용이 잘되있을수록 유리한 접근입니다. 일종의 함수 Return 타입을 클래스나 Wrapper 타입으로 전달받는 것입니다.&lt;br /&gt;클래스안의 상태값이 없어지게됩니다. 일급 컬렉션과 일급 클래스와 동일한 이야기이지만,&lt;br /&gt;클래스안에 인스턴스 변수가 많아지면 많아질 수록 일종의 상태값이 계속 생기게됩니다. 그럼 자동으로 getter를 사용해야할일이 늘며, 비즈니스 로직이 밖으로 노출될 가능성이 높아진다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public class LottoGame {
    private List&amp;lt;Lotto&amp;gt; lottos = new ArrayList&amp;lt;&amp;gt;();

    public void buyLotto(int price) {  //로또사기 함수를 만들때 우리는 이렇게 할수도 있다. 
        int gameCount = getGameCount(price);
        while(countLotto() &amp;lt; gameCount) {
            Lotto lotto = new Lotto(makingLottoNumbers());
            lottos.add(lotto);
        }
    }
}

//로직을 사용하기위해서는..
lottoGame.getLottos().stream().filter() 처럼 장황한 코드를 작성할 가능성이 높아진다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주로 저는 애매한 경우 적용하긴했는데, 이러는 것보다는... 아래처럼 적용하면&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public class LottoGame {
    //private List&amp;lt;Lotto&amp;gt; lottos = new ArrayList&amp;lt;&amp;gt;(); -&amp;gt; 이 부분이 삭제된다. 

    public List&amp;lt;Lotto&amp;gt; buyLotto(int price) { 
        int gameCount = getGameCount(price);
        while(countLotto() &amp;lt; gameCount) {
            Lotto lotto = new Lotto(makingLottoNumbers());
            lottos.add(lotto);
        }
        return lottos;
    }
}

// 해당 코드 사용시
List&amp;lt;Lotto&amp;gt; lottos = lottoGame.buyLotto() 
lottos.stream().filter()
비슷해보이지만 사실 큰 차이를 만드는 점은 다음과 같다.&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;일급컬렉션에서도 이야기하겠지만. 이러면 Lotto라는 객체를 클래스화한다음에 장황한 코드들을 뒤로 숨길 수 있다.&lt;br /&gt;당첨된 로또만 가져오기 &amp;rarr; &lt;code&gt;stream.filter()&lt;/code&gt;를 아예 메소드화 시켜서 빼버리면 장황한 코드가 함수하나로 표현이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 일급 컬렉션에 대한 내용은 묶어서 좀 더 풍성하게 구현해보겠습니다.&lt;/p&gt;</description>
      <category>Java</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/164</guid>
      <comments>https://sundries-in-myidea.tistory.com/164#entry164comment</comments>
      <pubDate>Sun, 31 Mar 2024 21:50:22 +0900</pubDate>
    </item>
    <item>
      <title>헉, 이거 나만 몰랐나? 3편 - 알아두면 회사에서 유용한 Intellij 단축키</title>
      <link>https://sundries-in-myidea.tistory.com/163</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;신입으로 들어간 내가 종종 시니어 팀원분들에게 개발한 것을 리뷰를 받기 위해 종종 코드를 보여주다보면, 종종 그... ctrl + G 키 눌러봐~ 아니면 그 메소드를 사용하는 다른 곳이 있는지를 확인을 해봐처럼 응애 신입 개발자이기전에는 한 번을 안써봤던 단축키를 이야기해보려 하는데요.&lt;br /&gt;하지만, 이런 단축키들은 굉장히 유용하므로, 해당하는 단축키를 잘 사용하면 선배님들에게 특급 칭찬을 받을 수 있지 않을까요?&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1. 사용 위치 찾기 (Find Usages)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼도우 (&lt;code&gt;Alt&lt;/code&gt;+ &lt;code&gt;F7&lt;/code&gt;) / 맥 (&lt;code&gt;Alt&lt;/code&gt; + &lt;code&gt;F7&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;742&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lJmml/btsFm25SBVv/Yzkqu4aKYbf8n2SCheSE81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lJmml/btsFm25SBVv/Yzkqu4aKYbf8n2SCheSE81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lJmml/btsFm25SBVv/Yzkqu4aKYbf8n2SCheSE81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlJmml%2FbtsFm25SBVv%2FYzkqu4aKYbf8n2SCheSE81%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;896&quot; height=&quot;742&quot; data-origin-width=&quot;896&quot; data-origin-height=&quot;742&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&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;3482&quot; data-origin-height=&quot;1292&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cp79Qc/btsFnYvpw50/3zA2GE4vbEBzEUOSUBwUj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cp79Qc/btsFnYvpw50/3zA2GE4vbEBzEUOSUBwUj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cp79Qc/btsFnYvpw50/3zA2GE4vbEBzEUOSUBwUj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcp79Qc%2FbtsFnYvpw50%2F3zA2GE4vbEBzEUOSUBwUj1%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;3482&quot; height=&quot;1292&quot; data-origin-width=&quot;3482&quot; data-origin-height=&quot;1292&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;라인과 해당하는 사용 위치를 알려주는 역할을 한다. 옆에 해당하는 코드가 어디에서 사용하고 있으며 , 위치도 파악 가능해지므로, 해당하는 메소드가 어디서에서 사용하며, 내가 만약 이 코드를 수정했을때 어디가 영향을 받을지를 미리 확인 할 수 있는 점도 도움을 줄 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2. 파일에서 찾기 (Find in files...)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;윈도우 (&lt;code&gt;ctrl&lt;/code&gt; + &lt;code&gt;shift&lt;/code&gt; + &lt;code&gt;f&lt;/code&gt;) / 맥북 (&lt;code&gt;cmd&lt;/code&gt;+ &lt;code&gt;shift&lt;/code&gt; + &lt;code&gt;f&lt;/code&gt;)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2406&quot; data-origin-height=&quot;1492&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cn73fS/btsFm6UytKY/IwckQXkN0UdkOjusruJws1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cn73fS/btsFm6UytKY/IwckQXkN0UdkOjusruJws1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cn73fS/btsFm6UytKY/IwckQXkN0UdkOjusruJws1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcn73fS%2FbtsFm6UytKY%2FIwckQXkN0UdkOjusruJws1%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;2406&quot; height=&quot;1492&quot; data-origin-width=&quot;2406&quot; data-origin-height=&quot;1492&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;파일에서 찾기 기능의 경우 해당하는 코드를 밑에서 보면서 확인이 가능하다. 해당하는 코드를 보면서 이 함수가 어디서 사용하는지 확인이 가능한데, &lt;b&gt;내가 이 메소드를 구현하다가 다른 작업후 다시 그 메소드가 있는 파일로 바로 가고 싶을때&lt;/b&gt; 많이 사용했던 기능중 하나이다.&lt;br /&gt;해당 사용 위치와 다른점이라면, 위의 사용하는 위치는 일단 내가 작성한 메소드 혹은 변수가 어디서 사용할지를 더 집중한다면, 이 파일에서 찾기의 경우는 조금 더 코드 기반에 가깝게 찾을 수 있다. 예를 들자면 이런건데...&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1336&quot; data-origin-height=&quot;1266&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PlJKH/btsFofRhbAy/pGfknAosvmX5FqkzwrQNY0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PlJKH/btsFofRhbAy/pGfknAosvmX5FqkzwrQNY0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PlJKH/btsFofRhbAy/pGfknAosvmX5FqkzwrQNY0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/PlJKH/btsFofRhbAy/pGfknAosvmX5FqkzwrQNY0/img.gif&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;1336&quot; height=&quot;1266&quot; data-origin-width=&quot;1336&quot; data-origin-height=&quot;1266&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;더 구체적으로 해당하는 값들을 탐색하는 역할이 있어서 해당기능을 조금더 많이 사용한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;3. 최근 파일&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;윈도우 (&lt;code&gt;ctrl&lt;/code&gt; + &lt;code&gt;E&lt;/code&gt;) / 맥북 (&lt;code&gt;Cmd&lt;/code&gt; + &lt;code&gt;E&lt;/code&gt;)&lt;br /&gt;최근 닫은 파일을 확인할 수 있다. 해당기능은 창을 닫았거나 Tab의 밀려서 안보이는 경우 해당하는 탭을 찾아야할때 중요하게 사용할 수 있는 기능이여서 많이 사용했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;1038&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMXXqq/btsFvZzms4c/acPM81KhK67jfaegHqiXx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMXXqq/btsFvZzms4c/acPM81KhK67jfaegHqiXx1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMXXqq/btsFvZzms4c/acPM81KhK67jfaegHqiXx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMXXqq%2FbtsFvZzms4c%2FacPM81KhK67jfaegHqiXx1%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;996&quot; height=&quot;1038&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;1038&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 동일 버튼을 눌러 다시 편집된 항목만 표시할 수도 있다. 편집한 파일만 보여주는 것으로 바뀌게 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;4. 리펙토링 기능 (Refactor This)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;윈도우 (&lt;code&gt;Ctrl&lt;/code&gt; + &lt;code&gt;Alt&lt;/code&gt; + &lt;code&gt;Shift&lt;/code&gt;+ &lt;code&gt;T&lt;/code&gt;) / 맥북 (&lt;code&gt;ctrl&lt;/code&gt; + &lt;code&gt;t&lt;/code&gt;)&lt;br /&gt;해당 기능은 리펙토링을 하고 싶은 값이 있긴한데, 해당하는 단축키가 생각나지 않을때 사용한다. 해당하는 단축어만 있으면 팝업창이 뜨고, 해당하는 값에 맞춰서 내 리펙토링 형태를 선택하면 되는 구조이다.&lt;br /&gt;변수로 빼기, 메소드로 변환 하기와 같은 기능들을 뭐 당연히 단축키를 기억해낼 수 있기야 하지만, 해당기능을 사용하면, 훨씬 편리하게 리펙토링을 활용할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;506&quot; data-origin-height=&quot;718&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzrwFx/btsFlQY6jcM/4LIhvsK55JTtalUvckFLg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzrwFx/btsFlQY6jcM/4LIhvsK55JTtalUvckFLg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzrwFx/btsFlQY6jcM/4LIhvsK55JTtalUvckFLg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzrwFx%2FbtsFlQY6jcM%2F4LIhvsK55JTtalUvckFLg1%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;506&quot; height=&quot;718&quot; data-origin-width=&quot;506&quot; data-origin-height=&quot;718&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;참고로 해당하는 내가 선택한 블럭에 따라서 선택하는 형태가 달라지기도 한다. 메소드만 선택되어있으면...&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;406&quot; data-origin-height=&quot;372&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqqAK6/btsFp6M0G4w/1BqT82pTiwwtAmcujsDui1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqqAK6/btsFp6M0G4w/1BqT82pTiwwtAmcujsDui1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqqAK6/btsFp6M0G4w/1BqT82pTiwwtAmcujsDui1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqqAK6%2FbtsFp6M0G4w%2F1BqT82pTiwwtAmcujsDui1%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;406&quot; height=&quot;372&quot; data-origin-width=&quot;406&quot; data-origin-height=&quot;372&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;처럼 그 형태에 맞는 팝업과 값이 떠오르므로 취사 선택하면 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. 매개변수 정보 (Parameter Info)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;윈도우 (Ctrl + P) / 맥 (Cmd + P)&lt;/p&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;1728&quot; data-origin-height=&quot;228&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwfyaO/btsFvXas3O2/puieMikNu4kpPdDMm5flSK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwfyaO/btsFvXas3O2/puieMikNu4kpPdDMm5flSK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwfyaO/btsFvXas3O2/puieMikNu4kpPdDMm5flSK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwfyaO%2FbtsFvXas3O2%2FpuieMikNu4kpPdDMm5flSK%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;1728&quot; height=&quot;228&quot; data-origin-width=&quot;1728&quot; data-origin-height=&quot;228&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;만약 매개 변수가 여러개라면 다음처럼 여러개가 뭔지도 알 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1770&quot; data-origin-height=&quot;282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGIKo5/btsFocts920/Bn1EvFEOmBC0ZnKH63nRk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGIKo5/btsFocts920/Bn1EvFEOmBC0ZnKH63nRk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGIKo5/btsFocts920/Bn1EvFEOmBC0ZnKH63nRk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGIKo5%2FbtsFocts920%2FBn1EvFEOmBC0ZnKH63nRk0%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;1770&quot; height=&quot;282&quot; data-origin-width=&quot;1770&quot; data-origin-height=&quot;282&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;또한 아래처럼 현재 파라미터가 뭘 뜻하는지도 보여줄 수도 있습니다. 실시간으로 반응하기도 하구요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2256&quot; data-origin-height=&quot;398&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cN16lJ/btsFt3INi7w/dMfy2laoYD3naEJ0fWYKG0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cN16lJ/btsFt3INi7w/dMfy2laoYD3naEJ0fWYKG0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cN16lJ/btsFt3INi7w/dMfy2laoYD3naEJ0fWYKG0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cN16lJ/btsFt3INi7w/dMfy2laoYD3naEJ0fWYKG0/img.gif&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;2256&quot; height=&quot;398&quot; data-origin-width=&quot;2256&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;h3 data-ke-size=&quot;size23&quot;&gt;마무리하며~~&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당하는 기능을 활용해서 조금이라도 효과적인 개발 생활을 해봤으면 좋겠습니다. 선배들에게도 조금 더 빠릿하게 기능을 찾을 수 있거나 해당하는 것들이 어디있는지 파악하기에도 쉬우니까요!&lt;/p&gt;</description>
      <category>헉, 이거 나만 몰랐나?</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/163</guid>
      <comments>https://sundries-in-myidea.tistory.com/163#entry163comment</comments>
      <pubDate>Sun, 3 Mar 2024 01:22:44 +0900</pubDate>
    </item>
    <item>
      <title>Stream을 Null Safety하게 사용하기</title>
      <link>https://sundries-in-myidea.tistory.com/162</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Java는 Null safety가 중요하다&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거두절미하고 이야기하겠다. 당연히 나는 이 코드가 동작할거라 생각했다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;names.stream().filter(Objects::nonNull).forEach(System.out::println);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 NPE가 발생한다. 응? 당연히 잘되는게 아닌가 싶지만...&lt;br /&gt;우리는 names가 List 타입인거 까지는 아는데.... 그게 어떤 값일지 모르기 때문에 발생하는 문제이다. 만약 names가 null이라면? &amp;rarr; 이때 NPE를 발생하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;심지어 이건 JPA에서도 발생하는 문제인데..&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;이런 방법을 해결하기 위한 방법?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하다. Optional로 Wrapping하는 거다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;Optional.ofNullable(names).orElseGet(ArrayList::new)
    .stream()
    .filter(Objects::nonNull)
    .forEach(System.out::println);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 좀더 Stream을 조금 더 ElseGet을 활용하기 위해서는 이렇게 써도 좋다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;Optional.ofNullable(names).orElseGet(Collections::emptyList)
    .stream()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 문제는 이게... Stream 사용할때 매번 이렇게 &lt;code&gt;Optional&lt;/code&gt;을 감싸서 쓰는건 매우 귀찮은 일이다.&lt;br /&gt;그럼 함수를 아예 만들어서 사용하자.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;public static &amp;lt;T&amp;gt; Stream&amp;lt;T&amp;gt; nullSafeStream(final Collection&amp;lt;T&amp;gt; collection) {
    return Optional.ofNullable(collection)
            .orElseGet(Collections::emptyList).stream();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 방어용 Stream을 만들어쓰면 NPE 발생을 막을 수 있다.&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;code&gt;Compile Error&lt;/code&gt;로 문제가 터지는게 아니다. 일단 List자체가 null인 경우에 발생하는 것이기 때문에, 무조건 &lt;code&gt;Runtime Exception&lt;/code&gt;이 발생한다. 즉, 내가 null인 상황을 확실히 컨트롤 할 수 있다면야.. 문제가 없다고 보지만...&lt;br /&gt;실무 상황에서 솔직히 null이 안 발생하냐 발생하냐를 완전히 컨트롤 하긴 쉽지 않을거라고 본다. 레거시 코드를 죄다 null이 불가능하도록 코드를 고치거나 해야하는데.. 이건 좀 쉬운 일은 아닐거라 본다. 레거시 코드를 함부로 막 고치다가 엄한곳에서 이상한 오류가 발생하던 경험이 몇번 있어서, 차라리 방어 코드를 설치하는게 좋다고 생각한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;후일담&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA에서는 어떤식으로 처리해야지 다음과 같은 상황을 막을 수 있을까 조금씩 수정해서 글좀 작성해보겠다.&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://sigmasabjil.tistory.com/43&quot;&gt;https://sigmasabjil.tistory.com/43&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://www.baeldung.com/java-null-safe-streams-from-collections&quot;&gt;https://www.baeldung.com/java-null-safe-streams-from-collections&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://stackoverflow.com/questions/41590134/null-safe-collection-as-stream-in-java-8&quot;&gt;https://stackoverflow.com/questions/41590134/null-safe-collection-as-stream-in-java-8&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;http://gist.githubusercontent.com/felipebelluco/defa683a7f9d437488d3a46e72665af2/raw/38ef1aec18eb845aab49023c05c77f59abc0c37f/Main.java&quot;&gt;http://gist.githubusercontent.com/felipebelluco/defa683a7f9d437488d3a46e72665af2/raw/38ef1aec18eb845aab49023c05c77f59abc0c37f/Main.java&lt;/a&gt;&lt;/p&gt;</description>
      <category>Java</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/162</guid>
      <comments>https://sundries-in-myidea.tistory.com/162#entry162comment</comments>
      <pubDate>Sat, 17 Feb 2024 22:54:00 +0900</pubDate>
    </item>
    <item>
      <title>@JsonInclude란?</title>
      <link>https://sundries-in-myidea.tistory.com/161</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;일단 글또 5주차를 맞이하여 테크테크스러운 글을 많이 작성하고 있다. 특히 요즘에는 별다른건 아니고, Jackson에 좀 꽂혀서 요즘 Serialize 혹으 Deserialize하는 방식에 대해서 어떤식으로 작동하는 거고 해당하는 코드들이 어떤 일련의 과정 속에서 움직이는 게 궁금했었다.&lt;br /&gt;일단 기본적으로 Include를 많이 사용은 하는데 어떤 상황에서 사용하는게 좋을지 취사선택해야하는 경우가 많은데, 해당하는 값을 어떤식으로 처리하는게 좋을지 궁금해졌다.&lt;br /&gt;사실은 Include nonNull 동작원리를 찾다가 동작방식을 찾아내질 못해서, 다른 주제로 선회하긴했다. 찾아보니까 이렇게 딥한 내용이었나 싶어서 좀 더 구체적으로 테스트 해봐도 좋겠다싶었다&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JsonInclude란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 JsonInclude라는 녀석은 무슨 녀석인가 하면, 이친구는 Json으로 Serialize변환될 때 이제 Dto의 값에따라서 이값을 넣을지? 아니면 뺄건지에 대해 결정하는 녀석으로 보면 된다.&lt;br /&gt;해당하는 옵션은 다음처럼 총 5개이상이 있다.&lt;br /&gt;조금 더 자세하게 JavaDoc을 찾아봤는데, 꽤 흥미롭다.&lt;br /&gt;그래서 각각의 테스트를 좀 더 깊게 뒤적거려봤다. 테스트코드도 작성해봤다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;ALWAYS&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;속성값의 상관 없이 속성이 항상 포함되는 것&lt;br /&gt;기본적으로는 이친구가 &quot;&lt;b&gt;기본값&lt;/b&gt;&quot;이다&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public class PojoDefault {  

    private String string;  
    private Long _long;  
    private Boolean aBoolean;  
    private Integer integer;  
    private LocalDateTime localDateTime;  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본값이 없이 만들면 일단 기본적으로는 null 형태로 쭉 들어간다.&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt; {
  &quot;string&quot; : null,
  &quot;_long&quot; : null,
  &quot;integer&quot; : null,
  &quot;localDateTime&quot; : null,
  &quot;aboolean&quot; : null
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;NON_NULL&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;null이면 Json에서 빠지게됨.&lt;br /&gt;일단 이 옵션을 키게되면 null이면 아예 빠지게된다. 뭐 null이면 애초에 빠지는 옵션이므로, 직관적으로 파악할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;NON_ABSENT&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값이 없는 프로퍼티가 포함됨.&lt;br /&gt;Optional, AtomicReference가 null이 아닌 값일때는 들어가게됨.&lt;/p&gt;
&lt;pre class=&quot;typescript&quot;&gt;&lt;code&gt;@Builder  
@Getter  
public class PojoDefaultAbsent {  

    private String string;  
    private Long _long;  
    private Boolean aBoolean;  
    private Integer integer;  
    private LocalDateTime localDateTime;  
    private Optional&amp;lt;String&amp;gt; stringOptional;  
    private AtomicInteger atomicInteger;  

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이걸 만들면 값이 null로 모두 들어가게되는데, 일단 이 상태에서는 아예 빈 Json이 나오게된다.&lt;br /&gt;문제는 Optional과 AtomicInteger.. 즉, AtomicReference는 형태가 다르게 나오게 되는데.. 다음처럼 나오게 된다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;// Optional.ofNullable(null)으로 값을 넣기만 해도...
{
  &quot;stringOptional&quot; : {
    &quot;empty&quot; : true,
    &quot;present&quot; : false
  }
}
//atomic 객체가 만들어졌다면? new AtomicInteger()
{
  &quot;atomicInteger&quot; : 0
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 통해서 일단 값이 들어가게되는 Optional의 경우는 주의해서 사용해야할 필요가 있다. 그리고 DTO의 형태에서 Optional을 지양하는 글들을 많이 봤는데 직접적으로 왜 그런지도 알거 같았다. 일단 Optional을 기본적으로 이상하게 처리한다. 뭐 empty니.. present니.. Json 형태가 기이해지므로 최대한 기본값이 있는 형태로 만드는 게 훨씬 효과적이라는 생각이 들었다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;NON_EMPTY&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;값이 0이거나 비어 있는 것으로 간주되는 속성만 포함하지 않도록 지정&lt;/li&gt;
&lt;li&gt;일단 &lt;b&gt;비어있는 것에 대한 기준&lt;/b&gt;은 다음과 같다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Null 값.&lt;/li&gt;
&lt;li&gt;&quot;부재중&quot; 값(NON_ABSENT 참조)&lt;br /&gt;를 기본값으로 설정하여 &quot;비어 있음&quot; 집합에 NON_NULL 및 NON_ABSENT에서 제외되는 값이 포함되도록 합니다.&lt;br /&gt;이 기준 외에도 다음 유형에는 추가 빈 값이 있습니다:&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;collection이나 map의 경우, &lt;b&gt;isEmpty를 호출해서true인 경우&lt;/b&gt; 뺌&lt;/li&gt;
&lt;li&gt;Java 배열의 경우, &lt;b&gt;빈 배열은 길이가 0인 배열&lt;/b&gt;입니다. 배열 길이 가지고 판단.&lt;/li&gt;
&lt;li&gt;Java 문자열의 경우, &lt;b&gt;length()가 호출되고 반환값이 0이면 빈 문자열&lt;/b&gt;을 나타냅니다.&lt;br /&gt;이런값들을 사용할 수는 있는데, JsonSerializer 구현을 직접해서 구체적으로 뭘 빼고, 뭘 포함하고 할 수 있음&lt;br /&gt;예를 들면 isEmpty를 사용하니 isEmpty를 아에 재정의 해서 하면 비어있는걸로 간주하지 않게 할 수도 있다는 뜻.&lt;/li&gt;
&lt;li&gt;2.6버젼까지는 다음도 비어있다고 포함했는데.. 그이후부터는 수정되었음.&lt;/li&gt;
&lt;li&gt;원시 유형의 기본값(예: &lt;code&gt;int&lt;/code&gt;/&lt;code&gt;java.lang.Integer&lt;/code&gt;의 경우 0, &lt;code&gt;bool&lt;/code&gt;/&lt;code&gt;Boolean&lt;/code&gt;의 경우 &lt;code&gt;false&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;날짜/시간 타입의 타임스탬프 0&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재는 Spring버젼을 높혀사용하므로 다음과 같은 이슈는 없을거라고 생각은 하되, 레거시 코드들이 다음과 같은 이슈를 가지고 있으면 확인이 필요하다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;@Builder  
@Getter  
public class PojoDefaultEmpty {  

    private String string;  
    private Map map;  
    private Integer[] array;  
    private int integer;  
    private boolean trueorFalse;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 형태를 다음처럼 primitive값인 경우는 노출이된다&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;integer&quot; : 0,
  &quot;trueorFalse&quot; : false
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, 저기처럼 배열인데, 값이 뭐가 들어가있기만 하면 무조건 노출된다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;//array new Integer[1]이면 null로 들어가긴 한다. 근데 이러면 노출은 됨.
// 대신 new Integer[0]은 안나온다. array.length가 0이므로
{
  &quot;integer&quot; : 0,
  &quot;trueorFalse&quot; : false,
  &quot;array&quot; : [ null ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;NON_DEFAULT&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 설정의 의미는 문맥에 따라 달라집니다. 즉, 어노테이션이 POJO 유형(클래스)에 대해 지정되었는지 여부에 따라 달라집니다.&lt;/li&gt;
&lt;li&gt;제외 케이스
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&quot;비어 있는&quot; 것으로 간주되는 모든 값(NON_EMPTY에 따라)은 제외됩니다.&lt;/li&gt;
&lt;li&gt;Primitive/wrapper 기본값은 제외됩니다.&lt;/li&gt;
&lt;li&gt;타임스탬프(에포크 이후 밀리초 단위의 &lt;code&gt;long&lt;/code&gt; 값, 날짜 참조)가 &lt;code&gt;0L&lt;/code&gt;인 날짜/시간 값은 제외됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;속성 값이 'null'인 경우를 제외하고는 equals() 메서드를 사용하여 값이 기본값으로 사용되며, 이 경우 곧바로 null 검사가 사용됩니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Builder  
@Getter  
@ToString  
public class PojoDefaultNon {  

    private String string;  
    private int integer;  
    private boolean trueorFalse;  
    private Timestamp timestamp;  
    private char aChar;  
    private float aFloat;   
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 만든다음에..&lt;br /&gt;TimeStamp를 0L으로 만들거나.. String을 &quot;&quot;으로 들어가면?&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;{ }
//아래는 기본값의 상태를 노출하는건데..timestamp가 0L이면 이렇게된다.
PojoDefaultNon(string=, integer=0, trueorFalse=false, timestamp=1970-01-01 09:00:00.0, aChar=, aFloat=0.0)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 만약 이값이 살짝만 바뀌어도 노출이 된다는 뜻이고..&lt;br /&gt;그럼 이값을 살짝만 바꿔보자. String은 &quot; &quot; 한칸은 띄고, timeStamp는 1만 추가했는데 값이 바귄다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;{
  &quot;string&quot; : &quot; &quot;,
  &quot;timestamp&quot; : 1
}
//아래처럼 &quot;&quot; , 1l의 경우 다르다.
PojoDefaultNon(string= , integer=0, trueorFalse=false, timestamp=1970-01-01 09:00:00.001, aChar=, aFloat=0.0)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 이경우는 primitive타입까지 고려해서 관리할때 유용한 옵션으로 보인다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;USE_DEFAULTS&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;/ul&gt;
&lt;/li&gt;
&lt;li&gt;예를 들어 프로퍼티에 대해 반환된 경우 프로퍼티가 정의된 경우 프로퍼티를 포함하는 클래스의 기본값을 사용하고, 정의된 것이 없는 경우 전역 직렬화 포함 세부 정보를 사용합니다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;정의가 안되있으면 글로벌 따라간다는 거같음. 클래스 자체에 포함되어있으면 그거 따라가고.&lt;/b&gt;&lt;br /&gt;이경우는 테스트를 하기가 곤란하긴 했지만 유용한 예제를 봤다.&lt;br /&gt;즉, 정의여부에 따라서 글로벌 옵션을 먼저 둘건지.. 아니면 글로벌 옵션이 없는 경우는 원래것을 먼저 따라가는 것이였다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;@Builder  
@Getter  
public class PojoDefaultEmpty {  

    private String string;  
    private Map map;  
    private Integer[] array;  
    private int integer;  
    private boolean trueorFalse;  
    @JsonInclude(JsonInclude.Include.NON_NULL)  
    private List&amp;lt;String&amp;gt; list;  


    @JsonInclude(JsonInclude.Include.NON_DEFAULT)  
    public List&amp;lt;String&amp;gt; getList() {  
        return list;  
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음처럼 Empty를 글로벌 옵션을 줄건데.. list는.. non null 옵션을 따라갈거다.&lt;br /&gt;즉, 이런 경우라면? 기본적으론 list가 new ArrayList(); 인 경우는 노출이 안될거다.&lt;br /&gt;&amp;rarr; 문제는 아래처럼 getter에 non Default가 걸려있다면?&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;integer&quot; : 0,
  &quot;trueorFalse&quot; : false
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;음..? list는 어디로간거지? 즉, getter에 이옵션이 달려있다보니까 이게 empty 정책을 따라가게 되버린거고.. 없어졌다.&lt;br /&gt;그럼 주석처리하게 된다면?&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{
  &quot;integer&quot; : 0,
  &quot;trueorFalse&quot; : false,
  &quot;list&quot; : [ ]
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;null이 아니므로 노출된다. 이런거 처럼 다양한 옵션을 활용할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;CUSTOM&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;별도의 필터 객체(값 자체의 경우 JsonInclude.valueFilter() 및/또는 구조화된 유형의 콘텐츠의 경우 JsonInclude.contentFilter()로 지정됨)가 사용됨.&lt;br /&gt;커스텀의 경우는 우리가 원하는 값을 막 조작할 수 있으므로 이번에는 검증하지 않았다.&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;회사에서 JsonInclude NON_NULL 옵션이 걸려있는 경우가 종종 있어서 해당하는 거가 null을 삭제한다는걸 대강 알고는 있었으나, 이번 기회에 어떤 옵션을 통해서 값이 어떤식으로 처리되었는지에 대해서 좀 더 꼼꼼하게 알아볼 수 있어서 좋았다.&lt;br /&gt;특히, Empty라던가.. absent는 꽤 유용해보이긴했고, default 옵션도 좋아보였다. 이런식으로 Deep Dive하는 경험은 꽤 많은 도움을 주는 것 같았는데, 피상적으로 남들이 Optional 자제할것.. 이런식으로 이야기 해주는 것보다.. 오히려 지금처럼 테스트 해보면서 처리하면 그 이야기가 왜 나오는지에 대해서 다시 한번 알아보는 기회가 된것 같기도해서 좋았다..&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;출처&lt;/h4&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://javadoc.io/doc/com.fasterxml.jackson.core/jackson-annotations/latest/index.html&quot;&gt;https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-annotations/latest/index.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.logicbig.com/tutorials/misc/jackson/json-include-use-defaults.html&quot;&gt;https://www.logicbig.com/tutorials/misc/jackson/json-include-use-defaults.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1707029616538&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Jackson JSON - @JsonInclude USE_DEFAULTS Example&quot; data-og-description=&quot;Jackson JSON - @JsonInclude USE_DEFAULTS Example [Last Updated: Aug 11, 2020]&quot; data-og-host=&quot;www.logicbig.com&quot; data-og-source-url=&quot;https://www.logicbig.com/tutorials/misc/jackson/json-include-use-defaults.html&quot; data-og-url=&quot;https://www.logicbig.com/tutorials/misc/jackson/json-include-use-defaults.html&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bvYa4g/hyVf9NpdIC/CxVK3Jg7M1ViRjfYPO8Cs0/img.png?width=310&amp;amp;height=290&amp;amp;face=0_0_310_290,https://scrap.kakaocdn.net/dn/SGInU/hyVf6iP5Ej/eq2KaYEnSu400jdkgEQDk1/img.png?width=310&amp;amp;height=290&amp;amp;face=0_0_310_290&quot;&gt;&lt;a href=&quot;https://www.logicbig.com/tutorials/misc/jackson/json-include-use-defaults.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.logicbig.com/tutorials/misc/jackson/json-include-use-defaults.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bvYa4g/hyVf9NpdIC/CxVK3Jg7M1ViRjfYPO8Cs0/img.png?width=310&amp;amp;height=290&amp;amp;face=0_0_310_290,https://scrap.kakaocdn.net/dn/SGInU/hyVf6iP5Ej/eq2KaYEnSu400jdkgEQDk1/img.png?width=310&amp;amp;height=290&amp;amp;face=0_0_310_290');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Jackson JSON - @JsonInclude USE_DEFAULTS Example&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Jackson JSON - @JsonInclude USE_DEFAULTS Example [Last Updated: Aug 11, 2020]&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.logicbig.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1707029608589&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;jackson-annotations 2.16.1 javadoc (com.fasterxml.jackson.core)&quot; data-og-description=&quot;Latest version of com.fasterxml.jackson.core:jackson-annotations https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-annotations Current version 2.16.1 https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-annotations/2.16.1 package-list path (us&quot; data-og-host=&quot;javadoc.io&quot; data-og-source-url=&quot;https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-annotations/latest/index.html&quot; data-og-url=&quot;https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-annotations/latest/index.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-annotations/latest/index.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-annotations/latest/index.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;jackson-annotations 2.16.1 javadoc (com.fasterxml.jackson.core)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Latest version of com.fasterxml.jackson.core:jackson-annotations https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-annotations Current version 2.16.1 https://javadoc.io/doc/com.fasterxml.jackson.core/jackson-annotations/2.16.1 package-list path (us&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;javadoc.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;P.S.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/66572470/compare-2-json-strings-using-junit5-and-mockito&quot;&gt;https://stackoverflow.com/questions/66572470/compare-2-json-strings-using-junit5-and-mockito&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://www.baeldung.com/jsonassert&quot;&gt;https://www.baeldung.com/jsonassert&lt;/a&gt;&lt;br /&gt;JSON Junit테스팅 할때는 예상보다 좀 쉬웠어서 해당하는 코드들을 사용했다. 나중에 도움이 될 수 있으므로. 확인해보는걸 추천한다.&lt;/p&gt;
&lt;figure id=&quot;og_1707029625176&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Compare 2 Json strings using Junit5 and Mockito&quot; data-og-description=&quot;I have the below Junit.i am trying to test compare if both Json string have same fields (order and value is not important). My test keep getting failed with below error org.opentest4j.&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/66572470/compare-2-json-strings-using-junit5-and-mockito&quot; data-og-url=&quot;https://stackoverflow.com/questions/66572470/compare-2-json-strings-using-junit5-and-mockito&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/PRVYN/hyVceCQrA4/gEBml8pKaowQeP0PsixoK0/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/66572470/compare-2-json-strings-using-junit5-and-mockito&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/66572470/compare-2-json-strings-using-junit5-and-mockito&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/PRVYN/hyVceCQrA4/gEBml8pKaowQeP0PsixoK0/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Compare 2 Json strings using Junit5 and Mockito&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;I have the below Junit.i am trying to test compare if both Json string have same fields (order and value is not important). My test keep getting failed with below error org.opentest4j.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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://github.com/ventulus95/test-tech-project/blob/JsonInclude/src/test/java/com/ventulus95/testtechproject/JsonIncludeTest.java&quot;&gt;https://github.com/ventulus95/test-tech-project/blob/JsonInclude/src/test/java/com/ventulus95/testtechproject/JsonIncludeTest.java&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/161</guid>
      <comments>https://sundries-in-myidea.tistory.com/161#entry161comment</comments>
      <pubDate>Sun, 4 Feb 2024 15:54:09 +0900</pubDate>
    </item>
    <item>
      <title>부모 - 자식 관계에 있는 DTO를 효과적으로 표현하는 JsonTypeInfo Deduction기능을 알아보자.</title>
      <link>https://sundries-in-myidea.tistory.com/160</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 쓰게 되었나..&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 회사 프로젝트가 잦은 야근으로 지난주를 훌렁 날려버린 나는 현재 눈물의 개발 글쓰기를 진행중인데, 해당하는 프로젝트 진행도중 Request는 공통정보가 있지만, 각 3rd-party 서비스 추가 응답을 활용해서 각각의 응답에 따라 다른 API를 넘겨줘야하는 상황에 봉착했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당하는 프로젝트에서 중복적으로 많이 사용되는 field가 있었고.. 해당하는 필드를 각 DTO별로 중복적으로 사용하는건 좀 귀찮은 일이기도 하니.. 해당하는 형태를 부모-자식 형태를 통해 상속으로 처리를 하려했다. 상속의 경우 Json Serialize가 값으로 처리되기가 곤란한 경우가 종종 있었는데, 특히나 Spring boot에서 별다른 어노테이션이 없이 무자비하게 떨렁 응답에 대한 처리를 해봤자.. 상속하려는 모쪽의 값만 처리될뿐 자쪽의 필드 값이 처리가 안되는 문제가 있었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.56.29.png&quot; data-origin-width=&quot;1466&quot; data-origin-height=&quot;72&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zMtO7/btsDKMWyzLL/qVeVZjJrszrP2HkbPPPvyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zMtO7/btsDKMWyzLL/qVeVZjJrszrP2HkbPPPvyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zMtO7/btsDKMWyzLL/qVeVZjJrszrP2HkbPPPvyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzMtO7%2FbtsDKMWyzLL%2FqVeVZjJrszrP2HkbPPPvyK%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;1466&quot; height=&quot;72&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.56.29.png&quot; data-origin-width=&quot;1466&quot; data-origin-height=&quot;72&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 일반의 경우는 naver 응답이 무시되었음.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.57.06.png&quot; data-origin-width=&quot;2357&quot; data-origin-height=&quot;108&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bQEn51/btsDGsS17Dw/ufFH5MjSyAi3pa8ob6gPvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bQEn51/btsDGsS17Dw/ufFH5MjSyAi3pa8ob6gPvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bQEn51/btsDGsS17Dw/ufFH5MjSyAi3pa8ob6gPvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbQEn51%2FbtsDGsS17Dw%2FufFH5MjSyAi3pa8ob6gPvK%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;2357&quot; height=&quot;108&quot; data-filename=&quot;스크린샷 2024-01-20 오후 7.57.06.png&quot; data-origin-width=&quot;2357&quot; data-origin-height=&quot;108&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 네이버 Json을 만들었지만 케스팅이 안된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;흠... 이런 문제는 분명 많은 불편함을 낳았을텐데..&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다 이런 문제는 이미 해결된 문제로 &lt;code&gt;@JsonTypeInfo&lt;/code&gt;기능을 활용하면 쉽게 해결이 된다. 해당 기능중 ID 옵션 값이 많다는 사실을 알았는데, name, class로 사용하는 것이 있고, 특이하게 &lt;b&gt;Deduction&lt;/b&gt; 기능을 활용하는게 훨씬 편하다는 사실을 알았다.&lt;br /&gt;일단, 사용법부터 일단 간단하게 알아보자. 기존 방식과 비교해서 어떤점이 좀 더 좋은지를 알아보고 마지막에는 이게 어떤 방식으로 동작 하는지 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기초적인 사용법은 해당하는 Request 혹은 Dto에 부모쪽에 다음처럼 붙이면 된다&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)  
@JsonSubTypes({
    @JsonSubTypes.Type(NaverJsonTypeRequest.class), 
    @JsonSubTypes.Type(KakaoJsonTypeRequest.class)
})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 JsonSubTypes안에 &amp;rarr; 자식 타입을 넣으므로써.. 해당하는 값이 자동으로 직렬화 되게 해주면 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기존 방식 Name방식 혹은 class가 있는 방식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JsonTypeInfo는 dto자체에 id값을 넣거나 class값을 넣게 되면 해당하는 class을 해당 타입으로 변환 해준다. 예를 들면...&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@JsonTypeInfo(use = JsonTypeInfo.Id.NAME)  
@JsonSubTypes({
    @JsonSubTypes.Type(NaverJsonTypeRequest.class), 
    @JsonSubTypes.Type(KakaoJsonTypeRequest.class)
})
public class JsonCommonTypeRequest {  
    private Integer id;  
    private String title;  
    private String content;  
    @DateTimeFormat(pattern = &quot;yyyy-MM-dd HH:mm:ss&quot;)  
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = &quot;yyy-MM-dd HH:mm:ss&quot;)  
    private LocalDateTime uploadTime;  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;JsonTypeInfo.Id.NAME&lt;/b&gt; 옵션이 있을때는 Json을 다음처럼 처리해야한다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{  
  &quot;id&quot;: 1,  
  &quot;title&quot;: &quot;123&quot;,  
  &quot;content&quot;: &quot;123&quot;,  
  &quot;uploadTime&quot;: &quot;2024-01-20 10:10:20&quot;,  
  &quot;@type&quot;: &quot;NaverJsonTypeRequest&quot;,  
  &quot;naverId&quot;: &quot;naverid명&quot;,  
  &quot;blogInTitle&quot;: &quot;블로그 타이틀명&quot;  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;매번마다 @type을 넣는 방식은 이런건 나중에 불편함을 초래하기 딱 좋다. 왜냐하면, 일단 기본적으로 들어가야할 값에 타입을 매번 넣어준다는건 각각 상황별로 type에 대한 Switch case가 존재하는 거고.. 그 상황일때는 Json 값을 넣어야하는건데...그건.. 좀 힘들고.. 심지어 class를 준경우는 class full path까지 사용해야한다.&lt;br /&gt;클래스는 더 나아가서 fullpath주소를 줘야한다. 아래처럼. 심지어 하나라도 빼먹으면 Json은 인식하지 못하고 error를 뱉어낸다.&lt;br /&gt;&lt;code&gt;&quot;@class&quot;: &quot;com.ventulus95.testtechproject.dto.request.NaverJsonTypeRequest&quot;,&lt;/code&gt;&lt;br /&gt;&amp;rarr; 이렇게 처리된다면.. 분명히 너죽고 나죽자.. 클라이언트 개발자와 서버개발자는 키배를 벌이게 될 수 있는 운명이다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 Deduction을 사용하게 된다면, 어떻게 쓰는게 좋을까?&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Deduction을 사용하는 방법&lt;/h3&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@JsonTypeInfo(use = JsonTypeInfo.Id. DEDUCTION)  
@JsonSubTypes({
    @JsonSubTypes.Type(NaverJsonTypeRequest.class), 
    @JsonSubTypes.Type(KakaoJsonTypeRequest.class)
})
public class JsonCommonTypeRequest {  
    private Integer id;  
    private String title;  
    private String content;  
    @DateTimeFormat(pattern = &quot;yyyy-MM-dd HH:mm:ss&quot;)  
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = &quot;yyy-MM-dd HH:mm:ss&quot;)  
    private LocalDateTime uploadTime;  
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인 경우&lt;br /&gt;&lt;b&gt;JsonTypeInfo.Id.DEDUCTION&lt;/b&gt; 옵션이 있을때는 Json을 다음처럼 처리해야한다.&lt;/p&gt;
&lt;pre class=&quot;json&quot;&gt;&lt;code&gt;{  
  &quot;id&quot;: 1,  
  &quot;title&quot;: &quot;123&quot;,  
  &quot;content&quot;: &quot;123&quot;,  
  &quot;uploadTime&quot;: &quot;2024-01-20 10:10:20&quot;,  
  &quot;naverId&quot;: &quot;naverid명&quot;,  
  &quot;blogInTitle&quot;: &quot;블로그 타이틀명&quot;  
}&lt;/code&gt;&lt;/pre&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-filename=&quot;스크린샷 2024-01-21 오후 5.29.11.png&quot; data-origin-width=&quot;1962&quot; data-origin-height=&quot;28&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3q84R/btsDKKYLlsT/fxKUmwesoGYR9kjSYOnaz0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3q84R/btsDKKYLlsT/fxKUmwesoGYR9kjSYOnaz0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3q84R/btsDKKYLlsT/fxKUmwesoGYR9kjSYOnaz0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3q84R%2FbtsDKKYLlsT%2FfxKUmwesoGYR9kjSYOnaz0%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;1962&quot; height=&quot;28&quot; data-filename=&quot;스크린샷 2024-01-21 오후 5.29.11.png&quot; data-origin-width=&quot;1962&quot; data-origin-height=&quot;28&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; Deduction은 자체적으로 자식 클래스에만 있는 필드들을 가지고 이 값들을 판단한다. 이것은 적어도 우리가 보기에는 훨씬 효과적으로 동작한다. 타입에대한 필드 값을 넣지 않더라도.. 우리는 해당하는 해당하는 class를 만들어 줄 수 있다는 것은 타 개발자들과 소통하는 측면에서 훨씬 불필요한 논의를 줄일 수 있고, 직관적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 해당하는 방식도 한계가 있을 수 있다. optional한 값으로 처리된 값들중 자식들끼리 필드가 겹쳐서 값에 대한 명확한 판단을 내릴 수 없다면 오류를 뱉는 점이다.&lt;br /&gt;다음처럼.. Kakao와 Naver의 값이 우연찮게 겹쳤는데.. 안겹치는 부분외의 값이 하필이면 optional인 상황인것이다.&lt;br /&gt;네이버는 naverId와 blogIntitle이고 kakao도 동일하게, blogtitle이 있으며, 해당하는 kakaoId가 optional 값이여서 누락될 수 있다면... 다음과 같은 오류가 발생하게 된다.&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;2024-01-21T17:32:56.131+09:00  WARN 52506 --- [nio-8080-exec-1] .w.s.m.s.DefaultHandlerExceptionResolver : Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Could not resolve subtype of [simple type, class com.ventulus95.testtechproject.dto.request.JsonCommonTypeRequest]: Cannot deduce unique subtype of `com.ventulus95.testtechproject.dto.request.JsonCommonTypeRequest` (2 candidates match)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 2개가 동시에 매칭이 되버리는바람에 어떤 타입인지 모를 수 있다는 점이다. 그래도 이런 경우는 드물기도 하며, 필수값이 아닌 optional로 겹치는 경우는 차라리 공통타입으로 변수를 올려서 값을 만드는게 더 나은게 아닌가? 라는 생각도 든다. 그렇게 겹칠거면 부모쪽 클래스로 처리해버리는게 더 좋다고 생각한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;궁금증&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무튼 이런 연유 때문에 deduction과 objectmapper에 대해서 좀 찾아보게되었는데, 이 동작 방식에 대해서 글을 작성하려니까 이 문제 해결기보다 더 길이 좀 길어질거 같아서 차후에 아예 각잡고 한개의 글을 적는게 나을듯해서 정리해보는걸루 마무리하겠다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;출처&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 코드 관련한 github 주소&lt;br /&gt;&lt;a href=&quot;https://github.com/ventulus95/test-tech-project/tree/feature-JsonTypeInfo&quot;&gt;https://github.com/ventulus95/test-tech-project/tree/feature-JsonTypeInfo&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그외 참고한 여러가지 자료들&lt;br /&gt;&lt;a href=&quot;https://www.baeldung.com/jackson-deduction-based-polymorphism&quot;&gt;https://www.baeldung.com/jackson-deduction-based-polymorphism&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://see-ro-e.tistory.com/340&quot;&gt;https://see-ro-e.tistory.com/340&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://stackoverflow.com/questions/55161374/get-derived-dto-from-base-class-request-body-dto&quot;&gt;https://stackoverflow.com/questions/55161374/get-derived-dto-from-base-class-request-body-dto&lt;/a&gt;&lt;/p&gt;</description>
      <category>Spring</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/160</guid>
      <comments>https://sundries-in-myidea.tistory.com/160#entry160comment</comments>
      <pubDate>Sun, 21 Jan 2024 18:25:58 +0900</pubDate>
    </item>
    <item>
      <title>연말 맞이 회고  - 2023년은 우왕자왕과 성장을 위한 발판</title>
      <link>https://sundries-in-myidea.tistory.com/159</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/v6pSR/btsCyocB6ax/NXvVryi5WKpq6gVhkDmcok/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/v6pSR/btsCyocB6ax/NXvVryi5WKpq6gVhkDmcok/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/v6pSR/btsCyocB6ax/NXvVryi5WKpq6gVhkDmcok/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fv6pSR%2FbtsCyocB6ax%2FNXvVryi5WKpq6gVhkDmcok%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;1080&quot; height=&quot;768&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연말을 맞이하여, 회고를 간단하게 해보고 싶어서 두서없이 적어봅니다. 연말까지 뭔가 굉장히 피곤하게 보내는 중이라서 사실 정리가 너무 안된면도 있다고 생각합니다 ㅎㅎ; 그래도 큰 틀에서 보자면, 회사와 개인의 측면으로 나눠서 회고를 해볼 수 있을 거라고 생각합니다.&lt;br /&gt;올해는 크게 회사에서는 변화가 좀 있었고, 해당하는 대응이 꽤나 다이나믹해서 배운점도 많고, 뭔가 실수 했던 점도 많아서 복잡스러웠습니다.&lt;br /&gt;덕분에 그만큼 개인의 무언가를 이뤘거나 행동의 큰 변화가 있던 적은 없었습니다. 그래도 과감하게 비싸고 빡센 강의를 들었던 것 덕분에, 해당 강의 이후 성장했다고 느껴지는 모습이 보여서 좋았습니다.&lt;br /&gt;조금 더 세부적으로 나눠서 보면 아래처럼 정리해볼 수 있을 것 같습니다.&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;h3 data-ke-size=&quot;size23&quot;&gt;팀 이동전 (~4월 초)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;팀 이동전까지는 내부 Lab처럼 구성된 조직으로 POC, ProtoType을 개발 했습니다. 그만큼 일정에 대한 여유도 존재했었고, 실적이 크게 눈에 체감되는 느낌은 아니였어서, 상대적으로 현 팀에 비해서 부담이 덜했습니다.&lt;br /&gt;&lt;b&gt;물론 신기술 적용. 개발 기능중 스스로 적용해보고 싶었던 기술들을 적용해보는 기회도 있었고, 내부적으로 제가 연구하여, Develop한 기술이 잘 적용 된거 같아서, 스스로 만족했었던 경험이 있었습니다.&lt;/b&gt;&lt;br /&gt;그러다 갑자기 팀장님의 이동과 함께 맞물려서 팀 이동이 있어서 마침 저희 팀원이 있었던, 신규 사업 조직으로 팀을 이동하게 됩니다.&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;br /&gt;기획을 포함한 제품을 만들기위한 조직에 포함 된 적이 이때가 처음이라 저는 나름의 기대를 많이 했지만 그만큼 걱정도 많았습니다. 물론, 걱정이 앞서는게 많았는데... 내가 가진 개발 스펙을 내가 다 구현하지 못하면 어떻게 하지. 일정이 밀리면 어쩌지와 같은 굉장히 스스로 능력에 대한 부담을 많이 가졌습니다.&lt;br /&gt;물론 잘 해내긴 했지만, 런칭이라는 &lt;b&gt;엄청 큰 산&lt;/b&gt;을 넘어가는 과정은 꽤나 고되긴했습니다. 일단 일정도 엄청 빡빡했고, 원래 일정보다 더 늦어지는 시간적인 문제도 있어, 런칭달이었던 6/7월은 거의 죽음의 기간이었다라고 부르고 싶습니다.&lt;/p&gt;
&lt;p&gt;&lt;del&gt;(맨날 야근함)&lt;/del&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;그래도 실제 서비스 배포 이후에는 조금 더 여유도 생기고, 프로세스들도 정립되는 과정을 느꼈기도 하고. 해당하는 서비스가 실제 사용자들이 들어오게되며, 실제 운영을 하다보니까 발생하던 이슈들에 대해서 더욱 크게 체감하게 되는 상황이 생겼습니다.&lt;br /&gt;내가 실수하는 무언가가 회사에 큰 영향을 미치는 상황이 되자, 어쨌든 관심이 나에게로 집중될 수 있는 상황이 계속 생기고, 부담스런 상황을 잘 떨쳐내기 위해서 노력은 했지만.... &lt;b&gt;실수를 방지를 잘 못해서 아쉽긴 했습니다.&lt;/b&gt;&lt;br /&gt;연말과 연초쯤에 큰 기능 배포가 대기하고 있어서 쉬는게 쉬는 느낌은 아닌데... &lt;b&gt;그래도 지금 아니면 못쉰다.. 좀 더 잘 쉬어야지 좀 덜 놀아야지 하면서 막판에 남은 휴가를 태우면서 보냈습니다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;KPT 방식으로 좀 정리를 해보자면...&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Keep&lt;/h3&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;Deep Dive를 통해서 회사 코드의 퀄리티를 높이려 한 점&lt;/li&gt;
&lt;li&gt;문서화를 놓치지 않고 최대한 적용 해보려 한점&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Problem&lt;/h3&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;기획 받은 내용 중 가끔 누락이나 놓치는게 있는 점.&lt;/li&gt;
&lt;li&gt;새로운 것을 보수적으로 바라보는 점.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. Try&lt;/h3&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;팀내 문화 조금 더 활발하게 만들기&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;개인적인 일은 따로 큰건 없었지만, 제일 기억에 남는건 3가지로 일단 크게는 IT 개발 동아리, 운동, 개발 이렇게 나눠볼 수 있을 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DDD 동아리 운영진을 한 기수를 진행하고, 나머지 한 기수는 운영진까지는 아니지만, 서포팅 역할로 동아리 행사 사진 촬영을 도와주면서 동아리의 행사 그리고 동아리의 아웃풋을 좀 더 확실하게 보여줄 수 있다는게 좋았습니다. 제일 마음에 들었던 건 전체적인 그림(와꾸)가 잘 나오는 형태가 보기 좋았다.&lt;br /&gt;그런 강점을 회사에 적용하고 해당하는 걸 어필해서 회사 개발자 행사에서 회사 지원을 받아 촬영도 해보고 좀 경험의 폭이 넓어진다고 해야하나... 다양한 경험을 많이 하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그외의 활동으로는 운동을 좀 꾸준히 다닐려고 노력했다? 헬스 한동안 꽤 많이 다녔고, 일일권 보다는 더 많이 가자고 해서 비용 지불을 막았기도 했습니다. ㅎㅎ 달리기나, 수영도 근근히 좀 했었기도 하고 하지만, 뭔가 바쁜 시즌일때는 운동 가기에도 벅찰때가 많아서 솔직히 운동에 1개월~2개월정도만 투자한거 같고, 나머지 달은 별의별 핑계를 대면서 안갔던것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;개발적인 StepUp&lt;/b&gt;은 사실 부족한 점을 많이 느끼기도 해서 강의를 NextStep에서 강의를 들었는데, 해당 강의의 퀄리티이 좋았고, 해당하는 과제를 풀어내는 과정을 보냈습니다&lt;br /&gt;특히 코드 리뷰를 받는 과정이 유익했습니다. 해당 강의의 과제들이 좀 어려웠고, 쉽게 과제를 해냈다 하더라도, 리뷰를 통해서 계속 발전하고 고민해야지 좋은 코드를 작성할 수 밖에 없는 구조로 되어있어서 해당과정을 끝까지 다 맞춘다는게 결코 쉬운 과정은 아니었다고 단언하고 싶다. 그외에도 특히 회식, 야근과 겹치면 진짜 우울해졌는데, &lt;b&gt;그러한 시간적 어려움을 극복하기 위해서 휴가도 쓰기도 하고, 잠도 줄여나가면서 했었던 과정이 나를 1cm 정도 성장시킨거 같기도 합니다.&lt;/b&gt;&lt;br /&gt;이러한 일련의 과정을 보내면서 개발을 좀 더 유연하게 개발하고 클린 코드를 쓰려고 신경쓰고 있고, 그러한 코드들이 회사에 어느정도 녹아내려가는 모습이 보여서 좋았다&lt;br /&gt;그러한 동기부여 덕분일까. 개발 관련해서 최근 사이버 먼데이를 노려서 CKAD를 신청해두기도 했다. 올해 당장 시작하기에는 어렵겠지만, 해당 시험도 통과해보려 노력할 거고, 내 다양한 지식들을 조금 더 넓고 포괄적으로 배울 수 있는 기회를 만들어 볼려합니다. &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;다 적고 나니까 무슨 강의 예찬론자 처럼 적었지만 ㅎㅎ 최근에 진행했었기도 했고, &lt;b&gt;이 과정 6주라는 기간동안 내가 스스로가 많이 성장했다는 느낌을 들어서 회고에 줄줄히 적고 싶었다.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;KPT 방식으로 정리해보자면...&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Keep&lt;/h3&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;헬스나 수영처럼 운동 꾸준히 이어가기&lt;/li&gt;
&lt;li&gt;개발자 행사에 사진 촬영과 같이 도움을 줄 수 있는 부분을 최대한 돕기&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Problem&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;준비했던거가 힘들다고 중간에 그만 두지 말기. (최소 3개월은 해보기)&lt;/li&gt;
&lt;li&gt;부담스러운 자리라고 생각하지 말고, 적극적으로 어필해보기 (저 이거 잘합니다처럼...)&lt;/li&gt;
&lt;li&gt;개발적인 지식 채우기는 많이 없었던 거 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. Try&lt;/h3&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;다양한 개발 강의 더 들어보기.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>잡담/회고</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/159</guid>
      <comments>https://sundries-in-myidea.tistory.com/159#entry159comment</comments>
      <pubDate>Sun, 24 Dec 2023 22:57:01 +0900</pubDate>
    </item>
    <item>
      <title>글또 9기를 시작하며...</title>
      <link>https://sundries-in-myidea.tistory.com/158</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/G5MHE/btsBKeG9IjM/lYHwa02tdUYKbdWxZ6quj1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/G5MHE/btsBKeG9IjM/lYHwa02tdUYKbdWxZ6quj1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/G5MHE/btsBKeG9IjM/lYHwa02tdUYKbdWxZ6quj1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FG5MHE%2FbtsBKeG9IjM%2FlYHwa02tdUYKbdWxZ6quj1%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;739&quot; height=&quot;526&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;글또 첫주차는 늘 뭘 적을까에 대한 고민을 많이 하곤 하는데, 결국에는 다짐글을 적게되는게 굳혀졌다. ㅋㅋ 왜냐하면 이미 내 글들을 살펴보면, 글또 글들이 1주차에는 무조건 9기를 시작하며와 같이 시작했으니까..&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;figure id=&quot;og_1702196967288&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;글또 5기를 시작하면서...&quot; data-og-description=&quot;글또를 시작해버렸다. 글쓰는 또라이라는 이 모임은 개발 블로그 글쓰기 모임과 같다. 사실 개발 블로그는 내가 배운 내용이 아니면 정기적으로 포스팅하기가 쉽지는 않다. 아무래도 내가 순간&quot; data-og-host=&quot;sundries-in-myidea.tistory.com&quot; data-og-source-url=&quot;https://sundries-in-myidea.tistory.com/99&quot; data-og-url=&quot;https://sundries-in-myidea.tistory.com/99&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/REBI9/hyUL3NcszT/tWt6Ti59VXPtMcX20WAI11/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/EhiYc/hyUIz1ijfM/L291OwcmyCT1B81GsiTYu0/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/b7y5jW/hyUIAlAdSe/8DIYajrS5F2AyKgenfrNW1/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720&quot;&gt;&lt;a href=&quot;https://sundries-in-myidea.tistory.com/99&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sundries-in-myidea.tistory.com/99&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/REBI9/hyUL3NcszT/tWt6Ti59VXPtMcX20WAI11/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/EhiYc/hyUIz1ijfM/L291OwcmyCT1B81GsiTYu0/img.png?width=800&amp;amp;height=450&amp;amp;face=0_0_800_450,https://scrap.kakaocdn.net/dn/b7y5jW/hyUIAlAdSe/8DIYajrS5F2AyKgenfrNW1/img.png?width=1280&amp;amp;height=720&amp;amp;face=0_0_1280_720');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;글또 5기를 시작하면서...&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;글또를 시작해버렸다. 글쓰는 또라이라는 이 모임은 개발 블로그 글쓰기 모임과 같다. 사실 개발 블로그는 내가 배운 내용이 아니면 정기적으로 포스팅하기가 쉽지는 않다. 아무래도 내가 순간&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sundries-in-myidea.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1702196981678&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;글또 6기를 시작하면서 - 올해의 마무리도 글또와 함께&quot; data-og-description=&quot;글또를 또 하게되었다. 이번에도 글또를 시작했다. 뭐 지난번에 이야기를 주절주절했으니까.. 글또에 대해서 무엇이고 어떤 비전을 가지고 있는지에 대해서는 딱히 언급하지 않고, 링크를 남긴&quot; data-og-host=&quot;sundries-in-myidea.tistory.com&quot; data-og-source-url=&quot;https://sundries-in-myidea.tistory.com/127&quot; data-og-url=&quot;https://sundries-in-myidea.tistory.com/127&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cPJDKc/hyUIEaspmC/KQAFIuLkDFefPCb7awBPl0/img.png?width=720&amp;amp;height=640&amp;amp;face=0_0_720_640,https://scrap.kakaocdn.net/dn/erWLjn/hyULV2HtU2/J6JrKxAL6MPHWWggnp7IKK/img.png?width=720&amp;amp;height=640&amp;amp;face=0_0_720_640,https://scrap.kakaocdn.net/dn/suFUs/hyULUvXCEf/KQ8H76bOFRShQxnWiSN3p1/img.png?width=720&amp;amp;height=640&amp;amp;face=0_0_720_640&quot;&gt;&lt;a href=&quot;https://sundries-in-myidea.tistory.com/127&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sundries-in-myidea.tistory.com/127&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cPJDKc/hyUIEaspmC/KQAFIuLkDFefPCb7awBPl0/img.png?width=720&amp;amp;height=640&amp;amp;face=0_0_720_640,https://scrap.kakaocdn.net/dn/erWLjn/hyULV2HtU2/J6JrKxAL6MPHWWggnp7IKK/img.png?width=720&amp;amp;height=640&amp;amp;face=0_0_720_640,https://scrap.kakaocdn.net/dn/suFUs/hyULUvXCEf/KQ8H76bOFRShQxnWiSN3p1/img.png?width=720&amp;amp;height=640&amp;amp;face=0_0_720_640');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;글또 6기를 시작하면서 - 올해의 마무리도 글또와 함께&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;글또를 또 하게되었다. 이번에도 글또를 시작했다. 뭐 지난번에 이야기를 주절주절했으니까.. 글또에 대해서 무엇이고 어떤 비전을 가지고 있는지에 대해서는 딱히 언급하지 않고, 링크를 남긴&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sundries-in-myidea.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;그러면&amp;nbsp;이번&amp;nbsp;글또에선&amp;nbsp;어떤&amp;nbsp;액션&amp;nbsp;아이템이&amp;nbsp;필요할까?&amp;nbsp;거창한&amp;nbsp;목표를&amp;nbsp;정할&amp;nbsp;수&amp;nbsp;도&amp;nbsp;&amp;nbsp;있겠지만,&amp;nbsp;실제로&amp;nbsp;거창한&amp;nbsp;목표는&amp;nbsp;대부분&amp;nbsp;글로&amp;nbsp;못적어내는게&amp;nbsp;현실이다.&amp;nbsp;나는&amp;nbsp;좀&amp;nbsp;더&amp;nbsp;현실적이지만,&amp;nbsp;하고&amp;nbsp;싶었던&amp;nbsp;것들에&amp;nbsp;대서&amp;nbsp;작성해보려고&amp;nbsp;한다.&amp;nbsp;진짜로&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있을것.&amp;nbsp;그리고&amp;nbsp;실현&amp;nbsp;가능해서&amp;nbsp;몸이&amp;nbsp;스스로&amp;nbsp;움직이는&amp;nbsp;상황.&amp;nbsp;이걸&amp;nbsp;전제로&amp;nbsp;액션&amp;nbsp;아이템을&amp;nbsp;가질&amp;nbsp;수&amp;nbsp;있도록&amp;nbsp;해봤다.&amp;nbsp;두가지는&amp;nbsp;다음과&amp;nbsp;같다.&lt;br /&gt;&lt;br /&gt;1.&amp;nbsp;재밌는&amp;nbsp;글과&amp;nbsp;그만큼&amp;nbsp;이해가&amp;nbsp;잘되는&amp;nbsp;글쓰기.&lt;br /&gt;2.&amp;nbsp;커피쳇&amp;nbsp;그리고&amp;nbsp;여러&amp;nbsp;행사&amp;nbsp;참여&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;재밌는 글쓰기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 첫째 재밌는 글쓰기. 재밌는 글쓰기는 사실 모호하다. 글이 재밌다라.. (글이 재밌을 수 있다고?) 너무 추상적이지 않는가 싶긴하지만, 내가 열심히 읽어온글들에는 위트와 유머가 섞여있는 글인 경우 잘 읽힌다고 느꼈다. 그게 어떤 장르든 간에 약간의 유머 그리고 위트들은 글의 호흡에 도움을 많이 준다.&amp;nbsp;&lt;br /&gt;아쉽지만,&amp;nbsp;개발&amp;nbsp;관련&amp;nbsp;글들은&amp;nbsp;그러한&amp;nbsp;이야기를&amp;nbsp;위트나,&amp;nbsp;유머를&amp;nbsp;녹여낼틈이&amp;nbsp;없다고&amp;nbsp;생각하지만,&amp;nbsp;그래도&amp;nbsp;최대한&amp;nbsp;노력해보려한다.&lt;br /&gt;또한&amp;nbsp;이해가&amp;nbsp;잘되는&amp;nbsp;글은&amp;nbsp;글을&amp;nbsp;읽고&amp;nbsp;난&amp;nbsp;다음&amp;nbsp;내&amp;nbsp;이야기가&amp;nbsp;무슨&amp;nbsp;이야기를&amp;nbsp;말하고자&amp;nbsp;하는지&amp;nbsp;정확하게&amp;nbsp;파악&amp;nbsp;할&amp;nbsp;수&amp;nbsp;&amp;nbsp;있는&amp;nbsp;글을&amp;nbsp;뜻한다.&amp;nbsp;그러한&amp;nbsp;글들을&amp;nbsp;적기위해서는&amp;nbsp;내가&amp;nbsp;뭘&amp;nbsp;말하고&amp;nbsp;싶은지에&amp;nbsp;대해서&amp;nbsp;골똘히&amp;nbsp;잘&amp;nbsp;생각해볼&amp;nbsp;예정이다.&lt;br /&gt;이전 글또에서 매우 적게나마 피드백 받았지만, &quot;너무 잘 읽혀요.&quot;, &quot;끝까지 완독했습니다&quot;와 같은 글을 쓰는 데에는 나는 위에서 말한 &lt;b&gt;&quot;재미&quot; 그리고 &quot;이해&quot;가 동반 되야 한다&lt;/b&gt;고 생각한다.&lt;br /&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-filename=&quot;GIF 2022-12-01 오후 1-07-11.gif&quot; data-origin-width=&quot;546&quot; data-origin-height=&quot;309&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvcFnR/btsBMR55Yof/PCqfX15KKjqLMzS7Ne8BsK/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvcFnR/btsBMR55Yof/PCqfX15KKjqLMzS7Ne8BsK/img.gif&quot; data-alt=&quot;라잌 빌게이츠 수즙고 뭔가 해내긴 해야하는데.. 재미는 없는...&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvcFnR/btsBMR55Yof/PCqfX15KKjqLMzS7Ne8BsK/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cvcFnR/btsBMR55Yof/PCqfX15KKjqLMzS7Ne8BsK/img.gif&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;546&quot; height=&quot;309&quot; data-filename=&quot;GIF 2022-12-01 오후 1-07-11.gif&quot; data-origin-width=&quot;546&quot; data-origin-height=&quot;309&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;라잌 빌게이츠 수즙고 뭔가 해내긴 해야하는데.. 재미는 없는...&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;커피챗 및 모임 참여&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막은&amp;nbsp;커피챗&amp;nbsp;및&amp;nbsp;여러&amp;nbsp;모임&amp;nbsp;참여하기이다.&amp;nbsp;글또가&amp;nbsp;인원수가&amp;nbsp;늘어감에&amp;nbsp;따라서,&amp;nbsp;해당하는&amp;nbsp;모임의&amp;nbsp;사이즈나&amp;nbsp;다양성도&amp;nbsp;많이&amp;nbsp;생겼다고&amp;nbsp;생각한다.&amp;nbsp;제가&amp;nbsp;이전에&amp;nbsp;참여했던&amp;nbsp;5,6기에서는&amp;nbsp;그러한&amp;nbsp;모임이&amp;nbsp;없었다.&amp;nbsp;(사실&amp;nbsp;없었다기&amp;nbsp;보다는&amp;nbsp;라이딩하고&amp;nbsp;싶어서...)&lt;br /&gt;이번&amp;nbsp;기수는&amp;nbsp;상당히&amp;nbsp;많은&amp;nbsp;모임이&amp;nbsp;생기기도&amp;nbsp;했고,&amp;nbsp;내가&amp;nbsp;좋아하는&amp;nbsp;모임이&amp;nbsp;많아&amp;nbsp;좋다.&amp;nbsp;사진또라던가,&amp;nbsp;영화또처럼&amp;nbsp;내가&amp;nbsp;마음이&amp;nbsp;동하는&amp;nbsp;모임이&amp;nbsp;있어서,&amp;nbsp;물&amp;nbsp;흐르듯&amp;nbsp;참여하게되어렸다.&amp;nbsp;영화또는&amp;nbsp;심지어&amp;nbsp;모임까지!!&amp;nbsp;개최되어&amp;nbsp;마음이&amp;nbsp;좋다.&amp;nbsp;&lt;br /&gt;또한 나는 도움을 필요한 사람들에게 도움을 잘 줄 의향도 있다. &lt;b&gt;특히, 주니어의 취업이나.. Spring 개발관련한&lt;/b&gt;&amp;nbsp;쪽에서는 2.n년차에 힘으로 도와볼 수 있다면 도와볼것이다.&amp;nbsp;&lt;br /&gt;내향적이였던&amp;nbsp;사람인&amp;nbsp;내가&amp;nbsp;여기저기&amp;nbsp;모임에&amp;nbsp;참여하며&amp;nbsp;주변에서&amp;nbsp;스쳐지나갔던&amp;nbsp;여러&amp;nbsp;사람들에게&amp;nbsp;조금의&amp;nbsp;말을&amp;nbsp;들으면서&amp;nbsp;방향성을&amp;nbsp;잡았던&amp;nbsp;것과&amp;nbsp;도움&amp;nbsp;받았던&amp;nbsp;것처럼&amp;nbsp;내가&amp;nbsp;할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;최선을&amp;nbsp;다해서&amp;nbsp;도움을&amp;nbsp;줄것이다.&amp;nbsp;&lt;br /&gt;개인적으로는&amp;nbsp;내가&amp;nbsp;먼져&amp;nbsp;다가가는&amp;nbsp;것보다는&amp;nbsp;역시&amp;nbsp;주변에서&amp;nbsp;도움을&amp;nbsp;요청을&amp;nbsp;해줘야지&amp;nbsp;내가&amp;nbsp;최선을&amp;nbsp;다해&amp;nbsp;돕기도&amp;nbsp;했었고,&amp;nbsp;질문&amp;nbsp;했었던&amp;nbsp;멘토도&amp;nbsp;훨씬&amp;nbsp;도움을&amp;nbsp;받았던거&amp;nbsp;같기도...&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;개인적으로&amp;nbsp;강의를&amp;nbsp;듣고&amp;nbsp;있는게&amp;nbsp;있는데,&amp;nbsp;굉장히&amp;nbsp;맘에&amp;nbsp;들기도하고,&amp;nbsp;내가&amp;nbsp;깨닳은&amp;nbsp;점이&amp;nbsp;많아서,&amp;nbsp;해당하는&amp;nbsp;글을&amp;nbsp;담주부터&amp;nbsp;쓰거나,&amp;nbsp;내&amp;nbsp;창고에&amp;nbsp;하도&amp;nbsp;많이&amp;nbsp;배워서&amp;nbsp;하늘을&amp;nbsp;뚫고말아버린&amp;nbsp;TIL을&amp;nbsp;작성하지&amp;nbsp;않을까&amp;nbsp;싶기도하다.&amp;nbsp;TIL를&amp;nbsp;TIL로만&amp;nbsp;남겨놓는것&amp;nbsp;보다는&amp;nbsp;좀&amp;nbsp;풀어서&amp;nbsp;내가&amp;nbsp;도움&amp;nbsp;받을&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;글을&amp;nbsp;작성해내면서&amp;nbsp;더욱&amp;nbsp;지식을&amp;nbsp;강화하는&amp;nbsp;과정을&amp;nbsp;겪고&amp;nbsp;싶기도하다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;남은&amp;nbsp;기간동안&amp;nbsp;모두&amp;nbsp;화이팅!&lt;/p&gt;</description>
      <category>잡담/경험담, 생각들</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/158</guid>
      <comments>https://sundries-in-myidea.tistory.com/158#entry158comment</comments>
      <pubDate>Sun, 10 Dec 2023 17:55:49 +0900</pubDate>
    </item>
    <item>
      <title>Deprecated 잘쓰는 법</title>
      <link>https://sundries-in-myidea.tistory.com/157</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Deprecated 잘쓰는 법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안쓰는 거긴하지만, 코드의 남아있는 경우 오용을 방지하기 위해서라도, Deprecated가 필요하다 그러면 이 경우 Deprecate를 사용하는 방법에 대해서 찾아봤다.&lt;br /&gt;나는 주석에 @Deprecated를 자주 사용했는데, 그외에 다른방법이 있는지도 찾아보니, 오라클에서 제공하는 가이드라인이 존재하여 찾아보았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결론을 먼저 말하자면,&lt;/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;@Deprecated라는 일반 어노테이션과, javadocs에 사용해야할 @deprecated를 동시에 섞어써야함.&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;기본적으로 @Deprecated는 컴파일때 주의 문구를 띄워줌. IDE에서 노란색 줄이 그어짐. 그런데, Javadocs에만 @deprecated를 작성해놓으면 그게 적용되지 않음.&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;즉, 문서에도 @deprecated가 적용되고 싶었더라면, docs에만 적용하는게 맞겠지만, 문서만 적용해두면 결국 그게 컴파일시에는 적용되지 않으므로 같이 적어주는게 가장 Base Practices 전략이라고 봐야한다.&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;br /&gt;&lt;b&gt;Deprecated elements should have both the annotation and the Javadoc tag&lt;/b&gt; 라는 룰이 아예 지정되어있음.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;참고&lt;/h3&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://rules.sonarsource.com/java/RSPEC-1123/&quot;&gt;https://rules.sonarsource.com/java/RSPEC-1123/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/technotes/guides/javadoc/deprecation/deprecation.html&quot;&gt;https://docs.oracle.com/javase/8/docs/technotes/guides/javadoc/deprecation/deprecation.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/20674135/deprecated-vs-deprecated&quot;&gt;https://stackoverflow.com/questions/20674135/deprecated-vs-deprecated&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;figure id=&quot;og_1693641052075&quot; style=&quot;color: #333333; text-align: start;&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Java static code analysis: Deprecated elements should have both the annotation and the Javadoc tag&quot; data-og-description=&quot;Deprecation should be marked with both the &amp;lt;code&amp;gt;@Deprecated&amp;lt;/code&amp;gt; annotation and @deprecated Javadoc tag. The annotation enables tools such as IDEs to warn about referencing deprecated elements, and the tag can be used to explain when it was deprecated, &quot; data-og-host=&quot;rules.sonarsource.com&quot; data-og-source-url=&quot;https://rules.sonarsource.com/java/RSPEC-1123/&quot; data-og-url=&quot;https://rules.sonarsource.com/java/rspec-1123/&quot; data-og-image=&quot;&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://rules.sonarsource.com/java/RSPEC-1123/&quot; data-source-url=&quot;https://rules.sonarsource.com/java/RSPEC-1123/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('&amp;quot;&amp;quot;');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;Java static code analysis: Deprecated elements should have both the annotation and the Javadoc tag&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;Deprecation should be marked with both the &amp;lt;code&amp;gt;@Deprecated&amp;lt;/code&amp;gt; annotation and @deprecated Javadoc tag. The annotation enables tools such as IDEs to warn about referencing deprecated elements, and the tag can be used to explain when it was deprecated,&lt;/p&gt;
&lt;p class=&quot;og-host&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;rules.sonarsource.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1693641044580&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;How and When to Deprecate APIs&quot; data-og-description=&quot;How and When To Deprecate APIs You may have heard the term, &amp;quot;self-deprecating humor,&amp;quot; or humor that minimizes the speaker's importance. A deprecated class or method is like that. It is no longer important. It is so unimportant, in fact, that you should no &quot; data-og-host=&quot;docs.oracle.com&quot; data-og-source-url=&quot;https://docs.oracle.com/javase/8/docs/technotes/guides/javadoc/deprecation/deprecation.html&quot; data-og-url=&quot;https://docs.oracle.com/javase/8/docs/technotes/guides/javadoc/deprecation/deprecation.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/technotes/guides/javadoc/deprecation/deprecation.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.oracle.com/javase/8/docs/technotes/guides/javadoc/deprecation/deprecation.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;How and When to Deprecate APIs&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;How and When To Deprecate APIs You may have heard the term, &quot;self-deprecating humor,&quot; or humor that minimizes the speaker's importance. A deprecated class or method is like that. It is no longer important. It is so unimportant, in fact, that you should no&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.oracle.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1693641054388&quot; style=&quot;color: #333333; text-align: start;&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;@deprecated vs @Deprecated&quot; data-og-description=&quot;I'm able to deprecate a function or class with the @Deprecated annotation. But there is a @deprecated javadoc tag in a javadoc comment itself marking the class/function as deprecated. Does the @&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/20674135/deprecated-vs-deprecated&quot; data-og-url=&quot;https://stackoverflow.com/questions/20674135/deprecated-vs-deprecated&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bVnL9i/hyTPxPxSTw/mLuhNAs0to5R4Bs6PidigK/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://stackoverflow.com/questions/20674135/deprecated-vs-deprecated&quot; data-source-url=&quot;https://stackoverflow.com/questions/20674135/deprecated-vs-deprecated&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bVnL9i/hyTPxPxSTw/mLuhNAs0to5R4Bs6PidigK/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;@deprecated vs @Deprecated&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;I'm able to deprecate a function or class with the @Deprecated annotation. But there is a @deprecated javadoc tag in a javadoc comment itself marking the class/function as deprecated. Does the @&lt;/p&gt;
&lt;p class=&quot;og-host&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Java</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/157</guid>
      <comments>https://sundries-in-myidea.tistory.com/157#entry157comment</comments>
      <pubDate>Sat, 2 Sep 2023 16:51:29 +0900</pubDate>
    </item>
    <item>
      <title>테스트 코드를 짜고는 싶은데, 테스트 실패시 빌드 실패가 걱정된다면?</title>
      <link>https://sundries-in-myidea.tistory.com/156</link>
      <description>&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;test.onlyIf { 
!project.hasProperty('test') 
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음처럼 작성해주면, 테스트 프로퍼티가 있는경우 혹은 해당 프로퍼티의 존재 여부를 따져서 테스트에 대한 실행을 막는다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;위에는 스크립트문으로 강제하는 방법이라면, 명령어로 강제하는 방법도 있음.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;gradle build -x test&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;하지만, 실제로 빌드 환경을 구축해두면 스크립트로 테스트가 다 돌아가므로, 굳이 아래 스크립트문 까지 사용하는 것보다는 위의 빌드 제약 조건으로 걸어 주는게 더 좋다고 생각한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 스크립트로 테스트가 돌아가는 것은 gradle build 스크립트를 반드시 돌리는 상황이 생겨서 그런거지.. 우리가 직접 스크립트문을 작성해서 돌린다면, 스크립트 명령문으로 강제 할 수는 있다&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://abbo.tistory.com/303#google_vignette&quot;&gt;https://abbo.tistory.com/303#google_vignette&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://docs.gradle.org/current/userguide/java_testing.html#sec:skipping_java_tests&quot;&gt;https://docs.gradle.org/current/userguide/java_testing.html#sec:skipping_java_tests&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1693640134809&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Gradle build 시 test 로 인해 FAILED 나는 경우 해결 로그&quot; data-og-description=&quot;보통 개발 시 IntelliJ 를 사용하여 Gradle의 컴파일 및 jar를 생성하여 작업을 진행합니다. 하지만 이게 도커에서 돌아가는 경우 보통 CLI 기반으로 작동이 되기 때문에 gradle build 또는 ./gradlew build 명&quot; data-og-host=&quot;abbo.tistory.com&quot; data-og-source-url=&quot;https://abbo.tistory.com/303#google_vignette&quot; data-og-url=&quot;https://abbo.tistory.com/303&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/s4mzc/hyTMcTPTGk/2jWXDCKa7jVePS7kK9rAaK/img.png?width=693&amp;amp;height=318&amp;amp;face=0_0_693_318,https://scrap.kakaocdn.net/dn/bnmMZA/hyTL3o1uQZ/IGPayRNC8cC68LqMPrlTtK/img.png?width=693&amp;amp;height=318&amp;amp;face=0_0_693_318,https://scrap.kakaocdn.net/dn/blhxQy/hyTPxhG8G8/Ikv7CEziOQaNip6SKd3Jpk/img.png?width=693&amp;amp;height=318&amp;amp;face=0_0_693_318&quot;&gt;&lt;a href=&quot;https://abbo.tistory.com/303#google_vignette&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://abbo.tistory.com/303#google_vignette&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/s4mzc/hyTMcTPTGk/2jWXDCKa7jVePS7kK9rAaK/img.png?width=693&amp;amp;height=318&amp;amp;face=0_0_693_318,https://scrap.kakaocdn.net/dn/bnmMZA/hyTL3o1uQZ/IGPayRNC8cC68LqMPrlTtK/img.png?width=693&amp;amp;height=318&amp;amp;face=0_0_693_318,https://scrap.kakaocdn.net/dn/blhxQy/hyTPxhG8G8/Ikv7CEziOQaNip6SKd3Jpk/img.png?width=693&amp;amp;height=318&amp;amp;face=0_0_693_318');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Gradle build 시 test 로 인해 FAILED 나는 경우 해결 로그&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;보통 개발 시 IntelliJ 를 사용하여 Gradle의 컴파일 및 jar를 생성하여 작업을 진행합니다. 하지만 이게 도커에서 돌아가는 경우 보통 CLI 기반으로 작동이 되기 때문에 gradle build 또는 ./gradlew build 명&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;abbo.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1693640166380&quot; style=&quot;color: #333333; text-align: start;&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Testing in Java &amp;amp; JVM projects&quot; data-og-description=&quot;On the few occasions that you want to debug your code while the tests are running, it can be helpful if you can attach a debugger at that point. You can either set the Test.getDebug() property to true or use the --debug-jvm command line option, or use --no&quot; data-og-host=&quot;docs.gradle.org&quot; data-og-source-url=&quot;https://docs.gradle.org/current/userguide/java_testing.html#sec:skipping_java_tests&quot; data-og-url=&quot;https://docs.gradle.org/current/userguide/java_testing.html#sec:skipping_java_tests&quot; data-og-image=&quot;&quot;&gt;&lt;a style=&quot;color: #000000;&quot; href=&quot;https://docs.gradle.org/current/userguide/java_testing.html#sec:skipping_java_tests&quot; data-source-url=&quot;https://docs.gradle.org/current/userguide/java_testing.html#sec:skipping_java_tests&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('&amp;quot;&amp;quot;');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; style=&quot;color: #000000;&quot; data-ke-size=&quot;size16&quot;&gt;Testing in Java &amp;amp; JVM projects&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;On the few occasions that you want to debug your code while the tests are running, it can be helpful if you can attach a debugger at that point. You can either set the Test.getDebug() property to true or use the --debug-jvm command line option, or use --no&lt;/p&gt;
&lt;p class=&quot;og-host&quot; style=&quot;color: #909090;&quot; data-ke-size=&quot;size16&quot;&gt;docs.gradle.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/156</guid>
      <comments>https://sundries-in-myidea.tistory.com/156#entry156comment</comments>
      <pubDate>Sat, 2 Sep 2023 16:38:16 +0900</pubDate>
    </item>
    <item>
      <title>Spring boot build gradle에서 test task시 하면 좋은 것.</title>
      <link>https://sundries-in-myidea.tistory.com/155</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;build.gradle 파일에서&lt;/p&gt;
&lt;pre class=&quot;isbl&quot;&gt;&lt;code&gt;tasks.named('test') }{
    useJunitPlatform()
} &lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 많이 쓰는데, gradle 가이드 문서에서 추천하는 방식은 다음처럼&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;tasks.named('test', Test) }{
    useJunitPlatform()
} &lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이래야지 useJunitPlatform이 메소드 역할을 하는것.&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;참고로 Spring Boot 기본 설정에서 제공하는 양식이 위의 양식으로 build.gradle이 되어있어서 그런거지 실제로 gradle 버전업을 통해서 적용 해야할 문법은 아래가 맞다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 없어도 동작은 하는듯한데.. 암튼 공식문서에선 그랬다.&lt;br /&gt;&lt;a href=&quot;https://docs.gradle.org/current/userguide/java_testing.html#sec:java_testing_basics&quot;&gt;https://docs.gradle.org/current/userguide/java_testing.html#sec:java_testing_basics&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1693639971338&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Testing in Java &amp;amp; JVM projects&quot; data-og-description=&quot;On the few occasions that you want to debug your code while the tests are running, it can be helpful if you can attach a debugger at that point. You can either set the Test.getDebug() property to true or use the --debug-jvm command line option, or use --no&quot; data-og-host=&quot;docs.gradle.org&quot; data-og-source-url=&quot;https://docs.gradle.org/current/userguide/java_testing.html#sec:java_testing_basics&quot; data-og-url=&quot;https://docs.gradle.org/current/userguide/java_testing.html#sec:java_testing_basics&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.gradle.org/current/userguide/java_testing.html#sec:java_testing_basics&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.gradle.org/current/userguide/java_testing.html#sec:java_testing_basics&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Testing in Java &amp;amp; JVM projects&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;On the few occasions that you want to debug your code while the tests are running, it can be helpful if you can attach a debugger at that point. You can either set the Test.getDebug() property to true or use the --debug-jvm command line option, or use --no&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.gradle.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/155</guid>
      <comments>https://sundries-in-myidea.tistory.com/155#entry155comment</comments>
      <pubDate>Sat, 2 Sep 2023 16:34:28 +0900</pubDate>
    </item>
    <item>
      <title>웹에서 잘 읽히는 글이 되는 방법</title>
      <link>https://sundries-in-myidea.tistory.com/154</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;웹에서 잘 읽히는 글이 되는 방법.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2pDmL/btscBDTPYji/fqQLGXeF2PmZg2vWur8f60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2pDmL/btscBDTPYji/fqQLGXeF2PmZg2vWur8f60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2pDmL/btscBDTPYji/fqQLGXeF2PmZg2vWur8f60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2pDmL%2FbtscBDTPYji%2FfqQLGXeF2PmZg2vWur8f60%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;1080&quot; height=&quot;768&quot; data-filename=&quot;웹에서 잘 읽히는 글이 되는 방법.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;사람들은 글을 읽지 않고 페이지를 흐름을 쭉 읽어 내려가면서 단어, 문장을 골라내는 경향이 있음&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 웹상에서 잘 읽히는 글을 만들어보고 싶은 사람이고 테크라이팅, &lt;b&gt;기술적 도큐먼트를 통해 잘 읽히는 글을 작성하는 것도 개발자가 가질 수 있는 좋은 능력중 하나라고 생각했다&lt;/b&gt;. 마침 이번 프로젝트 종료 직전에 Readme와 같은 개발 문서를 잘 작성하는 방법이 뭘까 고민 하던 도중 링크를 하나 찾았고 좋은 글이라 지점 별로 요약 정리를 해보았다.&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;UX 전문 업체인 닐슨노먼그룹에서 요청하는 웹에서 잘 읽히는 글에 대한 아티클을 읽어보자면, 첫 연구는 1997년이고 그것을 추가 연구를 통해 해석한 것이 2021년쯤에 나왔다. 그 내용은 웹 상에서 어떻게 하면 잘 읽히는 글을 만들 수 있는지에 대해서 작성되어있다.&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;i&gt;글을 다 읽어야지!!!&lt;/i&gt; 이런 느낌이라기 보다는 사실 필요한 정보가 있는가를 찾기 위해서 글을 읽게 되는 경향이 있다고 생각한다. (개인적인 주관이 좀 들어가있긴하지만, 우리가 검색하는 방식을 생각해보면 맞다.)&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;b&gt;도대체 그 놈의 &quot;웹&quot;에서 잘읽히는 글은 뭘까?&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&quot;웹에서&quot; 읽기 좋은 글?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 웹에서 읽히는 글은 반드시 &lt;b&gt;&quot;스캔 가능하도록&quot;&lt;/b&gt; 텍스트를 잘 써 내려가는 게 중요하게 된다.&lt;br /&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;의미 있는 부제목&lt;/li&gt;
&lt;li&gt;Dotted List (점 달려있는것)&lt;/li&gt;
&lt;li&gt;단락에는 한개의 주제만 있기 &amp;rarr; 단락 내부에서 주제가 많이 바뀌게 시작하면 단락자체를 건너뜀&lt;/li&gt;
&lt;li&gt;역피라미드식 글쓰기 스타일 &amp;rarr; 결론 먼저 알려줄 것.&lt;/li&gt;
&lt;li&gt;기존 글쓰기양보다 글자수 자체를 더 줄이기 (50개 단어로 표현 가능하면 50개로 써야지 100자로 늘려쓰지 말것)&lt;/li&gt;
&lt;/ul&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;고품질 그래픽 &amp;rarr; 사진을 넣어도 좀 신뢰성있는 사진 넣을것&lt;/li&gt;
&lt;li&gt;좋은 글&lt;/li&gt;
&lt;li&gt;아웃바운드 하이퍼링크 (출처를 남기거나 외부 링크 남기는 것)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 사람들은 마케팅적인 글들을 귀신같이 찾아내기 때문에 (&quot;역대최고&quot;) 그런 글을 보자마자 이탈률이 높아졌다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;최근 경향&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무래도 위의 글은 97년도에 작성된 글이였고, 그때와 현재는 많은 차이가 있다고 생각했는데, 마침 구글링을 통해 최근 연구 경향도 노출된 글을 찾아 확인해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 글에서 노출된 글의 읽는 방식은 다음처럼 구성되게 된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;읽어 내려가기 &amp;rarr; 스킵 &amp;rarr; 건너 뛴걸 이해하기 위하여 위로 올라가기 &amp;rarr; 다시 읽기&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;동기 부여수준: 유저한테 이정보가 얼마나 중요한지?&lt;/li&gt;
&lt;li&gt;작업 유형: 특별한 사실을 찾는지? 새롭거나 흥미로운 사실을 찾는건지, 주제에 대한 연구를 위해 찾는것인지?&lt;/li&gt;
&lt;li&gt;집중의 동기: 당장 이 찾는게 얼마나 중요한 업무인지?&lt;/li&gt;
&lt;li&gt;개인적인 차이: 독서하는 것처럼 세세하게 읽는 사람인지? 스캔하듯이 띄엄띄엄 읽는 사람인지?&lt;/li&gt;
&lt;/ol&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;&quot;사람들은 컨텐츠 자체를 완전히 혹은 일목요연하게 보지 않는다.&quot;&lt;/b&gt; 이렇기 때문에 필요한 방식으로 컨텐츠 설계하는게 좋다고 이야기한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;제목, 부제목 잘 짓고, 단락별 섹션을 만들기.&lt;/li&gt;
&lt;li&gt;중요 정보 앞에 설계&lt;/li&gt;
&lt;li&gt;글머리 기호(bulleted list) 혹은 &lt;b&gt;볼드체&lt;/b&gt;를 통해 시선 집중&lt;/li&gt;
&lt;li&gt;평이한 글로 작성할 것&lt;/li&gt;
&lt;/ol&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;h2 data-ke-size=&quot;size26&quot;&gt;마무리하며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 위의 글을 읽다보면 결국 보면 다 맞는 말이다. 물론 원본의 글 관점 자체가 &lt;b&gt;디자인&lt;/b&gt;, &lt;b&gt;마케팅&lt;/b&gt;에 부합하는 글쓰기론에 가깝긴하더라도, 글을 쓰는 사람 입장에서 어떤식으로 써야 웹에서 잘 읽히는 글을 작성할 수 있는지를 파악하기에는 수월 해보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 외국의 웹사이트 기조를 통한 글 작성법이기 때문에 한국과의 차이가 존재는 할 수 있겠으나 웹이라는게 크게 차이가 점점 줄어가는 형태이기도하며.. 최근 아티클에서는 &lt;b&gt;중국의 읽는 방식을 비교하면 큰 차이가 없다&lt;/b&gt;는 것을 이야기 했던 것을 보면 사실 큰 &lt;b&gt;문제는 없어보인다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹에서 글쓸일이 많은 나와 같은 개발 블로그를 운영하거나, &lt;b&gt;개발적인 테크 라이팅&lt;/b&gt;에서도 추우웅분히 도움이 될수 있는 글이라서 한번 정리를 해보았다. 나 혹은 다른 여러 사람들에게 도움을 줄 수 있는 글이라면 좋겠다.&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://www.nngroup.com/articles/how-users-read-on-the-web/&quot;&gt;How Users Read on the Web&lt;/a&gt;&lt;br /&gt;&lt;a href=&quot;https://www.nngroup.com/articles/how-people-read-online/&quot;&gt;How People Read Online: New and Old Findings&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1682432138946&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;How People Read Online: New and Old Findings&quot; data-og-description=&quot;Looking back at findings from a series of eyetracking studies over 13 years, we see that fundamental scanning behaviors remain constant, even as designs change.&quot; data-og-host=&quot;www.nngroup.com&quot; data-og-source-url=&quot;https://www.nngroup.com/articles/how-people-read-online/&quot; data-og-url=&quot;https://www.nngroup.com/articles/how-people-read-online/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bMCf4a/hySouad9J9/soRfLWLomI0csqFlGeG2tK/img.png?width=1687&amp;amp;height=882&amp;amp;face=0_0_1687_882,https://scrap.kakaocdn.net/dn/G7Uef/hySoIl0Rat/mK0lo8eUGflYkm93h0Yfj1/img.png?width=1457&amp;amp;height=2067&amp;amp;face=0_0_1457_2067,https://scrap.kakaocdn.net/dn/bwBVeF/hySpD4saTp/ZCC5iSlrucRry4w6jtqLvK/img.png?width=1400&amp;amp;height=1592&amp;amp;face=0_0_1400_1592&quot;&gt;&lt;a href=&quot;https://www.nngroup.com/articles/how-people-read-online/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.nngroup.com/articles/how-people-read-online/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bMCf4a/hySouad9J9/soRfLWLomI0csqFlGeG2tK/img.png?width=1687&amp;amp;height=882&amp;amp;face=0_0_1687_882,https://scrap.kakaocdn.net/dn/G7Uef/hySoIl0Rat/mK0lo8eUGflYkm93h0Yfj1/img.png?width=1457&amp;amp;height=2067&amp;amp;face=0_0_1457_2067,https://scrap.kakaocdn.net/dn/bwBVeF/hySpD4saTp/ZCC5iSlrucRry4w6jtqLvK/img.png?width=1400&amp;amp;height=1592&amp;amp;face=0_0_1400_1592');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;How People Read Online: New and Old Findings&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Looking back at findings from a series of eyetracking studies over 13 years, we see that fundamental scanning behaviors remain constant, even as designs change.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.nngroup.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1682432137309&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;How Users Read on the Web&quot; data-og-description=&quot;Users don't read Web pages, they scan. Highlighting and concise writing improved measured usability 47-58%. Marketese imposed a cognitive burden on users and was disliked.&quot; data-og-host=&quot;www.nngroup.com&quot; data-og-source-url=&quot;https://www.nngroup.com/articles/how-users-read-on-the-web/&quot; data-og-url=&quot;https://www.nngroup.com/articles/how-users-read-on-the-web/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cLm4qn/hySoFbJ7al/hTKh0kNew3sMJxxxQ1uKZk/img.png?width=1920&amp;amp;height=1004&amp;amp;face=0_0_1920_1004,https://scrap.kakaocdn.net/dn/q6lxb/hySpCYMuk9/iX2MBaY7RK8RZCpSdwbzG0/img.png?width=817&amp;amp;height=388&amp;amp;face=0_0_817_388,https://scrap.kakaocdn.net/dn/0vUO7/hySoDLLdmY/TuWJmnKpIb81HFTLE2ifL0/img.jpg?width=650&amp;amp;height=364&amp;amp;face=187_81_298_202&quot;&gt;&lt;a href=&quot;https://www.nngroup.com/articles/how-users-read-on-the-web/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.nngroup.com/articles/how-users-read-on-the-web/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cLm4qn/hySoFbJ7al/hTKh0kNew3sMJxxxQ1uKZk/img.png?width=1920&amp;amp;height=1004&amp;amp;face=0_0_1920_1004,https://scrap.kakaocdn.net/dn/q6lxb/hySpCYMuk9/iX2MBaY7RK8RZCpSdwbzG0/img.png?width=817&amp;amp;height=388&amp;amp;face=0_0_817_388,https://scrap.kakaocdn.net/dn/0vUO7/hySoDLLdmY/TuWJmnKpIb81HFTLE2ifL0/img.jpg?width=650&amp;amp;height=364&amp;amp;face=187_81_298_202');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;How Users Read on the Web&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Users don't read Web pages, they scan. Highlighting and concise writing improved measured usability 47-58%. Marketese imposed a cognitive burden on users and was disliked.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.nngroup.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>잡담/경험담, 생각들</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/154</guid>
      <comments>https://sundries-in-myidea.tistory.com/154#entry154comment</comments>
      <pubDate>Tue, 25 Apr 2023 23:17:22 +0900</pubDate>
    </item>
    <item>
      <title>Spring 설정파일 우선순위</title>
      <link>https://sundries-in-myidea.tistory.com/153</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;font-family: AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif;&quot;&gt;Application.yaml 설정 파일 실행시 우선순위&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Spring 설정 우선순위.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/P8uPS/btr9AZELuz8/ZYP3GnKQeMBqRpRY2jKBYk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/P8uPS/btr9AZELuz8/ZYP3GnKQeMBqRpRY2jKBYk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/P8uPS/btr9AZELuz8/ZYP3GnKQeMBqRpRY2jKBYk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FP8uPS%2Fbtr9AZELuz8%2FZYP3GnKQeMBqRpRY2jKBYk%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;1080&quot; height=&quot;768&quot; data-filename=&quot;Spring 설정 우선순위.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;768&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;br /&gt;default 셋업 아래에 on-profile 설정되어있는데 안먹어서 왜 안먹나 했더니 application.yaml이 우선순위가 높아서 default 옵션을 무시하고 다른 옵션이 들어가버려서 안되길래 도대체 어떤식으로 해결해야하는지 확인해봤다.&lt;br /&gt;이경우에는 application.yaml에 profile이 있으면 됨 혹은 아예 파일이 따로 존재한다던지 여러가지 방법이 존재하니 참고&lt;br /&gt;Application.yaml은 다음과 같은 우선순위를 가지게된다.&lt;/p&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;1.  [Application properties](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config.files) packaged inside your jar (`application.properties` and YAML variants).

2.  [Profile-specific application properties](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config.files.profile-specific) packaged inside your jar (`application-{profile}.properties` and YAML variants).

3.  [Application properties](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config.files) outside of your packaged jar (`application.properties` and YAML variants).

4.  [Profile-specific application properties](https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config.files.profile-specific) outside of your packaged jar (`application-{profile}.properties` and YAML variants).&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내부 Jar이 먼저 우선시 (application.yaml) &amp;rarr; 내부 Jar의 Profile이 다음 순위 (application-profile.yaml) &amp;rarr; Jar 밖의 Application-yaml &amp;rarr; Jar 외부의 Profile이 마지막 순위&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 구체적인 내부 로직&lt;/b&gt;은 다음과 같이 구성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot는 애플리케이션이 시작될 때 다음 위치에서 application.properties 및 application.yaml 파일을 자동으로 우선순위를 매기게 됩니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;classpath 에서
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;classpath 루트&lt;/li&gt;
&lt;li&gt;classpath /config 패키지&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;현재 디렉토리에서
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;현재 디렉토리&lt;/li&gt;
&lt;li&gt;현재 디렉터리의 config/하위 디렉터리&lt;/li&gt;
&lt;li&gt;config/서브디렉토리의 바로 하위 디렉토리&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. Profile이 지정된 경우&lt;/b&gt;에는 다음처럼 우선순위 구성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot는 application.yaml만 처리하는게 아닌, application-{profile}규칙을 가진 파일도 프로파일별 파일도 로드한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로필별 속성은 표준 application.properties와 &lt;b&gt;동일한 위치&lt;/b&gt;에서 로드되며, 프로필별 파일이 항상 비특정 파일보다 우선합니다. (비특정이라는 거는 아마 아예 yaml 파일인데, 이름이 완전 다른 케이스말하는듯? )&lt;br /&gt;여러 프로필이 지정된 경우 &lt;code&gt;last-wins&lt;/code&gt; 전략이 적용됩니다.&lt;br /&gt;예를 들어, prod,live 프로파일이 spring.profiles.active 속성에 의해 지정된 경우, application-prod.properties의 값을 application-live.properties의 값으로 오버라이드(overriden)할 수 있습니다.&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;code&gt;last-wins&lt;/code&gt; 전략은 다음과 같은 예로 이뤄집니다. &lt;code&gt;last-wins&lt;/code&gt; 전략은 로케이션 그룹 레벨을 적용합니다.&lt;br /&gt;&lt;code&gt;spring.config.location&lt;/code&gt;에서 &lt;b&gt;&quot;,&quot;&lt;/b&gt; 인 케이스와 &lt;b&gt;&quot;;&quot;&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;code&gt;prod,live&lt;/code&gt; 되어있고, 파일이 다음처럼 위치해있다고 가정합시다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/cfg&lt;br /&gt;application-live.properties (or yaml)&lt;br /&gt;/ext&lt;br /&gt;application-live.properties&lt;br /&gt;application-prod.properties&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;spring.config.location&lt;/code&gt; 가 &lt;code&gt;classpath:/cfg/,classpath:/ext/&lt;/code&gt; 일 경우는 무조건 /cfg가 /ext 보다 우선순위가 높습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;/cfg/application-live.properties&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/ext/application-prod.properties&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/ext/application-live.properties&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;spring.config.location&lt;/code&gt; 가 &lt;code&gt;classpath:/cfg/;classpath:/ext/&lt;/code&gt; 일 경우는 &lt;code&gt;/cfg&lt;/code&gt;와 &lt;code&gt;/ext&lt;/code&gt;가 동일 우선순위로 처리리 됩니다. 이 경우는 Prod가 높은 우선순위를 가집니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;/ext/application-prod.properties&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/cfg/application-live.properties&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/ext/application-live.properties&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;활성 프로필이 설정되어 있지 않은 경우 사용되는 기본 프로필 세트(기본값은 &lt;code&gt;default&lt;/code&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;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config&quot;&gt;https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1681220699503&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Core Features&quot; data-og-description=&quot;Spring Boot lets you externalize your configuration so that you can work with the same application code in different environments. You can use a variety of external configuration sources including Java properties files, YAML files, environment variables, a&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config&quot; data-og-url=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Core Features&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Spring Boot lets you externalize your configuration so that you can work with the same application code in different environments. You can use a variety of external configuration sources including Java properties files, YAML files, environment variables, a&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;</description>
      <category>Spring</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/153</guid>
      <comments>https://sundries-in-myidea.tistory.com/153#entry153comment</comments>
      <pubDate>Tue, 11 Apr 2023 22:46:32 +0900</pubDate>
    </item>
    <item>
      <title>전체 쿠키 날리지 않고 티스토리 400 Bad Request 해결하기</title>
      <link>https://sundries-in-myidea.tistory.com/152</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;전체 쿠키 날리지 않고 티스토리 400 Bad Request 해결하기.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bshGs5/btr5gQLlSku/ZLoAjiYBWx1tkXunUdIOrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bshGs5/btr5gQLlSku/ZLoAjiYBWx1tkXunUdIOrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bshGs5/btr5gQLlSku/ZLoAjiYBWx1tkXunUdIOrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbshGs5%2Fbtr5gQLlSku%2FZLoAjiYBWx1tkXunUdIOrK%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;1080&quot; height=&quot;768&quot; data-filename=&quot;전체 쿠키 날리지 않고 티스토리 400 Bad Request 해결하기.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;꽤 오랜 기간동안 오류가 발생했는데...&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;400 에러는 꽤 오래전부터 발생했긴했다.. 구글 검색을 찾아보면 대충..한 작년 12월쯤 부터인가 9월부터인가 로그인하게 되면 아예 400 Bad Request가뜨면서 오류가 발생했다. 해결법 자체는 많은데, 대부분 전체기록 삭제를 추천해주기 때문에 나는 그 방법 말고 다른 방법을 추천한다.&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;h2 data-ke-size=&quot;size26&quot;&gt;해결방식&lt;/h2&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;317&quot; data-origin-height=&quot;197&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k1A7D/btr5dAbGB15/FuHEckZ7bFiLSXQRIYMmkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k1A7D/btr5dAbGB15/FuHEckZ7bFiLSXQRIYMmkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k1A7D/btr5dAbGB15/FuHEckZ7bFiLSXQRIYMmkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk1A7D%2Fbtr5dAbGB15%2FFuHEckZ7bFiLSXQRIYMmkk%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;317&quot; height=&quot;197&quot; data-origin-width=&quot;317&quot; data-origin-height=&quot;197&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주소 옆의 좌물쇠 클릭 &amp;rarr;쿠키 클릭&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-03-22 오전 12.05.03.png&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;1052&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cikP1U/btr5aGjlZM5/52bCUxGuIfYZsu3gK0qwO0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cikP1U/btr5aGjlZM5/52bCUxGuIfYZsu3gK0qwO0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cikP1U/btr5aGjlZM5/52bCUxGuIfYZsu3gK0qwO0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcikP1U%2Fbtr5aGjlZM5%2F52bCUxGuIfYZsu3gK0qwO0%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;621&quot; height=&quot;751&quot; data-filename=&quot;스크린샷 2023-03-22 오전 12.05.03.png&quot; data-origin-width=&quot;870&quot; data-origin-height=&quot;1052&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;555&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4rYCa/btr45lmoTq8/kfFkcOcaTZbkHOXzblniPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4rYCa/btr45lmoTq8/kfFkcOcaTZbkHOXzblniPk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4rYCa/btr45lmoTq8/kfFkcOcaTZbkHOXzblniPk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4rYCa%2Fbtr45lmoTq8%2FkfFkcOcaTZbkHOXzblniPk%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;2000&quot; height=&quot;555&quot; data-origin-width=&quot;2000&quot; data-origin-height=&quot;555&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;b&gt;단, 이후에도 400 Error 동일하게 발생시 &lt;a href=&quot;http://Kakao.com&quot;&gt;Kakao.com&lt;/a&gt; &amp;nbsp;관련 쿠키도 삭제해야함. &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우는 아마 나와 비슷한 상황일 가능성이 높음. 현재 나는 티스토리를 Kakao 로그인을 통해서 이용하고 있는 상황이라.. 관련한 카카오 관련 쿠키가 말썽을 인듯으로 추측중..&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;하지만 티스토리 로그인 하는 사용자들도 비슷하기 때문에 kakao 로그인만의 문제는 아니라고 생각합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>잡담/경험담, 생각들</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/152</guid>
      <comments>https://sundries-in-myidea.tistory.com/152#entry152comment</comments>
      <pubDate>Wed, 22 Mar 2023 00:09:30 +0900</pubDate>
    </item>
    <item>
      <title>Spring boot 3.0.0 SQL Basic Binder 로깅 안되는 문제 해결기</title>
      <link>https://sundries-in-myidea.tistory.com/151</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Spring boot 3.0.0 SQL Binder 로깅 안되는 문제.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bV54wi/btrSHYvTP0n/ZAhzi6Y96PJNq8b9iP9mvk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bV54wi/btrSHYvTP0n/ZAhzi6Y96PJNq8b9iP9mvk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bV54wi/btrSHYvTP0n/ZAhzi6Y96PJNq8b9iP9mvk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbV54wi%2FbtrSHYvTP0n%2FZAhzi6Y96PJNq8b9iP9mvk%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;1080&quot; height=&quot;768&quot; data-filename=&quot;Spring boot 3.0.0 SQL Binder 로깅 안되는 문제.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스프링부트에서 SQL문을 로깅하자&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA에서 sql문 로깅하기 위해서는 application.yml에서 로깅레벨이나 옵션 사용해서 문제해결을 한다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;spring:
  jpa:
    properties:
      hibernate:
        format_sql: true
        use_sql_comments: true
logging:
  level:
    org.hibernate.SQL: debug
    org.hibernate.type.descriptor.sql: trace&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 JPA를 통해서 만들어지는 SQL문을 확인해볼 수도 있고&amp;hellip; SQL문에서 발생하는 ?를 통해 들어가진 값들도 확인이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 2.7.6 Spring boot 버젼입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-12-02 오후 8.40.51.png&quot; data-origin-width=&quot;2112&quot; data-origin-height=&quot;534&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOZJ4r/btrSHKkh6OF/EEHGeRJsuN4aU2qSVlco90/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOZJ4r/btrSHKkh6OF/EEHGeRJsuN4aU2qSVlco90/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOZJ4r/btrSHKkh6OF/EEHGeRJsuN4aU2qSVlco90/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOZJ4r%2FbtrSHKkh6OF%2FEEHGeRJsuN4aU2qSVlco90%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;2112&quot; height=&quot;534&quot; data-filename=&quot;스크린샷 2022-12-02 오후 8.40.51.png&quot; data-origin-width=&quot;2112&quot; data-origin-height=&quot;534&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 3.0.0 Spring Boot 버젼입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-12-02 오후 8.40.20.png&quot; data-origin-width=&quot;2304&quot; data-origin-height=&quot;580&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCsYEk/btrSHkzJ673/6ioQiJclODOAWciPZMzTO1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCsYEk/btrSHkzJ673/6ioQiJclODOAWciPZMzTO1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCsYEk/btrSHkzJ673/6ioQiJclODOAWciPZMzTO1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCsYEk%2FbtrSHkzJ673%2F6ioQiJclODOAWciPZMzTO1%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;2304&quot; height=&quot;580&quot; data-filename=&quot;스크린샷 2022-12-02 오후 8.40.20.png&quot; data-origin-width=&quot;2304&quot; data-origin-height=&quot;580&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot 3.0.0 버젼으로 넘어오면서 이게 안되는 사례가 발생했다.&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;문제는 Hibernate 버젼업...&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그가 안찍혀서.. 뭔가 이상하다 싶어서 &lt;b&gt;spring boot issue&lt;/b&gt;도 뒤져봐도 안나왔는데.. 잘 찾아보니까 이게 하이버네이트가 버젼업되면서 발생했던 문제이였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링부트 버젼 3.0.0 부터는 하이버네이트가 6.1.5가 버젼업이 되면서 하이버네이트가 로깅하는 방식을 변경해버린게 문제였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 공식문서에서 제공하는 로깅 파트를 보면 확실히 차이가 난다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kXi9X/btrSIrEvi3k/upSgdP9kAiW2mtb5KCfOfK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kXi9X/btrSIrEvi3k/upSgdP9kAiW2mtb5KCfOfK/img.png&quot; data-origin-width=&quot;996&quot; data-origin-height=&quot;410&quot; data-is-animation=&quot;false&quot; style=&quot;width: 44.2462%; margin-right: 10px;&quot; data-widthpercent=&quot;44.77&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kXi9X/btrSIrEvi3k/upSgdP9kAiW2mtb5KCfOfK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkXi9X%2FbtrSIrEvi3k%2FupSgdP9kAiW2mtb5KCfOfK%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;996&quot; height=&quot;410&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/slYPs/btrSHJyW2Sz/gKMExuKggpUdpHvmoLpS21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/slYPs/btrSHJyW2Sz/gKMExuKggpUdpHvmoLpS21/img.png&quot; data-origin-width=&quot;1085&quot; data-origin-height=&quot;362&quot; data-is-animation=&quot;false&quot; style=&quot;width: 54.591%;&quot; data-widthpercent=&quot;55.23&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/slYPs/btrSHJyW2Sz/gKMExuKggpUdpHvmoLpS21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FslYPs%2FbtrSHJyW2Sz%2FgKMExuKggpUdpHvmoLpS21%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;1085&quot; height=&quot;362&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;ver 6.1.5  ver 5.6.1&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 &lt;b&gt;org.hibernate.orm.jdbc.bind: trace&lt;/b&gt;를 걸어주면 정상적으로 기능이 다시 동작한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-12-02 오후 8.46.18.png&quot; data-origin-width=&quot;2070&quot; data-origin-height=&quot;540&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/2CNmN/btrSISaO83h/HXwgIzr6U0q9gC53p2X3L0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/2CNmN/btrSISaO83h/HXwgIzr6U0q9gC53p2X3L0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/2CNmN/btrSISaO83h/HXwgIzr6U0q9gC53p2X3L0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F2CNmN%2FbtrSISaO83h%2FHXwgIzr6U0q9gC53p2X3L0%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;2070&quot; height=&quot;540&quot; data-filename=&quot;스크린샷 2022-12-02 오후 8.46.18.png&quot; data-origin-width=&quot;2070&quot; data-origin-height=&quot;540&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;</description>
      <category>Spring/JPA</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/151</guid>
      <comments>https://sundries-in-myidea.tistory.com/151#entry151comment</comments>
      <pubDate>Fri, 2 Dec 2022 20:51:35 +0900</pubDate>
    </item>
    <item>
      <title>JWT Expired 시간이 토큰 발급시간보다 이전 시간인 경우</title>
      <link>https://sundries-in-myidea.tistory.com/150</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;JWT.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rX9kf/btrJ1BCwDDZ/rEE5ByN62EcsgO3jIR6Mxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rX9kf/btrJ1BCwDDZ/rEE5ByN62EcsgO3jIR6Mxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rX9kf/btrJ1BCwDDZ/rEE5ByN62EcsgO3jIR6Mxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrX9kf%2FbtrJ1BCwDDZ%2FrEE5ByN62EcsgO3jIR6Mxk%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;1080&quot; height=&quot;768&quot; data-filename=&quot;JWT.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제 상황&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JWT 토큰 발급시 이런 오류가 있었다. &lt;b&gt;JWT_EXPIRED_TIME: 43200&lt;/b&gt;&amp;nbsp; (24시간 * 60분 * 30일) &amp;rArr; 해서 나온값이 43200분이었고,&amp;nbsp; 위와 같은 설정값을 통해서 아래의 설정으로 세팅으로 토큰을 발급 하려고 했었는데....&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;return Jwts.builder()
        .setClaims(claims)
        .setIssuedAt(Date())
        .setExpiration(Date(System.currentTimeMillis()+(JWT_EXPIRED_TIME.toInt()*60*1000)))
        .signWith(getSignKey(), SignatureAlgorithm.HS256)
        .compact()
&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;1146&quot; data-origin-height=&quot;418&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmqpbM/btrJ2bwEYsO/MJVmK9cZWQ8x435TNXykIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmqpbM/btrJ2bwEYsO/MJVmK9cZWQ8x435TNXykIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmqpbM/btrJ2bwEYsO/MJVmK9cZWQ8x435TNXykIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmqpbM%2FbtrJ2bwEYsO%2FMJVmK9cZWQ8x435TNXykIK%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;1146&quot; height=&quot;418&quot; data-origin-width=&quot;1146&quot; data-origin-height=&quot;418&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;hellip;? 원래시간보다 오히려 줄었다? iat보다도 숫자가 꽤 작은걸 확인할 수 있는데, 단순하게 Date 객체만의 문제라고 생각했었는데 자세히 찾아보니까 그런 문제가 아니였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/41412200/adding-a-large-timestamp-to-the-current-time-in-java&quot;&gt;Adding a large timestamp to the current time in Java&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1660821584321&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Adding a large timestamp to the current time in Java&quot; data-og-description=&quot;I am working on an application that supports Google 2-step verification. This application also supports a feature to 'trust this device for 30 days'. I use a database to save all this information ...&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/41412200/adding-a-large-timestamp-to-the-current-time-in-java&quot; data-og-url=&quot;https://stackoverflow.com/questions/41412200/adding-a-large-timestamp-to-the-current-time-in-java&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/25h04/hyPuy7eV9a/B6JYbicxIkOiT02XhBHmMK/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/41412200/adding-a-large-timestamp-to-the-current-time-in-java&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/41412200/adding-a-large-timestamp-to-the-current-time-in-java&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/25h04/hyPuy7eV9a/B6JYbicxIkOiT02XhBHmMK/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Adding a large timestamp to the current time in Java&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;I am working on an application that supports Google 2-step verification. This application also supports a feature to 'trust this device for 30 days'. I use a database to save all this information ...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜 줄었는지를 확인해보니까 단순하게 &lt;b&gt;Integer overflow 떠서, 음수가 되어버렸고&amp;hellip;. 그래서 일자가 줄었던것&amp;hellip;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 만약 위의 코드를 그대로 이용하면서 문제해결을 하기위해서는 다음처럼 수정만 하면된다. JWT_EXPIRED_TIME을 Long Type으로 받아놓은 다음에 더해버리면...?&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;.setExpiration(Date(System.currentTimeMillis()+(JWT_EXPIRED_TIME.toLong()*60*1000)))
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러면 정상적으로 이후로 시간이 잘 저장됩니다!&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;하지만.. 더 좋은 방법!&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 이건 직관적인 느낌은 없어서 즉, 내가 분을 계산을 해야하는 상황이다. 예를 들면, 30일을 계산하기 위해서 60*24*30을 해야하는 것 처럼.. 몇초를 더해야하는지 계산을 해야하며, 지금 위에서 발견한 것도 역시 integer overflow가 발생할거라고 생각하는 것도&amp;nbsp; 못했던 것처럼 아예 시간계산에 대해서 신경을 쓰지 않도록 혹은 시간 계산에 집중된 클래스나 유틸 함수를 사용하는게 훨씬 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래처럼 사용하면...?&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;val issuedAt: Instant = Instant.now().truncatedTo(ChronoUnit.SECONDS)
val expiration: Instant = issuedAt.plus(JWT_EXPIRED_TIME.toLong(), ChronoUnit.DAYS)
return Jwts.builder()
        .setClaims(claims)
        .setIssuedAt(Date.from(issuedAt))
        .setExpiration(Date.from(expiration))
        .signWith(getSignKey(), SignatureAlgorithm.HS256)
        .compact()
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Instant&lt;/b&gt; 와&amp;hellip; 그 값에 대한 plus를 &lt;b&gt;ChronoUnit&lt;/b&gt;을 통해서 어떤 날짜 혹은 &lt;b&gt;시간으로 더할지를 명확하게 판단&lt;/b&gt;해서 더하게 하거나 혹은 빼는 방법을 취해서 내가 얼만큼의 값을 더해야할지를 명확하게 코드로 보이게 하면. 아까보다 훨씬 더 명확하게 시간계산을 할 수 있을거라고 생각한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Date.from을 활용하여, Date 객체를 삽입하는 방법을 이용하면 좋다.&lt;/p&gt;</description>
      <category>Java</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/150</guid>
      <comments>https://sundries-in-myidea.tistory.com/150#entry150comment</comments>
      <pubDate>Thu, 18 Aug 2022 20:35:08 +0900</pubDate>
    </item>
    <item>
      <title>Spring RestDocs를 통한 Swagger 페이지 구축기 - 1. 구성하기</title>
      <link>https://sundries-in-myidea.tistory.com/149</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Rest Docs + Swagger.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ETrAR/btrH1yUXm5P/kgicxmdZMbBx7k5ai3nH10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ETrAR/btrH1yUXm5P/kgicxmdZMbBx7k5ai3nH10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ETrAR/btrH1yUXm5P/kgicxmdZMbBx7k5ai3nH10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FETrAR%2FbtrH1yUXm5P%2FkgicxmdZMbBx7k5ai3nH10%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;1080&quot; height=&quot;768&quot; data-filename=&quot;Rest Docs + Swagger.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h1&gt;테스트 코드로 API docs 관리를 할 수 있다고?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Rest Docs 라이브러리는 테스트 코드를 작성하면 Asciidoc 파일을 통해서 테스트 코드를 구현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만, 불편한 점들이 눈에 밟혔다. Asciidocs 파일에 대한 이해도가 반드시 필요했으며, Rest Docs가 기본적으로 제공하는 Docs 제공 하는 페이지가 나랑 취향이 너무 안맞았다. 솔직히 보면서 이렇게 까지 써야할까라는 생각을 했었는데&amp;hellip; 웹사이트를 뒤적뒤적 거리다 마침 RestDocs 테스트 코드를 바탕으로 Swagger 문서를 구성할 수 있는 라이브러리를 찾아, 그 기능을 사용해보기로 했다.&lt;/p&gt;
&lt;h1&gt;구축기&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 본 바탕이 되었던 이 레포지토리를 기준으로 개발 환경 세팅 방법을 확인하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/traeper/api_documentation&quot;&gt;https://github.com/traeper/api_documentation&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1658639127752&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - traeper/api_documentation: OpenApi Spec을 이용한 API 문서화&quot; data-og-description=&quot;OpenApi Spec을 이용한 API 문서화. Contribute to traeper/api_documentation development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/traeper/api_documentation&quot; data-og-url=&quot;https://github.com/traeper/api_documentation&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/3oEH4/hyPbYdTRdn/LTgeO6JYVhOBaFOjllyia1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/traeper/api_documentation&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/traeper/api_documentation&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/3oEH4/hyPbYdTRdn/LTgeO6JYVhOBaFOjllyia1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - traeper/api_documentation: OpenApi Spec을 이용한 API 문서화&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;OpenApi Spec을 이용한 API 문서화. Contribute to traeper/api_documentation development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 구축기는 기능적인 면에서는 좋았던 점이 많은데, 하나 구축 도중에 스스로가 &lt;b&gt;개선해봐도 괜찮겠다고 생각했었던 부분&lt;/b&gt;이 있어서 그 부분을 중점적으로 설명해보도록 하겠다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개선해봤던 점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 Repository에서는 Spring boot가 swagger의 index.html을 그대로 정적 서빙했었다. 근데 곰곰히 생각해보면, 굳이 정적파일을 Spring Boot에서 서빙해야할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 프로젝트에서는 &lt;b&gt;Reverse Proxy로 Nginx&lt;/b&gt;를 사용하고 있었고, 관련한 정적 파일들을 모두 Nginx에서 관리하는것이 효과적이라고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론, Spring Boot의 상태에 따라서 Swagger 자체를 접근 불가능하게 하는 방법도 있겠으나, 일단 기본적으로 API Spec을 제공한다는 측면에서 나는 Nginx으로 Swagger의 index파일을 서빙하는 방식으로 배포 방식을 변경했다. 추가적으로 API Spec이 개발간의 변경 사항이 분명히 존재할 수 있겠으나, 같이 협업하는 개발자들은 API Docs를 통해서 개발을 진행하기 때문에, 정말 대격변적으로 변경하는 케이스가 아니라면, 지속적인 노출이 더욱 중요하다고 생각한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Nginx 정적 서빙하도록 구축하기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.&amp;nbsp;&lt;b&gt;&lt;a href=&quot;https://swagger.io/docs/open-source-tools/swagger-ui/usage/installation/&quot;&gt;Swagger UI Installation&lt;/a&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'Static files without HTTP or HTML' 부분에서 latest release를 다운 받아 /dist 디렉토리만 복사해줍니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;253&quot; data-origin-height=&quot;313&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/y1YJB/btrH1atKgxd/YXZFRuqEWjdhCPo3DSSub0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/y1YJB/btrH1atKgxd/YXZFRuqEWjdhCPo3DSSub0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/y1YJB/btrH1atKgxd/YXZFRuqEWjdhCPo3DSSub0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fy1YJB%2FbtrH1atKgxd%2FYXZFRuqEWjdhCPo3DSSub0%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;253&quot; height=&quot;313&quot; data-origin-width=&quot;253&quot; data-origin-height=&quot;313&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Nginx의 nginx.conf 혹은 default.d/*.conf에 index.html을 서빙하도록 설정&lt;/h3&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;#nginx.conf
server {
        listen       80;
	       #...

        # 이 설정때문에 사실상 아래의 설정이 들어가게된다. 
        include /etc/nginx/default.d/*.conf; 

}

# default.d/swagger.conf
location /swagger {
        root #swagger index파일이 있는 주소를 작성해준다. 
				# 단, location 주소도 영향을 받으니, ~/app/swagger/index.html 이런 위치라면,
				# ~/app 만 작성해줘야 위의 location의 작성한 swagger path가  
        index index.html;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 설정하게 되면, 해당 주소의 /swagger 주소로는 index.html으로 화면이 이동하게 된다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. index.html 내부의 정적파일 주소 변경 및 yaml 파일 매핑&lt;/h3&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;script src=&quot;./swagger-ui-bundle.js&quot; charset=&quot;UTF-8&quot;&amp;gt; &amp;lt;/script&amp;gt;

&amp;lt;script&amp;gt;
      window.onload = function() {
        // Begin Swagger UI call region
        const ui = SwaggerUIBundle({
          url: &quot;./openapi3.yaml&quot;,
          dom_id: '#swagger-ui',
          deepLinking: true,
          presets: [
            SwaggerUIBundle.presets.apis,
            SwaggerUIStandalonePreset
          ],
          plugins: [
            SwaggerUIBundle.plugins.DownloadUrl
          ],
          layout: &quot;StandaloneLayout&quot;
        });
        // End Swagger UI call region

        window.ui = ui;
      };
&amp;lt;/script&amp;gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과 같이 정적 위치와 yaml파일을 타게팅 할 수 있도록 스크립트문을 짜준다. openapi3.yaml 파일 역시 이 Swagger 파일이 있는 폴더 하위에 위치해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면, nginx를 통해서 index.html이 서빙되긴하는데, 이제는 내가 배포 할때마다 이 openapi3.yaml 파일을 옮겨줘야한다. 그 작업만 선행되면, 매번 배포때마다 nginx를 통해서 API docs를 관리할 수 있게 되어진다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;EC2 배포 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포 방식 역시 공유해보려한다, 이 배포 방식은 &lt;b&gt;Ec2 &amp;amp; CodeDeploy &amp;amp; github action&lt;/b&gt; 기준을 통해서 배포 된다는 점을 알아주면 좋을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 배포 방식, 예를 들면, ECS, ECK와 같은 배포 방식에는 다른 배포 방식을 택해야 할 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 기본이 되는 배포 방식 자체는 github action를 통해 구성된 yaml파일과 shell script으로 구성했습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;0. build과정에 필요없는 작업 삭제시키기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;tasks.register&amp;lt;Copy&amp;gt;(&quot;copyOasToSwagger&quot;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CopyOasToSwagger는 서버 내부 정적파일 서빙하는 경우에만 해당하는 케이스로 빌드 과정에서 빠져도 괜찮습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. build 과정에 만들어진 openapi3.yaml을 s3로 옮기자&lt;/h3&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt; - name: openAPI3.yaml S3 이동
      run: aws s3 sync ./build/api-spec/ s3://s3위치/dev/
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Code deploy에서 실행할수 있도록, appspec.yml에서, Script문 실행문 추가&lt;/h3&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;ApplicationStart:
    - location: ./script/swagger.sh
      timeout: 30
      runas: root
&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 스크립트문 작성 (swagger.sh)&lt;/h3&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;aws s3 cp s3://~~~/openapi3.yaml /home/~~~/swagger/ 2&amp;gt; /dev/null

if [ $? = 1 ]; then
  echo &quot;########## openapi3가 재대로 sync 되지 않았습니다 #######&quot;
  exit;
else
  echo &quot;##############&quot;
  echo &quot;&amp;gt; swagger 페이지 재배포 시작&quot;
  echo &quot;##############&quot;
  systemctl restart nginx.service ## 재시작해도 안해도 되는데 일단 재실행
fi
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되면, 코드 배포시마다 API Docs가 추가되는 것을 확인할 수 있을 겁니다.&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;Rest Docs + Swagger 변환 과정에서 이슈들이 종종 있었고, 이걸 해결하는 과정들이 꽤 많이 복잡했었다. 혹은 어이없는 이유로 되는 경우도 왕왕 있었고... 그래서 그런 문제들을 위해서 해결하는 과정이 반드시 필요했었는데, 이슈들과 구축기는 좀 분리될 필요가 있어 보여서, 이어서 이슈 정리를 하는 글을 다시 작성하고 이어서 링크를 붙이도록 할 예정이다.&lt;/p&gt;</description>
      <category>Spring</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/149</guid>
      <comments>https://sundries-in-myidea.tistory.com/149#entry149comment</comments>
      <pubDate>Sun, 24 Jul 2022 14:05:00 +0900</pubDate>
    </item>
    <item>
      <title>Slack으로 Spring boot 에러 로깅 관제하기</title>
      <link>https://sundries-in-myidea.tistory.com/148</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;슬렉 스프링부트 로깅하기.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RjTyy/btrHBz8kZdK/ol5T98dvCWrBvYueP10ATK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RjTyy/btrHBz8kZdK/ol5T98dvCWrBvYueP10ATK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RjTyy/btrHBz8kZdK/ol5T98dvCWrBvYueP10ATK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRjTyy%2FbtrHBz8kZdK%2Fol5T98dvCWrBvYueP10ATK%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;1080&quot; height=&quot;768&quot; data-filename=&quot;슬렉 스프링부트 로깅하기.png&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;토이프로젝트에 로깅은 필수..&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로깅(log)은 프로젝트를 처음 접해본 사람이 아니면, 솔직히 한번쯤은 겪고 가야할 문제이다. 로깅 자체의 구현이 어렵진 않을 것이다. Spring과 같은 Web Framework에서는 이미 로깅 프레임워크를 제공해주기 때문이다. 그중에서 우리는 logback 기능을 활용해서, 서비스를 구현해놓는게 심적으로 편하다. (이미 구현되어있으니까)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;로깅 다 좋은데 말야&amp;hellip;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로깅이 에러 대응과 같은 역할을 하는게 있고, logback에서는 파일, 콘솔과 같은 툴로 이미 로그를 여러 모습으로 관리를 해주고 있기 때문에, 우리가 이러한 로그 파악에 있어서 파일을 통한 로깅, 혹은 콘솔창에 대한 로그를 통해서 확인을 해줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 그것이 실시간성으로 확인이 어렵다는 점이다. 왜냐하면, 일단 장애가 발생하면, 클라이언트는 장애를 인지하지만 실제로 서버는 관제를 이용하지 않는 이상 확인할 길이 클라이언트 개발자, 혹은 운영을 통해서 관제를 들어온 케이스를 제외하고는 확인할 길이 없다. (매일 실시간 관제를 하는 방법도 있기야 하겠지만&amp;hellip; 비효율적이다)&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;토이프로젝트나 사이드 프로젝트에서 어쨌든 중요한 점은 지속 가능성이고, 지속 가능성을 높이기 위해서는 결과적으로는 서버 운영에 드는 비용이 거의 0에 가까워야 끊임 없이 운영할 수 있다고 생각한다.&lt;/b&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Slack을 써보자&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 관제용 서버를 띄우는 것이 아닌 이상 실시간으로 관제를 받기 위해서 Slack을 활용해서 관제를 할 수 있는 방향으로 관점을 변경했다. 결국 관제의 핵심은 로그 분석과 실시간성인데, 그 둘을 동시에 챙기기 위해서는 Slack에서 제공하는 웹훅 기능을 활용하면 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Slack 웹훅은 다음처럼 설정가능하다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 내가 설정을 원하는 채널로 접근후 채널 세부 정보&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-07-18 오후 7.41.49.png&quot; data-origin-width=&quot;216&quot; data-origin-height=&quot;128&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bUCrjn/btrHAv5IDEl/Kz9Lxs9yDFSJcXKVWfHPD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bUCrjn/btrHAv5IDEl/Kz9Lxs9yDFSJcXKVWfHPD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bUCrjn/btrHAv5IDEl/Kz9Lxs9yDFSJcXKVWfHPD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbUCrjn%2FbtrHAv5IDEl%2FKz9Lxs9yDFSJcXKVWfHPD0%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;216&quot; height=&quot;128&quot; data-filename=&quot;스크린샷 2022-07-18 오후 7.41.49.png&quot; data-origin-width=&quot;216&quot; data-origin-height=&quot;128&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. 앱 추가&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-07-18 오후 7.41.40.png&quot; data-origin-width=&quot;586&quot; data-origin-height=&quot;768&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsTVz6/btrHDQHQSVy/qeS6Z6ZEr3UQSXQd8OAdiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsTVz6/btrHDQHQSVy/qeS6Z6ZEr3UQSXQd8OAdiK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsTVz6/btrHDQHQSVy/qeS6Z6ZEr3UQSXQd8OAdiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsTVz6%2FbtrHDQHQSVy%2FqeS6Z6ZEr3UQSXQd8OAdiK%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;586&quot; height=&quot;768&quot; data-filename=&quot;스크린샷 2022-07-18 오후 7.41.40.png&quot; data-origin-width=&quot;586&quot; data-origin-height=&quot;768&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. Incoming Webhook 등록 &amp;gt;&amp;nbsp; 등록하려는 채널에 등록하기&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/C0gL2/btrHC0qDaUS/z9HvPAJVLANPIEuzmrpS5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C0gL2/btrHC0qDaUS/z9HvPAJVLANPIEuzmrpS5K/img.png&quot; data-origin-width=&quot;683&quot; data-origin-height=&quot;105&quot; data-is-animation=&quot;false&quot; data-filename=&quot;스크린샷 2022-07-18 오후 7.42.34.png&quot; style=&quot;width: 65.0708%; margin-right: 10px;&quot; data-widthpercent=&quot;65.84&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C0gL2/btrHC0qDaUS/z9HvPAJVLANPIEuzmrpS5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC0gL2%2FbtrHC0qDaUS%2Fz9HvPAJVLANPIEuzmrpS5K%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;683&quot; height=&quot;105&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l4dBw/btrHDi5Du3E/eyCwJSPPRkakbrTg5mR2ck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l4dBw/btrHDi5Du3E/eyCwJSPPRkakbrTg5mR2ck/img.png&quot; data-origin-width=&quot;962&quot; data-origin-height=&quot;285&quot; data-is-animation=&quot;false&quot; data-filename=&quot;스크린샷 2022-07-18 오후 7.42.54.png&quot; style=&quot;width: 33.7664%;&quot; data-widthpercent=&quot;34.16&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l4dBw/btrHDi5Du3E/eyCwJSPPRkakbrTg5mR2ck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl4dBw%2FbtrHDi5Du3E%2FeyCwJSPPRkakbrTg5mR2ck%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;962&quot; height=&quot;285&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4.&amp;nbsp; 등록후 나온 웹후크 url 주소를 복사해둡시다.&amp;nbsp;&lt;/h4&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-07-18 오후 7.43.34.png&quot; data-origin-width=&quot;1005&quot; data-origin-height=&quot;653&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cj45sg/btrHEeWbRKJ/k5so26MJ1IRuvVqqArngsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cj45sg/btrHEeWbRKJ/k5so26MJ1IRuvVqqArngsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cj45sg/btrHEeWbRKJ/k5so26MJ1IRuvVqqArngsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcj45sg%2FbtrHEeWbRKJ%2Fk5so26MJ1IRuvVqqArngsK%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;1005&quot; height=&quot;653&quot; data-filename=&quot;스크린샷 2022-07-18 오후 7.43.34.png&quot; data-origin-width=&quot;1005&quot; data-origin-height=&quot;653&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 주소를 아래 Spring boot 설정에서 사용해줄것이다.&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Spring Boot의 옵션들도 추가하자&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로는 빌드옵션에 logback-slack-appender를 추가하고&amp;hellip; (22년 7월 기점으로 1.6.1이 가장 최신)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;build.gradle.kts&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;//Logging
implementation(&quot;com.github.maricn:logback-slack-appender:1.6.1&quot;)
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 Spring boot의 application.yaml 파일에 다음과 같이 추가를 해준다음에&amp;hellip;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;application.yaml&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;logging:
  slack:
    webhook-uri: #웹훅 주소 삽입
config: classpath:logback-spring.xml
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 &lt;b&gt;Logback&lt;/b&gt;을 활용한 Appender를 통한 추가를 제공해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LogBack의 옵션은 다음과 같이 두개를 추가했다. Slack에서 제공하는 옵션과 Console창에서 제공하는 옵션 두가지를 제공하는 방향으로 바꾸었다. 물론, File 로그도 제공해도 괜찮지만, 실시간 관제 이후 systemd를 통해 로그 분석을 하는 방향으로 가닥을 잡았기 때문에, 옵션을 다른건 주지는 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 로그 창에서 제공하는 Pattern을 약간 변경점을 주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Logback-Spring.xml&lt;/p&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;?xml version=&quot;1.0&quot; encoding=&quot;UTF-8&quot; ?&amp;gt;
&amp;lt;configuration&amp;gt;
    &amp;lt;include resource=&quot;org/springframework/boot/logging/logback/defaults.xml&quot;/&amp;gt;

    &amp;lt;springProperty name=&quot;SLACK_WEBHOOK_URI&quot; source=&quot;logging.slack.webhook-uri&quot;/&amp;gt;
    &amp;lt;appender name=&quot;SLACK&quot; class=&quot;com.github.maricn.logback.SlackAppender&quot;&amp;gt;
        &amp;lt;webhookUri&amp;gt;${SLACK_WEBHOOK_URI}&amp;lt;/webhookUri&amp;gt;
        &amp;lt;layout class=&quot;ch.qos.logback.classic.PatternLayout&quot;&amp;gt;
            &amp;lt;pattern&amp;gt;%d{yyyy-MM-dd HH:mm:ss.SSS} - %-5le %logger{35} : %msg%n&amp;lt;/pattern&amp;gt;
        &amp;lt;/layout&amp;gt;
        &amp;lt;username&amp;gt;서버 관제&amp;lt;/username&amp;gt;
        &amp;lt;iconEmoji&amp;gt;:hammer_and_wrench:&amp;lt;/iconEmoji&amp;gt;
        &amp;lt;colorCoding&amp;gt;true&amp;lt;/colorCoding&amp;gt;
    &amp;lt;/appender&amp;gt;

    &amp;lt;!-- Consol appender 설정 --&amp;gt;
    &amp;lt;appender name=&quot;Console&quot; class=&quot;ch.qos.logback.core.ConsoleAppender&quot;&amp;gt;
        &amp;lt;encoder&amp;gt;
            &amp;lt;Pattern&amp;gt;${CONSOLE_LOG_PATTERN}&amp;lt;/Pattern&amp;gt;
            &amp;lt;charset&amp;gt;utf8&amp;lt;/charset&amp;gt;
        &amp;lt;/encoder&amp;gt;
    &amp;lt;/appender&amp;gt;

    &amp;lt;appender name=&quot;ASYNC_SLACK&quot; class=&quot;ch.qos.logback.classic.AsyncAppender&quot;&amp;gt;
        &amp;lt;appender-ref ref=&quot;SLACK&quot;/&amp;gt;
        &amp;lt;filter class=&quot;ch.qos.logback.classic.filter.ThresholdFilter&quot;&amp;gt;
            &amp;lt;level&amp;gt;ERROR&amp;lt;/level&amp;gt;
        &amp;lt;/filter&amp;gt;
    &amp;lt;/appender&amp;gt;

    &amp;lt;root level=&quot;INFO&quot;&amp;gt;
        &amp;lt;appender-ref ref=&quot;ASYNC_SLACK&quot;/&amp;gt;
        &amp;lt;appender-ref ref=&quot;Console&quot;/&amp;gt;
    &amp;lt;/root&amp;gt;
&amp;lt;/configuration&amp;gt;
&lt;/code&gt;&lt;/pre&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;791&quot; data-origin-height=&quot;242&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mTiGm/btrHCwpMOnw/UX8FwJKecl42xsXxw2JKg1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mTiGm/btrHCwpMOnw/UX8FwJKecl42xsXxw2JKg1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mTiGm/btrHCwpMOnw/UX8FwJKecl42xsXxw2JKg1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmTiGm%2FbtrHCwpMOnw%2FUX8FwJKecl42xsXxw2JKg1%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;791&quot; height=&quot;242&quot; data-origin-width=&quot;791&quot; data-origin-height=&quot;242&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에러는 붉은색 띠로.. Warn은 노란색띠로 노출된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;추가적으로 개선사항이 있긴한데&amp;hellip;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 기본적으로, Log Trace가 보기가 은근 불편하다. 이게 붉은 띠와 추가적으로 제공하는 자세히표시와 같이 folding 기능을 제공해주기 때문에 Trace에 대해서 라이브러리가 attachment 로 자동적으로 제공하게 해주는데, 이게 개행과 Indent도 안맞고 하니까 볼때마다 뭔가 좀 짜증난다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 차라리 이렇게 줄거면, Code Block으로 감싸거나 attachment 가 아닌 일반 메세지 타입으로 전달하는 방향으로 제공하던가해서 처리하는게 좋을거라 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이건에 대해서는 Class 확장을 통해서 개선을 해볼까 고민중이니&amp;hellip; 차후에 글을 작성하게 된다면 여기에 추가로 링크를 남기도록 하겠다.&lt;/p&gt;</description>
      <category>Spring</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/148</guid>
      <comments>https://sundries-in-myidea.tistory.com/148#entry148comment</comments>
      <pubDate>Mon, 18 Jul 2022 19:54:34 +0900</pubDate>
    </item>
    <item>
      <title>이 기능 있는줄 나만 몰랐나? - 2편- Intellij CE에서 Spring Boot 돌릴때 쓰기에 좋은 옵션들</title>
      <link>https://sundries-in-myidea.tistory.com/147</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2160&quot; data-origin-height=&quot;1536&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cia4xx/btrGDLPRUdZ/MpgDfPLtK627rGj3nm9lhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cia4xx/btrGDLPRUdZ/MpgDfPLtK627rGj3nm9lhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cia4xx/btrGDLPRUdZ/MpgDfPLtK627rGj3nm9lhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcia4xx%2FbtrGDLPRUdZ%2FMpgDfPLtK627rGj3nm9lhk%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;2160&quot; height=&quot;1536&quot; data-origin-width=&quot;2160&quot; data-origin-height=&quot;1536&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;b&gt;사이드 프로젝트&lt;/b&gt;를 하다보면 회사에서 사용하던 IDE를 사용하지 못하고, CE버젼 즉, 커뮤니티 버젼을 사용해야할 경우가 있다. 커뮤니티 버젼 즉, 무료 버젼은 UE버젼, 얼티밋 버젼에 비해서는 정상적인 기능들을 대부분 제공은 해주긴 하지만... 막상 써보면 뭐가 하나 나사 빠져 있는 상태인 케이스들이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;분명 회사에서는 후다닥 했었던 기능, 당연히 제공 되는 기능이 CE버젼으로 다운그레이드 되면서 &quot;아니... 이 단순한 기능이 뭐길래&quot; 도대체 왜 나를 괴롭히는지 신경이 쓰이는 경우가 왕왕 있다.&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;225&quot; data-origin-height=&quot;225&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZBzkX/btrCRQskFSb/E2QLC9v7qmrGdlTFkgluM1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZBzkX/btrCRQskFSb/E2QLC9v7qmrGdlTFkgluM1/img.jpg&quot; data-alt=&quot;아우 속시원해&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZBzkX/btrCRQskFSb/E2QLC9v7qmrGdlTFkgluM1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZBzkX%2FbtrCRQskFSb%2FE2QLC9v7qmrGdlTFkgluM1%2Fimg.jpg&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;225&quot; height=&quot;225&quot; data-origin-width=&quot;225&quot; data-origin-height=&quot;225&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;물론 당신이 부자라면, Intellij UE버젼을 돈을 왕창 내면서 사용하겠지만, 아마 나도 그렇고, 다른 사람들도 그렇겠지만... 월간 &lt;span style=&quot;background-color: #ffffff; color: #19191c;&quot;&gt;$14.90 정도의 금액이 빠진다는 건 솔직히 말하면, 뭔가 아깝다는 생각을 할것이다. 그도 그럴 것이... 회사에서 풀타임으로 할 코드를 다시 회사가 끝나고 나서도 코드를 짜야하는 것인데, 그만큼 시간을 투자하지도 않을거며&lt;span style=&quot;background-color: #ffffff; color: #19191c;&quot;&gt;....&lt;/span&gt;(하는 분도 있긴 하겠지만) 풀타임에 대한 시간을 그만큼 뽑아낼 수 도 없기때문에 망설일 수 밖에 없다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #19191c;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;이런 상태라면 우리는 이 기능을 가능하게 만들면 되는거 아닐까? 커뮤니티 버젼에서 UE버젼을 &lt;b&gt;완벽하게 구현하기는 어렵지만 엄청 사소한 아주 사소한 기능추가만&lt;/b&gt;해줘도 심적으로 편안 해지는 상황을 마주할 수 있다.&lt;/span&gt;&lt;/span&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;일단 이 기능들은 Intellij CE버젼으로 그리고 특히 Spring Boot 기능에 관련해서 알아낸 사소한 차이점들을 공유해본다.&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1.&amp;nbsp; Spring boot 서버를 실행할때마다 gradle로 켜져요...&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 CE버젼에서는 Gradle을 Run Application이 되버리기때문에 Run tab에서 실행시키기 위해서는 방법이 필요하다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-07-06 오후 9.51.15.png&quot; data-origin-width=&quot;1641&quot; data-origin-height=&quot;349&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/x4qIM/btrGDL99gjE/i9eByg73K8oGhH2Y2ya2RK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/x4qIM/btrGDL99gjE/i9eByg73K8oGhH2Y2ya2RK/img.png&quot; data-alt=&quot;.... 이게 아닌데...&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/x4qIM/btrGDL99gjE/i9eByg73K8oGhH2Y2ya2RK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fx4qIM%2FbtrGDL99gjE%2Fi9eByg73K8oGhH2Y2ya2RK%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;1641&quot; height=&quot;349&quot; data-filename=&quot;스크린샷 2022-07-06 오후 9.51.15.png&quot; data-origin-width=&quot;1641&quot; data-origin-height=&quot;349&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d0Mj1O/btrGEKvPLAQ/8HptqcVqjOKi6hvyr7Vy41/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d0Mj1O/btrGEKvPLAQ/8HptqcVqjOKi6hvyr7Vy41/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d0Mj1O/btrGEKvPLAQ/8HptqcVqjOKi6hvyr7Vy41/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd0Mj1O%2FbtrGEKvPLAQ%2F8HptqcVqjOKi6hvyr7Vy41%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;720&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경 설정 -&amp;gt; 빌드, 실행, 배포 -&amp;gt; 빌드 도구 -&amp;gt; gradle -&amp;gt; 다음을 사용하여 빌드 및 실행. -&amp;gt; IntelliJ IDEA로 실행시키게된다면, 정상적으로 Run tab에서 실행이 가능해집니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-07-06 오후 10.06.28.png&quot; data-origin-width=&quot;285&quot; data-origin-height=&quot;371&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwXCCv/btrGDL3nuXu/uZFWZGXkXTjGR0bScExCV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwXCCv/btrGDL3nuXu/uZFWZGXkXTjGR0bScExCV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwXCCv/btrGDL3nuXu/uZFWZGXkXTjGR0bScExCV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwXCCv%2FbtrGDL3nuXu%2FuZFWZGXkXTjGR0bScExCV0%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;285&quot; height=&quot;371&quot; data-filename=&quot;스크린샷 2022-07-06 오후 10.06.28.png&quot; data-origin-width=&quot;285&quot; data-origin-height=&quot;371&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 로그창이 회색인데요. 로그 레벨별로 색이 노출되었으면 좋겠어요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Info는 초록색으로... Error는 붉은색으로.. 표시되던데, CE버젼은 온통 회색뿐... 해결해봅시다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행 및 디버그로 들어가서 이 Spring boot를 실행시키는 코드에서&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-07-06 오후 10.11.19.png&quot; data-origin-width=&quot;693&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rjiIu/btrGHlaxkwq/UXk0OHo7QkSWmZET1SWKsK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rjiIu/btrGHlaxkwq/UXk0OHo7QkSWmZET1SWKsK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rjiIu/btrGHlaxkwq/UXk0OHo7QkSWmZET1SWKsK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrjiIu%2FbtrGHlaxkwq%2FUXk0OHo7QkSWmZET1SWKsK%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;693&quot; height=&quot;286&quot; data-filename=&quot;스크린샷 2022-07-06 오후 10.11.19.png&quot; data-origin-width=&quot;693&quot; data-origin-height=&quot;286&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경변수에 삽입하거나, Spring Option에 추가하는 방향으로 설정해준다면...&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-07-06 오후 10.12.17.png&quot; data-origin-width=&quot;1003&quot; data-origin-height=&quot;52&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6RNtN/btrGHkW0dkl/PaUJlkrkKULN7QVnW2LCYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6RNtN/btrGHkW0dkl/PaUJlkrkKULN7QVnW2LCYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6RNtN/btrGHkW0dkl/PaUJlkrkKULN7QVnW2LCYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6RNtN%2FbtrGHkW0dkl%2FPaUJlkrkKULN7QVnW2LCYK%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;1003&quot; height=&quot;52&quot; data-filename=&quot;스크린샷 2022-07-06 오후 10.12.17.png&quot; data-origin-width=&quot;1003&quot; data-origin-height=&quot;52&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;h3 data-ke-size=&quot;size23&quot;&gt;3. Run tab에서 돌리고 싶은데, 자꾸 services 탭에서 뜨는 것 같아요&amp;hellip;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가끔 설정 잘못 눌러서 Run Tab에서 실행되야할 서버가 가끔 서비스 탭에서 실행되는 경우가 왕왕 있다. 어떻게 원복하는지 모르는 경우가 있어서 한번 찾아보았다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JxYK5/btrGDL3omNp/L4Uh7hYHXamwJdgzzq59Wk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JxYK5/btrGDL3omNp/L4Uh7hYHXamwJdgzzq59Wk/img.png&quot; data-origin-width=&quot;397&quot; data-origin-height=&quot;219&quot; data-is-animation=&quot;false&quot; style=&quot;width: 53.9225%; margin-right: 10px;&quot; data-widthpercent=&quot;54.56&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JxYK5/btrGDL3omNp/L4Uh7hYHXamwJdgzzq59Wk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJxYK5%2FbtrGDL3omNp%2FL4Uh7hYHXamwJdgzzq59Wk%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;397&quot; height=&quot;219&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjhAPk/btrGDKDo3Uu/vPeBbyKYDNJtjGHjNkKC70/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjhAPk/btrGDKDo3Uu/vPeBbyKYDNJtjGHjNkKC70/img.png&quot; data-origin-width=&quot;379&quot; data-origin-height=&quot;251&quot; data-is-animation=&quot;false&quot; style=&quot;width: 44.9147%;&quot; data-widthpercent=&quot;45.44&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjhAPk/btrGDKDo3Uu/vPeBbyKYDNJtjGHjNkKC70/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjhAPk%2FbtrGDKDo3Uu%2FvPeBbyKYDNJtjGHjNkKC70%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;379&quot; height=&quot;251&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스에서 구성타입을 제거 (Remove Configuration Type from Services)를 통해서 빼주면 다시 Run Tab에서 실행이 가능해진다&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;끝으로...&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이외에도 추가로 활용 가능한 것들이 있다면 계속 추가해보겠다... 기약 없긴하지만!&amp;nbsp;&lt;/p&gt;</description>
      <category>헉, 이거 나만 몰랐나?</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/147</guid>
      <comments>https://sundries-in-myidea.tistory.com/147#entry147comment</comments>
      <pubDate>Wed, 6 Jul 2022 22:37:00 +0900</pubDate>
    </item>
    <item>
      <title>스프링 롤백 테스트 하는 방식에 대한 생각</title>
      <link>https://sundries-in-myidea.tistory.com/146</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;JPA 관련해서 게시물 찾다가 위와 같은 블로그를 발견했어서 무슨 문제인지를 파악해보고 싶어서 해결기와 관련한 글을 작성해보았습니다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ttl-blog.tistory.com/523?category=906284&quot;&gt;https://ttl-blog.tistory.com/523?category=906284&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1651412227605&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[JPA] 살려주세요&quot; data-og-description=&quot;문제 상황을 간단하게 나타내 보도록 하겠습니다.. TestEntity @Getter @Setter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity public class TestEntity { @Id @GeneratedValue(strategy = Generat..&quot; data-og-host=&quot;ttl-blog.tistory.com&quot; data-og-source-url=&quot;https://ttl-blog.tistory.com/523?category=906284&quot; data-og-url=&quot;https://ttl-blog.tistory.com/523&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bhUetR/hyOdu6Fems/bHCTq8sjdUrcZfEJlYHtCk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/b5Svkx/hyOdAFLOqw/XdOhbkkkqDG14055OrKKKk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://ttl-blog.tistory.com/523?category=906284&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ttl-blog.tistory.com/523?category=906284&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bhUetR/hyOdu6Fems/bHCTq8sjdUrcZfEJlYHtCk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/b5Svkx/hyOdAFLOqw/XdOhbkkkqDG14055OrKKKk/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[JPA] 살려주세요&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;문제 상황을 간단하게 나타내 보도록 하겠습니다.. TestEntity @Getter @Setter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity public class TestEntity { @Id @GeneratedValue(strategy = Generat..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ttl-blog.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h1&gt;TL; DR!&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로그의 질문 사항의 핵심은 롤백 상황을 테스트 환경에서 테스트 가능하도록 만드는것이었다. 블로그에서는 @Transactional를 포함한 통합테스트 환경에서 &amp;ldquo;롤백&amp;rdquo; 되는 상황을 만들어버려서 문제라고 생각했다. 즉, 테스트 환경 자체에서 아예 롤백되지 않는 상태를 만들어놓고 롤백 메소드를 실행시키면 롤백되는 상황 체크할 수 있고, 애초에 롤백되는 상황을 가정한거니 테스트 독립성, 정합성 문제도 어느정도는 해결된다고 생각한다. 롤백 테스트를 해보고 싶다면, 테스트 메소드에 포함되어있는 @Transactional을 제외 시켜버리고 진행하면 된다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;코드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 TestEntity 객체는 String type의 name과 Long타입 Id만 존재하는 매우 단순한 객체이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;testService의 test는 값을 변경하고 uncheckedException을 발생 시키는 함수이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로그의 존재하는 테스트 코드에서 약간은 변형되어있는 테스트 코드이다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Test
    public void rollBackTest() throws Exception {

        TestEntity test = new TestEntity();
        test.setName(&quot;바보&quot;);
        TestEntity save = testRepository.save(test);
        //'바보' 라는 TestEntity가 저장

        assertThrows(IllegalStateException.class, ()-&amp;gt; testService.test());
        //'바보' 를 '멍청이'로 바꾸려는데 RunTimeException이 발생 여기서 이미 롤백해야하는 상황이지만...

        TestEntity test1 = testRepository.findById(save.getId()).get();
        assertEquals(&quot;바보&quot;, test1.getName());
        System.out.println(test1.getName());  //'멍청이' 가 출력...ㅠ
    }&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;상황 설명&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;블로그의 소개된 테스트 상황은 메소드 자체에 UnCheckedException을 터트린뒤에 이 변경 내역이 롤백 환경이 만들어지고 그 변경 내역이 원래 상태로 돌아가는 것을 테스트 코드화 시켜서 확인해보고 싶었던것 같다. ( 본인이 아니라서 정확하게 의도를 파악한건지는 모르겠다만...  )&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;친한 동료분과 대화를 통해서 어느정도 실마리를 찾았는데... SpringBootTest에서 @Transactional를 메소드든 클래스 단위로 사용하게 된다면, 자동으로 롤백이된다. (DB 테스트간 동일 상황을 맞춰야하니까)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 이것 역시 전파옵션과 비슷한 사례라고 보는데, 블로그 분의 해법중 하나인 @Transactional(propagation=Propagation.REQUIRES_NEW) 를 실제 메소드에서 옵션을 걸어서 사용하는 방식을 택해서 성공시켰던 것을 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트를 위해 만들어놓은 메소드에서는 이 롤백되는 경우에 대한, 검증은 어떤식으로 해야할까?&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;b&gt;테스트를 하기위해서 과연 트랜젝션 옵션을 주는게 맞는가?&lt;/b&gt; 라는 대전제를 피해가기는 어려웠다.&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;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Test
public void rollBackTest() throws Exception {

        TestEntity test = new TestEntity();
        test.setName(&quot;바보&quot;);
        TestEntity save = testRepository.save(test);

        assertThrows(IllegalStateException.class, ()-&amp;gt; testService.test());

        TestEntity test1 = testRepository.findById(save.getId()).get();
        assertEquals(&quot;바보&quot;, test1.getName());
        System.out.println(test1.getName()); 
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 경우는 트랜잭션이 바로 test에서만 생기니.. 그 값이 롤백이 되버리고, 값이 변경이 되더라도 메소드 트랜잭션에 의해서 이미 값자체는 롤백이 되었으니 이전 초기값과 일치하는 상황이 생겨서 롤백되는 것 역시 테스트코드로 확인할 수 있었다.&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;/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;b&gt;트랜잭션 자체를 없에는것이 정합성에 문제가 있을수도 있지 않느냐?&lt;/b&gt; 라는 문제 역시 존재 할 수 있다고 생각한다. 왜냐하면, 결국 테스트는 동일한 상황에 동일한 값을 내뱉을거라고 구현하는건데, 테스트 DB 상의 Transacational을 없에버리는 바람에 초기 DB가 섞이는 이상한 상황이 생길 수 도 있을 거라고 생각한다.&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;즉, 트랜잭션 롤백이 될것을 가정하고 테스트를 하기 때문에, 애초에 원래 값으로 돌아가는걸 상정하고 테스트를 하는게 아닌가? 그러면 굳이 테스트 단위의 롤백이 필요하지는 않을것 같다는 게 나의 생각이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 테스트 단위의 롤백 역시, 테스트 이전과 형태를 동일하게 만들려고 목적을 가지고 있으니까 테스트 단위의 동일한 행동을 하는 롤백 행동을 테스트 하는 것 자체가 이미 테스트 롤백 상황과 동일하다고 생각했다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;추가&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;통합테스트에 @Transactional을 포함시켜서 DB 롤백테스트를 시도하셨었는데, 나는 더 나아가서 @DataJpaTest인 상황에서 롤백 테스트 하는 경우 방법이 있을지 확인했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@DataJpaTest 는 안에 @Transactional이 포함되어있고, 심지어 모든 테스트 메소드에 @Trasactional이 걸리는 상황이 생긴다. 이런 경우에는 전파 옵션 자체를 꺼버리는게 나는 좋다고 판단했다&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Test
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void rollBackTest() throws Exception {

    TestEntity test = new TestEntity();
    test.setName(&quot;바보&quot;);
    TestEntity save = testRepository.save(test);


	assertThrows(IllegalStateException.class, ()-&amp;gt; testService.test());
    

    TestEntity test1 = testRepository.findById(save.getId()).get();
	assertEquals(&quot;바보&quot;, test1.getName());
    
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NEVER, NOT_SUPPORT, SUPPORT 3개 모두 가능하다. 현재 트랜잭션을 없에고 실제 메소드에 관한 트랜잭션을 넣는 옵션이라면 모두 가능했었다. 그래도 이왕이면 NOT_SUPPORT가 좋지 않을까라는 동료분의 의견을 받아서 처리했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 더 나아가 @Transactional(readOnly=true)도 롤백되는 상황이 생기지 않을까 했었는데... readOnly 옵션의 경우 역시 테스트 메소드 자체의 롤백을 통해서 만들어둔 테스트 코드가 붉어졌고.. 아무리 읽기전용이여도 롤백상황에는 그 트랜잭션이 작동되는 것을 확인했었다.&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 data-ke-size=&quot;size16&quot;&gt;가끔 블로그나 StackoverFlow와 같은 질의응답게시물을 통해서 문제점 파악 &amp;rarr; 왜 문제가 일어났는지 원리 체크 &amp;rarr; 해결방안 제시와 같은 방식으로 공부를 해보는 것도 어떤 면에서는 Deep Dive하게 공부하는 방법이 되는 것 같아서 좋았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 역시 이 가장 큰 원동력이 되었던 블로그 주인님에게 가장큰 감사의 말씀올린다.&lt;/p&gt;</description>
      <category>Spring</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/146</guid>
      <comments>https://sundries-in-myidea.tistory.com/146#entry146comment</comments>
      <pubDate>Sun, 1 May 2022 22:51:37 +0900</pubDate>
    </item>
    <item>
      <title>Spring Batch를 활용한 토이프로젝트 - 1. 영화 업체를 가져와보자!</title>
      <link>https://sundries-in-myidea.tistory.com/145</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스프링배치 시리즈 .png&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWhI06/btrvSfF3yMs/GM4IKo7goO0UZheQSwRXkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWhI06/btrvSfF3yMs/GM4IKo7goO0UZheQSwRXkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWhI06/btrvSfF3yMs/GM4IKo7goO0UZheQSwRXkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWhI06%2FbtrvSfF3yMs%2FGM4IKo7goO0UZheQSwRXkk%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;712&quot; height=&quot;406&quot; data-filename=&quot;스프링배치 시리즈 .png&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;406&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요. 2021년도 연말에 회사에서 사내 배치 프로그램을 만들일이 있어서 어떤 툴을 쓸지 고민하던 도중 Spring 공부에도 큰 도움이 되지 않을까 싶어서 Spring Batch를 공부하기 시작했었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 Spring Batch에 대해서 공부하는데 큰 도움을 줄만한 게 뭐가 있을까 고민해보았는데요. 아무래도 가장 큰 도움을 줄만한 건 딱봐도 &lt;b&gt;토이(파일럿) 프로젝트&lt;/b&gt; 같았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 어떤 프로젝트를 하는게 좋을지 큰 고민을 했었는데 Spring Batch 관련한 토이프로젝트를 만들어주셨던 한 블로그에 글에 큰 도움을 받아 형식은 좀 다르지만, 여러 가지 Batch에 대해서 구현을 해보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Batch를 공부하시는 분들이라면, 어느 정도의 절차를 바탕으로 진행하면서 점진적으로 내 프로그램의 질을 늘려가는 방향으로 개발을 진행해보는 것을 추천한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 이번에 작성하는 포스트는 대부분 내가 개발을 하면서 구현해왔었던 순서대로 진행하려고 한다는 점을 알고 개발을 참고해보는걸 추천한다.&lt;/p&gt;
&lt;h1&gt;도대체 뭘 만들어볼까?&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Batch라는 특성인 배치 작업 즉, 일괄 작업이라는 프로세스상 분명히 많은 데이터가 필요했었다. 그리고 프로젝트 특성상 요구사항을 만족시키기 위한 조건도 존재했었다. 중요 쟁점을 요약해보자면 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;첫째, 데이터를 많이 담을 수 있는 프로젝트&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;두번째, API를 통해서 Data를 받고 그걸 DB에 삽입&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러던 도중에 아래 블로그에서 영화 API를 통해서 Batch 프로세스를 통해서 데이터를 삽입하는 프로젝트를 진행한 글을 보고 영진위 API를 통해서 data양을 더 늘릴 수 있는 프로젝트를 해보겠다는 생각을 하게된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1647177084431&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;&amp;lt;Spring Batch&amp;gt; API 호출해서 DB에 저장하기(with JPA)&quot; data-og-description=&quot;2021/02/25 - [개발공부/Spring ] - 예제 프로그램 따라하기 예제 프로그램 따라하기&amp;quot; data-og-description=&amp;quot;스케줄러 형태로 되어있는 배치는 많이 봤는데 Spring Batch를 사용한 배치 프로그램은 본적이 없는 &quot; data-og-host=&quot;willbfine.tistory.com&quot; data-og-source-url=&quot;https://willbfine.tistory.com/494&quot; data-og-url=&quot;https://willbfine.tistory.com/494&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/caz2mo/hyNGHZoLzE/kyyhn6LAxDuQRQFEXUs8Z0/img.png?width=800&amp;amp;height=216&amp;amp;face=0_0_800_216,https://scrap.kakaocdn.net/dn/5QeTn/hyNGLAKn4N/vEQUeVymeR5PkppLvih3Ok/img.png?width=800&amp;amp;height=216&amp;amp;face=0_0_800_216,https://scrap.kakaocdn.net/dn/bB63Fc/hyNHUv4cEs/Qn3WVQMuJRKsKpe8LQkXS0/img.png?width=1815&amp;amp;height=491&amp;amp;face=0_0_1815_491&quot;&gt;&lt;a href=&quot;https://willbfine.tistory.com/494&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://willbfine.tistory.com/494&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/caz2mo/hyNGHZoLzE/kyyhn6LAxDuQRQFEXUs8Z0/img.png?width=800&amp;amp;height=216&amp;amp;face=0_0_800_216,https://scrap.kakaocdn.net/dn/5QeTn/hyNGLAKn4N/vEQUeVymeR5PkppLvih3Ok/img.png?width=800&amp;amp;height=216&amp;amp;face=0_0_800_216,https://scrap.kakaocdn.net/dn/bB63Fc/hyNHUv4cEs/Qn3WVQMuJRKsKpe8LQkXS0/img.png?width=1815&amp;amp;height=491&amp;amp;face=0_0_1815_491');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;&amp;lt;Spring Batch&amp;gt; API 호출해서 DB에 저장하기(with JPA)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;2021/02/25 - [개발공부/Spring ] - 예제 프로그램 따라하기 예제 프로그램 따라하기&quot; data-og-description=&quot;스케줄러 형태로 되어있는 배치는 많이 봤는데 Spring Batch를 사용한 배치 프로그램은 본적이 없는&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;willbfine.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 영진위 API를 가서 이 영진위 API에서 적당히 많은 데이터를 가질만한게 도대체 뭘까라는 생각을 하니..&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-03-06 오후 11.24.23.png&quot; data-origin-width=&quot;927&quot; data-origin-height=&quot;864&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OvCv8/btrvVACFIL0/jSV2RFJ3cM3s2rAUDWVH6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OvCv8/btrvVACFIL0/jSV2RFJ3cM3s2rAUDWVH6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OvCv8/btrvVACFIL0/jSV2RFJ3cM3s2rAUDWVH6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOvCv8%2FbtrvVACFIL0%2FjSV2RFJ3cM3s2rAUDWVH6k%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;927&quot; height=&quot;864&quot; data-filename=&quot;스크린샷 2022-03-06 오후 11.24.23.png&quot; data-origin-width=&quot;927&quot; data-origin-height=&quot;864&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영진위에서 API key를 신청하는 것은 회원가입후 키 신청을 통해서 이루어진다. 그 키를 바탕으로 위의 API URL에 자신이 받은 키를 입력해서 Query Parameter를 입력해서 작동하는 방식으로 구성되어있었다.&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;영화사 목록은 특히 괜찮았던 점이 있는데, 결과 Row값과 현재 페이지까지 제공을하는 것을 보아 아무래도 정말 많은 양의 Data를 가지고 있을거라고 생각했다. 그리고 &lt;b&gt;실제로 API를 통해서 가져올때 Paging되어있는 데이터를 가지고 올 일 역시 존재했었으며&lt;/b&gt;.. ㅋㅋ; 꽤나 중요한 역할을 맡아줄 거라고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이 API를 기반으로 Spring Batch 프로젝트를 진행했었다.&lt;/p&gt;
&lt;h1&gt;프로젝트 구성&lt;/h1&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;plugins {
    id 'org.springframework.boot' version '2.5.7'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

group = 'com.ventulus95'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '17'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-batch'
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-quartz'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    runtimeOnly 'com.h2database:h2'
    implementation 'org.springframework.boot:spring-boot-starter-webflux'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
    testImplementation 'org.springframework.batch:spring-batch-test'
}

test {
    useJUnitPlatform()
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 java 버전은 가장 최신을 했었는데 새롭게 도입된 여러가지 기능들을 써볼려고 했었던 차원이므로, 최소 버전만 지켜서 진행하면 좋을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그외에 특이점에 대해서 간단하게 이야기 하자면...&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JPA로 DB 삽입을 해보려고 했었다. JPA에 대해서 경험쌓기가 중요해서 무조건 JPA 익히는데 프로젝트를 모두 만들 었었다.&lt;/li&gt;
&lt;li&gt;Quartz의 경우는 차후에 설명하겠지만. CronJob과 관련된 일정 시간마다의 스케쥴링 되는 배치 프로세스를 구성하기 위해서 삽입했었다.&lt;/li&gt;
&lt;li&gt;API를 가져오는 방법은 RestTemplate를 사용하는 방법이 있었지만 사용하지 않고 webFlux를 통해서 삽입했다.&lt;/li&gt;
&lt;li&gt;H2 InMemoryDB를 통해서 DB 구현에 드는 비용을 최소화 했다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h1&gt;API를 어떻게 가져올것인가?  &lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring에서는 RestTemplate를 통해서도 인터넷에서 API를 가져올 수 있긴하다. 하지만, WebFlux를 왜 썼는가? Webclient를 사용하도록 구현하도록 추천하기도 했어서, 후다닥 적용했었다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Configuration
public class WebClientConfig {

    @Bean
    public WebClient webclient(){
        return WebClient
                .builder()
                .clientConnector(new ReactorClientHttpConnector(
                        HttpClient.create().secure(t-&amp;gt;{
                            try {
                                t.sslContext(SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build());
                            } catch (SSLException e) {
                                e.printStackTrace();
                            }
                        })
                ))
                .baseUrl(&quot;https://www.kobis.or.kr/kobisopenapi/webservice/rest/&quot;)
                .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                .build();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러가지 모드가 존재하긴 했었으나, &lt;code&gt;https&lt;/code&gt;를 사용했었어서 다음과 같은 설정을 세팅했다.&lt;/p&gt;
&lt;h1&gt;Batch 프로세스&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배치 프로세스는 물론 어느정도의 이해가 필요한 부분이긴해서 그 설명을 초입부터 설명하는 것이 좋다. 하지만, 나의 이해도와 나보다 훨씬 더 자세한 설명으로 알 수 있는 블로그는 정말 차고 차고 넘쳤기 때문에 구체적인 설명 하지 않겠지만, 간단하게 프로세스에 대해서 짚고 넘어가보도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Batch에서의 한 작업의 단위는 대략 다음과 같은 구성으로 조합된다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://docs.spring.io/spring-batch/docs/current/reference/html/images/spring-batch-reference-model.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한개의 Job단위로 한개의 작업으로 구성되고 그 한번의 작업은 여러개의 Step으로 구성될수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쉽게 이야기하자면, DB 추출 -&amp;gt; 월 정산 금액 추출 -&amp;gt; 미납 회원 정지 프로세스 와 같이 여러가지 단계로 작업이 진행되는 배치작업이 있다고 치자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 이 3개의 각각의 작업은 Step으로 구성되고, 이 Step으로 구성된 여러가지 단계 DB 추출, 월정산 금액 추출과 같은 여러 일을 한개의 잡에서 몇개의 단계로 나눠서 일을 처리할 수 있게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 또한 Step도 3가지 단계를 거쳐서 구성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 배치 작업들은 많은 양의 Data를 받아서, 일정 작업을 하고 -&amp;gt; 우리 서버에서 어디로든(DB, 파일) 삽입하면서,&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ItemReader -&amp;gt; 일단 특정 DataSet에서 데이터를 읽어오는 역할을 한다.&lt;/li&gt;
&lt;li&gt;ItemProcessor -&amp;gt; 읽어온 data를 변환한다.&lt;/li&gt;
&lt;li&gt;ItemWriter -&amp;gt; 변환된 data를 삽입한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 몇몇 조합을 섞어서 내가 원하는 방향으로 배치 프로세스를 구성할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그렇다면 영화 API Batch를 구성해보자.&lt;/h2&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemWriter;
import org.springframework.batch.item.database.JpaItemWriter;
import org.springframework.batch.item.support.ListItemReader;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.ResponseEntity;
import org.springframework.web.reactive.function.client.WebClient;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import java.util.ArrayList;
import java.util.List;

@Configuration
@RequiredArgsConstructor
public class MovieConfig {

    private static final Logger logger = LoggerFactory.getLogger(MovieConfig.class);
    private final MovieCmmRepository movieCmmRepository;
    private final WebClient client;
    private final EntityManagerFactory managerFactory;

    @Bean //Job
    public Job movieScrappingJob(JobBuilderFactory jobBuilderFactory, Step movieScrappingStep){
        return jobBuilderFactory.get(&quot;movieScrappingJob&quot;)
                .preventRestart()
                .start(movieScrappingStep)
                .build();
    }

    @Bean //Step
    public Step movieScrappingJobStep(StepBuilderFactory stepBuilderFactory){
        return stepBuilderFactory.get(&quot;movieScrapStep&quot;).&amp;lt;MovieCompany, MovieCmm&amp;gt;chunk(30)
                .reader(movieScrapper())
                .processor(processor())
                .writer(insert())
                .build();
    }

    @Bean
    @StepScope
    public ListItemReader&amp;lt;MovieCompany&amp;gt; movieScrapper(){
        logger.info(&quot;$$$$$$$$$$$ 배치 읽어오기 시작&quot;);
        int page = 1;
        List&amp;lt;MovieCompany&amp;gt; list = new ArrayList&amp;lt;&amp;gt;();
        for (;page&amp;lt;4; page++){
            ResponseEntity&amp;lt;kobisResponse&amp;gt; res =client.get().uri(&quot;/company/searchCompanyList.json?key=f5eef3421c602c6cb7ea224104795888&amp;amp;itemPerPage=10&amp;amp;curPage=&quot;+page)
                    .retrieve().toEntity(kobisResponse.class)
                    .block();
            list.addAll(res.getBody().companyListResult.getCompanyList());
        }
        return new ListItemReader&amp;lt;&amp;gt;(list);
    }

    public ItemProcessor&amp;lt;MovieCompany, MovieCmm&amp;gt; processor(){
        return item -&amp;gt; {
            logger.info(&quot;~~~~~~~~~~~~~~~ 배치 프로세스 진행중!!!&quot;);
            return item.toEntity();
        };
    }

    public JpaItemWriter&amp;lt;MovieCmm&amp;gt; insert(){
        JpaItemWriter&amp;lt;MovieCmm&amp;gt; writer = new JpaItemWriter&amp;lt;&amp;gt;();
        writer.setEntityManagerFactory(managerFactory);
        return writer;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;MovieCompany&lt;/code&gt;, &lt;code&gt;MovieCmm&lt;/code&gt;, &lt;code&gt;kobisResponse&lt;/code&gt;와 같은 클래스는 Entity, Dto, Response를 받아오기위한 프로세스를 구성했었다. 아래는 다음 코드들에 대해서 작성해보았다 접은글을 펴서 보면 될 것 같다.&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MovieCmm -&amp;gt; 실제로 회사명을 받기 위해 필요한 Entity&lt;/p&gt;
&lt;pre id=&quot;code_1647176637866&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;


import javax.persistence.Entity;
import javax.persistence.Id;


@Entity
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class MovieCmm {


    @Id
    private Long id;


    private String name;


    private String partName;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;MovieCompany -&amp;gt; DTO 파일&lt;/p&gt;
&lt;pre id=&quot;code_1647176706594&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import com.ventulus95.springbatch.MovieCmm;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;


@Getter
@NoArgsConstructor
@ToString
public class MovieCompany {


    private long companyCd;
    private String companyNm;
    private String companyPartNames;


    public MovieCmm toEntity(){
        return MovieCmm.builder()
                .id(companyCd)
                .name(companyNm)
                .partName(companyPartNames)
                .build();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kobisResponse&amp;nbsp; -&amp;gt; 영진위 사이트에서 받은 Json 정보를 받을 때 필요한 DTO&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 당시에는 일단 박스 오피스 정보를 통해서 가져온 것이므로 차후에 변경이 좀 있긴하다&lt;/p&gt;
&lt;pre id=&quot;code_1647176740171&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;

import java.util.List;

@Getter
@NoArgsConstructor
@ToString
public class kobisResponse {

    private BoxOfficeResult boxOfficeResult;
    public CompanyResult companyListResult;

    @Getter
    @ToString
    private static class BoxOfficeResult {
        private String boxofficeType;
        private String showRange;
        private List&amp;lt;BoxOfficeDto&amp;gt; dailyBoxOfficeList;
    }

    @Getter
    @ToString
    public static class CompanyResult {
        private int totCnt;
        private List&amp;lt;MovieCompany&amp;gt; companyList;
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&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;code&gt;movieScrapper&lt;/code&gt;, &lt;code&gt;processor&lt;/code&gt;, &lt;code&gt;insert&lt;/code&gt; 라는 reader, processor, writer을 구성해서 stepBuilderFactory를 통해서 Step을 구성하고 그 jobBuilderFactory을 통해 Job을 구성하는 방식으로 구성하게된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특이한 점을 하나 더 소개하자면, ListItemReader 의 형태가 현재는 상당히 조악한 상태로 구성되어있다. 이상하다고 느낀 사람들이 있을텐데, 본 글은 시리즈로 조악한 상태에서 점점 발전된 형태로 구성하는 글의 형태로 구성할 예정이니 참고 부탁한다.&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;이렇게 코드를 구성하게 되면, 배치가 작동하게 되는데... 이 배치 프로세스를 작동시키기 위해서는 옵션이 필요하다.&lt;/p&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;spring:
  batch:
    jdbc:
      initialize-schema: embedded&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;initialize-schema를 통해서 이 배치작업과 관련한 Batch DB를 구성해줘야한다. 즉, 이런 Batch DB를 미리 구성해서 그 Batch단계별 진행정도 이 작업의 실행이 성공했었는지 실패했었는지? 그리고 무슨 이유로 실패했었는지등을 Spring Batch가 자동으로 인지하고, 그것을 바탕으로 실패이력을 남겨준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 이런 구성을 해줘야지 정상작동 하니까 세팅해주자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 Quartz와 관련한 포스팅은 추가로 진행할 예정이긴하지만... 일단 이 부분은 제외하고 봐주셨으면 좋겠다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 링크는 Quartz까지 추가한 Batch 관련 프로세스를 Branch화한 레포지토리 링크를 남겨두겠다. 혹시 의문점이 있다면, 댓글로 남겨 주시면 질의 응답할 수 있을 것이라고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1647176997036&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - ventulus95/SpringBatch&quot; data-og-description=&quot;Contribute to ventulus95/SpringBatch development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/ventulus95/SpringBatch/tree/batch-api&quot; data-og-url=&quot;https://github.com/ventulus95/SpringBatch&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bnFMGp/hyNHXzyAgz/kHlqCWfjXMJjZVGxDmWFk1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/ventulus95/SpringBatch/tree/batch-api&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/ventulus95/SpringBatch/tree/batch-api&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bnFMGp/hyNHXzyAgz/kHlqCWfjXMJjZVGxDmWFk1/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - ventulus95/SpringBatch&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to ventulus95/SpringBatch development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 1~2주 간격으로 계속 글을 작성해보려고 하는데, 일정상 변경될 수 있을수도 있고, 이미 종료한 프로젝트라서 이해도가 좀 떨어졌을 수도 있어서 그 점은 양해 부탁드린다. 끝까지 글 읽어주셔서 감사하다!&lt;/p&gt;</description>
      <category>Spring</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/145</guid>
      <comments>https://sundries-in-myidea.tistory.com/145#entry145comment</comments>
      <pubDate>Sun, 13 Mar 2022 22:18:04 +0900</pubDate>
    </item>
    <item>
      <title>2021년 회고 - 새로운 우주를 만난다는 것은...</title>
      <link>https://sundries-in-myidea.tistory.com/143</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;360&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oJF9l/btrpjyZSral/a0B47JhPBQK83FD3qte7k0/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oJF9l/btrpjyZSral/a0B47JhPBQK83FD3qte7k0/img.gif&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oJF9l/btrpjyZSral/a0B47JhPBQK83FD3qte7k0/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/oJF9l/btrpjyZSral/a0B47JhPBQK83FD3qte7k0/img.gif&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;480&quot; height=&quot;360&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;360&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;안녕하세요. 매년 그러니까 2019년, 2020년마다 우울함과 미래의 불안함을 가지고 회고를 썼었던 것 같은데, 올해는 일도 많고 회고를 위해서 올해를 돌아보는 시간도 적어져서 과연 이 글을 잘 쓸 수 있는 게 맞는가에 대한 여러 가지 생각이 들었는데요. 그래도 회고를 해본다는 게 결국 한 번쯤은 내가 뭘 했었고, 어쨌든 그 노력들이 모여서 지금의 결과를 만든 게 아닐까라는 생각만 가지고 회고를 작성했어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;170&quot; data-origin-height=&quot;170&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tTwIR/btrptg47dAl/WgCHTc3uXStkmEc7l2CRk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tTwIR/btrptg47dAl/WgCHTc3uXStkmEc7l2CRk1/img.png&quot; data-alt=&quot;벌써 12월 31일..?&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tTwIR/btrptg47dAl/WgCHTc3uXStkmEc7l2CRk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtTwIR%2Fbtrptg47dAl%2FWgCHTc3uXStkmEc7l2CRk1%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;170&quot; height=&quot;170&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;170&quot; data-origin-height=&quot;170&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;벌써 12월 31일..?&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;2021년에는 도대체 무슨일이?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2020년에 뭔가 이루고 싶어서 되게 많은 노력을 했었던 것 같았는데, 실상 결과가 뒤따르지 않고 있으니, 저한테 꽤 많은 실망을 했었던 한해였는데요. 그래도 여러 가지 프로젝트를 도전해고 노력하면서 꽤 의미 있는 결과를 만들어 내긴 했으나, 그것을 바탕으로 과연 정말로 제가 원하는 곳에 가게 될 것인가는 또 모르는 문제라 더욱 불안함을 많이 느꼈었던 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히, 2021년도는 상, 하반기로 나눠서 작성해보는 게 더 좋아 보여서 나눠서 작성하기로 하죠.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;  상반기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상반기에는 많은 부침이 있었습니다. 서류 지원이 끝없이 있었고, 결과적으로 큰 수확은 없었던 상반기였습니다. 캘린더에 빼곡하게 적혀있었던 여러 가지 서류 일정과 알고리즘 스터디를 계속적으로 진행했었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 결과가 좋지는 않았고, 실제로 작년 10월부터 알고리즘을 위한 준비를 꽤 열심히 했다고 생각했었던 저는 스터디원들이 거의 대부분 취업을 성공하고, 그 결과가 좋았기에 3월쯤에는 분명 그 성과가 드러나는 시기가 3월이 아닐까라고 생각했지만, 그것은 오판.. 대부분의 공채에서 알고리즘 테스트에서 탈락의 고배를 마시게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 덕분에 3월부터 이를 갈면서 알고리즘 스터디를 더욱 정진해야 한다. 나는 아직 문제를 푸는 수준이 높지 않다.. 골드로만 문제를 풀어야 한다. 시간을 측정하면서 공부하려고 노력했었고... 특히, 알고리즘에 더욱 심혈을 기울이게 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2021-12-31 오후 1.08.35.png&quot; data-origin-width=&quot;1009&quot; data-origin-height=&quot;485&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oXZkv/btrpgyfGSjO/gS7bGpEwlNDYVzJ10lLn5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oXZkv/btrpgyfGSjO/gS7bGpEwlNDYVzJ10lLn5k/img.png&quot; data-alt=&quot;나름 알고리즘에 진심이었음&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oXZkv/btrpgyfGSjO/gS7bGpEwlNDYVzJ10lLn5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoXZkv%2FbtrpgyfGSjO%2FgS7bGpEwlNDYVzJ10lLn5k%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;1009&quot; height=&quot;485&quot; data-filename=&quot;스크린샷 2021-12-31 오후 1.08.35.png&quot; data-origin-width=&quot;1009&quot; data-origin-height=&quot;485&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;3~5월까지 90문제를 꾸역꾸역 풀어냈었지만, 그 5월 이후 여러 회사의 인턴들과 여러 가지 서류 전형 이후에 생기는 알고리즘 테스트에서 턱턱 막히면서, 도대체 90문제는 왜 풀었으며... 90문제를 도대체 어떤 식으로 풀었길래 시험에서 내가 무슨 문제를 푸는지, 그리고 이 시험을 과연 통과를 할 수 있는지에 대해서 많은 고민을 하게 되는 시기가 됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2021-12-31 오후 1.03.40.png&quot; data-origin-width=&quot;1616&quot; data-origin-height=&quot;430&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3H1Sn/btrpoVAfscl/Lw1YQuukjoUS4uRrXAAIK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3H1Sn/btrpoVAfscl/Lw1YQuukjoUS4uRrXAAIK0/img.png&quot; data-alt=&quot;5월달은 진짜 많은 일정 빼곡하게 있었다. 물론, 부분부분 골라서 선택했지만, 전부 탈락&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3H1Sn/btrpoVAfscl/Lw1YQuukjoUS4uRrXAAIK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3H1Sn%2FbtrpoVAfscl%2FLw1YQuukjoUS4uRrXAAIK0%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;1616&quot; height=&quot;430&quot; data-filename=&quot;스크린샷 2021-12-31 오후 1.03.40.png&quot; data-origin-width=&quot;1616&quot; data-origin-height=&quot;430&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;5월달은 진짜 많은 일정 빼곡하게 있었다. 물론, 부분부분 골라서 선택했지만, 전부 탈락&lt;/figcaption&gt;
&lt;/figure&gt;
&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2021-12-31 오후 1.15.29.png&quot; data-origin-width=&quot;207&quot; data-origin-height=&quot;114&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/3xubg/btrpq7ADUBI/dcVXkbSjBRnOtEKoAYNFCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/3xubg/btrpq7ADUBI/dcVXkbSjBRnOtEKoAYNFCK/img.png&quot; data-alt=&quot;다 한번쯤 들어본 회사였는데, 기회가 있을때 놓친게 마음이 제일 아프다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/3xubg/btrpq7ADUBI/dcVXkbSjBRnOtEKoAYNFCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F3xubg%2Fbtrpq7ADUBI%2FdcVXkbSjBRnOtEKoAYNFCK%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;207&quot; height=&quot;114&quot; data-filename=&quot;스크린샷 2021-12-31 오후 1.15.29.png&quot; data-origin-width=&quot;207&quot; data-origin-height=&quot;114&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;그리고 놀랍게도, 이것 외에 다른 일을 해본 기억이 잘 안 나네요. 알고리즘 테스트에서 열심히 그리고 많이 탈락하니까, 막상 앞에 스타트업에서 일을 잠깐 도와줬었던 기억은 나는데 큰 기억이 잘 나지는 않아요.&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;물론 하반기라고 해서 엄청나게 상황이 바뀐 건 아니었습니다. 하반기 들어서도, 면접을 보았지만, 탈락했었고, 덕분에 그 여파로 나는 아무고토 못해... 난 누워있는 무생물이다. 이 상태로 한 1주일을 좌절했었지만, 인턴십을 할 기회가 생겼고, 그 기회를 잡아서 채용 전환형 인턴십 과정에 도전하게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 7월 중반부터 시작했었던 인턴십이 9월 말에 종료를 하고, 그 종료된 이후에 정규직으로서 입사하는 일정을 잘 소화해낼 수 있을까 두려웠는데, 그렇다고 기회가 왔는데, 그 기회를 발로 차 버리고 현상 유지를 하는 것은 스스로 가치관에 맞지 않는다고 생각해서, 인턴십 프로세스를 진행하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스스로 느끼기에 예상보다 힘든 서류, 면접, 테스트 과정들이 있었으나, 그래도 그런 여러 가지 문제들을 잘 떨쳐내고 인턴십에 합격해서 인턴 과제 기간을 거치게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2021-12-31 오후 2.46.07.png&quot; data-origin-width=&quot;486&quot; data-origin-height=&quot;200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIWe8o/btrpq7Vaa02/86QoWkL9uwwaG8ivc6KLwK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIWe8o/btrpq7Vaa02/86QoWkL9uwwaG8ivc6KLwK/img.png&quot; data-alt=&quot;이걸 인턴십을...?&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIWe8o/btrpq7Vaa02/86QoWkL9uwwaG8ivc6KLwK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIWe8o%2Fbtrpq7Vaa02%2F86QoWkL9uwwaG8ivc6KLwK%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;486&quot; height=&quot;200&quot; data-filename=&quot;스크린샷 2021-12-31 오후 2.46.07.png&quot; data-origin-width=&quot;486&quot; data-origin-height=&quot;200&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;3개월 간의 인턴 과제 기간 동안 꽤 많은 것들을 학습할 수 있었는데, 특히 기술적 성장에 대해서 많은 생각을 하게 되었습니다. 막상 인턴십을 진행하면서, 학부생 때 많은 것을 이뤘던 건 사실이지만, 뭔가 되게 겉핥기 식으로 많은 것들을 알고 있었다는 사실을 정말 뼈저리게 느끼게 되었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정말 제가 모르는 것들이 많았고, 말만 들어보고 막상 깊이는 없었다는 사실을 정말 많이 알게 되었던 것 같습니다. 특히, 배치 프로세싱과 ElasticSearch을 과제와 함께 생각해봐야 할 것들에 대해서 많이 익혔던 기간이었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 상황을 겪으면서 스스로에게 예상보다 쉬운 것들에 대해서 잘 모르는 것들이 너무나도 많았으며, 도대체 나는 거기서 어떤 것을 얻어낼 수 있을까라는 생각을 엄청 엄청 많이 하게 됩니다.&amp;nbsp;&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;/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;  정규직 전환&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;230&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cW09f9/btrpu53mmYd/QcDaScwLkyhq4tlDRxeobK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cW09f9/btrpu53mmYd/QcDaScwLkyhq4tlDRxeobK/img.png&quot; data-alt=&quot;ㅎ... 직접 언급은 피하겠습니다 ㅎㅎ&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cW09f9/btrpu53mmYd/QcDaScwLkyhq4tlDRxeobK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcW09f9%2Fbtrpu53mmYd%2FQcDaScwLkyhq4tlDRxeobK%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;538&quot; height=&quot;206&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;230&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;제일 아쉬운 건 아무래도 결국 기술적인 능력치가 낮아서 다른 것들을 잘해보려고 하는 것들에 집중도가 떨어지고 자꾸 실수를 하게 된다는 것입니다. 그래도 기술적 성장을 하면서 마음의 중심이 생기게 된다면 조금 더 안정적으로 여러 가지 것들을 해낼 수 있지 않을까라는 생각을 많이 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 마음에 드는 건 역시 공개 채용을 통해서 같이 입사하게 된 동기들이 옆에 있다는 점이고, 그 동기분들과 함께 개발 스터디를 열 수 있다는 것도 현재 회사에서 성장하고 싶은 제 마음에 큰 역할을 해주는 것 같아서 좋았는데요.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 개발 동기분들과 함께 개발 성장 욕구도 채웠으며, 물론 동기들끼리이긴 하지만, 나름 사내에 개발 스터디를 할 수 있게 환경을 조성을 해뒀던 것도 마음에 들었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 스터디를 하면서 스터디를 잘 돌아가게 하려면 어떤 식으로 운영되야하며, 그 운영이 굉장히 효과적으로 작동하는 것들도 많이 느끼고 있습니다. 저희 스터디 원들이 준비해왔던 자료, 지식을 듣고 있다 보면 내가 상당히 모르는 것이 많았구나 이런 식으로 효과적으로 공부할 수 있었던 것을 많이 느꼈던 일이 많아서 좋았던 것 같습니다.&lt;/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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 한 발이라도 약간 앞서있다는 점을 바탕으로 친구들에게 부족함 없이 베풀 수 있다는 것과 친구들에게 도움을 줄 수 있다는 점도 굉장히 취업하면서 느꼈던 좋았던 점입니다.&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;아듀 2021&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;항상 21년 초반에는 늘 2020으로 연도를 잘 못쓰곤 했었습니다. 많은 실패, 노력에 불구하고도 나오지 않는 성과처럼 뭐 하나 잘되는 거 없이 하루하루 의미 없게 흘러간다고 생각했었는데, 이제는 명확하게 2021년도를 21년이라고 잘 작성할 수 있는 한 가지 흐름을 만들어냈었던 한 해가 되었던 것 같습니다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;368&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdD7Rn/btrpox1b1Rl/vFNW4hXZHlKioMzOHr9Dy1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdD7Rn/btrpox1b1Rl/vFNW4hXZHlKioMzOHr9Dy1/img.gif&quot; data-alt=&quot;감사합니다 여러분~&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdD7Rn/btrpox1b1Rl/vFNW4hXZHlKioMzOHr9Dy1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/cdD7Rn/btrpox1b1Rl/vFNW4hXZHlKioMzOHr9Dy1/img.gif&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;480&quot; height=&quot;368&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;368&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;감사합니다 여러분~&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>잡담/회고</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/143</guid>
      <comments>https://sundries-in-myidea.tistory.com/143#entry143comment</comments>
      <pubDate>Fri, 31 Dec 2021 15:40:43 +0900</pubDate>
    </item>
    <item>
      <title>⌜처음 배우는 네트워크 보안⌟ 서평</title>
      <link>https://sundries-in-myidea.tistory.com/142</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pIg54/btroSVvfbSg/xqbwUUxxg66HTkbj3BM4w1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pIg54/btroSVvfbSg/xqbwUUxxg66HTkbj3BM4w1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pIg54/btroSVvfbSg/xqbwUUxxg66HTkbj3BM4w1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpIg54%2FbtroSVvfbSg%2FxqbwUUxxg66HTkbj3BM4w1%2Fimg.jpg&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;820&quot; height=&quot;312&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;312&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;514&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Tiohm/btroT6QAGL8/LIr0UkQaedgyXjDgSEffi1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Tiohm/btroT6QAGL8/LIr0UkQaedgyXjDgSEffi1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Tiohm/btroT6QAGL8/LIr0UkQaedgyXjDgSEffi1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTiohm%2FbtroT6QAGL8%2FLIr0UkQaedgyXjDgSEffi1%2Fimg.jpg&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;400&quot; height=&quot;514&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;514&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;한빛미디어 &amp;lt;나는 리뷰어다&amp;gt; 활동을 위해서 책을 제공받아 작성된 서평입니다.&lt;/span&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;보안은 늘 중요하다. 그리고 익명의 사용자를 받아오는 서비스 제공자 입장에서는 보안은 정말정말 중요하다는 걸 많이 깨닿고 있다. 하지만, 이러한 네트워크 보안은 도대체 어디서 부터 시작해야하는 것인지? 그리고 어디까지 보안을 하는게 맞는것인지? 를 파악하는 것이 매우 어려운 주제이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음 배우는 네트워크 보안을 통해서, 보안이라는 것을 첫 설정하기 위해서 어떤 발걸음을 걸어야하는 것에 대해서 알려주는 책이다.&lt;/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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4l6Ax/btroT5D9PH5/x3LTEKudxhh8H8nxXJkZY0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4l6Ax/btroT5D9PH5/x3LTEKudxhh8H8nxXJkZY0/img.jpg&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1920&quot; data-filename=&quot;이미지.jpeg&quot; style=&quot;width: 32.7295%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4l6Ax/btroT5D9PH5/x3LTEKudxhh8H8nxXJkZY0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4l6Ax%2FbtroT5D9PH5%2Fx3LTEKudxhh8H8nxXJkZY0%2Fimg.jpg&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;1440&quot; height=&quot;1920&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WOUBV/btroWo4krtF/dwCIBwgHWS9nJblRNYdC3k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WOUBV/btroWo4krtF/dwCIBwgHWS9nJblRNYdC3k/img.png&quot; data-origin-width=&quot;1427&quot; data-origin-height=&quot;942&quot; data-filename=&quot;blob&quot; style=&quot;width: 66.1077%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WOUBV/btroWo4krtF/dwCIBwgHWS9nJblRNYdC3k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWOUBV%2FbtroWo4krtF%2FdwCIBwgHWS9nJblRNYdC3k%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;1427&quot; height=&quot;942&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뿐만 아니라, 보안 사고가 일어났을 경우보안 프로세스에 대해서&amp;nbsp; 보안 담당자로써 어떤 대응을 해야하는지에 대해서도 도식화해서 알려준다.&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;1440&quot; data-origin-height=&quot;1262&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcMBP1/btro09Fls1H/FDE0uri5O9sr49vgSInD71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcMBP1/btro09Fls1H/FDE0uri5O9sr49vgSInD71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcMBP1/btro09Fls1H/FDE0uri5O9sr49vgSInD71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcMBP1%2Fbtro09Fls1H%2FFDE0uri5O9sr49vgSInD71%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;1440&quot; height=&quot;1262&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1262&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;소프트웨어적인 보안도 당연히 있다.&lt;/h3&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&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLp6K3/btro08GoS3P/iH9jjrs4hZXqAOKb1CVtFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLp6K3/btro08GoS3P/iH9jjrs4hZXqAOKb1CVtFK/img.png&quot; data-origin-width=&quot;1260&quot; data-origin-height=&quot;1225&quot; data-filename=&quot;blob&quot; style=&quot;width: 57.1589%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLp6K3/btro08GoS3P/iH9jjrs4hZXqAOKb1CVtFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLp6K3%2Fbtro08GoS3P%2FiH9jjrs4hZXqAOKb1CVtFK%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;1260&quot; height=&quot;1225&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QrHmT/btroSVPzPNp/7SSsDSI5bYlxg0D4FFCvg0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QrHmT/btroSVPzPNp/7SSsDSI5bYlxg0D4FFCvg0/img.jpg&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1920&quot; data-filename=&quot;이미지 5.jpeg&quot; style=&quot;width: 41.6783%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QrHmT/btroSVPzPNp/7SSsDSI5bYlxg0D4FFCvg0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQrHmT%2FbtroSVPzPNp%2F7SSsDSI5bYlxg0D4FFCvg0%2Fimg.jpg&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;1440&quot; height=&quot;1920&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마지막으로&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보안 기능을 제공하는 것은 늘상 중요하지만, 정확하게 어떤 프로세스로 제공해야할지는 모르기 때문에, 처음 배우는 네트워크 보안을 통해서 보안 기능이 무엇이고 어떤 프로세스로 구성되어있는지를 확인할 수 있는 좋은 기회가 되는 책이라고 생각한다.&amp;nbsp;&lt;/p&gt;</description>
      <category>서평</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/142</guid>
      <comments>https://sundries-in-myidea.tistory.com/142#entry142comment</comments>
      <pubDate>Sun, 26 Dec 2021 23:19:02 +0900</pubDate>
    </item>
    <item>
      <title>Spring Batch Partition 병렬 처리 해결기</title>
      <link>https://sundries-in-myidea.tistory.com/141</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스프링배치 .png&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yv4zf/btrogwa2PMr/7YZ3dBF3zshAOn59yhHs50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yv4zf/btrogwa2PMr/7YZ3dBF3zshAOn59yhHs50/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yv4zf/btrogwa2PMr/7YZ3dBF3zshAOn59yhHs50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fyv4zf%2Fbtrogwa2PMr%2F7YZ3dBF3zshAOn59yhHs50%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;712&quot; height=&quot;406&quot; data-filename=&quot;스프링배치 .png&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;406&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;TL;DR&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병렬처리는 아예 ItemReader에 &lt;code&gt;AbstractPagingItemReader&lt;/code&gt; 재정의후 페이징 doPageRead() 재정의하면 쉽게 처리가능. ListItemReader에는 InputStreamReader가 없어서 non-thread-safe해서, 아예 병렬처리 없이 싱글 스레드로 돌았던 것.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;해결을 위한 여정..&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;병렬 처리는 스프링 배치에서 여러가지 방법을 제공해주는데 그중 가장 좋은 방법인 partition방식을 이용해서 해결하려 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 병렬처리는 마음처럼 잘 해결이 안되는 게 문제였는데, 동기식으로 Blocking되는 방식으로 시스템이 돌아갔던것이 가장 큰 문제였다.&lt;/p&gt;
&lt;table class=&quot;table table-bordered table-hover table-condensed&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th title=&quot;Field #1&quot;&gt;배치 개수&lt;/th&gt;
&lt;th title=&quot;Field #2&quot;&gt;배치 방법&lt;/th&gt;
&lt;th title=&quot;Field #3&quot;&gt;시작 시간&lt;/th&gt;
&lt;th title=&quot;Field #4&quot;&gt;종료 시간&lt;/th&gt;
&lt;th title=&quot;Field #5&quot;&gt;총 걸린시간&lt;/th&gt;
&lt;th title=&quot;Field #6&quot;&gt;비고&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td rowspan=&quot;3&quot;&gt;700개&lt;/td&gt;
&lt;td&gt;partition&lt;/td&gt;
&lt;td&gt;2021년 12월 10일 오후 2:38&lt;/td&gt;
&lt;td&gt;2021년 12월 10일 오후 2:38&lt;/td&gt;
&lt;td&gt;15초&lt;/td&gt;
&lt;td&gt;쓰레드풀 5개로 고정&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;동기적&lt;/td&gt;
&lt;td&gt;2021년 12월 10일 오후 2:43&lt;/td&gt;
&lt;td&gt;2021년 12월 10일 오후 2:43&lt;/td&gt;
&lt;td&gt;11초&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;partition&lt;/td&gt;
&lt;td&gt;2021년 12월 10일 오후 2:52&lt;/td&gt;
&lt;td&gt;2021년 12월 10일 오후 2:53&lt;/td&gt;
&lt;td&gt;13초&lt;/td&gt;
&lt;td&gt;20개 / page 5개 단위&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td rowspan=&quot;3&quot;&gt;7000개&lt;/td&gt;
&lt;td&gt;partition&lt;/td&gt;
&lt;td&gt;2021년 12월 10일 오후 3:05&lt;/td&gt;
&lt;td&gt;2021년 12월 10일 오후 3:06&lt;/td&gt;
&lt;td&gt;1분 53초&lt;/td&gt;
&lt;td&gt;20개&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;동기적&lt;/td&gt;
&lt;td&gt;2021년 12월 10일 오후 3:10&lt;/td&gt;
&lt;td&gt;2021년 12월 10일 오후 3:11&lt;/td&gt;
&lt;td&gt;1분 54초&lt;/td&gt;
&lt;td&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;partition&lt;/td&gt;
&lt;td&gt;2021년 12월 10일 오후 3:30&lt;/td&gt;
&lt;td&gt;2021년 12월 10일 오후 3:31&lt;/td&gt;
&lt;td&gt;1분54초&lt;/td&gt;
&lt;td&gt;20개 / page 20개 단위 이렇게 하니까 그냥 에러나는 Step 생김. Connection pool 부족이 원인인듯&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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-filename=&quot;Untitled.png&quot; data-origin-width=&quot;1293&quot; data-origin-height=&quot;121&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhFRPk/btrolT38NZO/6v8IZqDXSaW55bsRILxTkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhFRPk/btrolT38NZO/6v8IZqDXSaW55bsRILxTkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhFRPk/btrolT38NZO/6v8IZqDXSaW55bsRILxTkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhFRPk%2FbtrolT38NZO%2F6v8IZqDXSaW55bsRILxTkK%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;1293&quot; height=&quot;121&quot; data-filename=&quot;Untitled.png&quot; data-origin-width=&quot;1293&quot; data-origin-height=&quot;121&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;시행착오들&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 이런 문제 해결을 위해서 필요한 점이 정확하게 무엇인지 파악하기가 어려웠는데, Blocking이 될만한 곳이 어디인지 파악하는 것이 급선무였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단, API 콜의 문제인가 싶어, 아예 비동기적으로 구성해서 Mono라던가, Flux를 통해서 값을 처리하려 했으나, 애초에 값자체가 안들어가고 바로 Response를 Return 해버렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 이런 시행 착오를 통해서 얻어낸 점이 하나 있었는데, 1. 비동기적 값이 안들어가게 하자 처리률이 확 늘어났었다. 값이 없는 경우 병렬처리가 되는 것 처럼 보였다. 왜냐하면, 시간의 누적이 없었기 때문이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 파트장님에게 질문후 어느정도 윤곽을 찾아냈는데,&lt;/p&gt;
&lt;figure id=&quot;og_1639906263502&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Can I use FlatfileItemReader with Taskexecutor?&quot; data-og-description=&quot;Can I use FlatfileItemReader with Taskexecutor in spring batch?? I have implemented FlatFileItemReader with ThreadPoolTaskExecutor. When I print the records in ItemProcessor, I do not get consist...&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/61998974/can-i-use-flatfileitemreader-with-taskexecutor&quot; data-og-url=&quot;https://stackoverflow.com/questions/61998974/can-i-use-flatfileitemreader-with-taskexecutor&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/c0c2sH/hyMKPJUvEg/nfbq0kdwDOKZ9ok1UL3fI0/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/61998974/can-i-use-flatfileitemreader-with-taskexecutor&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/61998974/can-i-use-flatfileitemreader-with-taskexecutor&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/c0c2sH/hyMKPJUvEg/nfbq0kdwDOKZ9ok1UL3fI0/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Can I use FlatfileItemReader with Taskexecutor?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Can I use FlatfileItemReader with Taskexecutor in spring batch?? I have implemented FlatFileItemReader with ThreadPoolTaskExecutor. When I print the records in ItemProcessor, I do not get consist...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;FlatfileItemReader의 경우 Not Thread-safe하기 때문에, Thread-Safe하게 코딩하기 위해서는 &lt;code&gt;SynchronizedItemStreamReader&lt;/code&gt; 을 통해서, 렙핑해줘야했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Reader 자체를 래핑했어야했고 내 코드에서는&lt;/p&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;@Bean
public SynchronizedItemStreamReader&amp;lt;MovieCompany&amp;gt; itemReader() {
    ListItemReader&amp;lt;MovieCompany&amp;gt; itemReader = ... // your item reader

    SynchronizedItemStreamReader&amp;lt;MovieCompany&amp;gt; synchronizedItemStreamReader = new SynchronizedItemStreamReader&amp;lt;&amp;gt;();
    synchronizedItemStreamReader.setDelegate(itemReader);
    return synchronizedItemStreamReader;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 처리하려 했으나, 실패했는데, Casting 오류가 발생했다. 왜 도대체 캐스팅 오류가 발생했는지에 대해서 찾아보니..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;SynchronizedItemStreamReader&lt;/code&gt; 은 상속관계는 아래와 같은데,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled 1.png&quot; data-origin-width=&quot;764&quot; data-origin-height=&quot;378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dPU7Yq/btrolUIKVn6/E81LnOrl7HvPPYJYGKegoK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dPU7Yq/btrolUIKVn6/E81LnOrl7HvPPYJYGKegoK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dPU7Yq/btrolUIKVn6/E81LnOrl7HvPPYJYGKegoK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdPU7Yq%2FbtrolUIKVn6%2FE81LnOrl7HvPPYJYGKegoK%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;764&quot; height=&quot;378&quot; data-filename=&quot;Untitled 1.png&quot; data-origin-width=&quot;764&quot; data-origin-height=&quot;378&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;ItemStreamReader&lt;/code&gt;를 통해서 동기화와 관련한 여러 구현을 Interface로써 사용할 수 있도록 도와주고 있었다. 즉, &lt;code&gt;ItemStream&lt;/code&gt;이나, &lt;code&gt;ItemStreamReader&lt;/code&gt;만 구현하고 있다면 동기화 프로세스를 사용할 수 있었는데...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Casting 문제는 ListItemReader는 ItemStream을 implements하지 않고 있었던것이 문제이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled 2.png&quot; data-origin-width=&quot;318&quot; data-origin-height=&quot;232&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzVfRK/btrolUPwMV3/Ac3cil2ywEHiDl6nISYaOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzVfRK/btrolUPwMV3/Ac3cil2ywEHiDl6nISYaOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzVfRK/btrolUPwMV3/Ac3cil2ywEHiDl6nISYaOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzVfRK%2FbtrolUPwMV3%2FAc3cil2ywEHiDl6nISYaOk%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;318&quot; height=&quot;232&quot; data-filename=&quot;Untitled 2.png&quot; data-origin-width=&quot;318&quot; data-origin-height=&quot;232&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 기본 &lt;code&gt;ListItemReader&lt;/code&gt;를 동기적으로 구현하기 위해서는 &lt;code&gt;ItemStreamReader&lt;/code&gt;를 구현하던... &lt;code&gt;ItemStream&lt;/code&gt;을 통해서 동기적인 처리를 해버리는 방식을 통해서 내부 메소드를 재정의를 통해서 구현하면 동기적인 코드를 구현할 수 있었는데...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ItemStream의 구현해야할 코드들은 예상보다 좀 많이 까다로운 함수들이었다.&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;public interface ItemStream {
    void open(ExecutionContext executionContext) throws ItemStreamException;

    void update(ExecutionContext executionContext) throws ItemStreamException;

    void close() throws ItemStreamException;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; open, update, close등 정체를 정확하게 파악하기도 어려웠으며, 내부적으로 Sync를 맞춰줘야하는 작업을 개발자 스스로 잡아줘야했었는데 솔직히 그정도로 자세히 하긴 어려워보였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대충 포기하고, mutil-Thread 방식으로 구현하려 했으나, (Step 자체를 paging할 수 있게 아예 나눠버리고 그걸 병렬처리하면 되는 방식으로 구현하려했다. 지금 생각해보면 안될 가능성이 커보였다. 결국 Reader때문에... 병렬처리 안될수도..?)&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;드디어 성공!&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퇴근직전에 동욱님이 작성한 QueryDSL을 페이징 처리가능하게 구현했었던 Reader를 만들었던것을 보고, 이거면 혹시 가능할지도라고 생각했던 &lt;i&gt;내 생각이 매우 좋은 발상이었는데,&lt;/i&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1639906198311&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Spring Batch와 Querydsl | 우아한형제들 기술블로그&quot; data-og-description=&quot;{{item.name}} Spring Batch와 QuerydslItemReader 안녕하세요 우아한형제들 정산시스템팀 이동욱입니다. 올해는 무슨 글을 기술 블로그에 쓸까 고민하다가, 1월초까지 생각했던 것은 팀에 관련된 주제였습&quot; data-og-host=&quot;techblog.woowahan.com&quot; data-og-source-url=&quot;https://techblog.woowahan.com/2662/&quot; data-og-url=&quot;https://techblog.woowahan.com/2662/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Gz41R/hyMKOxtCY0/XXZWXw1xFkqo3i1v8x1jGk/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/wVFrF/hyMKT6Cr7b/kLD3x6GoJAR88My0kAHXu0/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856&quot;&gt;&lt;a href=&quot;https://techblog.woowahan.com/2662/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://techblog.woowahan.com/2662/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Gz41R/hyMKOxtCY0/XXZWXw1xFkqo3i1v8x1jGk/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856,https://scrap.kakaocdn.net/dn/wVFrF/hyMKT6Cr7b/kLD3x6GoJAR88My0kAHXu0/img.jpg?width=1640&amp;amp;height=856&amp;amp;face=0_0_1640_856');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring Batch와 Querydsl | 우아한형제들 기술블로그&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;{{item.name}} Spring Batch와 QuerydslItemReader 안녕하세요 우아한형제들 정산시스템팀 이동욱입니다. 올해는 무슨 글을 기술 블로그에 쓸까 고민하다가, 1월초까지 생각했던 것은 팀에 관련된 주제였습&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;techblog.woowahan.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;figure id=&quot;og_1639906203128&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Spring Batch - Reference Documentation&quot; data-og-description=&quot;If a group of Steps share similar configurations, then it may be helpful to define a &amp;quot;parent&amp;quot; Step from which the concrete Steps may inherit properties. Similar to class inheritance in Java, the &amp;quot;child&amp;quot; Step combines its elements and attributes with the pa&quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-batch/docs/4.2.x/reference/html/index-single.html#readersAndWriters&quot; data-og-url=&quot;https://docs.spring.io/spring-batch/docs/4.2.x/reference/html/index-single.html#readersAndWriters&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-batch/docs/4.2.x/reference/html/index-single.html#readersAndWriters&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-batch/docs/4.2.x/reference/html/index-single.html#readersAndWriters&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring Batch - Reference Documentation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;If a group of Steps share similar configurations, then it may be helpful to define a &quot;parent&quot; Step from which the concrete Steps may inherit properties. Similar to class inheritance in Java, the &quot;child&quot; Step combines its elements and attributes with the pa&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각해보면 QueryDSL은 내부적으로 Spring Batch에서는 제공하는 Reader가 없어서 커스텀한 Reader를 만들었다는 것에 착안했고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제로 사용한&lt;/p&gt;
&lt;pre class=&quot;scala&quot;&gt;&lt;code&gt;public class ProductRepositoryItemReader extends AbstractPagingItemReader&amp;lt;Product&amp;gt; {&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;을 참고해보니까 오히려 좀 더 구현하기에 쉬운 방식이 아닌가 싶어서 이방식을 통해서도, API Batch 방식을 좀 더 개선해볼 수 있겠다 싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;AbstractPagingItemReader&lt;/code&gt; 를 알아볼 필요가 있는데, 이 친구를 잘 살펴보면 연관관계가 아래와 같은데,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Untitled 3.png&quot; data-origin-width=&quot;1006&quot; data-origin-height=&quot;706&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/liXJH/btroecDxl4f/SJCWxFv9TsYpajCM5FwwM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/liXJH/btroecDxl4f/SJCWxFv9TsYpajCM5FwwM0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/liXJH/btroecDxl4f/SJCWxFv9TsYpajCM5FwwM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FliXJH%2FbtroecDxl4f%2FSJCWxFv9TsYpajCM5FwwM0%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;1006&quot; height=&quot;706&quot; data-filename=&quot;Untitled 3.png&quot; data-origin-width=&quot;1006&quot; data-origin-height=&quot;706&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오잉? 아래아래쪽만 가도 &lt;code&gt;ItemReader&lt;/code&gt;과 &lt;code&gt;ItemStreamReader&lt;/code&gt;를 통해서 implemnet되어있기도 하고, 많이 쉽게, 구현할 수 있을지도 모른다 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로, &lt;code&gt;AbstractItemStreamItemReader&lt;/code&gt;, &lt;code&gt;AbstractItemCountingItemStreamItemReader&lt;/code&gt; 보다 훨씬 쉽게 구현할 수 있으며, CountingItem의 경우 Open,closed, update와 같이 복잡하고 어려운 Deep Dive한 코드를 건들어야했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 구현해야할 점은 두가지이다.&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;abstract protected void doReadPage(); //페이징해서 결과 만들어냄.
abstract protected void doJumpToPage(int itemIndex); //혹시 모를 중복을 건너 뛰는 페이징 함수.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주석에서 알아봤듯 &lt;code&gt;doReadPage()&lt;/code&gt;와 &lt;code&gt;doJumpToPage()&lt;/code&gt;를 동시에 구현해야하는데, 실제로 jump의 경우 잘 사용하지 않았고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;doReadPage()&lt;/code&gt; 의 작동방식을 정확하게 파악하기가 상당히 까다로웠는데,&lt;/p&gt;
&lt;pre class=&quot;gradle&quot;&gt;&lt;code&gt;protected T doRead() throws Exception {

   synchronized (lock) {

      if (results == null || current &amp;gt;= pageSize) {

         if (logger.isDebugEnabled()) {
            logger.debug(&quot;Reading page &quot; + getPage());
         }

         doReadPage(); //여기서 사용되었다.
         page++;
         if (current &amp;gt;= pageSize) {
            current = 0;
         }

      }

      int next = current++;
      if (next &amp;lt; results.size()) {
         return results.get(next);
      }
      else {
         return null;
      }

   }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서 사용하는 리더는 Reader는 proccessor에 넣을 값을 리턴해주는 것인(리턴타입이 T이므로) 듯했고, &lt;code&gt;doReadPage()&lt;/code&gt;를 그럼 어케 구현할것인가에 대해 고민이 깊어졌다. 여기서 나는 이 정확한 로직 프로세스를 판단하기 어려웠고, 이전에 사용하던 &lt;code&gt;public T read()&lt;/code&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;즉, 무조건 doReadPage를 사용해야했었는데 종료 조건을 어떤식으로 구성해야할지 몰랐다..&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;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;파티션 프로세스를 알아보자...&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 이 Partition을 하는 이유는 처음에는 청크를 짚고 넘어가야 한다. 기본적으로 이 파티션에서 값을 읽기 위해서는 청크단위의 Reader를 읽고 프로세싱하고, 청크단위로 넣게 된다. (청크단위로 넣지않으면 애초에 Paging 하는 의미도 없을것이다.)&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;그럼 이 파티션은 그것을 Reader를 더 세분화하게 잘게 쪼개서 그것을 병렬적으로 처리하고 싶은 것이므로, &lt;code&gt;doReadPage()&lt;/code&gt;, &lt;code&gt;doRead()&lt;/code&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;결국 doRead는 한 개의 아이템만 뱉는게 주 목적이다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, result가 null이거나 current보다 pageSize가 크면 0 page를 늘리고 PageSize 비교해서 current=0 만들면 다시 pagesize개 만큼 가져오고..&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 doReadpage가 해야하는 역할인데, PageSize 갯수만큼을 매번 Result에 삽입하는 것이 이 메소드가 해야할 역할이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 가장 이해에 어려움을 겪었던 점은 도대체 &lt;b&gt;&lt;code&gt;doReadPage()&lt;/code&gt;로 이 전체 배치를 어떤 방식으로 종료 하는 가였다.&lt;/b&gt;&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;code&gt;ChunkOrientedTasklet&lt;/code&gt; 은 청크를 다루는 클래스이고, 여기서 &lt;code&gt;ChunkProvider&lt;/code&gt; 가 Reader를 담당하여 관리를 하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 &lt;code&gt;ChunkProvider&lt;/code&gt;가 인터페이스로 구현되어있으므로, 실 클래스로 사용되어지는&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;chunkProvider.provide()&lt;/code&gt; 를 통해 청크 사이즈로 데이터를 가져오므로, Provide를 확인해야하며, &lt;code&gt;SimpleChunkProvider&lt;/code&gt;를 확인해보자면, 다음과 같이&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;public Chunk&amp;lt;I&amp;gt; provide(final StepContribution contribution) throws Exception {
    final Chunk&amp;lt;I&amp;gt; inputs = new Chunk&amp;lt;&amp;gt;();
        repeatOperations.iterate(new RepeatCallback() {

            @Override
            public RepeatStatus doInIteration(final RepeatContext context) throws Exception {
                ...
                try {
                    item = read(contribution, inputs); //일단 읽고 read함수는 SimpleChunckProvider의 함수..
                }

                if (item == null) { //item중에 null이면, 종료시켜버림
                    inputs.setEnd();
                    return RepeatStatus.FINISHED;
                }
                inputs.add(item); //청크에 계속 밀어넣고..
}

//아래 쪽으로 쭉 내려가면....

protected I read(StepContribution contribution, Chunk&amp;lt;I&amp;gt; chunk) throws SkipOverflowException, Exception {
        return doRead(); //실제로 사용하는 것은 doRead() 이역시 현 class에 존재
}

protected final I doRead() throws Exception {
        try {
            listener.beforeRead();
            I item = itemReader.read(); //결국 itemReader에서 read 함수를 통해 읽어서 오는 것.
            if(item != null) {
                listener.afterRead(item);
            }
            return item;
        } 
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 Read는 어디서 또 사용하나..? 우리는 지금 &lt;code&gt;AbstractPagingItemReader&lt;/code&gt; 에서는 doRead밖에 없는데...?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;AbstractItemCountingItemStreamItemReader&lt;/code&gt; 에서 Read가 있는데&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Nullable
@Override
public T read() throws Exception, UnexpectedInputException, ParseException {
   if (currentItemCount &amp;gt;= maxItemCount) { //만약 maxItemCount만 정해놓으면 파티션별로 최대 갯수도 지정 가능하지만, 굳이 그럴필요가 있나 싶다.
      return null;
   }
   currentItemCount++;
   T item = doRead(); // 이 Read에서도 doRead만 잘 이용하면 되는 것이었음. 
   if(item instanceof ItemCountAware) {
      ((ItemCountAware) item).setItemCount(currentItemCount);
   }
   return item;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 &lt;code&gt;SimpleChunkProvider&lt;/code&gt; 에서 이 코드와 같이&lt;/p&gt;
&lt;pre class=&quot;gauss&quot;&gt;&lt;code&gt;if (item == null) { //item중에 null이면, 종료시켜버림
                inputs.setEnd();
                return RepeatStatus.FINISHED;
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 Null만 리턴해주면 되는 문제이기 때문에, doPageRead()를 통해서 null를 리턴해주던가... 아니면 아까처럼 maxItemCount를 통해서 null 리턴해주는 방식으로 구현하게된다면 알아서 Paging과 관련한 처리가 완료되어진다.&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@Override //페이지 직접 읽기.
    protected void doReadPage() {
        if (results == null) {
            results = new LinkedList&amp;lt;&amp;gt;();
        } else {
            results.clear();
        }
        int curr = this.start+getPage(); 
//        log.info(&quot;현재 쓰레드의 [{}] 현재 curr 값: {}, END: {}&quot;,Thread.currentThread().getName(), curr, this.end);
        if(curr&amp;lt;=this.end) {
            ResponseEntity&amp;lt;kobisResponse&amp;gt; res = client.get().uri(&quot;/company....?itemPerPage=10&amp;amp;curPage=&quot;+curr)
                    .retrieve().toEntity(kobisResponse.class)
                    .block();
            results.addAll(res.getBody().companyListResult.getCompanyList());
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아쉬운 점은 getPage()를 건들기가 상당히 애매하다는 것이다. abstract class에서 이미 모든 것을 다 구현 되어서 getPage와 관련한 값을 변경할 수는 없었고, 나는 시작 번호 + page를 늘려가는 방식으로 구현했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 page 까지 구현 가능했다면? 아마 int curr과 같이 각 파티션 마다 다른 페이지 번호를 적용하는것에 애를 먹지는 않았을 것 같다.&lt;/p&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-filename=&quot;Untitled 4.png&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;218&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/colp0w/btroj7an2wU/AqK0bUKmCbRrkMhqn4Pbzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/colp0w/btroj7an2wU/AqK0bUKmCbRrkMhqn4Pbzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/colp0w/btroj7an2wU/AqK0bUKmCbRrkMhqn4Pbzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcolp0w%2Fbtroj7an2wU%2FAqK0bUKmCbRrkMhqn4Pbzk%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;1262&quot; height=&quot;218&quot; data-filename=&quot;Untitled 4.png&quot; data-origin-width=&quot;1262&quot; data-origin-height=&quot;218&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각각의 쓰레드의 큰 영향을 주지 않기 때문에, 문제없이 파티셔닝 할 수 있다.&lt;/p&gt;
&lt;figure id=&quot;og_1639906162429&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;6. Spring Batch 가이드 - Chunk 지향 처리&quot; data-og-description=&quot;Spring Batch의 큰 장점 중 하나로 Chunk 지향 처리를 얘기합니다. 이번 시간에는 Chunk 지향 처리가 무엇인지 한번 살펴보겠습니다. 6-1. Chunk? Spring Batch에서의 Chunk란 데이터 덩어리로 작업 할 때 각 커&quot; data-og-host=&quot;jojoldu.tistory.com&quot; data-og-source-url=&quot;https://jojoldu.tistory.com/331&quot; data-og-url=&quot;https://jojoldu.tistory.com/331&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/eAymTb/hyMKHSEgk9/yKVSAHK9u5erzpxWsNIzPK/img.png?width=800&amp;amp;height=246&amp;amp;face=0_0_800_246,https://scrap.kakaocdn.net/dn/cn8ioE/hyMKTMj0PF/ccBMD4VKctPSw9uiqcslQK/img.png?width=800&amp;amp;height=246&amp;amp;face=0_0_800_246,https://scrap.kakaocdn.net/dn/brAE07/hyMKOROpox/pYV1yHKvayYuiWUHK1r6c1/img.png?width=2082&amp;amp;height=1304&amp;amp;face=0_0_2082_1304&quot;&gt;&lt;a href=&quot;https://jojoldu.tistory.com/331&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://jojoldu.tistory.com/331&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/eAymTb/hyMKHSEgk9/yKVSAHK9u5erzpxWsNIzPK/img.png?width=800&amp;amp;height=246&amp;amp;face=0_0_800_246,https://scrap.kakaocdn.net/dn/cn8ioE/hyMKTMj0PF/ccBMD4VKctPSw9uiqcslQK/img.png?width=800&amp;amp;height=246&amp;amp;face=0_0_800_246,https://scrap.kakaocdn.net/dn/brAE07/hyMKOROpox/pYV1yHKvayYuiWUHK1r6c1/img.png?width=2082&amp;amp;height=1304&amp;amp;face=0_0_2082_1304');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;6. Spring Batch 가이드 - Chunk 지향 처리&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Spring Batch의 큰 장점 중 하나로 Chunk 지향 처리를 얘기합니다. 이번 시간에는 Chunk 지향 처리가 무엇인지 한번 살펴보겠습니다. 6-1. Chunk? Spring Batch에서의 Chunk란 데이터 덩어리로 작업 할 때 각 커&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;jojoldu.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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; 참고:&amp;nbsp; Batch 구현 레포지토리&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;혹시 몰라서 레포지토리를 파놓고, 직접 구현한 것을 만들어 뒀다. 차후에 좀 더 Readme쪽을 수정해서 이해하기 쉽도록 구현기를 작성하던, 배치 프로세스에 대해서 좀 더 자세하게 작성해볼 예정이다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1639906599919&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - ventulus95/SpringBatch&quot; data-og-description=&quot;Contribute to ventulus95/SpringBatch development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/ventulus95/SpringBatch&quot; data-og-url=&quot;https://github.com/ventulus95/SpringBatch&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cFb4b6/hyMKPXrVbj/neqkpTSgiGOR97QYKtesVK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/ventulus95/SpringBatch&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/ventulus95/SpringBatch&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cFb4b6/hyMKPXrVbj/neqkpTSgiGOR97QYKtesVK/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - ventulus95/SpringBatch&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to ventulus95/SpringBatch development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;</description>
      <category>Spring</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/141</guid>
      <comments>https://sundries-in-myidea.tistory.com/141#entry141comment</comments>
      <pubDate>Sun, 19 Dec 2021 18:39:39 +0900</pubDate>
    </item>
    <item>
      <title>Spring boot 버전별 업데이트 사항 정리 - 1. 2.4버전 업데이트</title>
      <link>https://sundries-in-myidea.tistory.com/140</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스프링부트 버전 업데이트 1.png&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdBCTi/btrmYUEwqtu/wKdCaprE4RLvKs7ymd3iQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdBCTi/btrmYUEwqtu/wKdCaprE4RLvKs7ymd3iQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdBCTi/btrmYUEwqtu/wKdCaprE4RLvKs7ymd3iQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdBCTi%2FbtrmYUEwqtu%2FwKdCaprE4RLvKs7ymd3iQK%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;712&quot; height=&quot;406&quot; data-filename=&quot;스프링부트 버전 업데이트 1.png&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;406&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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;b&gt;버전 업 이슈 사항들이 딱히 전파가 잘 되지 않는다&lt;/b&gt;라는 점이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring boot를 사용하다보면 가끔 내가 쓰고 있는 버전과 블로그나 인터넷에서 제공하는 기능이 정상작동을 하지 않는 경우가 있었는데, 그런 경우에는 버전업에 따른 Deprecated 이슈가 있는 상황이 간혹 있었다. 그리고 전에는 분명히 이렇게 하면 됬는데, &lt;a href=&quot;https://start.spring.io/%EC%97%90%EC%84%9C%EB%8A%94&quot;&gt;https://start.spring.io/에서는&lt;/a&gt; 기본 스프링부트의 버전을 거의 최신으로 유지하고 있어서 낮은 버전을 강제할 수도 없는 상황이 많이 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 이런 낮은 상황을 강제하려면 Maven, gradle의 도움을 받아서 억지로 낮은 버전을 찾아서 세팅하는 방법이 있기도 하지만.. 신규 프로젝트의 경우 낮은 버전을 강제하지는 않을 수도 있으며 이전 버전을 사용하던 개발자들이 분명히 전 버전과 달라서 마주하는 상황을 당연히 겪을수도 있겠다 싶어서 정리해보고자 한다.&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;data.sql 순서 오류&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 나는 미리 데이터를 넣어두고 Test code를 작성하기 위해서 &lt;code&gt;data.sql&lt;/code&gt;을 사용한 케이스가 있었다. 근데 schema.sql이 먼저 켜지면서 table이 만들어지지 않은 상태로 data.sql이 작동을 해. 시작조차 하지 않은 경우가 있었다. 이것 역시 2.5버전으로 버전업하면서 초기화 이슈가 있었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring.profiles 양식 변경&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.4부터는 Spring boot의 Spring.profile 양식이 약간 달라졌다. 물론 레거시 모드를 사용하는 것이 사용 가능했으나, 여러 단계를 갖춰서 환경세팅을 분리하려고 했으나, 외안대? 하는 상황이 많이 생겼었다. 특히, dev, prod, stg을 분리해야하는 상황을 만들어야하는데, 이런 상황자체가 정상적으로 사용하기 어렵다는 것이 나를 열받게 했던 포인트이다.&lt;/p&gt;
&lt;h1&gt;Spring boot 2.4 릴리즈 노트&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 스스로 중요하게 생각하는 부분 위주로 업데이트 됬다는 것을 확인해보고 싶어서 정리해봤습니다. 몇몇 부분들은 빼고 번역하였습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  번역의 의역이나, 오류등이 많습니다! 이상한 번역, 이해 안되는 점은 무조건 댓글 남겨주세요. 미숙한 영어 실력 때문에 번역 자체에 이슈가 많을것으로 예상됩니다.&lt;span style=&quot;color: #333333; font-family: -apple-system, BlinkMacSystemFont, AppleSDGothicNeo-Regular, 'Malgun Gothic', '맑은 고딕', dotum, 돋움, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1638688555094&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - spring-projects/spring-boot: Spring Boot&quot; data-og-description=&quot;Spring Boot. Contribute to spring-projects/spring-boot development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.4-Release-Notes&quot; data-og-url=&quot;https://github.com/spring-projects/spring-boot&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/p5XBY/hyMAAGP6mB/uiNsVUsPoVVoGXbfmfmCsk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.4-Release-Notes&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.4-Release-Notes&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/p5XBY/hyMAAGP6mB/uiNsVUsPoVVoGXbfmfmCsk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - spring-projects/spring-boot: Spring Boot&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Spring Boot. Contribute to spring-projects/spring-boot development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;주요 설정 업데이트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.3 버전에서 업데이트 된 내용&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;버전 양식 변경&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버전 양식을 변경. 2.3.5 RELEASE -&amp;gt; 2.4.0 이렇게 변경되었습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;code&gt;spring-boot-starter-test&lt;/code&gt; 에서 Vintage Engine 제거&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Junit4에서 사용하던, 빈티지 엔진을 사용하기 위해서는 아래 처럼 사용해야합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Maven&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.junit.vintage&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;junit-vintage-engine&amp;lt;/artifactId&amp;gt;
    &amp;lt;scope&amp;gt;test&amp;lt;/scope&amp;gt;
    &amp;lt;exclusions&amp;gt;
        &amp;lt;exclusion&amp;gt;
            &amp;lt;groupId&amp;gt;org.hamcrest&amp;lt;/groupId&amp;gt;
            &amp;lt;artifactId&amp;gt;hamcrest-core&amp;lt;/artifactId&amp;gt;
        &amp;lt;/exclusion&amp;gt;
    &amp;lt;/exclusions&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;Gradle&lt;/code&gt;&lt;/p&gt;
&lt;pre class=&quot;stylus&quot;&gt;&lt;code&gt;testImplementation(&quot;org.junit.vintage:junit-vintage-engine&quot;) {
    exclude group: &quot;org.hamcrest&quot;, module: &quot;hamcrest-core&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구성 파일 프로세싱 (Application.properties or . yaml 파일)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.4버전부터는 두 파일이 변경되었는데 만약 복잡하게 셋업을 해놓은경우 profile을 통한 여러 환경에 대해서 적용해야하는 경우는 &lt;a href=&quot;https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-Config-Data-Migration-Guide&quot;&gt;링크&lt;/a&gt;를 참고해서 migration에 관련한 정보를 확인해보시면 좋습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;이것은 또 제가 정리를 하고 링크를 또 남기겠습니다.&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약에 2.3.버전의 로직을 사용하려면 &lt;code&gt;spring.config.use-legacy-processing&lt;/code&gt;을 true로 해서 이전 스타일로 사용할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Logback Configuration Properties&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그백 설정과 관련한 설정을 명시적으로 구현해두었습니다. 이전 설정명은 모두 &lt;code&gt;deprecated&lt;/code&gt; 처리.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링부트에서 다음속성은 이렇게 변화했고...&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;logging.pattern.rolling-file-name&lt;/code&gt; &amp;rarr; &lt;code&gt;logging.logback.rollingpolicy.file-name-pattern&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;logging.file.clean-history-on-start&lt;/code&gt; &amp;rarr; &lt;code&gt;logging.logback.rollingpolicy.clean-history-on-start&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;logging.file.max-size&lt;/code&gt; &amp;rarr; &lt;code&gt;logging.logback.rollingpolicy.max-file-size&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;logging.file.total-size-cap&lt;/code&gt; &amp;rarr; &lt;code&gt;logging.logback.rollingpolicy.total-size-cap&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;logging.file.max-history&lt;/code&gt; &amp;rarr; &lt;code&gt;logging.logback.rollingpolicy.max-history&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비슷하게 시스템 속성은 다음과 같이 변경 되었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;ROLLING_FILE_NAME_PATTERN&lt;/code&gt; &amp;rarr; &lt;code&gt;LOGBACK_ROLLINGPOLICY_FILE_NAME_PATTERN&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LOG_FILE_CLEAN_HISTORY_ON_START&lt;/code&gt; &amp;rarr; &lt;code&gt;LOGBACK_ROLLINGPOLICY_CLEAN_HISTORY_ON_START&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LOG_FILE_MAX_SIZE&lt;/code&gt; &amp;rarr; &lt;code&gt;LOGBACK_ROLLINGPOLICY_MAX_FILE_SIZE&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LOG_FILE_TOTAL_SIZE_CAP&lt;/code&gt; &amp;rarr; &lt;code&gt;LOGBACK_ROLLINGPOLICY_TOTAL_SIZE_CAP&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;LOG_FILE_MAX_HISTORY&lt;/code&gt; &amp;rarr; &lt;code&gt;LOGBACK_ROLLINGPOLICY_MAX_HISTORY&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;기본 서블렛 등록&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.4 버전부터는 &lt;code&gt;DefaultServlet&lt;/code&gt; 서블렛 컨테이너를 등록하지 않음. 대부분 Spirng MVC에서 &lt;code&gt;DispatcherServlet&lt;/code&gt;으로 사용되서 사용이 안되서 등록하지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 설정시 &lt;code&gt;server.servlet.register-default-servlet&lt;/code&gt; 를 &lt;code&gt;true&lt;/code&gt; 설정할 것.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;인베디드 DB 추적&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인베디드 데이터베이스의 로직을 재정의했습니다. 인메모리아닌 경우는 다른 DB로 간주하게 되어있었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;sa의 유저이름은 이제 유효하지 않습니다. 만약 쓰고 싶다면, &lt;code&gt;spring.datasource.username=sa&lt;/code&gt; 설정 달아주면 됩니다.&lt;/li&gt;
&lt;li&gt;&lt;i&gt;의아한점: 유효하지 않게 만들었으나, 위의 설정 달지 않으면 로그인이 불가하다는 점&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;임베디드의 초기화 기능은 이제 고려되지 않았습니다.. 만약에 쓰고 싶으면 &lt;code&gt;spring.datasource.initialization-mode&lt;/code&gt; 로 설정해야합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;code&gt;exec-maven-plugin&lt;/code&gt; 위한 버전 매니징&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;exec-maven-plugin&lt;/code&gt; 는 삭제되었습니다. 이 플러그인을 사용하고 싶다면, 자신에 맞는 pluginManagement`를 특정 버전을 지정해야합니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스프링부트 Gradle Plugin&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot Gradle Plugin을 위한 DSL인 &lt;code&gt;bootJar&lt;/code&gt; 는&lt;code&gt;Property&amp;lt;String&amp;gt;&lt;/code&gt; 을 사용해 &lt;code&gt;mainClass&lt;/code&gt;를 구성 할 수 있도록 업데이트 되었고, 아래는 mainClassName를 사용하는 예제이며&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;bootJar {
    mainClassName 'com.example.ExampleApplication'
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;mainClass&lt;/code&gt; 로 변경 가능해졌습니다.&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;bootJar {
    mainClass 'com.example.ExampleApplication'
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Spring Boot 2.2 and 2.3의 Deprecation&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 부트의 &lt;a href=&quot;https://github.com/spring-projects/spring-boot/wiki/Supported-Versions#release-compatibility&quot;&gt;release compatibility policy&lt;/a&gt; 를 반영하여, Spring Boot 2.2의 code deprecated를 2.4버전에서 삭제되며, 2.3버전의 Code deprecated은 아직 남아있지만, 2.5버전에서 삭제될 예정입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;새롭게 추가되거나, 주목해봐야할 것들&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;✅&amp;nbsp; 새로운 configuration에 대한 변경은 상기 &lt;a href=&quot;https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.4.0-Configuration-Changelog&quot;&gt;링크&lt;/a&gt;를 참고해주세요&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스프링 5.3&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.4부터는 스프링 5.3 사용&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Java 15 공식 지원&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 15를 공식 지원. &lt;b&gt;최소 제공 버전은 8임.&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;커스텀 property 이름 지원&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생성자 바인딩을 사용하는 경우에는 속성이름을 매개변수에 사용할 수 있다. 이렇게 사용할 경우 &lt;b&gt;자바 예약어 키워드&lt;/b&gt;로 사용하는 경우 문제가 생겼습니다.. 이런 문제를 해결하기 위해서 다음과 같은 &lt;code&gt;@Name&lt;/code&gt; 태그를 통해서 사용하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음은 &lt;code&gt;sample.import&lt;/code&gt; 의 경우 설정 가능합니다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@ConfigurationProperties(prefix = &quot;sample&quot;)
@ConstructorBinding
public class SampleConfigurationProperties {

  private final String importValue;

  public SampleConfigurationProperties(@Name(&quot;import&quot;) String importValue) {
    this.importValue = importValue;
  }

}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;웹 환경설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring MVC와 WebFlux에 공통적으로 제공하는 web 지역, 리소스 위치 속성을 추가하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로 추가된 값&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;spring.web.locale&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;spring.web.locale-resolver&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;spring.web.resources.*&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서블릿 또는 리액티브 웹 스택을 사용하여 actuators 관리 엔드포인트에 대한 새로운 속성이 추가되었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;management.server.base-path&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 속성들은 공통으로 매니징 되기 위해 MVC/서블릿 속성에서 deprecated 되었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;spring.mvc.locale&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;spring.mvc.locale-resolver&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;spring.resources.*&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;management.server.servlet.context-path&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Flyway 7&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flyway 7이 버전 업그레이드 되며 새로운 속성들을 가져왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;spring.flyway&lt;/code&gt; 속성은 다음과 같습니다:&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;url&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;user&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;password&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;teams&quot; 버젼 사용하는 경우 다음의 것들을 추가적으로 사용가능합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;cherry-pick&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;jdbc-properties&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;oracle-kerberos-cache-file&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;oracle-kerberos-config-file&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;H2 콘솔의 웹 어드민 비밀번호 속성 추가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 속성 h2 웹 어드민 패스워드가 설정에 등록되었습니다. &lt;code&gt;spring.h2.console.settings.web-admin-password&lt;/code&gt; 를 통해서 어드민 페이지의 기본 설정, 툴의 접근을 제어합니다.&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;code&gt;FailureAnalizers&lt;/code&gt; 는 &lt;code&gt;ApplicationContext&lt;/code&gt; 가 생성되어지지 않았을때 고려되어집니다. 이를 통해서 환경 처리 중 발생하는 예외 분석이 가능해집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;ApplicationContext&lt;/code&gt; 를 만들지 않는 경우 &lt;code&gt;BeanFactoryAware&lt;/code&gt; or &lt;code&gt;EnvironmentAware&lt;/code&gt; 를 구현하는 분석기가 작동하지 않았던 점을 참고했습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Jar 최적화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실행가능한 스프링부트 Jar를 생성시 비어있는 Starter 의존성들을 자동적으로 제거했습니다. 비록 대부분의 Starter들이 타의적인 의존성을 제공했지만, 마지막 jar 패키지에서는 거의 의미가 없었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot 어노테이션 프로세서 역시 빌드하는 동안 오직 사용하므로 제거하게 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드가 없는 고유한 스타터 POM이 있는 경우 Spring-Boot-Jar-Type 항목을 매니페스트에 추가할 수 있습니다.&quot;&lt;code&gt;dependencies-starter&quot;&lt;/code&gt; 값을 갖는 MF. 주석 프로세서를 필터링하려면 &quot;어노테이션-프로세서&quot; 값으로 동일한 속성을 추가할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;추가적인 Application Config 삽입&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;spring.config.use-legacy-processing&lt;/code&gt; 를 True 처리하지 않았다면, 추가 정보들이 &lt;code&gt;application.properties&lt;/code&gt; or &lt;code&gt;application.yml&lt;/code&gt; 자동으로 추가되서 들어가집니다.&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://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.4-Release-Notes#miscellaneous&quot;&gt;링크&lt;/a&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의존성 업데이트&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.4버전으로 업데이트 하면서, 다음 스프링 프로젝트를 새로운 버전으로 업데이트 시켰습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring AMQP 2.3 (&lt;a href=&quot;https://docs.spring.io/spring-amqp/reference/html/#whats-new&quot;&gt;what&amp;rsquo;s new&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Spring Batch 4.3 (&lt;a href=&quot;https://docs.spring.io/spring-batch/docs/current/reference/html/whatsnew.html#whatsNew&quot;&gt;what&amp;rsquo;s new&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Spring Data 2020.0 (&lt;a href=&quot;https://github.com/spring-projects/spring-data-commons/wiki/Release-Train-Ockham-(2020.0.0)&quot;&gt;changelog&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Spring Framework 5.3 (&lt;a href=&quot;https://github.com/spring-projects/spring-framework/wiki/What's-New-in-Spring-Framework-5.x&quot;&gt;what&amp;rsquo;s new&lt;/a&gt; | &lt;a href=&quot;https://github.com/spring-projects/spring-framework/wiki/Upgrading-to-Spring-Framework-5.x#upgrading-to-version-53&quot;&gt;upgrading&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Spring Integration 5.4 (&lt;a href=&quot;https://docs.spring.io/spring-integration/docs/current/reference/html/whats-new.html#whats-new&quot;&gt;what&amp;rsquo;s new&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Spring HATEOAS 1.2 (&lt;a href=&quot;https://docs.spring.io/spring-hateoas/docs/1.2.0/reference/html/#migrate-to-1.0&quot;&gt;migration guide&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Spring Kafka 2.6 (&lt;a href=&quot;https://docs.spring.io/spring-kafka/reference/html/#spring-kafka-intro-new&quot;&gt;what&amp;rsquo;s new&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Spring Retry 1.3&lt;/li&gt;
&lt;li&gt;Spring Security 5.4 (&lt;a href=&quot;https://docs.spring.io/spring-security/site/docs/5.4.0/reference/html5/#new&quot;&gt;what&amp;rsquo;s new&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Spring Session 2020.0&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 서드파티 의존성 역시 업데이트 처리되었습니다. 몇몇 패키지는 새로운점이 추가되었으므로 링크를 확인해보시길 바랍니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Artemis 2.13&lt;/li&gt;
&lt;li&gt;AssertJ 3.18&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.datastax.com/en/developer/java-driver/4.9/&quot;&gt;Cassandra Driver 4.7&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Elasticsearch 7.9&lt;/li&gt;
&lt;li&gt;Flyway 7&lt;/li&gt;
&lt;li&gt;Jersey 2.31&lt;/li&gt;
&lt;li&gt;JUnit 5.7&lt;/li&gt;
&lt;li&gt;Liquibase 3.10&lt;/li&gt;
&lt;li&gt;Lettuce 6.0 (&lt;a href=&quot;https://github.com/lettuce-io/lettuce-core/releases/tag/6.0.0.RELEASE&quot;&gt;release notes&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Micrometer 1.6 (&lt;a href=&quot;https://github.com/micrometer-metrics/micrometer/releases/tag/v1.6.0&quot;&gt;release notes&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;Mockito 3.4&lt;/li&gt;
&lt;li&gt;MongoDB 4.1&lt;/li&gt;
&lt;li&gt;Oracle Database 19.7&lt;/li&gt;
&lt;li&gt;Reactor 2020.0 (&lt;a href=&quot;https://github.com/reactor/reactor/releases/tag/2020.0.0&quot;&gt;release notes&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;RSocket 1.1&lt;/li&gt;
&lt;li&gt;Undertow 2.2&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Spring</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/140</guid>
      <comments>https://sundries-in-myidea.tistory.com/140#entry140comment</comments>
      <pubDate>Sun, 5 Dec 2021 16:28:59 +0900</pubDate>
    </item>
    <item>
      <title>Java String + 연산은 정말 빠른가?</title>
      <link>https://sundries-in-myidea.tistory.com/139</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;java +.png&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OV1H9/btrlFKDc7Fj/1GPJQoTkAoTddXjYH0vqI0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OV1H9/btrlFKDc7Fj/1GPJQoTkAoTddXjYH0vqI0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OV1H9/btrlFKDc7Fj/1GPJQoTkAoTddXjYH0vqI0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOV1H9%2FbtrlFKDc7Fj%2F1GPJQoTkAoTddXjYH0vqI0%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;712&quot; height=&quot;406&quot; data-filename=&quot;java +.png&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;406&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&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://www.facebook.com/groups/codingeverybody/posts/6423007531073062/?comment_id=6427580693949079&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;페이스북&lt;/a&gt;에서 본 기억이 나서 공유했었던 의제이다. &lt;b&gt;+가 StringBuilder보다 빠릅니다!&lt;/b&gt; 라고 당당하게 언급했었는데, 막상 스터디 팀원들의 질문 공세에 어버버하며 재대로 대답을 못해드리게 되었다. 그 덕분에 자세히 찾아보게 되었는데, 원글을 읽고 &lt;b&gt;글의 정확성을 한번 확인해보는 과정이 필요했었으며 심지어 더 자세히 아래쪽을 보면 더 정확하게 설명이 있음에도&lt;/b&gt;&amp;nbsp;불구하고 &lt;b&gt;+가 StringBuilder보다 빠릅니다!&lt;/b&gt;&amp;nbsp; 더 빠르다는 것에 심취해 있어 잘못된 정보를 전달할뻔 했었다.&amp;nbsp;&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;+ 연산이 진짜 빠른가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;String 연산은 코딩테스트와 여러 상황에서 + 보다 StringBuilder가 사용되는 경우는 많았습니다. 특히, 백준과 같은 코딩테스트 사이트에서 일정시간 1초로 지정되어있고 정말 많은 수의 문자열 연산을 해야하는 경우 +를 하게된다면 시간 초과가 나오는 경우가 많았는데요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예전에 여럼풋이 기억난 것중&amp;nbsp; 디 컴파일링시 + 연산은 대부분 StringBuilder를 통해 append되는 모습을 볼 수 있다. 자바 내부적으로는 실제로 + 연산을 모두 .append화 시키는데 &quot;+&quot; 연산자체가 빠른가에 대해서 논의 하는거 자체가 의마가 없지 않나라는 생각이 많이 들었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 혼자서 상상한것은 의미가 없어보이고 직접 해보는게 더 좋아보였고, 확인해보았다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그래서 직접 해보았다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10~100만이상 for문을 돌리는 방식으로 실험을 해보았다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public static void main(String[] args) {
    long start = System.currentTimeMillis();
    String test = &quot;aa&quot;; //StringBuilder test = new StringBuilder(&quot;aa&quot;);
    for (int i = 0; i &amp;lt;100000; i++) {
        test= test+&quot;aa&quot;; // test.append(&quot;aa&quot;);
    }
    long end = System.currentTimeMillis()-start;
    System.out.println(end);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #009a87;&quot;&gt;3797&lt;/span&gt; vs &lt;span style=&quot;color: #f89009;&quot;&gt;15&lt;/span&gt;&lt;/b&gt; 정도로 &lt;span style=&quot;color: #009a87;&quot;&gt;&lt;b&gt;+&lt;/b&gt;&lt;/span&gt;와 &lt;span style=&quot;color: #f89009;&quot;&gt;&lt;b&gt;StringBuilder&lt;/b&gt;&lt;/span&gt; 차이가 많이 났다. &lt;b&gt;200배&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;내가 생각했었던 문자열을 컴파일시 StringBuilder로 묶어주는게 아닌가 했었는데, 막상 Compile시에서는 합쳐지는게 보여지진 않았다. 그럼 내가 생각했던것은 뭐지?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오히려 신기한점은 문자열을 아예 하나의 문자로써 완전히 붙혀버렸다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;String a = &quot;a&quot;;
String b = &quot;1234555&quot;;
System.out.println(a+b+&quot;123455&quot;+1);
System.out.println(&quot;안녕, &quot;+&quot;안녕, &quot;+&quot;안녕, &quot;+&quot;안녕, &quot;+&quot;안녕, &quot;+&quot;안녕, &quot;+&quot;안녕, &quot;+&quot;안녕, &quot;+&quot;안녕, &quot;+&quot;안녕, &quot;+&quot;안녕, &quot;+&quot;안녕, &quot;+&quot;안녕, &quot;+&quot;끝!&quot;);

//컴파일시 이렇게 된다. 1이나 문자열이 그냥 아예 한 문자열로 붙혀져서 처리된다.

System.out.println(a + b + &quot;1234551&quot;);
System.out.println(&quot;안녕, 안녕, 안녕, 안녕, 안녕, 안녕, 안녕, 안녕, 안녕, 안녕, 안녕, 안녕, 안녕, 끝!&quot;); &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;근데 바이트 코드를 까보니까&amp;nbsp; 버전별로 다른 방식으로 처리되는 점을 확인해봤는데, 그 점은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Java 11버전&lt;/b&gt;은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;#0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;&lt;/code&gt;&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;b&gt;8버전&lt;/b&gt;은 이렇게 StringBuilder로 붙임&lt;/p&gt;
&lt;pre class=&quot;shell&quot; data-ke-language=&quot;shell&quot;&gt;&lt;code&gt;19: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
22: ldc           #7                  // String 1234
24: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 생각했던것이 바이트 코드에서 실현이 되었던것.. 그래서 Java 8과 StringBuilder과 +를 비교해보면 더 명확하지 않을까 싶어서 또 시도해보았다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;덧붙이기 Java 8에서 +가 느린이유 SB에 비해서 훨씬 훨씬 느린이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+로 붙이면 바이트 코드는 다음과 같이 나오고&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt; public static void main(java.lang.String[]);
    Code:
       0: ldc           #2                  // String !23
       2: astore_1
       3: iconst_0
       4: istore_2
       5: iload_2
       6: ldc           #3                  // int 100000
       8: if_icmpge     37
      11: new           #4                  // class java/lang/StringBuilder
      14: dup
      15: invokespecial #5                  // Method java/lang/StringBuilder.&quot;&amp;lt;init&amp;gt;&quot;:()V
      18: aload_1
      19: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      22: ldc           #7                  // String 1234
      24: invokevirtual #6                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      27: invokevirtual #8                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
      30: astore_1
      31: iinc          2, 1
      34: goto          5
      37: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
      40: aload_1
      41: invokevirtual #10                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      44: return&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StringBuilder로 하면 아래처럼 나오는데&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;Code:
       0: new           #2                  // class java/lang/StringBuilder
       3: dup
       4: ldc           #3                  // String !23
       6: invokespecial #4                  // Method java/lang/StringBuilder.&quot;&amp;lt;init&amp;gt;&quot;:(Ljava/lang/String;)V
       9: astore_1
      10: iconst_0
      11: istore_2
      12: iload_2
      13: ldc           #5                  // int 100000
      15: if_icmpge     31
      18: aload_1
      19: ldc           #6                  // String 1234
      21: invokevirtual #7                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
      24: pop
      25: iinc          2, 1
      28: goto          12
      31: getstatic     #8                  // Field java/lang/System.out:Ljava/io/PrintStream;
      34: aload_1
      35: invokevirtual #9                  // Method java/io/PrintStream.println:(Ljava/lang/Object;)V
      38: return&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;/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;StringBuilder를 생성 초기화 적제 -&amp;gt; Append후 붙혀주기 -&amp;gt; toString으로.. 바꾼다음에 적재 -&amp;gt; 그리고 다시 초기화 적제-&amp;gt; Append를하니까&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;Java 11버전 (9버전부터는 makeConcatWithConstants을 통해서 획기적인 개선 그래서 둘 다 똑같음.) +를 쓰면 이렇게 나와버림.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt; Code:
       0: ldc           #2                  // String !23
       2: astore_1
       3: iconst_0
       4: istore_2
       5: iload_2
       6: ldc           #3                  // int 100000
       8: if_icmpge     24
      11: aload_1
      12: invokedynamic #4,  0              // InvokeDynamic #0:makeConcatWithConstants:(Ljava/lang/String;)Ljava/lang/String;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;StringBuilder를 쓰는 경우는 똑같음 근데 속도는 StringBuilder가 좀더 빠름. 대신 9버전의 String을 합치는 방식이 8버전보다는 10배이상은 빨라보였다.&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;//이런 코드를 작성하면...
Long start = System.currentTimeMillis();
String a = &quot;1234&quot;;
for (int i = 0; i &amp;lt;100000; i++) {
    a+=&quot;92292929&quot;;
}
System.out.println(System.currentTimeMillis()-start);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Java 11: &lt;b&gt;4318&lt;/b&gt; Java 8: &lt;b&gt;31110&lt;/b&gt; 이었는데,&amp;nbsp; 진짜 10배 가량 차이가 난다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compile방식의 차이가 속도의 차이가 발생한다.&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;code&gt;makeConcatWithConstants&lt;/code&gt; 로 개선된 이유에 대해서 찾아봤는데, 이 로직을 찾는건 좀 어려웠고, 자바 9부터 &lt;b&gt;Indify String Concatenation&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;a href=&quot;http://openjdk.java.net/jeps/280&quot;&gt;http://openjdk.java.net/jeps/280&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1637486505355&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;JEP 280: Indify String Concatenation&quot; data-og-description=&quot;JEP 280: Indify String Concatenation OwnerAleksey ShipilevTypeFeatureScopeSEStatusClosed&amp;thinsp;/&amp;thinsp;DeliveredRelease9Componenttools&amp;thinsp;/&amp;thinsp;javacDiscussioncore dash libs dash dev at openjdk dot java dot net, compiler dash dev at openjdk dot java dot net, hotspot &quot; data-og-host=&quot;openjdk.java.net&quot; data-og-source-url=&quot;http://openjdk.java.net/jeps/280&quot; data-og-url=&quot;http://openjdk.java.net/jeps/280&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;http://openjdk.java.net/jeps/280&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;http://openjdk.java.net/jeps/280&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;JEP 280: Indify String Concatenation&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;JEP 280: Indify String Concatenation OwnerAleksey ShipilevTypeFeatureScopeSEStatusClosed&amp;thinsp;/&amp;thinsp;DeliveredRelease9Componenttools&amp;thinsp;/&amp;thinsp;javacDiscussioncore dash libs dash dev at openjdk dot java dot net, compiler dash dev at openjdk dot java dot net, hotspot&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;openjdk.java.net&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;결국 8버전 바이트코드와 같이 StringBuilder를 잘못 쓰는 케이스가 있기 때문에, 이러한 String을 붙이는 것에 대해서 개선을 하였고, 그것이 &lt;i&gt;makeConcatWithConstants&lt;/i&gt;로 조금 더 빠른 속도로 String +를 개선하는 방향 버전 업그레이드 방향을 잡은듯했고, 실제로 반영이 되어있다.&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;i&gt;makeConcatWithConstants&lt;/i&gt;의 작동 로직역시 궁금하였으나, 부족한 검색 실력으로 찾지는 못했고... 그리고 찾더라도 너무너무 딥다이브하게 들어가는 것 같아서 일단 개선 되었다는 점 그리고 개선의 이유까지만 찾아서 마무리했다.&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;b&gt;'+' 는 실제로 StringBuilder보다는 느린것은 사실&lt;/b&gt;... 하지만 속도 자체는 &lt;b&gt;8에 비해서는 그 이후 버전이 말 그대로 10배나 차이날정도로 빨라졌음&lt;/b&gt;. 매우 많은 수(최소 10만 이상)를 붙이는 것이 아닌 이상.... +를 통해서 얻는 손해는 커지 않아 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;적은 갯수의 String을 합치는 것에 대해서는 오로지 속도적인 측면에서는 그렇게 느리지 않다고 봐도 좋을 것 같다.&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;a href=&quot;http://wonwoo.ml/index.php/post/1039&quot;&gt;http://wonwoo.ml/index.php/post/1039&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1637486525329&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;java bytecode - 머루의개발블로그&quot; data-og-description=&quot;오늘은 java 바이트코드에 대해서 잠깐 살펴보자. 우리는 거의 바이트코드를 볼일이 없다. 바이트코드 레벨까지 내려갈 필요가 없기에 딱히 볼일이 없다. 바이트코드를 상세하게 볼건 없고 그냥 &quot; data-og-host=&quot;wonwoo.ml&quot; data-og-source-url=&quot;http://wonwoo.ml/index.php/post/1039&quot; data-og-url=&quot;http://wonwoo.ml/index.php/post/1039&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/guHTA/hyMq2v8ruk/PbELQkkpiXZ7H5oybiK1Xk/img.png?width=644&amp;amp;height=660&amp;amp;face=0_0_644_660,https://scrap.kakaocdn.net/dn/PBP3K/hyMq3IyTOc/q310CsPfhxZQubjxm1hQPK/img.png?width=863&amp;amp;height=786&amp;amp;face=0_0_863_786,https://scrap.kakaocdn.net/dn/cuOoaJ/hyMq2pl65p/Vl6RgqufxEofIyZgbOffS1/img.png?width=875&amp;amp;height=505&amp;amp;face=0_0_875_505&quot;&gt;&lt;a href=&quot;http://wonwoo.ml/index.php/post/1039&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;http://wonwoo.ml/index.php/post/1039&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/guHTA/hyMq2v8ruk/PbELQkkpiXZ7H5oybiK1Xk/img.png?width=644&amp;amp;height=660&amp;amp;face=0_0_644_660,https://scrap.kakaocdn.net/dn/PBP3K/hyMq3IyTOc/q310CsPfhxZQubjxm1hQPK/img.png?width=863&amp;amp;height=786&amp;amp;face=0_0_863_786,https://scrap.kakaocdn.net/dn/cuOoaJ/hyMq2pl65p/Vl6RgqufxEofIyZgbOffS1/img.png?width=875&amp;amp;height=505&amp;amp;face=0_0_875_505');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;java bytecode - 머루의개발블로그&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;오늘은 java 바이트코드에 대해서 잠깐 살펴보자. 우리는 거의 바이트코드를 볼일이 없다. 바이트코드 레벨까지 내려갈 필요가 없기에 딱히 볼일이 없다. 바이트코드를 상세하게 볼건 없고 그냥&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;wonwoo.ml&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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://stackoverflow.com/questions/46512888/how-is-string-concatenation-implemented-in-java-9&quot;&gt;https://stackoverflow.com/questions/46512888/how-is-string-concatenation-implemented-in-java-9&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1637486526455&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;How is String concatenation implemented in Java 9?&quot; data-og-description=&quot;As written in JEP 280: Indify String Concatenation: Change the static String-concatenation bytecode sequence generated by javac to use invokedynamic calls to JDK library functions. This will ena...&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/46512888/how-is-string-concatenation-implemented-in-java-9&quot; data-og-url=&quot;https://stackoverflow.com/questions/46512888/how-is-string-concatenation-implemented-in-java-9&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bkBJwe/hyMqTeSpMZ/o990UfCobtv0oKIRTVsj8k/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/46512888/how-is-string-concatenation-implemented-in-java-9&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/46512888/how-is-string-concatenation-implemented-in-java-9&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bkBJwe/hyMqTeSpMZ/o990UfCobtv0oKIRTVsj8k/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;How is String concatenation implemented in Java 9?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;As written in JEP 280: Indify String Concatenation: Change the static String-concatenation bytecode sequence generated by javac to use invokedynamic calls to JDK library functions. This will ena...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/139</guid>
      <comments>https://sundries-in-myidea.tistory.com/139#entry139comment</comments>
      <pubDate>Sun, 21 Nov 2021 18:29:14 +0900</pubDate>
    </item>
    <item>
      <title>Vue props을 임의로 바꾸면 생기는 일</title>
      <link>https://sundries-in-myidea.tistory.com/138</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;vue props.png&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;406&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqWklE/btrj3NCaBBm/D41OjKtR3SHlotLP9AHSK0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqWklE/btrj3NCaBBm/D41OjKtR3SHlotLP9AHSK0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqWklE/btrj3NCaBBm/D41OjKtR3SHlotLP9AHSK0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqWklE%2Fbtrj3NCaBBm%2FD41OjKtR3SHlotLP9AHSK0%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;712&quot; height=&quot;406&quot; data-filename=&quot;vue props.png&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;406&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;뭐야 이게...&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;uetify에서&amp;nbsp;Dialog를&amp;nbsp;구성하던&amp;nbsp;도중&amp;nbsp;한&amp;nbsp;페이지에&amp;nbsp;너무&amp;nbsp;많은&amp;nbsp;Dialog가&amp;nbsp;존재했다.&amp;nbsp;그로인해&amp;nbsp;코드가&amp;nbsp;너무&amp;nbsp;길어졌는데,&amp;nbsp;차라리&amp;nbsp;Component를&amp;nbsp;분리해서&amp;nbsp;관리하면&amp;nbsp;좋겠다고&amp;nbsp;생각했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2021-11-07 오후 9.20.00.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;288&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/IVPnO/btrkegCgZbP/YM7qcVIeADoY4N37TqKTY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/IVPnO/btrkegCgZbP/YM7qcVIeADoY4N37TqKTY1/img.png&quot; data-alt=&quot;dialog는 결국 버튼을 누르면 브라우저 내부적인 새창이 튀어오른다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/IVPnO/btrkegCgZbP/YM7qcVIeADoY4N37TqKTY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FIVPnO%2FbtrkegCgZbP%2FYM7qcVIeADoY4N37TqKTY1%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;288&quot; data-filename=&quot;스크린샷 2021-11-07 오후 9.20.00.png&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;288&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;dialog는 결국 버튼을 누르면 브라우저 내부적인 새창이 튀어오른다&lt;/figcaption&gt;
&lt;/figure&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;  Avoid mutating a prop directly since the value will be overwritten whenever the parent component re-renders. Instead, use a data or computed property based on the prop's value. Prop being mutated: &quot;dialog&quot;&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;아니 내가 자식 컴포넌트에서 Props로 받은 값을 임의로 막 변경하면 오류가 난다고...?&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;구성 형식을 구체적으로 한번 더 자세히보자.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vuetify 공식 홈페이지에서는 dialog를 구성할때 v-slot:activator를 통해서 구성하라고 버튼을 구성했는데, 실제로 dialog는 v-model을 통해서 받아진 값만 &lt;code&gt;true&lt;/code&gt; or &lt;code&gt;false&lt;/code&gt; 이기만 해도.. dialog가 켜지고 닫혀진다. 굳이 v-slot:activator를 구성해서 dialog안에 굳이 v-button을 만들필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 특정 boolean일 만한 data 변수 하나만 가지고 있으면 특정창을 키고 끌 수 가 있는것이다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!--상위 컴포넌트 --&amp;gt;
&amp;lt;v-btn
       color=&quot;blue lighten-2&quot;
       dark
       @click=&quot;window=true&quot;
       &amp;gt;
  눌러!
&amp;lt;/v-btn&amp;gt;

&amp;lt;Semidialog  :window=&quot;window&quot; @close=&quot;window=false&quot;/&amp;gt;


...
window: false
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 대충 이런 형식으로 dialog를 키고 끌 수 있게되는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예제에서 이 dialog를 키게하는 주체는 window라는 Boolean 변수를 통해서 전달되어지는데, 상위 컴포넌트에서 하위 컴포넌트에 window를 전달해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 하위쪽에서는 Props를 통해서 전달 받아야한다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!--자식 컴포넌트 --&amp;gt;
&amp;lt;v-dialog
      v-model=&quot;window&quot;
      width=&quot;500&quot;
    &amp;gt;
  ....
&amp;lt;v-btn color=&quot;primary&quot; @click=&quot;window=false&quot;&amp;gt; //props의 값인 window를 false로 임의로 변경
  확인
&amp;lt;/v-btn&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 예제처럼 내가 Props를 통해 전달 받은 값은 -&amp;gt; false로 자식 컴포넌트 내부에서 임의로 바꾸게되면 위의 에러가 발생하게 된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;왜 이러는걸까?&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;내 추측은 부모의 값을 자식에서 임의로 쓰슥 값을 막 만지면, 값이 내부적으로 꼬일 가능성이 커진다. 한 컴포넌트내에서 자신의 값만 건들여야하는데, 임의로 다른 컴포넌트가 이 값을 건들기 시작하면 내부적으로 값이 수정되고 바꿔치기 당할 성이 높지 않을까? 아마 그래서 임의로 Vue 내부에서 커트한게 아닌가 싶다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러면 위처럼 저런 문제를 해결하기 위해서는 어떤 해결책이 존재할까?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Props를 아예 건들지 않기&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;&amp;lt;v-btn
  color=&quot;primary&quot;
  @click=&quot;closeDialog(window)&quot;
&amp;gt;
  확인
&amp;lt;/v-btn&amp;gt;

closeDialog(dialog){
  this.$emit('close', dialog) 
}

&amp;lt;!-- 상위 컴포넌트 --&amp;gt;
&amp;lt;Semidialog  :window=&quot;window&quot; @close=&quot;window=false&quot;/&amp;gt; =&amp;gt; emit으로 값을 전달해서 그걸 바로 처리한다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 Props를 통해서 받아온 값을 상위에서 값을 변경할 수 있도록 아예 값을 전이해버리는 방식이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;$emit&lt;/code&gt; 을 통해서 자식에서 Props를 통해서 받은 값을 그대로 통로처럼 전달해서 자식 컴포넌트에서 직접 그 값을 만지는 일은 없되, 그 값이 자식 컴포넌트에 영향을 줄수 있도록 구현하는 방식을 채용하는 법이 있다.&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;아래의 다른 방식으로도 구현하려 했으나 Dialog를 켜고 끄는데는 개발 노력이 더 들어가서 굳이 이렇게 안해도 될것 같다는 생각을 했었다. 아래는 여러가지 방식에 대한 여러가지 의견을 담아보았으니 가볍게 읽고 넘어가도 좋을 것 같다.&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;이런 방법도 존재하지만, 실제로 Dialog에서는 쓸모 없어보인다.&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자식 컴포넌트에서 Props의 값을 로컬 변수로 전달받아 사용하기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자식 컴포넌트에서 값을 바로 받아서 사용하는 방식도 존재하는데 아래와 같은 방식은 &lt;b&gt;작동이 불가능&lt;/b&gt;하다.&lt;/p&gt;
&lt;pre class=&quot;xml&quot;&gt;&lt;code&gt;&amp;lt;!--자식 컴포넌트 --&amp;gt;
&amp;lt;v-dialog
      v-model=&quot;local&quot;
      width=&quot;500&quot;
    &amp;gt;

data
  local: this.window &lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐면, 자식 컴포넌트를 통해서 props로 받아서 사용하면 초기화가 안되서 정상작동이 안된다. 이렇게 사용할 수 없으므로, 다른 방식을 사용해야하는데, 바로 이럴때 사용하는게 computed기능이다.&lt;/p&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;&amp;lt;v-dialog
      v-model=&quot;computedWindow&quot;
      width=&quot;500&quot;
    &amp;gt;

  computed:{
      computedWindow(){
        return this.window
      }
    },&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 기능을 사용하기 위해서는 computed로 값을 초기화를 먼저 시켜준 뒤 -&amp;gt; 그값을 아까와 같은 방식으로 &lt;code&gt;$emit&lt;/code&gt;으로 전달하는것인데, 이러면 local 변수로 받아서 사용하는 의미가 사라진다.&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;mounted, created&lt;/b&gt;와 같은 방식을 통해서 값을 넣어서 값을 초기화하는 방식도 존재하나.. 내가 보기에는 값이 업데이트 되는 방식에서 vue의 life cycle을 정확하게 알아야지&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;또한 computed의 setter나 Watch를 통한 값의 변경을 인식한다던가의 여러가지 방법은 존재하겠지만.. &lt;code&gt;$emit&lt;/code&gt;보다 너무 많은 일처리를 하는 것으로 보이고, dialog을 켜고 끄는 방식에서만큼은 &lt;code&gt;$emit&lt;/code&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;물론 로컬변수로 값을 불러와서 그 값을 변경시켜야하는 경우에는 여러방식을 고려해보는것도 좋다고 생각하지만, 단지 boolean만의 값을 처리하는데 너무 복잡하게 안해도 될것 같다.&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;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 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;span style=&quot;background-color: #ffffff; color: #1d1c1d;&quot;&gt;Props를 자식 컴포넌트에서 임의로 바꾸지 못하게 하는 이유는&amp;nbsp;&lt;/span&gt;&quot;단방향&amp;nbsp;바인딩&amp;nbsp;규칙을&amp;nbsp;유지함으로써&amp;nbsp;데이터&amp;nbsp;변경&amp;nbsp;주체를&amp;nbsp;명확하게&amp;nbsp;관리할&amp;nbsp;수&amp;nbsp;있음&quot;&lt;/b&gt;이라고&amp;nbsp;정리할&amp;nbsp;수&amp;nbsp;있을&amp;nbsp;것&amp;nbsp;같아요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 원문입니다!&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;434&quot; data-origin-height=&quot;269&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/K2HQp/btrlEJ5PYlT/yq1367BwG8D7jwJWrFalWk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/K2HQp/btrlEJ5PYlT/yq1367BwG8D7jwJWrFalWk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/K2HQp/btrlEJ5PYlT/yq1367BwG8D7jwJWrFalWk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FK2HQp%2FbtrlEJ5PYlT%2Fyq1367BwG8D7jwJWrFalWk%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;434&quot; height=&quot;269&quot; data-origin-width=&quot;434&quot; data-origin-height=&quot;269&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>FrontEnd/Vue</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/138</guid>
      <comments>https://sundries-in-myidea.tistory.com/138#entry138comment</comments>
      <pubDate>Sun, 7 Nov 2021 21:20:49 +0900</pubDate>
    </item>
    <item>
      <title>⌜미국 주식으로 시작하는 슬기로운 퀀트투자⌟ 서평</title>
      <link>https://sundries-in-myidea.tistory.com/137</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;312&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZI7xf/btriFhRwDkl/zS7SJauJS30KTsZ8c8Kc6K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZI7xf/btriFhRwDkl/zS7SJauJS30KTsZ8c8Kc6K/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZI7xf/btriFhRwDkl/zS7SJauJS30KTsZ8c8Kc6K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZI7xf%2FbtriFhRwDkl%2FzS7SJauJS30KTsZ8c8Kc6K%2Fimg.jpg&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;312&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;942&quot; data-origin-height=&quot;1200&quot; width=&quot;532&quot; height=&quot;678&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k2G0A/btriGjO6BTq/5vMsCgS9Esjj2Gor7VkeEk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k2G0A/btriGjO6BTq/5vMsCgS9Esjj2Gor7VkeEk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k2G0A/btriGjO6BTq/5vMsCgS9Esjj2Gor7VkeEk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk2G0A%2FbtriGjO6BTq%2F5vMsCgS9Esjj2Gor7VkeEk%2Fimg.jpg&quot; data-origin-width=&quot;942&quot; data-origin-height=&quot;1200&quot; width=&quot;532&quot; height=&quot;678&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #222222;&quot;&gt;한빛미디어 &amp;lt;나는 리뷰어다&amp;gt; 활동을 위해서 책을 제공받아 작성된 서평입니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;미국 주식과 Python&lt;/h2&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;쏟아지는 오픈소스와 여러가지 전략들을 어디까지 사용해야할지 실제로 사용자들 입장에서는 정확한 답을 내리기도 어려우며, 설명도 자세하지 않아 선택에 어려움을 가질 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이책은&amp;nbsp; Data Analysis의 영역에서의 가장 유용성이 높은 언어인 Python을 바탕으로 하는 투자 프로그래밍과 그 투자 전략을 어떤식으로 세워야할지에 대한 전반적인 투자 지식에 관련한 책이다.&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1213&quot; data-origin-height=&quot;1419&quot; data-filename=&quot;blob&quot; width=&quot;455&quot; height=&quot;532&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dUdaI8/btriFs57VUi/XLAGhmpHqIoOjIn4fiDd0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dUdaI8/btriFs57VUi/XLAGhmpHqIoOjIn4fiDd0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dUdaI8/btriFs57VUi/XLAGhmpHqIoOjIn4fiDd0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdUdaI8%2FbtriFs57VUi%2FXLAGhmpHqIoOjIn4fiDd0k%2Fimg.png&quot; data-origin-width=&quot;1213&quot; data-origin-height=&quot;1419&quot; data-filename=&quot;blob&quot; width=&quot;455&quot; height=&quot;532&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 오히려 프로그래밍만 포함된 책이라고 보는 것보다는 주식 관련된 정보와 함께 퀀트투자와 관련한 프로그래밍과 관련한 지식도 포함되어있는 책이라고 보면 더 좋을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히, 주식과 관련한 지식들을&amp;nbsp; Python코드를 바탕으로 설명을 진행하게 되는데, 물론 지식들에 대한 설명이 프로그래밍과 별개의 설명이 들어가 있긴하지만, 프로그래밍적으로 설명해주는 영역이 더 많기 때문에, 프로그래밍을 알고 있는 분들이라면 조금 더 이해에 도움이 될 것이다.&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&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/di4dV2/btriGk1xpYy/ZOcpL5B1E0dZ0LGcN67wzK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/di4dV2/btriGk1xpYy/ZOcpL5B1E0dZ0LGcN67wzK/img.jpg&quot; width=&quot;484&quot; height=&quot;645&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1920&quot; data-filename=&quot;이미지 2.jpeg&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/di4dV2/btriGk1xpYy/ZOcpL5B1E0dZ0LGcN67wzK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdi4dV2%2FbtriGk1xpYy%2FZOcpL5B1E0dZ0LGcN67wzK%2Fimg.jpg&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;1440&quot; height=&quot;1920&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dM94i1/btriDkgfO2x/wm0kXqmr2pNgpo5gxc2ZM0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dM94i1/btriDkgfO2x/wm0kXqmr2pNgpo5gxc2ZM0/img.jpg&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1920&quot; data-filename=&quot;이미지 3.jpeg&quot; style=&quot;width: 49.4186%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dM94i1/btriDkgfO2x/wm0kXqmr2pNgpo5gxc2ZM0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdM94i1%2FbtriDkgfO2x%2Fwm0kXqmr2pNgpo5gxc2ZM0%2Fimg.jpg&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;1440&quot; height=&quot;1920&quot;/&gt;&lt;/span&gt;&lt;/div&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;h3 data-ke-size=&quot;size23&quot;&gt;결론적으로...&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;</description>
      <category>서평</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/137</guid>
      <comments>https://sundries-in-myidea.tistory.com/137#entry137comment</comments>
      <pubDate>Mon, 25 Oct 2021 01:21:51 +0900</pubDate>
    </item>
    <item>
      <title>포스트맨으로 테스트하던 나에게</title>
      <link>https://sundries-in-myidea.tistory.com/136</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;406&quot; data-filename=&quot;postman.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwBLLa/btrgWazfi1p/i4Nz9q3PyHbFhHMBjgt9kK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwBLLa/btrgWazfi1p/i4Nz9q3PyHbFhHMBjgt9kK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwBLLa/btrgWazfi1p/i4Nz9q3PyHbFhHMBjgt9kK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwBLLa%2FbtrgWazfi1p%2Fi4Nz9q3PyHbFhHMBjgt9kK%2Fimg.png&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;406&quot; data-filename=&quot;postman.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 인턴을 진행하면서 테스트 코드를 작성해볼일이 있었고, 그것에 대해서 느낀점을 작성하려고 한다. 테스트 코드를 왜 써야하며, 그리고 테스트 코드에 대한 생각을 나눠보겠다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;테스트 코드를 왜 작성해야하나...?&lt;/b&gt;&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;당시에는 이런 방법이 안정적으로 테스트를 할 수 있는데 큰 도움을 준다고 생각했기 때문에, 테스트 코드의 당위성에 대해서 크게 큰 신경을 쓰지 못했다. PostMan으로 테스트 하면 되는데 왜...? 굳이..? 그리고 그렇다고 해서 확실히 된다고 보장을 할 수는 있나..?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;364&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhDFhc/btrgMzAikIG/XTRbAjtWo4zl0CFl2asjik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhDFhc/btrgMzAikIG/XTRbAjtWo4zl0CFl2asjik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhDFhc/btrgMzAikIG/XTRbAjtWo4zl0CFl2asjik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhDFhc%2FbtrgMzAikIG%2FXTRbAjtWo4zl0CFl2asjik%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;364&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;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-origin-width=&quot;284&quot; data-origin-height=&quot;169&quot; data-filename=&quot;스크린샷 2021-10-05 오후 10.53.41.png&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3FnZy/btrgXgMv8iU/ACvgCg3Pz41XKhkUwGNgD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3FnZy/btrgXgMv8iU/ACvgCg3Pz41XKhkUwGNgD0/img.png&quot; data-alt=&quot;대충 이런식으로 각 프로젝트별로 따로 관리했었다.&amp;amp;amp;nbsp;&amp;amp;amp;nbsp;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3FnZy/btrgXgMv8iU/ACvgCg3Pz41XKhkUwGNgD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3FnZy%2FbtrgXgMv8iU%2FACvgCg3Pz41XKhkUwGNgD0%2Fimg.png&quot; data-origin-width=&quot;284&quot; data-origin-height=&quot;169&quot; data-filename=&quot;스크린샷 2021-10-05 오후 10.53.41.png&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;대충 이런식으로 각 프로젝트별로 따로 관리했었다.&amp;nbsp;&amp;nbsp;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;247&quot; data-origin-height=&quot;211&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qCTSx/btrgVM6oQXy/kU6FbvcnoFsNJo1wu76VV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qCTSx/btrgVM6oQXy/kU6FbvcnoFsNJo1wu76VV0/img.png&quot; data-alt=&quot;&amp;amp;amp;nbsp;각 상황별로 플로우를 다 체크해봐야했는데... 점점 늘어날 수록 테스트가 빡세진다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qCTSx/btrgVM6oQXy/kU6FbvcnoFsNJo1wu76VV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqCTSx%2FbtrgVM6oQXy%2FkU6FbvcnoFsNJo1wu76VV0%2Fimg.png&quot; data-origin-width=&quot;247&quot; data-origin-height=&quot;211&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;&amp;nbsp;각 상황별로 플로우를 다 체크해봐야했는데... 점점 늘어날 수록 테스트가 빡세진다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 그만큼 한번의 서비스 플로우(회원가입후 로그인, 서비스 사용처럼 여러가지 행동 양식들)가 증가하면 증가할 수록 여러가지의 경우에 대해서 모두 따져줘야하는 점이 늘어난다는 것이 벅차긴했으나, 소규모 서비스의 경우는 테스트를 그만큼 하지 않아도 대부분은 동작했기때문에(버그도 산재해있었겠지만...) 대부분 일단 기능을 빠르게 구현하는 것이 우선이었다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;기능을 구현이야 빠르지만...&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아까 말했듯이 기능 구현자체는 어떻게든 구현할 수 있다. 결국 내가 여기저기서 삽질과 들이박음을 통해서 겨우겨우 기능 구현을 하게되면, 그 기능에 대해서또 수기로 postman을 통해서 다시 테스트를 해줘야한다. 그러다 보면, 점점 테스트가 버거워지는 경우가 생긴다.&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;i&gt;악으로 깡으로 버텨라..&lt;/i&gt;)으로 어떻게 처리하면 된다지만, 이렇게 테스트했을때 가장 큰 단점은 &lt;b&gt;코드를 고쳤을때&lt;/b&gt;이다. 사람이 기능 수정, 리펙토링, 로직변경과 같은 세부적인 내용을 고쳤거나 짜잘한 개선사항을 한 뒤에, 바로 테스트하는 경우는 있긴하겠지만 빼먹기 쉽다. (맘처럼 잘 안된다. 커밋 하나당 모든 테스트를 다해야한다고 생각해보라.)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;460&quot; data-origin-height=&quot;258&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dJ13NB/btrgU1v2Hhm/9xTk6yz0UIAOkxnE9YfMI1/img.gif&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dJ13NB/btrgU1v2Hhm/9xTk6yz0UIAOkxnE9YfMI1/img.gif&quot; data-alt=&quot;고치면 고칠수록 문제는 심각해진다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dJ13NB/btrgU1v2Hhm/9xTk6yz0UIAOkxnE9YfMI1/img.gif&quot; srcset=&quot;https://blog.kakaocdn.net/dn/dJ13NB/btrgU1v2Hhm/9xTk6yz0UIAOkxnE9YfMI1/img.gif&quot; data-origin-width=&quot;460&quot; data-origin-height=&quot;258&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;고치면 고칠수록 문제는 심각해진다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 커밋 한 두개쯤 넘기면서 진행한 테스트들은 항상 그 한 두번 빼먹은 곳에서 항상 에러가 난다. FE와의 개발 도중 뜬금없이 발생하는 오류라던가 잘되던게 어디서부턴가 잘 안된다던가.. 오류가 없던 장소에서 오류가 난다던지. 별의 별 상황이 많이 생긴다.&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;b&gt;반복행동을 생성하는 등의 리스크를 가지는 코드 수정&lt;/b&gt;을 굳이 해야할까라는 마음이 생기고, 하지말자라는 결론이 나기 시작하면 그때부터는 되돌릴수도 없는 상태가 된다. 이제 그때부터는 산발적인 수정을 계속 반복해야하는 상태가 되는것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국에는 중요한 배포,발표와 같은 일이 있을때 문제가 있어보이지만, 코드를 수정을 했다가는 아예 실행이 안되거나, 멈추는 상황이 생길수도 있으니 그냥 아예 코드를 건드릴수 없는 상황까지 만들어진다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;테스트 코드&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 코드는 위에서 말하는 &lt;b&gt;코드를 고쳤을때&lt;/b&gt;의 리스크를 훨씬 많이 줄여준다. 테스트 코드를 막상 작성하기 전에는 엄청 귀찮은 작업이긴하다. 각 로직별로 이것들이 문제가 있는지 그리고 이게 A &amp;rarr; B가 맞게 나오는지? 그리고 A&amp;rarr;B가 안나오면, 왜 안나오는지를 어쨌든 테스트코드를 짜면서 문제점을 한번은 체크하고 넘어가게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 결정적으로 이런 &lt;b&gt;테스트 코드를 작성하면 수정&lt;/b&gt;이 편해진다. 만약 수정을 했는데 문제가 발생했다? 그러면 테스트를 확인해보면 된다. 어떤 부분에서 문제가 생겼을지 확인도 훨씬 빨라지며, 그리고 애초에 테스트 코드가 있어서, Postman과 같은 툴 자체를 쓸 이유도 없다. 내부 테스트를 돌려서 문제가 발생하는 지 먼저 체크를 해서 수정에 문제가 있는지만 확인하면 된다.&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;자신감과 더불어 너무 길고 수없이 보기 어렵게 불편하게 작성되있는 코드들을 고칠수 있을거라고 생각할 수 있으며, 결국 가독성 좋은 코드, 간결한 논리로 작성된 코드를 구현할 수 있게 만들 수 있다. 코드를 잘 작성하는 것이 큰 도움을 받을 수 있기 때문에 크게 도움을 받을 수 있다고 적어도 짤막하게 해본 내가 도움을 얻은 내 의견이긴하다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;P. S. 테스트 코드를 환상적으로 더 쓰는 법&lt;/b&gt;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;특히, 그것이 어렵다고 느껴지는 부분은 A &amp;rarr; B 가 올바르게 나오는게 할수는 있겠으나, 이렇게 하는게 맞는지...? 라는 부분에서는 정답을 내릴만한 근거가 상당히 부족한 면이 있다. 왜냐하면, A&amp;rarr;B를 만드는 과정은 사람마다 정말 제각각이기때문이다. 여러 사람들의 테스트 코드들이 각자가 설정한 기준이 없기도하며, 이 작성하고 있는 테스트 코드가 맞는지에 대한 기준이 내려져있지 않은 것이 이러한 골치거리를 마음 한켠에 생성하는 원인 중 하나라고 본다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;97&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wHdHF/btrgXhdz4lp/RlwtkxzPj9vklJptPGAhTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wHdHF/btrgXhdz4lp/RlwtkxzPj9vklJptPGAhTK/img.png&quot; data-alt=&quot;코드 커버리지는 기준점이 있어서 테스트 코드가 쉬워진다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wHdHF/btrgXhdz4lp/RlwtkxzPj9vklJptPGAhTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwHdHF%2FbtrgXhdz4lp%2FRlwtkxzPj9vklJptPGAhTK%2Fimg.png&quot; data-origin-width=&quot;648&quot; data-origin-height=&quot;97&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;코드 커버리지는 기준점이 있어서 테스트 코드가 쉬워진다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 우리는 이 기준에 대해서 무엇인가 도움을 받을 수 있는 것이 있다, 그것은 바로 코드 커버리지 도구인데, 내가 작성한 코드들에 대해서 어디까지 테스트 코드가 검사하는지를 알려주는 도구로써,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;if문과 같은 분기에 대해서도 내 테스트 코드가 검사하는 지를 파악할 수 있는 지표중 하나가 된다. 끝도 없이 작성했었던, 수없이 많은 줄의 내 코드가 얼만큼 테스트 코드로 검사받는지를 알 수 있어서, 내가 테스트 코드를 작성하는 부분이 어떤 부분인지를 알려준다.&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;code&gt;Code Coverage&lt;/code&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;/p&gt;</description>
      <category>Backend</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/136</guid>
      <comments>https://sundries-in-myidea.tistory.com/136#entry136comment</comments>
      <pubDate>Tue, 5 Oct 2021 22:56:31 +0900</pubDate>
    </item>
    <item>
      <title>⌜처음 시작하는 마이크로서비스⌟ 서평</title>
      <link>https://sundries-in-myidea.tistory.com/135</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;312&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Ms8Hy/btrfA0Y7TP9/qKJbNxuOKKJlRSlc4hfJD1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Ms8Hy/btrfA0Y7TP9/qKJbNxuOKKJlRSlc4hfJD1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ms8Hy/btrfA0Y7TP9/qKJbNxuOKKJlRSlc4hfJD1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMs8Hy%2FbtrfA0Y7TP9%2FqKJbNxuOKKJlRSlc4hfJD1%2Fimg.jpg&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;312&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;934&quot; data-origin-height=&quot;1200&quot; width=&quot;484&quot; height=&quot;621&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bl87NA/btrfs5fQSQQ/webPT9sjThcHPozCrgsL80/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bl87NA/btrfs5fQSQQ/webPT9sjThcHPozCrgsL80/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bl87NA/btrfs5fQSQQ/webPT9sjThcHPozCrgsL80/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbl87NA%2Fbtrfs5fQSQQ%2FwebPT9sjThcHPozCrgsL80%2Fimg.jpg&quot; data-origin-width=&quot;934&quot; data-origin-height=&quot;1200&quot; width=&quot;484&quot; height=&quot;621&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;한빛미디어 &amp;lt;나는 리뷰어다&amp;gt; 활동을 위해서 책을 제공받아 작성된 서평입니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;최신 트렌드 MSA&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 여러 플렛폼 기업들이 예전 방식 모놀리틱 서비스에서 마이크로 서비스 아키텍쳐 즉, 여러가지 도메인을 분리해서 관리하는 방식의 MSA를 도입하는 추세이다. 그렇다면 모놀리틱에서 왜 마이크로 서비스로 전환해야하는지..? 그리고 뭐 트랜드인건 알겠으나 도대체 왜 이런 아키텍쳐까지 도달했는지에 대해서 궁금하지 않는가?&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;이 책에서는 5개 챕터를 통해서 MSA로의 변환 과정 그리고 고려해야하는 점등을 자세히 작성한다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1920&quot; data-filename=&quot;이미지 3.jpeg&quot; width=&quot;515&quot; height=&quot;687&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VpKG9/btrfBsIxa0G/enRqhKQs4mD0OCnRGS7j8k/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VpKG9/btrfBsIxa0G/enRqhKQs4mD0OCnRGS7j8k/img.jpg&quot; data-alt=&quot;5장까지는 마이크로서비스가 무엇이고, 고려해야할점은 무엇인지에 대해서 설명한다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VpKG9/btrfBsIxa0G/enRqhKQs4mD0OCnRGS7j8k/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVpKG9%2FbtrfBsIxa0G%2FenRqhKQs4mD0OCnRGS7j8k%2Fimg.jpg&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1920&quot; data-filename=&quot;이미지 3.jpeg&quot; width=&quot;515&quot; height=&quot;687&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;5장까지는 마이크로서비스가 무엇이고, 고려해야할점은 무엇인지에 대해서 설명한다&lt;/figcaption&gt;
&lt;/figure&gt;
&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dW1N34/btrfG2beOug/hVhh9QBvxVqdnRAOpQpBx0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dW1N34/btrfG2beOug/hVhh9QBvxVqdnRAOpQpBx0/img.jpg&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; width=&quot;482&quot; height=&quot;643&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1920&quot; data-filename=&quot;이미지.jpeg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dW1N34/btrfG2beOug/hVhh9QBvxVqdnRAOpQpBx0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdW1N34%2FbtrfG2beOug%2FhVhh9QBvxVqdnRAOpQpBx0%2Fimg.jpg&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;1440&quot; height=&quot;1920&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqHa9o/btrftnuHZAy/xQWp6Mwz2UGJkLfs7oYjkK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqHa9o/btrftnuHZAy/xQWp6Mwz2UGJkLfs7oYjkK/img.jpg&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1920&quot; data-filename=&quot;이미지 2.jpeg&quot; style=&quot;width: 49.4186%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqHa9o/btrftnuHZAy/xQWp6Mwz2UGJkLfs7oYjkK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqHa9o%2FbtrftnuHZAy%2FxQWp6Mwz2UGJkLfs7oYjkK%2Fimg.jpg&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;1440&quot; height=&quot;1920&quot;/&gt;&lt;/span&gt;&lt;/div&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;나는 입문자이기도하며, 테라폼에 대해서는 처음 접하는 입장에서 실제로 샘플을 모두 따라해보는 것은 어려웠으나, 그외에의 입문시에 생각해봐야할 점들에 대해서 이야기 들었던 부분은 좋게 느껴졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 가장 마음에 드는 부분은 여러 부분중에서도 개인적으로 마음에 들었던 부분은 아래 사진과 같은 데이터 처리에 관련한 파트 5장인데, 이부분이 가장 마음에 들었던 이유는 마이크로 서비스에서 막연하게 들었던 생각중에 가장 막연했던 부분이 데이터 정합성에 관련한 부분들이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;모놀리틱&lt;/b&gt;이야 데이터를 정합성을 관리하겠으나, 여러가지 도메인을 분리해서 구현했을때 &lt;b&gt;트랜젝션, 정합성&lt;/b&gt;과 같은 오류들은 어떤식으로 처리되는 가에 대해서 궁금했었다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1920&quot; data-filename=&quot;이미지 4.jpeg&quot; width=&quot;533&quot; height=&quot;711&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blCBFb/btrfIYTVLYj/0KKICo95rpOnYnjKbuVQf1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blCBFb/btrfIYTVLYj/0KKICo95rpOnYnjKbuVQf1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blCBFb/btrfIYTVLYj/0KKICo95rpOnYnjKbuVQf1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblCBFb%2FbtrfIYTVLYj%2F0KKICo95rpOnYnjKbuVQf1%2Fimg.jpg&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1920&quot; data-filename=&quot;이미지 4.jpeg&quot; width=&quot;533&quot; height=&quot;711&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;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;물론 테라폼을 배웠거나 이미 IaaS나 인프라를 관리하는 사람입장에서 TerraForm과 같은 툴을 이해하거나 쿠버네티스와 같은 여러가지 툴을 다루는 것이 어렵지 않게 느껴질수 있지만, 아직 백엔드 개발을 하고 있는 주니어(응애) 개발자로써는 테라폼까지 실습을 하고, 이해하는 것은 조금 어려운 일이 아니었나 싶다. ㅠㅠ&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;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선수 지식으로 쿠버네티스, 테라폼, 도커와 같은 지식들을 잘 알고 있는 마이크로 서비스에 대해서 관심 있는 사람들 그리고 마이크로 서비스에 대해서 도입을 고민하고 있는 사람들에게 유용한 책이다. 일단 기본적으로 마이크로 서비스로 구현하기 위해서 팀 자체를 꾸리는 법, 어떤식으로 서비스를 설계할 것인지에 대해서 알아보는 책이라 여러 사람들에게 큰 도움을 줄 수 있는 책으로 느껴진다.&amp;nbsp;&lt;/p&gt;</description>
      <category>서평</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/135</guid>
      <comments>https://sundries-in-myidea.tistory.com/135#entry135comment</comments>
      <pubDate>Wed, 22 Sep 2021 23:19:07 +0900</pubDate>
    </item>
    <item>
      <title>Spring Boot와 React를 분리해서 연동해보자 2) Spring Boot 구성</title>
      <link>https://sundries-in-myidea.tistory.com/134</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;576&quot; data-filename=&quot;springReact2.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GaySU/btrfsechYzg/758AHD7g7B8naayO1jbKxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GaySU/btrfsechYzg/758AHD7g7B8naayO1jbKxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GaySU/btrfsechYzg/758AHD7g7B8naayO1jbKxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGaySU%2FbtrfsechYzg%2F758AHD7g7B8naayO1jbKxk%2Fimg.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;576&quot; data-filename=&quot;springReact2.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;왜 쓰게 되었는가?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제 블로그 중 가장 많이 사랑을 받은 게시글인&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://sundries-in-myidea.tistory.com/71&quot;&gt;Spring Boot와 React를 연동하여 개발환경을 만들어보자&lt;/a&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;를 통해서 Spring Boot를 React와 동시에 구성하는 방식에 대해서 알아보았습니다.&lt;/p&gt;
&lt;figure id=&quot;og_1632054141485&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Spring Boot와 React를 연동하여 개발환경을 만들어보자&quot; data-og-description=&quot;이글을 읽어보기전에!! 한번 고려해볼 점을 제가 적어뒀습니다 한번 참고 해보고 프로젝트를 진행해주세요! [Java/Spring] - Spring Boot와 React를 통한 개발환경을 구성할때 고민해볼점 Spring Boot와 React&quot; data-og-host=&quot;sundries-in-myidea.tistory.com&quot; data-og-source-url=&quot;https://sundries-in-myidea.tistory.com/71&quot; data-og-url=&quot;https://sundries-in-myidea.tistory.com/71&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/fpDLl/hyLFu7qhZH/HPstRkRl89m7tESk1vHTZk/img.png?width=310&amp;amp;height=163&amp;amp;face=0_0_310_163,https://scrap.kakaocdn.net/dn/bUieaX/hyLFttUwKl/KHTxpZXx8jawpdu3OemQ2k/img.png?width=310&amp;amp;height=163&amp;amp;face=0_0_310_163,https://scrap.kakaocdn.net/dn/dJ38pz/hyLDTVi7eU/E5HOpTlYqqItHAlcAbc6U1/img.png?width=1552&amp;amp;height=989&amp;amp;face=0_0_1552_989&quot;&gt;&lt;a href=&quot;https://sundries-in-myidea.tistory.com/71&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sundries-in-myidea.tistory.com/71&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/fpDLl/hyLFu7qhZH/HPstRkRl89m7tESk1vHTZk/img.png?width=310&amp;amp;height=163&amp;amp;face=0_0_310_163,https://scrap.kakaocdn.net/dn/bUieaX/hyLFttUwKl/KHTxpZXx8jawpdu3OemQ2k/img.png?width=310&amp;amp;height=163&amp;amp;face=0_0_310_163,https://scrap.kakaocdn.net/dn/dJ38pz/hyLDTVi7eU/E5HOpTlYqqItHAlcAbc6U1/img.png?width=1552&amp;amp;height=989&amp;amp;face=0_0_1552_989');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring Boot와 React를 연동하여 개발환경을 만들어보자&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이글을 읽어보기전에!! 한번 고려해볼 점을 제가 적어뒀습니다 한번 참고 해보고 프로젝트를 진행해주세요! [Java/Spring] - Spring Boot와 React를 통한 개발환경을 구성할때 고민해볼점 Spring Boot와 React&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sundries-in-myidea.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Spring Boot를 통해 개발 환경 구성하기&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. Spring Boot 기본 설정하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 부트를 설정하는 방법은 이전 포스트 처럼 &lt;a href=&quot;https://start.spring.io/&quot;&gt;https://start.spring.io/&lt;/a&gt;&lt;span style=&quot;color: #222222;&quot;&gt;에서&lt;/span&gt;&lt;span style=&quot;color: #222222;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #222222;&quot;&gt;설정했다. 별다른 의존성은 추가하지 않고, web mvc만 추가해서 가장 기초적인 기능만 추가하도록한다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;2032&quot; data-origin-height=&quot;1169&quot; data-filename=&quot;스크린샷 2021-09-19 오후 5.20.09.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X5XVe/btrfzlnSAvc/xc90Z6lnm8Lxp4jGgKz4FK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X5XVe/btrfzlnSAvc/xc90Z6lnm8Lxp4jGgKz4FK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X5XVe/btrfzlnSAvc/xc90Z6lnm8Lxp4jGgKz4FK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX5XVe%2FbtrfzlnSAvc%2Fxc90Z6lnm8Lxp4jGgKz4FK%2Fimg.png&quot; data-origin-width=&quot;2032&quot; data-origin-height=&quot;1169&quot; data-filename=&quot;스크린샷 2021-09-19 오후 5.20.09.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #222222;&quot;&gt;&lt;/span&gt;2.&amp;nbsp; API 구성하기&amp;nbsp;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Boot를 API를 가장 간단한 모습으로 구현한다. 간단하게 서버 시간을 알려주는 Controller를 구현했다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1632054416207&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;package com.ventulus95.reactdemo;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.Date;

@RestController
public class HelloController {

    @GetMapping(&quot;/test&quot;)
    public String time(){
        return &quot;안녕하세요. 현재 서버의 시간은 &quot; + new Date() + &quot; 입니다!&quot;;
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 다음의 http://localhost:8080/test로 url을 브라우저로 전달하게되면 다음과 같은 화면이 뜹니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;180&quot; data-filename=&quot;blob&quot; width=&quot;789&quot; height=&quot;454&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/to1tu/btrfoSnZoqA/WCNdId9qMYmA7uwlwOVRa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/to1tu/btrfoSnZoqA/WCNdId9qMYmA7uwlwOVRa0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/to1tu/btrfoSnZoqA/WCNdId9qMYmA7uwlwOVRa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fto1tu%2FbtrfoSnZoqA%2FWCNdId9qMYmA7uwlwOVRa0%2Fimg.png&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;180&quot; data-filename=&quot;blob&quot; width=&quot;789&quot; height=&quot;454&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. CORS 문제 해결하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;react를 구성해두었던 폴더에 있는 &lt;span style=&quot;color: #222222;&quot;&gt;Package.json에 Proxy를 설정해줘야합니다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1632055644476&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
  &quot;name&quot;: &quot;fe&quot;,
  &quot;version&quot;: &quot;0.1.0&quot;,
  &quot;private&quot;: true,
  &quot;dependencies&quot;: {
    &quot;@testing-library/jest-dom&quot;: &quot;^5.11.4&quot;,
    &quot;@testing-library/react&quot;: &quot;^11.1.0&quot;,
    &quot;@testing-library/user-event&quot;: &quot;^12.1.10&quot;,
    &quot;react&quot;: &quot;^17.0.2&quot;,
    &quot;react-dom&quot;: &quot;^17.0.2&quot;,
    &quot;react-scripts&quot;: &quot;4.0.3&quot;,
    &quot;web-vitals&quot;: &quot;^1.0.1&quot;
  },
  &quot;scripts&quot;: {
    &quot;start&quot;: &quot;react-scripts start&quot;,
    &quot;build&quot;: &quot;react-scripts build&quot;,
    &quot;test&quot;: &quot;react-scripts test&quot;,
    &quot;eject&quot;: &quot;react-scripts eject&quot;
  },
  &quot;proxy&quot;: &quot;http://localhost:8080/&quot;,
  &quot;eslintConfig&quot;: {
    &quot;extends&quot;: [
      &quot;react-app&quot;,
      &quot;react-app/jest&quot;
    ]
  },
  &quot;browserslist&quot;: {
    &quot;production&quot;: [
      &quot;&amp;gt;0.2%&quot;,
      &quot;not dead&quot;,
      &quot;not op_mini all&quot;
    ],
    &quot;development&quot;: [
      &quot;last 1 chrome version&quot;,
      &quot;last 1 firefox version&quot;,
      &quot;last 1 safari version&quot;
    ]
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 상태에서 실행을 하게되면 http:localhost:3000/test를 하게되면, 다음과 같이 backend의 값이 그대로 전달되는 것을 확인해볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1054&quot; data-origin-height=&quot;572&quot; data-filename=&quot;스크린샷 2021-09-19 오후 5.29.23.png&quot; width=&quot;777&quot; height=&quot;422&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QPReV/btrfo4uMbA8/GKaYkpwn6tOgoVowE902X1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QPReV/btrfo4uMbA8/GKaYkpwn6tOgoVowE902X1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QPReV/btrfo4uMbA8/GKaYkpwn6tOgoVowE902X1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQPReV%2Fbtrfo4uMbA8%2FGKaYkpwn6tOgoVowE902X1%2Fimg.png&quot; data-origin-width=&quot;1054&quot; data-origin-height=&quot;572&quot; data-filename=&quot;스크린샷 2021-09-19 오후 5.29.23.png&quot; width=&quot;777&quot; height=&quot;422&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. React에 Spring Boot의 API을 연결하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;React에 Spring Boot에서 생성한 API를 연결해서, 화면을 출력하는 것을 확인해봅니다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1632056250788&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import logo from './logo.svg';
import './App.css';
import { useEffect, useState } from 'react';


function App() {
  const [message, setMessage] = useState(&quot;&quot;)

  useEffect(()=&amp;gt;{
    fetch(&quot;/test&quot;)
      .then(res =&amp;gt; res.text())
      .then(m=&amp;gt;setMessage(m))
  }, [])

  return (
    &amp;lt;div className=&quot;App&quot;&amp;gt;
      &amp;lt;header className=&quot;App-header&quot;&amp;gt;
        &amp;lt;img src={logo} className=&quot;App-logo&quot; alt=&quot;logo&quot; /&amp;gt;
        &amp;lt;p&amp;gt;
          {message}
        &amp;lt;/p&amp;gt;
        &amp;lt;a
          className=&quot;App-link&quot;
          href=&quot;https://reactjs.org&quot;
          target=&quot;_blank&quot;
          rel=&quot;noopener noreferrer&quot;
        &amp;gt;
          Learn React
        &amp;lt;/a&amp;gt;
      &amp;lt;/header&amp;gt;
    &amp;lt;/div&amp;gt;
  );
}

export default App;&lt;/code&gt;&lt;/pre&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-origin-width=&quot;2032&quot; data-origin-height=&quot;1169&quot; data-filename=&quot;스크린샷 2021-09-19 오후 5.37.44.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VtJcM/btrfpsWrbZ4/ASzVbkVkMPi97tASZrI7xK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VtJcM/btrfpsWrbZ4/ASzVbkVkMPi97tASZrI7xK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VtJcM/btrfpsWrbZ4/ASzVbkVkMPi97tASZrI7xK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVtJcM%2FbtrfpsWrbZ4%2FASzVbkVkMPi97tASZrI7xK%2Fimg.png&quot; data-origin-width=&quot;2032&quot; data-origin-height=&quot;1169&quot; data-filename=&quot;스크린샷 2021-09-19 오후 5.37.44.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;5. Nginx에 연결시키기.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4번까지는 사실상 이전 포스팅과 동일한 방식으로 진행이 되었는데, 우리는 이전 포스팅에서 Frontend를 Nginx로 연결을 해놓은 상태이고 NPM을 통해서 운영하지 않기 때문에, nginx를 통해서 Spring Boot를 통해서 연결되는 것을 확인해보아야한다.&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;Frontend를 설정하기 위해서 미리 설정해두었던 파일을 수정해야한다.&lt;/p&gt;
&lt;pre id=&quot;code_1632056856301&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;vi /etc/nginx/sites-available/fe.conf&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;/test라는 api를 nginx에서도 사용해야하기때문에 다음처럼 Proxy를 생성해서 붙여야한다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1632056806709&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;location / {
  root /home/build; //build파일 폴더 위치 삽입할 것
  index index.html index.htm;
      try_files $uri $uri/ /index.html;
  }
  
  location /test  {
  	proxy_pass http://localhost:8080;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런식으로 연결을 하게되면, http://localhost를 통해서 이전과 같은 화면이 등장하게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;2032&quot; data-origin-height=&quot;1169&quot; data-filename=&quot;스크린샷 2021-09-19 오후 10.11.35.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wmxZi/btrftmVpwcc/AbPiv66DHKw2K0KOA8H0Jk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wmxZi/btrftmVpwcc/AbPiv66DHKw2K0KOA8H0Jk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wmxZi/btrftmVpwcc/AbPiv66DHKw2K0KOA8H0Jk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwmxZi%2FbtrftmVpwcc%2FAbPiv66DHKw2K0KOA8H0Jk%2Fimg.png&quot; data-origin-width=&quot;2032&quot; data-origin-height=&quot;1169&quot; data-filename=&quot;스크린샷 2021-09-19 오후 10.11.35.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;왜 이렇게 써야하는가?&lt;/b&gt;&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;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 방식보다 현재 이 방식으로 코드를 구성하는 것이 코드 관리를 훨씬 효율적으로 할 수 있다고 생각한다. 그리고,&amp;nbsp;이런 식으로 구성하게되면, 프론트는 프론트대로 백엔드는 백엔드대로 따로 구성할 수 있게 된다.&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;특히, 여러 프로젝트를 많이 진행하면서, BE와 FE를 붙힐 이유도 없으며, 따로 구현하는 것이 코드 관리에 있어서 편리함을 많이 느꼈기도 했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글을 처음 썼을 당시에는 연동기가 많이 없기도 했거니와... 내가 프로젝트 경험 자체가 너무 부족해서 Proxy는 뭐며.. React에서 왜 저런 코드를 사용해야하며와 같은 여러가지 미숙함으로 인해서 예상밖으로 많이 비효율적인 프로젝트 구성기가 되지 않았나 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이런 글을 다시 쓰게되었고, 물론 현재는 React+SpringBoot를 연동하는 게시물들이 많이 늘어났으며 방식도 여러가지로 늘어났지만, 유입되었던 글을 읽으면서 좀 더 개선 시킨 버전을 읽어볼 수 있으면 좋겠다.&lt;/p&gt;</description>
      <category>Spring</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/134</guid>
      <comments>https://sundries-in-myidea.tistory.com/134#entry134comment</comments>
      <pubDate>Sun, 19 Sep 2021 22:28:07 +0900</pubDate>
    </item>
    <item>
      <title>JPA, JDBC의 DB MultiDataSource 적용하기</title>
      <link>https://sundries-in-myidea.tistory.com/133</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;576&quot; data-filename=&quot;JPAJDBC.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bNbe0N/btrdHek8zIh/a3s4jGd6w7opCkKzlgBfz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bNbe0N/btrdHek8zIh/a3s4jGd6w7opCkKzlgBfz1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bNbe0N/btrdHek8zIh/a3s4jGd6w7opCkKzlgBfz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbNbe0N%2FbtrdHek8zIh%2Fa3s4jGd6w7opCkKzlgBfz1%2Fimg.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;576&quot; data-filename=&quot;JPAJDBC.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;JPA와 JDBC를 동시에 쓰고 싶다!&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA 그리고 JDBC 를 동시에 사용하는 방법은 그렇게 어렵지 않다. JPA 의존성 밑에 JDBC가 포함되어있어서 JDBC template를 이미 사용할 수 있는 구조로 되어있어서 의존성을 또 추가하지 않아도 되는 구조로 구성되어있다. 그래서 JPA 와 JDBC를 통해서 코드를 구성하는 편이 훨씬 편한 방식으로 코드를 짜는데 더 편리하다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;170&quot; data-filename=&quot;스크린샷 2021-09-01 오전 12.44.04.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKMdlw/btrdISWdx6A/jKNVaiXpXl5orIr5ywFXK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKMdlw/btrdISWdx6A/jKNVaiXpXl5orIr5ywFXK1/img.png&quot; data-alt=&quot;Data JPA에는 JDBC 의존성이 이미 포함되어있다. mybatis처럼 의존성을 또 잡아줄 필요가 없다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKMdlw/btrdISWdx6A/jKNVaiXpXl5orIr5ywFXK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKMdlw%2FbtrdISWdx6A%2FjKNVaiXpXl5orIr5ywFXK1%2Fimg.png&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;170&quot; data-filename=&quot;스크린샷 2021-09-01 오전 12.44.04.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Data JPA에는 JDBC 의존성이 이미 포함되어있다. mybatis처럼 의존성을 또 잡아줄 필요가 없다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왜냐하면 JPA를 사용하면서 Mybatis와 같은 다른 형식의 코드를 사용하려면 그것에 맞는 형식을 또 따로 잡아줘야하는데 JDBC는 결국 의존성이 포함되어있는 형식으로 되어있어서, 큰틀은 변하지 않는다는 점을 잘 활용하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 이 두개를 동시에 다른 DB를 사용하는 경우 간단한 세팅으로는 문제 해결이 재대로 진행되지 않는다는 점이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;핵심 문제는?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB가 다른 경우 JPA와 JDBC가 모두 다른 DB를 사용해야하는 경우나 JPA를 단독으로 사용하더라도 2개의 DB를 사용하는 케이스에 DB를 적용하는 경우에 대해서 적용하는 경우를 적어보도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 블로그의 경우 Mybatis + JPA , JPA 2개의 경우에 대해서만 적고 있어서 나는 JDBC, JPA에 대한 경우를 모두 포함해서 작성해보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 DB를 한개 포함하는 경우에서는 우리는 DB 설정을 Application.yml 혹은 properties에서 다음과 같이 설정했을 것이다.&lt;/p&gt;
&lt;pre class=&quot;yaml&quot;&gt;&lt;code&gt;spring:
   h2:
    console:
      enabled: false
  jpa:
    show-sql: true
    hibernate:
      ddl-auto: none
  datasource:
      url: DB주소
      username: DB 유저 이름
      password: DB 유저 비밀번호&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 DB가 두개인 경우에는 이렇게 사용하면 default에서 2개의 DB를 인식하지 못하고, 한개만 처리해서 내부의 DB url을 하나로만 처리한다. 즉, 완전히 다르게 코드를 구성해야한다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2개 이상의 DB DataSource를 사용하는 법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 Configuration 파일을 각 DB별로 따로 설정해줘야한다. 즉, 우리는 이 Configuration파일을 통해서 DB의 Datasource를 직접 집어넣는 방식으로 적용해야한다. 이전의 방식으로는 불가능하다.&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;이제 Yaml 파일는 사실상 값을 가지는 설정파일로써의 역할로 내가 yaml파일에서 class로 읽어들여오는 방식으로 코드를 가져와야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 DB Configuration을 설정하려면 몇몇 조건을 설정해야한다. 이것도 좀 웃긴게 JDBC일때와 JPA일 경우 설정해야하는 메소드 갯수가 다르다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JPA의 경우&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;p data-ke-size=&quot;size16&quot;&gt;기본 전제는 다음과 같다. 가장 기본으로 사용해야할 DB에 @Bean설정시 반드시 &lt;code&gt;@Primary&lt;/code&gt; 설정을 잡아줘야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잡지 않는 경우 같은 이름의 Bean이 여러개 잡히는걸로 인식해버렸다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt; @EnableJpaRepositories(
        basePackages = &quot;xxx.xxxx.xxx.servicetask&quot;,
        entityManagerFactoryRef = &quot;adminEntityManager&quot;,
        transactionManagerRef = &quot;adminTransactionManager&quot;
)&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JPA의 설정을 새로 다 잡아줘야되기 때문에 기본값으로 설정되는 것을 대체하기 위한 &lt;b&gt;Entity를 담당 하는 매니저 팩토리&lt;/b&gt;와 &lt;b&gt;Transaction매니저&lt;/b&gt;를 이 class에 설정해야한다. 그리고 실제로 이 JPA가 어디서 설정되는지 위치(&lt;code&gt;basePackages&lt;/code&gt;)를 알려줘야한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Bean
@Primary
@ConfigurationProperties(prefix = &quot;spring.datasource&quot;)
public DataSource adminDataSource(){
    return DataSourceBuilder.create().build();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 이 Datasource를 설정하기 위한 것 driver주소, username password를 설정할 수 있다. SpringBoot의 경우 다음과 같이 ConfigurationProperties를 통해서 yaml파일에 설정되어있는 것을 보고 이름과 매핑시켜서 알아서 세팅해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 큰 차이를 만들어 내는 것은&amp;nbsp;@ConfigurationProperties(prefix = &quot;spring.datasource&quot;)&amp;nbsp;인데 여기서 yaml파일을 수정 prefix를 변경하면 다른 DB와 관련된 설정을 할수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면 다음처럼, &lt;code&gt;@ConfigurationProperties(prefix = &quot;spring.jdbc-datasource&quot;)&lt;/code&gt; yaml에 prefix만 다르게 해서 설정하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fafafa;&quot;&gt;Spring 버젼에서는 그것을 일일히 코드화 시켜야했다. 다음과 같이 사용할 수 있었다.&lt;/span&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre class=&quot;mel&quot;&gt;&lt;code&gt;@Primary
@Bean
public DataSource userDataSource() {
         DriverManagerDataSource dataSource
          = new DriverManagerDataSource();
        dataSource.setDriverClassName(
          env.getProperty(&quot;jdbc.driverClassName&quot;));
        dataSource.setUrl(env.getProperty(&quot;user.jdbc.url&quot;));
        dataSource.setUsername(env.getProperty(&quot;jdbc.user&quot;));
        dataSource.setPassword(env.getProperty(&quot;jdbc.pass&quot;));

        return dataSource;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;극혐&lt;/s&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Bean
@Primary
public LocalContainerEntityManagerFactoryBean adminEntityManager(){
    LocalContainerEntityManagerFactoryBean em =new LocalContainerEntityManagerFactoryBean();
    em.setDataSource(adminDataSource());
    em.setPackagesToScan(newString[]{&quot;xxxx.xx.xxx.servicetask&quot;});
    HibernateJpaVendorAdapter vendorAdapter =newHibernateJpaVendorAdapter();
    em.setJpaVendorAdapter(vendorAdapter);
    HashMap&amp;lt;String, Object&amp;gt; properties =new HashMap&amp;lt;&amp;gt;();
    properties.put(&quot;hibernate.hbm2ddl.auto&quot;, property.getHbm2ddl().get(&quot;auto&quot;));
    properties.put(&quot;hibernate.format_sql&quot;,true);
    properties.put(&quot;hibernate.show-sql&quot;,property.getShowSql());
    properties.put(&quot;hibernate.dialect&quot;, property.getDialect());
    properties.put(&quot;hibernate.storage_engine&quot;, property.getStorage_engine());
    em.setJpaPropertyMap(properties);
    return em;
}

@Bean
@Primary
public PlatformTransactionManager adminTransactionManager(){
  JpaTransactionManager transactionManager =new JpaTransactionManager();
  transactionManager.setEntityManagerFactory(adminEntityManager().getObject());
    return transactionManager;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 트랜젝션과 엔티티를 설정해주는 매니저 팩토리를 이 class에서 설정한다.&amp;rarr; YAML properties적용 방식&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 hibernate의 기본 설정들을 적용하는 것 역시 &lt;code&gt;EntityManager&lt;/code&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;Properties 적용 방식에 대해서는 다음 포스팅을 참고하자 -&amp;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;현재 나의 경우 YAML 파일을 강제하고 싶어서 다른 사람들이 쓴 코드와 약간의 차이점이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들면 &lt;code&gt;Environment env&lt;/code&gt; 과 같은 코드를 사용하지 않았는데, yaml의 경우 env.getProperty()를 이용할 수 없기때문에 사용하지 않았고, yaml을 강제하려면 @value를 통해서 코드를 구성해야했지만, 그렇게 하면 코드가 난잡해지고 길어지기 때문에 Property를 컴포넌트로 빼서 mapping하는 방식으로 작성했다.&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; 만약 자신이 JPA를 통해서 2개의 DB를 사용해야하는 경우에는 Config.class를 한개 더 생성하고 이 Class에서는 &lt;code&gt;@Primary&lt;/code&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;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;
import java.util.HashMap;

@Configuration
@RequiredArgsConstructor
@EnableJpaRepositories(
        basePackages = &quot;xxx.xx.xxx.servicetask&quot;,
        entityManagerFactoryRef = &quot;adminEntityManager&quot;,
        transactionManagerRef = &quot;adminTransactionManager&quot;
)
public classPersistenceAdminConfig {

private finalJpaHibernateProperty property;

@Bean
@Primary
@ConfigurationProperties(prefix = &quot;spring.datasource&quot;)
public DataSource adminDataSource(){
	return DataSourceBuilder.create().build();
}

@Bean
@Primary
public LocalContainerEntityManagerFactoryBean adminEntityManager(){
        LocalContainerEntityManagerFactoryBean em =new LocalContainerEntityManagerFactoryBean();
        em.setDataSource(adminDataSource());
        em.setPackagesToScan(newString[]{&quot;xx.xxxx.xxxxx.servicetask&quot;});
        HibernateJpaVendorAdapter vendorAdapter =new HibernateJpaVendorAdapter();
        em.setJpaVendorAdapter(vendorAdapter);
        HashMap&amp;lt;String, Object&amp;gt; properties =newHashMap&amp;lt;&amp;gt;();
        properties.put(&quot;hibernate.hbm2ddl.auto&quot;, property.getHbm2ddl().get(&quot;auto&quot;));
        properties.put(&quot;hibernate.format_sql&quot;,true);
        properties.put(&quot;hibernate.show-sql&quot;,property.getShowSql());
        properties.put(&quot;hibernate.dialect&quot;, property.getDialect());
        properties.put(&quot;hibernate.storage_engine&quot;, property.getStorage_engine());
        em.setJpaPropertyMap(properties);
	return em;
}

@Bean
@Primary
public PlatformTransactionManager adminTransactionManager(){
    JpaTransactionManager transactionManager =newJpaTransactionManager();
    transactionManager.setEntityManagerFactory(adminEntityManager().getObject());
    return transactionManager;
	}

}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;JDBC의 경우&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오히려 JDBC는 더 쉽다. 더 간략하게 처리가 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JDBC는 결국 JdbcTemplate를 통해서 query가 날라가는 구조이기 때문에, JDBC template만 datasource와 일치화만 잘 잡아주면 되는 구조이다.&lt;/p&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;

@Configuration
public class PersistenceConfig {

    @Bean
    @ConfigurationProperties(prefix = &quot;spring.jdbc-datasource&quot;)
    public DataSource jdbcDatasource(){
        return DataSourceBuilder.create().build();
    }

    @Bean
    public JdbcTemplate jdbcTemplate(DataSource dataSource){
        return new JdbcTemplate(jdbcDatasource());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 여기서도 Transactionmanger와 같은 manger등록 역시 가능하다. 나는 Select문만 사용하므로 굳이 TransactionManager를 사용하지는 않았다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 2개의 DB를 사용하기 위해서는 이전에 간단하게 사용하는 Yaml에 datasource로는 사용하기 어렵고, 따로 Configuration 클래스를 2개 이상 잡아서 그곳에서 datasource와 그설정을 따로 잡아줘야한다.&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;JPA DB를 따로 적용해야하는 경우&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://linkeverything.github.io/springboot/multiple-datasource/&quot;&gt;https://linkeverything.github.io/springboot/multiple-datasource/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1630424005179&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Spring Boot에서 여러 Datasource 사용하기&quot; data-og-description=&quot;Spring Boot를 사용하면서 다중 데이터소스를 사용하는 필요성이 가끔 있습니다. 과거 EJB환경이나 Web/WAS 등의 구조를 보면 이렇게 다중 데이터소스를 활용하는 일이 많았고, 현재도 여러 데이터소&quot; data-og-host=&quot;linkeverything.github.io&quot; data-og-source-url=&quot;https://linkeverything.github.io/springboot/multiple-datasource/&quot; data-og-url=&quot;https://linkeverything.github.io/springboot/multiple-datasource/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/gSju9/hyLskc7zpg/7QStCyjWJEoliImEP2Aeek/img.jpg?width=1944&amp;amp;height=1100&amp;amp;face=0_0_1944_1100&quot;&gt;&lt;a href=&quot;https://linkeverything.github.io/springboot/multiple-datasource/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://linkeverything.github.io/springboot/multiple-datasource/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/gSju9/hyLskc7zpg/7QStCyjWJEoliImEP2Aeek/img.jpg?width=1944&amp;amp;height=1100&amp;amp;face=0_0_1944_1100');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring Boot에서 여러 Datasource 사용하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Spring Boot를 사용하면서 다중 데이터소스를 사용하는 필요성이 가끔 있습니다. 과거 EJB환경이나 Web/WAS 등의 구조를 보면 이렇게 다중 데이터소스를 활용하는 일이 많았고, 현재도 여러 데이터소&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;linkeverything.github.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://2dongdong.tistory.com/33&quot;&gt;https://2dongdong.tistory.com/33&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1630424005802&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Spring JPA  Multiple Databases 설정&quot; data-og-description=&quot;&amp;lt;!doctype html&amp;gt; Spring JPA &amp;ndash; Multiple Databases 1. 다중 DB 설정 시, 알아야 할 것2. 소스코드2-1. Entity2-2. Repository2-3. DB Configuration2-3-1 application.properties2-3-2 Main Datasource2-3-3 Seco..&quot; data-og-host=&quot;2dongdong.tistory.com&quot; data-og-source-url=&quot;https://2dongdong.tistory.com/33&quot; data-og-url=&quot;https://2dongdong.tistory.com/33&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cHsTtk/hyLsqj52Y2/RS3DOFUQ7nymnF7EPI1AYK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/FYPAG/hyLqTOXqn1/Bk1IMOzo4nuXAKrfKgYNO1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://2dongdong.tistory.com/33&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://2dongdong.tistory.com/33&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cHsTtk/hyLsqj52Y2/RS3DOFUQ7nymnF7EPI1AYK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/FYPAG/hyLqTOXqn1/Bk1IMOzo4nuXAKrfKgYNO1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring JPA Multiple Databases 설정&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Spring JPA &amp;ndash; Multiple Databases 1. 다중 DB 설정 시, 알아야 할 것2. 소스코드2-1. Entity2-2. Repository2-3. DB Configuration2-3-1 application.properties2-3-2 Main Datasource2-3-3 Seco..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;2dongdong.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JDBC 적용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://knight76.tistory.com/entry/spring-boot-multidatasource-multi-db-%EA%B0%84%EB%8B%A8%ED%95%98%EA%B2%8C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0&quot;&gt;https://knight76.tistory.com/entry/spring-boot-multidatasource-multi-db-간단하게-사용하기&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1630424007942&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[spring boot] multi-datasource (multi db) 간단하게 사용하기&quot; data-og-description=&quot;spring boot에서 아주 간단히 multi-datasource를 관리하고 jdbctemplate를 사용할 수 있는 예제를 소개한다. 먼저 maven또는 gradle 파일의 dependency에&amp;nbsp;db의 driver, spring-boot-starter-jdbc, commons-dbcp2..&quot; data-og-host=&quot;knight76.tistory.com&quot; data-og-source-url=&quot;https://knight76.tistory.com/entry/spring-boot-multidatasource-multi-db-%EA%B0%84%EB%8B%A8%ED%95%98%EA%B2%8C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0&quot; data-og-url=&quot;https://knight76.tistory.com/entry/spring-boot-multidatasource-multi-db-%EA%B0%84%EB%8B%A8%ED%95%98%EA%B2%8C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/buAor8/hyLslb1SqN/yQYNkZunz9lprr88TzKKM1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/becTCE/hyLq8eh7eJ/wedhk9ANYHjRJdvKBORoD1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800&quot;&gt;&lt;a href=&quot;https://knight76.tistory.com/entry/spring-boot-multidatasource-multi-db-%EA%B0%84%EB%8B%A8%ED%95%98%EA%B2%8C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://knight76.tistory.com/entry/spring-boot-multidatasource-multi-db-%EA%B0%84%EB%8B%A8%ED%95%98%EA%B2%8C-%EC%82%AC%EC%9A%A9%ED%95%98%EA%B8%B0&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/buAor8/hyLslb1SqN/yQYNkZunz9lprr88TzKKM1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/becTCE/hyLq8eh7eJ/wedhk9ANYHjRJdvKBORoD1/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[spring boot] multi-datasource (multi db) 간단하게 사용하기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;spring boot에서 아주 간단히 multi-datasource를 관리하고 jdbctemplate를 사용할 수 있는 예제를 소개한다. 먼저 maven또는 gradle 파일의 dependency에&amp;nbsp;db의 driver, spring-boot-starter-jdbc, commons-dbcp2..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;knight76.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Spring/JPA</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/133</guid>
      <comments>https://sundries-in-myidea.tistory.com/133#entry133comment</comments>
      <pubDate>Wed, 1 Sep 2021 00:47:27 +0900</pubDate>
    </item>
    <item>
      <title>엘라스틱서치 API ES6 fetch 이용시 ReadableStream을 해결하는 방법</title>
      <link>https://sundries-in-myidea.tistory.com/132</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;576&quot; data-filename=&quot;ElasticReadable.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/n34Nh/btrdqQR5YIv/Vb6CtxQ8e6Xs3Y3M8SkFa1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/n34Nh/btrdqQR5YIv/Vb6CtxQ8e6Xs3Y3M8SkFa1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/n34Nh/btrdqQR5YIv/Vb6CtxQ8e6Xs3Y3M8SkFa1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fn34Nh%2FbtrdqQR5YIv%2FVb6CtxQ8e6Xs3Y3M8SkFa1%2Fimg.png&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;576&quot; data-filename=&quot;ElasticReadable.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;ElasticSearch를 활용한 개발을 하게 되었다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;최근 Spring Boot와 함께 엘라스틱서치를 활용한 인턴 온보딩 프로젝트 개발을 시작하게 되었다. 종종 가끔씩 이제 Elastic과 관련된 글을 쓰지 않을까 싶다. 엘라스틱이 헛갈리는 개념도 많고 여러가지 이슈사항들이 많이 발생하게 되어서 아마 많은 글들을 남기지 않을까 싶다! 다시 본론으로 돌아가자면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항중 엘라스틱의 저장되있는 모든 index들의 상태를 파악해야했었다. 이러한 요구 사항은 실제로 ES에서는 API로 제공되어서 그 API를 사용하면 됬다.&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;GET /_cat/indices&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 같은 API를 사용하면, ElasticSearch에서 전체에서 저장한 ES 내부의 index를 확인해볼 수 있었다. 요구사항에 대해서 처리하는 것이 어렵지 않은 일이기도 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 API를 사용하게 된다면 다음 아래와 같은 값을 툭 내뱉기 때문에, 그걸 잘 받아서 처리만 하면 됬었다.&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;health status index            uuid                   pri rep docs.count docs.deleted store.size pri.store.size
yellow open   my-index-000001  u8FNjxh8Rfy_awN11oDKYQ   1   1       1200            0     88.1kb         88.1kb
green  open   my-index-000002  nYFWZEO7TUiOjLQXBaYJpA   1   0          0            0       260b           260b&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;문제의 발생...&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 이게 문제가 있었는데 무엇이 문제었냐하면, Spring Boot를 통해서 개발을 진행하다보니까 아무래도 좀 한 문제가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;ES Java Client를 가지고는 위의 API를 메소드로 불러 올 수 없었다&lt;/b&gt;는 문제가 생긴다. 물론 불러올 수는 있었지만, 메소드 단위로 불러오는 high client가 아닌 low client 를 사용해야지만, 이 API와 통신을 할 수 있는 상황...&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beUqWG/btrduHGPb2u/p4Un4zTkPPuReCdKkrn3rk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beUqWG/btrduHGPb2u/p4Un4zTkPPuReCdKkrn3rk/img.png&quot; data-origin-width=&quot;743&quot; data-origin-height=&quot;189&quot; data-filename=&quot;스크린샷 2021-08-29 오후 5.13.45.png&quot; style=&quot;width: 68.8583%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beUqWG/btrduHGPb2u/p4Un4zTkPPuReCdKkrn3rk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeUqWG%2FbtrduHGPb2u%2Fp4Un4zTkPPuReCdKkrn3rk%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;743&quot; height=&quot;189&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/B93k4/btrduUGreK9/hjfLv3pgijaKHZZhdbbX10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/B93k4/btrduUGreK9/hjfLv3pgijaKHZZhdbbX10/img.png&quot; data-origin-width=&quot;712&quot; data-origin-height=&quot;416&quot; data-filename=&quot;스크린샷 2021-08-29 오후 5.13.50.png&quot; style=&quot;width: 29.9789%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/B93k4/btrduUGreK9/hjfLv3pgijaKHZZhdbbX10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FB93k4%2FbtrduUGreK9%2FhjfLv3pgijaKHZZhdbbX10%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;712&quot; height=&quot;416&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;low와 high의 API 갯수차이는 어마어마하게 났음..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차라리 이럴거면 걍 FE Client에서 처리해주는게 훨씬 더 좋지 않나? 라고 생각한 나는 생각 그대로를 실현 시키기 위해서 바로 API를 FE에서 처리할 수 있도록 했다.&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;code&gt;fetch&lt;/code&gt; 함수를 통해서 API를 전달 받아서 JSON형태로 변환만 시켜 주면 훨씬 쉽게 만들 수 있을거라고 생각해서 값을 전달 받으면 body에서 잘꺼내다가 쓰면 좋겠다고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 아래 형식을 통해서 전달받기만 한다면, JSON 형식으로 받을 수 있었다. 웹 브라우저에서는...&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;GET /_cat/indices?format=json&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 존재했었다. 그녀석을 만나기 전까지 말이다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;또 문제 있어~&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;568&quot; data-filename=&quot;blob&quot; width=&quot;450&quot; height=&quot;465&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ciZHI6/btrdrmqdutf/wXjzqgFZ7jNi5MRpPL48N0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ciZHI6/btrdrmqdutf/wXjzqgFZ7jNi5MRpPL48N0/img.png&quot; data-alt=&quot;응 또 문제 있는뒈~~ 붸뷉&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ciZHI6/btrdrmqdutf/wXjzqgFZ7jNi5MRpPL48N0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FciZHI6%2Fbtrdrmqdutf%2FwXjzqgFZ7jNi5MRpPL48N0%2Fimg.png&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;568&quot; data-filename=&quot;blob&quot; width=&quot;450&quot; height=&quot;465&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;응 또 문제 있는뒈~~ 붸뷉&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 당연히 &lt;code&gt;await&lt;/code&gt;나 &lt;code&gt;then&lt;/code&gt;을 통해서 비동기 처리하면 될거라고 생각했으나... 예상외의 난관을 만났었는데, 이 API가 이상한 타입으로 값을 받는 타입이 가장 큰 문제였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 API만 그런것인지 아니면 다른 상황에서도 그런건지는 모르겠으나, 이 전달 받는 response의 body의 값 타입이 &lt;code&gt;ReadableStream&lt;/code&gt; 타입으로 전달을 받게 되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;264&quot; data-filename=&quot;스크린샷 2021-08-29 오후 5.46.40.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bM3fSB/btrdpC7Riik/zsjffIK48WCzu8JsB5dbk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bM3fSB/btrdpC7Riik/zsjffIK48WCzu8JsB5dbk1/img.png&quot; data-alt=&quot;Body에 ReadableStream으로 전달된다.&amp;amp;amp;nbsp;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bM3fSB/btrdpC7Riik/zsjffIK48WCzu8JsB5dbk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbM3fSB%2FbtrdpC7Riik%2FzsjffIK48WCzu8JsB5dbk1%2Fimg.png&quot; data-origin-width=&quot;946&quot; data-origin-height=&quot;264&quot; data-filename=&quot;스크린샷 2021-08-29 오후 5.46.40.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Body에 ReadableStream으로 전달된다.&amp;nbsp;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Json으로 backend에서 전달해본적은 있지 Json 타입을 받기위해서 &lt;code&gt;ReadableStream&lt;/code&gt; 이라는 처음 보는 타입으로 전달 받아본적은 또 이번이 처음이라 도대체 뭔가 했었는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 각종 삽질들을 반복하기도 했었고... 자바스크립트를 잘은 모르다보니까 벌어졌었던 삽질들&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;467&quot; data-filename=&quot;스크린샷 2021-08-29 오후 6.13.17.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KbkWe/btrdr4iuVmZ/noYMkQRk3HtKiCBqynl7F1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KbkWe/btrdr4iuVmZ/noYMkQRk3HtKiCBqynl7F1/img.png&quot; data-alt=&quot;await 문에다가 then쓰고 앉아있었다. (어쩌자는거지...)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KbkWe/btrdr4iuVmZ/noYMkQRk3HtKiCBqynl7F1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKbkWe%2Fbtrdr4iuVmZ%2FnoYMkQRk3HtKiCBqynl7F1%2Fimg.png&quot; data-origin-width=&quot;794&quot; data-origin-height=&quot;467&quot; data-filename=&quot;스크린샷 2021-08-29 오후 6.13.17.png&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;await 문에다가 then쓰고 앉아있었다. (어쩌자는거지...)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 해결하기 위해서는 다음과 같은 전략을 사용해야했었다.&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;//let타입을 할 수 밖에 없었다. const 타입으로 했을시 값을 한번 읽고 땡처리 되었었기 때문...
let example4 = fetch(&quot;http://localhost:9200/_cat/indices?format=json&quot;);
example4.then(r =&amp;gt; r.json()) //Json 타입으로 변환
                .then(d=&amp;gt;console.log(d)) //그후 값 받아서 처리했었다. &lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 값이 잘 튀어놔왔다. (보기좋게 형식을 약간 수정했다.)&lt;/p&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;[
    {
    docs.count: &quot;5&quot;,
    docs.deleted: &quot;0&quot;,
    health: &quot;yellow&quot;,
    index: &quot;article&quot;,
    pri: &quot;1&quot;,
    pri.store.size: &quot;31.8kb&quot;,
    rep: &quot;1&quot;,
    status: &quot;open&quot;,
    store.size: &quot;31.8kb&quot;,
    uuid: &quot;RLCbq6QrQHqZx0HPzUstjQ&quot;
  }
]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JavaScript의 fetch를 통한 ES API 불러올 경우에는 다음과 같은 방법으로 readable타입을 처리하면 될 것 같다.&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;code&gt;ReadableStream&lt;/code&gt; 타입으로 전달된 값은 한번 불러오고 나면 그 이후 내가 then을 통해서 어떤 행위를 하고 난 뒤에는 다시 사용할 수 없는 상태였다.&lt;/p&gt;
&lt;pre class=&quot;smali&quot;&gt;&lt;code&gt;Promise {&amp;lt;rejected&amp;gt;: TypeError: Failed to execute 'json' on 'Response': body stream already read at &amp;lt;anonymous&amp;gt;:1:22}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, Stream을 통해서 값의 변형을 불러올때만 가능하게 만들어놓은 상태로 만들기 위해서 엘라스틱 서치에서 이런 형식으로 값 전달을 만들어 놓은 것 같았다. 스트림형식으로 값이 남아있게 하는 것이아니라 한번 사용하고 나면 바로 없어지는 방식을 통해서 Elastic에서 값을 전달할때 이런 형식으로 전달했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 이렇게 stream을 다시 사용하기 위해서는 let타입을 통해서 fetch를 다시 불러와서 사용하는 방법을 이용해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;then형식으로 처리는 쉽게 되었는데, 문제는 await방식을 통해서 처리를 해보려 했으나, 예상처럼 잘 되지는 않았다. 혹시나 await 방식을 통해서 코드 개선이 가능하다면, 댓글로 남겨주시면 좋을 것 같습니다!  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출처:&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/40385133/retrieve-data-from-a-readablestream-object&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://stackoverflow.com/questions/40385133/retrieve-data-from-a-readablestream-object&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1630231155748&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Retrieve data from a ReadableStream object?&quot; data-og-description=&quot;How may I get information from a ReadableStream object? I am using the Fetch API and I don't see this to be clear from the documentation. The body is being returned as a ReadableStream and I wo...&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/40385133/retrieve-data-from-a-readablestream-object&quot; data-og-url=&quot;https://stackoverflow.com/questions/40385133/retrieve-data-from-a-readablestream-object&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dfmp9h/hyLqWCXsuN/BwRk2lucJAu73nJAbTSK2K/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/40385133/retrieve-data-from-a-readablestream-object&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/40385133/retrieve-data-from-a-readablestream-object&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dfmp9h/hyLqWCXsuN/BwRk2lucJAu73nJAbTSK2K/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Retrieve data from a ReadableStream object?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;How may I get information from a ReadableStream object? I am using the Fetch API and I don't see this to be clear from the documentation. The body is being returned as a ReadableStream and I wo...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>FrontEnd</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/132</guid>
      <comments>https://sundries-in-myidea.tistory.com/132#entry132comment</comments>
      <pubDate>Sun, 29 Aug 2021 18:57:58 +0900</pubDate>
    </item>
    <item>
      <title>⌜실리콘밸리 리더십⌟ 서평</title>
      <link>https://sundries-in-myidea.tistory.com/131</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;312&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PvY6g/btrcOeTP7GR/zycWvFNHgzwBX42VaVjEQk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PvY6g/btrcOeTP7GR/zycWvFNHgzwBX42VaVjEQk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PvY6g/btrcOeTP7GR/zycWvFNHgzwBX42VaVjEQk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPvY6g%2FbtrcOeTP7GR%2FzycWvFNHgzwBX42VaVjEQk%2Fimg.jpg&quot; data-origin-width=&quot;820&quot; data-origin-height=&quot;312&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;568&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k0VfI/btrcVkermDi/CQdU4DLkS7CmkPIC1kFwJ0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k0VfI/btrcVkermDi/CQdU4DLkS7CmkPIC1kFwJ0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k0VfI/btrcVkermDi/CQdU4DLkS7CmkPIC1kFwJ0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk0VfI%2FbtrcVkermDi%2FCQdU4DLkS7CmkPIC1kFwJ0%2Fimg.jpg&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;568&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;한빛미디어 &amp;lt;나는 리뷰어다&amp;gt; 활동을 위해서 책을 제공받아 작성된 서평입니다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;첫 기술서적 이외 책&lt;/h2&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;이책의 저자는 넷스케이프, 애플, 슬랙을 거치면서 실리콘 벨리의 속칭 잘나가는 회사에 꽤 오랜기간동안 관리직으로 몸 담은 사람이 리더십이라는 주제로 책을 작성했는데, 처음에는 관리자, 임원, 경영자가 가지고 있어야할 몇가지 태도 그러니까 30가지 정도로 각각 10개씩 마치 명언처럼 한줄에 이해하기 쉬운 글들로 정리해놓고, 구체적으로 사례들을 풀면서 이야기를 진행한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 10개의 명언들은 예상보다는 단순한 내용들인데... 그리고 어디서 들어봄직한 말들이 많긴하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1920&quot; data-filename=&quot;이미지.jpeg&quot; width=&quot;512&quot; height=&quot;683&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgIKZd/btrcYaCsejz/gB2a41yMkMOeoyBACvQ6r1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgIKZd/btrcYaCsejz/gB2a41yMkMOeoyBACvQ6r1/img.jpg&quot; data-alt=&quot;대체적으로 이런 내용들을 풀어서 경험담과 함께 혹은 예시와 함께 전달한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgIKZd/btrcYaCsejz/gB2a41yMkMOeoyBACvQ6r1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcgIKZd%2FbtrcYaCsejz%2FgB2a41yMkMOeoyBACvQ6r1%2Fimg.jpg&quot; data-origin-width=&quot;1440&quot; data-origin-height=&quot;1920&quot; data-filename=&quot;이미지.jpeg&quot; width=&quot;512&quot; height=&quot;683&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;대체적으로 이런 내용들을 풀어서 경험담과 함께 혹은 예시와 함께 전달한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p 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;굳이 차별점이라하면...&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FNyHX/btrcOiO71xX/RGiSbu1kjP0nS5qdMCvpPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FNyHX/btrcOiO71xX/RGiSbu1kjP0nS5qdMCvpPK/img.png&quot; data-origin-width=&quot;1704&quot; data-origin-height=&quot;1361&quot; data-filename=&quot;blob&quot; width=&quot;558&quot; height=&quot;419&quot; style=&quot;width: 49.6206%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FNyHX/btrcOiO71xX/RGiSbu1kjP0nS5qdMCvpPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFNyHX%2FbtrcOiO71xX%2FRGiSbu1kjP0nS5qdMCvpPK%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;1704&quot; height=&quot;1361&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/digUJz/btrcOSCD7Eh/KntfRvJ7g2YYL2MngkkrUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/digUJz/btrcOSCD7Eh/KntfRvJ7g2YYL2MngkkrUK/img.png&quot; data-origin-width=&quot;1624&quot; data-origin-height=&quot;1228&quot; data-filename=&quot;blob&quot; width=&quot;585&quot; height=&quot;439&quot; style=&quot;width: 49.2166%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/digUJz/btrcOSCD7Eh/KntfRvJ7g2YYL2MngkkrUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdigUJz%2FbtrcOSCD7Eh%2FKntfRvJ7g2YYL2MngkkrUK%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;1624&quot; height=&quot;1228&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;가이드를 주는 것에 있어서 꽤 큰 도움을 받을 수 있다.&amp;nbsp;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;책의 제목과 함께 그가 지나다녔던 3개의 회사들은 정말 훨훨 날아다니는 회사이고, 그만큼 쟁쟁한 사람들이 쌔고 쌘 사람들이 넘처나는 조직에서 처음에는 그가 개발자관점과 테크리더와 같은 직군을 맡으면서 결국 테크적인 회사에서 리더십을 발휘해야하는 순간 그가 했었던 행동들에 대해서 작성하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개인적으로 그가 개발자를 했었던 경험들, 그리고 테크 회사에서 경험해왔던 것들이 리더십을 펼쳐야하는 상황과 어느정도 일치하는 맥락이 있을 거라고 생각한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자들에게 리더십을 강요받는 상황이 생길 수 도 있기도 하며, 리더십을 펼쳐야하는 순간에 한 문장정도 되는 글들로 가이드라인을 잡을 수 있다는 것은 순전한 개발자 입장에서는 예상보다 큰 도움을 받을 수 있는 책이라고 생각한다.&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;리더십은 어디에서나 존재한다.&lt;/h3&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 분명 부족하긴 했었는데 도대체 뭐가 부족했었는지? 내가 규합하지 못했었던 사람들에게 도대체 내가 어떤식으로 행동을 했어야만 했었는지에 대해서 좀 많은 고민을 하게 했었던 책이기도 했다.&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;&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>서평</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/131</guid>
      <comments>https://sundries-in-myidea.tistory.com/131#entry131comment</comments>
      <pubDate>Mon, 23 Aug 2021 00:19:55 +0900</pubDate>
    </item>
    <item>
      <title>Vuetify 방식의 실시간 검증 방식</title>
      <link>https://sundries-in-myidea.tistory.com/130</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근 인턴 프로젝트에서 Vue를 사용하게되었는데, UI프레임워크를 사용해서 Validation처리를 손쉽게 하고 싶었고, UI 프레임워크중 material 스타일에 가장 맞는 스타일중 하나인 Vuetify를 선택해서 개발을 시작하게 되었다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;805&quot; data-origin-height=&quot;805&quot; width=&quot;433&quot; height=&quot;433&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nVqHB/btrcFYvPi0X/ng3FEmTIzPsn6fT50plBBK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nVqHB/btrcFYvPi0X/ng3FEmTIzPsn6fT50plBBK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nVqHB/btrcFYvPi0X/ng3FEmTIzPsn6fT50plBBK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnVqHB%2FbtrcFYvPi0X%2Fng3FEmTIzPsn6fT50plBBK%2Fimg.png&quot; data-origin-width=&quot;805&quot; data-origin-height=&quot;805&quot; width=&quot;433&quot; height=&quot;433&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Vuetify의 Form-validation&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;vuetify는 기본적으로 form input에서 validation을 제공한다. :rule 을 활용하면 validate를 적용할 수있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실시간 검증을 하기위해서는 비동기적으로 구현해야한다. 개인적으로 ES6를 활용하고 싶었으나, 아쉽게 실패했고 그 대안으로 axios를 활용해서 구현했었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래는 vuetify의 form 안의 input 값들을 validation 하는 기능을 기본적으로 제공한다. 그중 &lt;code&gt;v-text-field&lt;/code&gt;의 rule을 활용한 것인데, 다음처럼 넣으면 validate처리가 쉽게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;elixir&quot;&gt;&lt;code&gt; &amp;lt;v-text-field v-model=&quot;fields.email&quot; label=&quot;이메일 주소&quot; :error-messages=&quot;email_dup&quot; :rules=&quot;[rules.email.required, rules.email.valid, rules.email.duplicate]&quot; &amp;gt;&amp;lt;/v-text-field&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Vue파일 내부에 data에 rules를 지정해놓으면 된다. 아래 메소드를 다음과 같이 적용하면된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;javascript&quot;&gt;&lt;code&gt;  data(){
         return {
             email_dup: [],
             fields: {
                 ...
                 email: '',
                 ...
             },
             rules: {
 ...
                 email: {
                     required: v =&amp;gt; !!v || 'E-mail is required',
                     duplicate: v =&amp;gt; this.duplicateEmail(v)
                 }
  ....
     },
     methods:{
 ...
         duplicateEmail(v) {
             axios.post('/email-validate', this.fields).then(response =&amp;gt; {
                 this.email_dup = []
             }).catch(error =&amp;gt; {
                 this.email_dup = ['이미 탈퇴하거나 이미 가입한 회원입니다.']
             })
         }
     }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;rules라는 object 타입을 굳이 만들지 않아도 되긴하지만 일정 형식을 구성해서 만드는게 더 구체적으로 명시하는것 같아서 만들어 놓은것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 Rules 타입은 return 타입이 boolean 이거나, string 타입인 경우에만 validation이 정상적으로 작동하는데, &lt;code&gt;required: v =&amp;gt; !!v || 'E-mail is required',&lt;/code&gt;와 같은 코드를 확인해보면 v안의 값이 있는지 확인하고 false라면 string안의 문구를 input에 띄워주는 방식으로 작동해보였다.&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;code&gt;&amp;lt;v-text-field :rules&lt;/code&gt; 자체에서 제공하는 기능 function으로 true 혹은 string을 리턴하면 유효성처리를 알아서 해주는데, axios return을 하다보니까 아무래도 Promise과 같이 좀 오류점이 있는듯했다.&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://stackoverflow.com/questions/49132167/how-to-validate-vuetify-text-field-asynchronously&quot;&gt;https://stackoverflow.com/questions/49132167/how-to-validate-vuetify-text-field-asynchronously&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1629355606968&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;How to validate Vuetify text field asynchronously?&quot; data-og-description=&quot;Text fields in Vuetify have rules props, which take an array of functions returning true or an error string. How to make them async, so that the validation could be made server-side using XHR? Som...&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/49132167/how-to-validate-vuetify-text-field-asynchronously&quot; data-og-url=&quot;https://stackoverflow.com/questions/49132167/how-to-validate-vuetify-text-field-asynchronously&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bAEydS/hyLi5mIWyE/5LlaKQAZx6OGc2aFweZJe0/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/49132167/how-to-validate-vuetify-text-field-asynchronously&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/49132167/how-to-validate-vuetify-text-field-asynchronously&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bAEydS/hyLi5mIWyE/5LlaKQAZx6OGc2aFweZJe0/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;How to validate Vuetify text field asynchronously?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Text fields in Vuetify have rules props, which take an array of functions returning true or an error string. How to make them async, so that the validation could be made server-side using XHR? Som...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 링크를 보면 확인해보면, 결국 return되는 값이 Promise로 전달되서 오류가 발생해보이는 듯 했다. console.log로 찍어봤는데 &lt;code&gt;undefined&lt;/code&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;그렇다면 바로 적용시킬수는 없고 어떤 방식으로 처리할 수 있을까?&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;나는 이것을 :rule에 맞는 방식은 아니지만, 다른 방식으로 valid 문구를 띄울 수 있었는데, 그 방법은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;data에 email_dup를 state로 설정한다. 그리고 rules에서 valid를 적용. 그리고 이 duplicateEmail에서는 상태를 변경하고 이 상태가 변경되면, :error_message에서 노출시키는 방법을 활용했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법은 SPA 방식으로 만드는 사람들에게는 유효하지 않을까라는 생각을 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;추가적으로&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;await와 async를 통해서 좀 더 깔끔하게 처리할 수 있을거라고 생각했는데, 결국 async의 경우는 결국에는 promise 타입형태로 return 되기 때문에, :rules 타입은 string, boolean 타입이외에는 받을수가 없어서 오류처리가 되어 fail... 비동기적인 방식을 살리면서 vuetify의 기본 validation 기능을 활용하는것은 사실상 주어진 방식으로 할 수는 없다.&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;한 외국형님은 많이 화가 나셨는지 이런 포스팅도 올렸다. (suc..? zㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://orkhanhuseyn.medium.com/why-vuetifys-native-validation-sucks-47094d621734&quot;&gt;https://orkhanhuseyn.medium.com/why-vuetifys-native-validation-sucks-47094d621734&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1629355625963&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Why Vuetify&amp;rsquo;s native form validation sucks&quot; data-og-description=&quot;About Vuetify&amp;rsquo;s form validation, what it lacks and why you might wanna use third party library instead of using rules prop.&quot; data-og-host=&quot;orkhanhuseyn.medium.com&quot; data-og-source-url=&quot;https://orkhanhuseyn.medium.com/why-vuetifys-native-validation-sucks-47094d621734&quot; data-og-url=&quot;https://orkhanhuseyn.medium.com/why-vuetifys-native-validation-sucks-47094d621734&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/MYpBj/hyLikFOY2x/yyWiggM6z0QseBsT9bvh7k/img.png?width=1200&amp;amp;height=730&amp;amp;face=0_0_1200_730,https://scrap.kakaocdn.net/dn/dFqn0J/hyLicVjfR4/vp4MOngqHebU2JHXKKXVRk/img.png?width=60&amp;amp;height=39&amp;amp;face=0_0_60_39,https://scrap.kakaocdn.net/dn/5MzZM/hyLh9dbVQh/KzbHPKZbbhw0UUZODGp8fk/img.png?width=1400&amp;amp;height=852&amp;amp;face=0_0_1400_852&quot;&gt;&lt;a href=&quot;https://orkhanhuseyn.medium.com/why-vuetifys-native-validation-sucks-47094d621734&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://orkhanhuseyn.medium.com/why-vuetifys-native-validation-sucks-47094d621734&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/MYpBj/hyLikFOY2x/yyWiggM6z0QseBsT9bvh7k/img.png?width=1200&amp;amp;height=730&amp;amp;face=0_0_1200_730,https://scrap.kakaocdn.net/dn/dFqn0J/hyLicVjfR4/vp4MOngqHebU2JHXKKXVRk/img.png?width=60&amp;amp;height=39&amp;amp;face=0_0_60_39,https://scrap.kakaocdn.net/dn/5MzZM/hyLh9dbVQh/KzbHPKZbbhw0UUZODGp8fk/img.png?width=1400&amp;amp;height=852&amp;amp;face=0_0_1400_852');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Why Vuetify&amp;rsquo;s native form validation sucks&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;About Vuetify&amp;rsquo;s form validation, what it lacks and why you might wanna use third party library instead of using rules prop.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;orkhanhuseyn.medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;차라리 라이브러리를 사용할 수 있도록 Vuelidate 혹은 Vee-validate라는 툴을 제공해주긴하는데... 이것도 그렇게 편한건 아닌듯해서, vuetify를 쓰는 만큼 어느정도의 타협이 필요해보였다. 귀찮더라도 :error_message를 통해서 에러메세지를 처리하게하는 게 더 좋아보인다.  &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://minu0807.tistory.com/82&quot;&gt;[VueJS] Vuetify v-text-field 유효성 검사 (rules)&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1629355669894&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[VueJS] Vuetify v-text-field 유효성 검사 (rules)&quot; data-og-description=&quot;v2.vuetifyjs.com/ko/components/forms/ Form component &amp;mdash; Vuetify.js The form component provides a wrapper that makes it easy to process and control validation states of input components. v2.vuetifyjs..&quot; data-og-host=&quot;minu0807.tistory.com&quot; data-og-source-url=&quot;https://minu0807.tistory.com/82&quot; data-og-url=&quot;https://minu0807.tistory.com/82&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/cVdp2y/hyLi1Log6M/xUSCdpJKC5VbIUM4An0gak/img.png?width=635&amp;amp;height=764&amp;amp;face=0_0_635_764,https://scrap.kakaocdn.net/dn/K6Gyk/hyLil5Lodd/40miqcSlR4EQ4qE5q0LDKK/img.png?width=635&amp;amp;height=764&amp;amp;face=0_0_635_764,https://scrap.kakaocdn.net/dn/brWfw6/hyLiYOFhrU/iNYFJPxpoWKeBdABCaAEak/img.jpg?width=746&amp;amp;height=720&amp;amp;face=0_0_746_720&quot;&gt;&lt;a href=&quot;https://minu0807.tistory.com/82&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://minu0807.tistory.com/82&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/cVdp2y/hyLi1Log6M/xUSCdpJKC5VbIUM4An0gak/img.png?width=635&amp;amp;height=764&amp;amp;face=0_0_635_764,https://scrap.kakaocdn.net/dn/K6Gyk/hyLil5Lodd/40miqcSlR4EQ4qE5q0LDKK/img.png?width=635&amp;amp;height=764&amp;amp;face=0_0_635_764,https://scrap.kakaocdn.net/dn/brWfw6/hyLiYOFhrU/iNYFJPxpoWKeBdABCaAEak/img.jpg?width=746&amp;amp;height=720&amp;amp;face=0_0_746_720');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[VueJS] Vuetify v-text-field 유효성 검사 (rules)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;v2.vuetifyjs.com/ko/components/forms/ Form component &amp;mdash; Vuetify.js The form component provides a wrapper that makes it easy to process and control validation states of input components. v2.vuetifyjs..&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;minu0807.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/49884611/api-requests-with-axios-always-unauthorized-with-laravel-api&quot;&gt;https://stackoverflow.com/questions/49884611/api-requests-with-axios-always-unauthorized-with-laravel-api&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1629355669057&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;API requests with axios always unauthorized with Laravel API&quot; data-og-description=&quot;I'm working on a personal project using Laravel 5.6 and Axios library (standard Laravel 5.6 package). I need to send first GET then POST request using Laravel's API and axios' http requests, but ...&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/49884611/api-requests-with-axios-always-unauthorized-with-laravel-api&quot; data-og-url=&quot;https://stackoverflow.com/questions/49884611/api-requests-with-axios-always-unauthorized-with-laravel-api&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bImbfg/hyLi84OqKE/KjYrpnQZJ86Cy6WmHfj7V0/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/49884611/api-requests-with-axios-always-unauthorized-with-laravel-api&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/49884611/api-requests-with-axios-always-unauthorized-with-laravel-api&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bImbfg/hyLi84OqKE/KjYrpnQZJ86Cy6WmHfj7V0/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;API requests with axios always unauthorized with Laravel API&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;I'm working on a personal project using Laravel 5.6 and Axios library (standard Laravel 5.6 package). I need to send first GET then POST request using Laravel's API and axios' http requests, but ...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&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;a href=&quot;https://stackoverflow.com/questions/49132167/how-to-validate-vuetify-text-field-asynchronously&quot;&gt;https://stackoverflow.com/questions/49132167/how-to-validate-vuetify-text-field-asynchronously&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1629355659769&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;How to validate Vuetify text field asynchronously?&quot; data-og-description=&quot;Text fields in Vuetify have rules props, which take an array of functions returning true or an error string. How to make them async, so that the validation could be made server-side using XHR? Som...&quot; data-og-host=&quot;stackoverflow.com&quot; data-og-source-url=&quot;https://stackoverflow.com/questions/49132167/how-to-validate-vuetify-text-field-asynchronously&quot; data-og-url=&quot;https://stackoverflow.com/questions/49132167/how-to-validate-vuetify-text-field-asynchronously&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bAEydS/hyLi5mIWyE/5LlaKQAZx6OGc2aFweZJe0/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316&quot;&gt;&lt;a href=&quot;https://stackoverflow.com/questions/49132167/how-to-validate-vuetify-text-field-asynchronously&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://stackoverflow.com/questions/49132167/how-to-validate-vuetify-text-field-asynchronously&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bAEydS/hyLi5mIWyE/5LlaKQAZx6OGc2aFweZJe0/img.png?width=316&amp;amp;height=316&amp;amp;face=0_0_316_316');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;How to validate Vuetify text field asynchronously?&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Text fields in Vuetify have rules props, which take an array of functions returning true or an error string. How to make them async, so that the validation could be made server-side using XHR? Som...&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;stackoverflow.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>FrontEnd/Vue</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/130</guid>
      <comments>https://sundries-in-myidea.tistory.com/130#entry130comment</comments>
      <pubDate>Thu, 19 Aug 2021 15:48:28 +0900</pubDate>
    </item>
    <item>
      <title>라라벨의 비동기를 활용한 실시간 검증</title>
      <link>https://sundries-in-myidea.tistory.com/129</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Laravel 실시간 검증 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라라벨에서 실시간 검증을 해야하는 경우가 있을것이다. 실시간 검증의 경우 매번 서버검증을 통해서 해결하는 방식이 아닌, 비동기적인 방식으로 검증을 매번 처리해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 laravel에서는 비동기적으로 이미 구현할 수 있게 내부적으로 구현이 되어있다. &lt;code&gt;$validated = $request-&amp;gt;validated();&lt;/code&gt;처리를 하면 알아서 비동기적으로 발송되게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구체적으로 예시를 들어서 설명하겠다. 라라벨에서 vue로 스케폴딩한 auth기능을 사용하면,&lt;/p&gt;
&lt;pre class=&quot;php&quot;&gt;&lt;code&gt;  public function register(Request $request)
     {
         $this-&amp;gt;validator($request-&amp;gt;all())-&amp;gt;validate();
         ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;register 기능에 이미 validate() 처리가 되어있다. 이렇듯 validation을 위한 기능은 라라벨에서 제공하는 &lt;code&gt;Reqeust&lt;/code&gt; 객체(라라벨에서 객체의 유효성과 관련된 룰을 설정할 수 있는 역할을 하는 객체)를 Parameter를 통해 처리해서 그에 따라 &lt;code&gt;validate()&lt;/code&gt;메소도만 설정해주면 알아서 JSON으로 전달을 해준다는 것이다.&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;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AJAX 요청인 경우 유효성 검사 오류가 JSON 형식으로 재구성되어 422 상태 코드가있는 HTTP 응답이 사용자에게 반환됩니다.&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;그러면 우리는 이걸 잘 떠먹기만 하면 된다.&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;공식문서의 경우 AJAX 라고 작성했는데, 비동기적인 호출을 하는 방식이라면 모두 응답이 전달이 되는 것 같다. Axios나 ES버젼의 JS의 Fetch도 같은 기능을 할 것이라고 생각한다. (Axios까지는 해봤는데 fetch는 직접 해본건 아니기때문에 확답은 못하겠음)&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;신규 Request를 통해서 룰을 새로 만든 경우 &lt;code&gt;ex) Email 중복성 검사&lt;/code&gt;만 해야하는 경우에는 아예 새로 Request를 만들고 Controller에서 메소드 생성후 라우팅을 통해 url을 붙이는게 좋다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예상보다 비동기 전달시 401 오류를 해결할 방법을 찾지 못해서 꽤 고생했었다. middleware나 인증 인가와 관련된 문제라고 생각했지만... 아닌 경우가 많았다.&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;비동기로 실시간 유효성 처리시 401이나 403과 같은 authenticated 문제가 발생시 새로 생성한 Request 의 아래의 메소드의 Return을 &lt;code&gt;true&lt;/code&gt;로 전환을 해줘야지 401,403문제가 해결된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt; public function authorize()
   {
     return true;
   }&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;blockquote data-ke-style=&quot;style2&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;즉, 이걸 통해서 유저가 이 폼을 사용할 수 있는지를 먼저 확인해보고 만약 사용할 수 없는 경우라면, 403과 같은 리퀘스트를 자동으로 반환해줄 것이다.&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;여러분이 애플리케이션의 다른 부분에 있는 인증로직을 사용할 계획이라면, authorize 메소드에서 true를 리턴하면 됩니다.&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>Backend/PHP&amp;amp;Laravel</category>
      <author>개발섭</author>
      <guid isPermaLink="true">https://sundries-in-myidea.tistory.com/129</guid>
      <comments>https://sundries-in-myidea.tistory.com/129#entry129comment</comments>
      <pubDate>Thu, 19 Aug 2021 13:54:49 +0900</pubDate>
    </item>
  </channel>
</rss>