<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>일단 해보고, 아니면 뭐</title>
    <link>https://eunveloper.tistory.com/</link>
    <description>김은혜
Backend Developer
(2019.11.04 ~)</description>
    <language>ko</language>
    <pubDate>Fri, 10 Jul 2026 07:35:38 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>개발자 김은혜</managingEditor>
    <image>
      <title>일단 해보고, 아니면 뭐</title>
      <url>https://tistory1.daumcdn.net/tistory/5325637/attach/4938b3de4246418a8492e06c7d3f45ce</url>
      <link>https://eunveloper.tistory.com</link>
    </image>
    <item>
      <title>늦은 2022년 회고 - 잘해왔고, 잘할 거야.</title>
      <link>https://eunveloper.tistory.com/entry/%EB%8A%A6%EC%9D%80-2022%EB%85%84-%ED%9A%8C%EA%B3%A0-%EC%9E%98%ED%95%B4%EC%99%94%EA%B3%A0-%EC%9E%98%ED%95%A0-%EA%B1%B0%EC%95%BC</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;2023년이 된 지 거의 한 달이 다 되어가지만, 더 늦기 전에 2022년 회고를 하려고 블로그를 켰다.&lt;br&gt;올 한해는 정말 많은 일이 있었고 그 안에서 많은 것들을 배웠고 느꼈다! 나의 한 해를 돌아보자.&lt;/p&gt;
&lt;figure data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignLeft&quot; data-emoticon-type=&quot;niniz&quot; data-emoticon-name=&quot;001&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/001.gif&quot;&gt;
 &lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/001.gif&quot; width=&quot;150&quot;&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&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;올해는 첫 회사를 재직한 지 2년이 넘어서는 해였고 3년 차 개발자가 되는 해였으며 회사 방침(?)에 따라 대리로 진급하는 해였다.&lt;br&gt;대리로 진급을 하면서 신입사원 분들의 사수역할도 해보고 작은 프로젝트였지만 프로젝트 리딩 역할을 하게 되었다.&lt;br&gt;&lt;b&gt;나는 아직 부족한게 많다&lt;/b&gt;고 생각하는 나로써는 내가 어떤 프로젝트를 리딩한다는 것이 두렵기도 하고 얼떨떨하기도 했다.&lt;br&gt;하지만 팀장님께서 나를 믿는다며 힘든 일이 생기면 언제든지 도와줄 테니 한번 맡아서 해보라며 재차 권유를 하셨고 나는 이전보다는 홀가분한 마음으로 자리를 맡게 되었다.&lt;br&gt; &lt;br&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;이전에는 내 업무만 잘하면 됐고 내 일만 생각했으면 됐지만 이제는 그게 아니었다.&lt;/b&gt;&lt;/span&gt;&lt;br&gt;프로젝트를 함께 하는 인원들의 역량을 파악해야했고 개발 일정을 산정하고 업무를 분담하고 코드 품질을 위한 노력도 했어야 했다.&lt;br&gt;다른 프로젝트와 협업이 필요하면 대표로 커뮤니케이션 창구가 됐어야 했고 도움이 필요한 분들에게 도움도 됐어야 했다.&lt;br&gt;이런 것들이 힘들지 않았다면 사실 거짓말 일것이다. 굉장히 부담스러웠고 힘들었고 어려웠다.&lt;br&gt; &lt;br&gt;하지만 하루하루 이런 업무들을 하다보니 어느 순간 힘들고 부담스러운 마음보다 얻는 것이 더 많아졌다.&lt;br&gt;개발하는 시야가 넓어지고 책임감으로 인해 오히려 더 많은 공부를 하게되고 커뮤니케이션 능력도 한층 증가했다.&lt;br&gt;또한 평소에 해보고 싶었던 기술들을이나 꿈꿔왔던(?) 건강한 개발문화의 도입이 한층 쉬워졌다.&lt;br&gt;이러한 시간들을 경험하면서 글과 말로는 표현할 수 없는 많은 것들을 배우게 되었다.&lt;br&gt;이때부터 나는 &lt;span style=&quot;color: #ee2323;&quot;&gt;개발만 잘하는 개발자보단 개발도 잘하는 개발자&lt;/span&gt;가 되고 싶다 라는 생각을 하게 된 것 같다.&lt;br&gt; &lt;br&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;경험을 해야지만 성장할 수 있는 부분들이 분명히 있다고 생각하고 그만큼 경험은 값진 것&lt;/span&gt;이다.&lt;br&gt;나는 이번 경험을 계기로 여태 성장하지 못했던 역량들을 느끼고 깨닫고 성장할 수 있었던 거 같다.&lt;br&gt;앞으로는 겁먹지말고 다양한 경험을 하면서 다양한 방면으로 역량을 쌓아보고 싶다!&lt;br&gt;&lt;b&gt;2023년에도 겁먹지 말고 일단 해보자!&lt;/b&gt; 내 슬로건 처럼 실패했어도 배우면 실패가 아니다!&lt;br&gt; &lt;br&gt; &lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&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;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;643&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Bw9aQ/btrXmCn9dUv/iFF0betwt8oyLygWu1YbQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Bw9aQ/btrXmCn9dUv/iFF0betwt8oyLygWu1YbQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Bw9aQ/btrXmCn9dUv/iFF0betwt8oyLygWu1YbQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBw9aQ%2FbtrXmCn9dUv%2FiFF0betwt8oyLygWu1YbQ0%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;543&quot; height=&quot;576&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;643&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 분기마다 소위말하는 우수사원상을 주는데,&lt;br&gt;본부당 한두명만 받을 수 있고 본부의 리더가 뽑는 만큼 회사에서 역량을 잘 펼치는 사람들에게 주는 상이다. &lt;b&gt;(는 나의 희망사항이다)&lt;/b&gt;&lt;br&gt;내가 이번에 그 우수사원상을 받게 된 것이다!&lt;br&gt; &lt;br&gt;누군가 나에게 이 상을 받을 만큼 열심히 했냐고 물어본다면 나는 1초도 망설이지 않고 그렇다고 할 것이다.&lt;br&gt;어이없어 보일진 모르겠지만, &lt;span style=&quot;color: #ee2323;&quot;&gt;내가 잘했다는 게 아니라 회사를 위해서 정말 열심히 했다는 것&lt;/span&gt;이다.&lt;br&gt;야근도 많이 했고(야근이 열심히 했다는 결과 지표는 절대 아니다)&lt;br&gt;더 나은 성능을 위해 수 없이 공부하고 고민하고 개선해 나갔으며 수많은 이슈를 대응했다.&lt;br&gt;내가 한 일들이 모두 좋은 결과를 냈다고 자부할 순 없지만, &lt;b&gt;그럼에도 난 그저 최선&lt;/b&gt;을 다했다.&lt;br&gt; &lt;br&gt;그래서 나는 이 상을 받았을 때 열심히 한 나에게 주는 작은 선물이라고 생각하며 기분 좋게 받았던 것 같다.&lt;br&gt;가끔 이런 보상이 있어야 더 열심히 달릴 연료가 되지 않을까? &lt;span style=&quot;color: #ee2323;&quot;&gt;어디서든 무엇을 하든 지금처럼 그저 묵묵히 최선을 다하자!&lt;/span&gt;&lt;br&gt; &lt;br&gt; &lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&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;br&gt;취준생 시절에도 자소서 쓰는 거에는 자신이 있었던 터라 이번 이직준비에도 서류 합격은 어렵지 않았던 것 같다.&lt;br&gt; &lt;br&gt;하지만 &lt;b&gt;기술면접에서 나는 큰 벽을&lt;/b&gt; 마주치고 말았다.&lt;br&gt;신입 시절에는 사실 기술 질문은 거의 없거나 기본적인 CS 질문 수준이었다면,&lt;br&gt;경력직 이직은 &lt;span style=&quot;color: #ee2323;&quot;&gt;내가 경험한 기술들을 기반으로 깊고 넓게 질문&lt;/span&gt;을 한다.&lt;br&gt;어떤 식으로 준비해야 할지도 어떤 질문이 나올지도 몰랐던 나는 그냥 그렇게 여러 면접을 보면서 좌절하고 우울하고 힘들어했다.&lt;br&gt; &lt;br&gt;하지만 그럴 때마다 &lt;b&gt;딱 하루만 좌절하자!라는 마음&lt;/b&gt;으로 있는 힘껏 힘들어한 후에 다시 일어나서 면접 준비를 했다.&lt;br&gt;면접을 보다 보니 매번 면접마다 공통적으로 궁금해하는 나의 경험들이 있었고,&lt;br&gt;내가 &lt;span style=&quot;color: #ee2323;&quot;&gt;강점으로 내세워야 하는 경험&lt;/span&gt;이 무엇인지 알게 되었고 &lt;span style=&quot;color: #ee2323;&quot;&gt;원하는 면접 질문을 이끌어 나가는 스킬&lt;/span&gt;이 필요하다는 것을 알게 되었다.&lt;br&gt; &lt;br&gt;어느덧 처음 면접을 볼 때의 긴장감은 많이 해소되고 합불 결과보단 &lt;b&gt;면접을 통해서 하나라도 얻어가고자 하는 마음&lt;/b&gt;이 커졌던 것 같다.&lt;br&gt;그러다 보니 부담감이 줄어들었고 편하게 면접을 볼 수 있었고 오히려 더 자신감이 있는 모습으로 변했다.&lt;br&gt;이렇게 나는 무사히 이직을 마치고 꿈에 그리던 서비스 회사에 합격하게 되었다! &lt;br&gt; &lt;br&gt; &lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;우테캠 Pro 5기  &lt;/h2&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;861&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8uBRM/btrXnkUA0th/dYoCtZuoNM9uuKoBjMB1rK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8uBRM/btrXnkUA0th/dYoCtZuoNM9uuKoBjMB1rK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8uBRM/btrXnkUA0th/dYoCtZuoNM9uuKoBjMB1rK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8uBRM%2FbtrXnkUA0th%2FdYoCtZuoNM9uuKoBjMB1rK%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;593&quot; height=&quot;433&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;861&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;우테캠 Pro 5기를 수료한 것이 올해 나에게 있어서 기술적으로 가장 큰 아웃풋이&lt;/b&gt;라고 생각한다.&lt;br&gt; &lt;br&gt;회사를 다니면서 수업을 듣는다는 게 굉장히 힘든 일이었고, 매주 과제를 하는 게 꽤나 많은 시간을 투자해야 하는 일이었다.&lt;br&gt;과제도 그냥 한다고 되는 게 아니라 매번 리뷰어에게 리뷰를 받아야 했고 일정 요구사항을 만족시켜야 다음 단계로 넘어갈 수 있었다.&lt;br&gt; &lt;br&gt;직접 경험하지 않은 사람은 저걸 왜 힘들어하지?라고 생각할 수도 있다. &lt;b&gt;(나 또한 그랬으니)&lt;/b&gt;&lt;br&gt;과제라고 나오는 주제는 로또 생성기 만들기 같은 굉장히 간단한 과제들이다.&lt;br&gt;하지만 그 안에서 지켜야 하는 요구사항들과 TDD 작성, 객체지향 생활체조등의 조건을 만족시키며 개발하다 보면 시간이 꽤나 소요된다.&lt;br&gt;주차가 지날수록 이전 주차의 요구사항 + 이번 주차의 요구사항이 중첩되어 늘어나면서 점점 지켜야 하는 부분들은 늘어나고&lt;br&gt;또 배운 것들은 학습하고 적용하다 보면 과제의 난이도는 점점 올라간다.&lt;br&gt; &lt;br&gt;그래서 &lt;span style=&quot;color: #ee2323;&quot;&gt;오히려 쉬운 주제의 과제를 줬었다는 것이 더 많은 것을 느끼게 한다.&lt;/span&gt;&lt;br&gt;과연 정말 그 과제가 쉬웠던 건지, 내가 늘 해오던 방식 빠르지만 잘못된 방식으로 개발을 해서 쉽게 느껴졌던 것인지를 돌이켜 보게 됐다.&lt;br&gt;그만큼 우테캠 Pro 과정은 나에게 스킬적인 레벨업도 선물해 주었지만,&lt;br&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;여태까지 그리고 앞으로의 개발자로서의 건강한 마인드&lt;/span&gt;를 선물해 준 것 같다.&lt;br&gt; &lt;br&gt; &lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&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;br&gt; &lt;br&gt;메신저로 대뜸 휴업 통보를 받았던 날은 어안이 벙벙했고 어이가 없었다.&lt;br&gt;회사의 내부사정과 이유도 모른 채 그저 회사가 휴업 절차를 밟고 있으니 따르라는 것이었다.&lt;br&gt;이 회사에 더 다닐 수 없다고 판단한 나는 떠돌이 신세가 되었고 다시 한번 이직 준비를 하게 되었다.&lt;br&gt; &lt;br&gt;처음에 이직할 때는 그저 &lt;b&gt;회사의 기술과 네임벨류&lt;/b&gt; 같은 것들만 보고 지원을 했는데 이런 일들을 겪고 나니까 다른 것들도 보게 되었다.&lt;br&gt;지원하려는 회사가 &lt;span style=&quot;color: #ee2323;&quot;&gt;무슨 도메인이고 그 도메인에서 어떤 경쟁력을 갖추고 있는지 그리고 어떤 성과를 내고 있는지를&lt;/span&gt; 더 우선시 보게 되었다.&lt;br&gt;그리고 매번 면접을 볼 때 회사의 &lt;span style=&quot;color: #ee2323;&quot;&gt;내부상황을 투명하게 공유&lt;/span&gt;하는지 타운홀 미팅이 주기적으로 열리는지는 질문하게 되었다.&lt;br&gt; &lt;br&gt;회사에 애사심을 가지고 다니면서 개발을 하려면 적어도 &lt;b&gt;내가 속한 회사의 상황이 어떻고 내가 무엇을 만들고 있으며 이로 인해 유저들에게 어떤 경험을 선사&lt;/b&gt;하는지 알고 있어야 한다는 것을 느끼게 되었다.&lt;br&gt;더 까다로워졌지만 오히려 그래서 더 어렵지 않게 회사를 고르고 이직을 할 수 있었던 것 같다.&lt;br&gt;의도치 않았지만 더 좋은 서비스 회사로 이직하게 되었고 이 글을 작성하고 있는 2023년 1월 기준 열심히 적응하고 업무를 해나가고 있다.&lt;br&gt; &lt;br&gt; &lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;공부가 재밌었나  &lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해는 &lt;span style=&quot;color: #ee2323;&quot;&gt;스터디 활동도 유독 열심히&lt;/span&gt; 했던 거 같다.&lt;br&gt;이직 준비를 시작해서 그런지 모르겠지만 &lt;b&gt;학습에 욕구&lt;/b&gt;가 생겼고 혼자 공부하는 것보단 다른 사람들과 서로 열정을 가지고 학습하는 게 좀 더 좋은 효과가 있을 것 같다는 생각에 온라인 스터디를 시작했다.&lt;br&gt; &lt;br&gt;우테캠 프로라는 수업을 시작으로 JPA 스터디와 이펙티브 자바 스터디도 시작을 했고, 작게나마 토이 프로젝트 기획을 시작하게 되었다.&lt;br&gt;확실히 혼자 공부하는 것보다 더 열심히 할 수 있었고 &lt;b&gt;같은 내용이더라도 여러 시야로 바라보니 오히려 더 폭넓은 공부&lt;/b&gt;를 할 수 있게 되었다.&lt;br&gt;그리고 일정에 맞춰 진행하다 보니 &lt;b&gt;약간의 강제성이 있어에 루즈해지지 않고 꾸준히 진행&lt;/b&gt;할 수 있었다.&lt;br&gt;현재까지도 꾸준히 진행 중이며 그 안에서 많은 것들을 배우고 있다.&lt;br&gt;한 주제에 스터디가 마무리되면 또 다른 스터디를 꾸려서 진행하는 2023년이 되고 싶다.&lt;br&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;학습을 게을리하지 말자!&lt;/span&gt;&lt;br&gt; &lt;br&gt; &lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;저 진짜 재밌어서 하는 거예요 (억울)  &lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2022년은 어쩌다 보니 &lt;b&gt;자의로 한번 타의로 한번 이직준비&lt;/b&gt;를 하게 되었다. 그래서 1년 동안 정말 많은 기업의 면접을 봤다.&lt;br&gt; &lt;br&gt;아직 주니어 개발자라서(?) 그런지 면접을 볼 때마다 면접관분들이 공통적으로 하던 질문이 있었는데&lt;br&gt;&lt;b&gt;‘은혜님에게 개발이란 무엇인가요’&lt;/b&gt; 나 &lt;b&gt;‘왜 개발을 하게 되었나요’&lt;/b&gt; 같은 뉘앙스의 질문들이었다.&lt;br&gt;신입 시절에 나였다면 예상 면접 질문을 준비하며 작성했던 멋진 나만의 멘트로 대답을 했겠지만,&lt;br&gt;(사용자에게 좋은 서비스를 제공하고 싶다거나 사용자 친화적인 서비스를 개발하고 싶다거나.. 물론 아직도 이 마음은 가지고 있다)&lt;br&gt;이번엔 따로 준비를 안 하기도 했고 솔직하고 싶어서 정말 나의 날 것 그대로 대답을 하였다.&lt;br&gt; &lt;br&gt;내 대답은 어느 기업이든 상관없이 &lt;span style=&quot;color: #ee2323;&quot;&gt;‘그냥 개발이 재밌어서요’였다.&lt;/span&gt;&lt;br&gt;내 대답을 들으신 어느 면접관 분은 그저 말없이 웃기만 하시기도 하셨고 또 다른 면접관 분은 그런 거 말고 진짜 은혜님의 생각을 듣고 싶다고도 하기도 하였다.&lt;br&gt;그럴 때마다 나는 &lt;b&gt;‘진짜예요…’라고 다시 답변&lt;/b&gt;을 하곤 했다.&lt;br&gt; &lt;br&gt;물론 나는 개발을 한지 오래되지 않았다.&lt;br&gt;나보다 몇 배 오래 일하신 개발자 분들이라면 &lt;b&gt;'아직 한참 재밌을 때지 싶겠지만'&lt;/b&gt; 나는 취준 시절부터 단 한순간도 개발이 재미가 없었던 적이 없었다.&lt;br&gt;물론 ‘일’을 하다 보면 뜻대로 되지 않아서 힘들기도 하고 짜증 나기도 하고 한 때는 번아웃도 심하게 오기도 하지만 &lt;span style=&quot;color: #ee2323;&quot;&gt;그럼에도 불구하고 나는 그저 개발이 재미있었기&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;에 지금까지 할 수 있었다.&lt;/span&gt;&lt;br&gt;유저에게 제공할 기능을 만들고 그 기능을 개선하고 그 과정에서 여러 장애도 만나보고 그 장애를 밤새 해결해 보고 더 나은 성능을 위해 공부하고 토론하고 개발하고 &lt;span style=&quot;color: #ee2323;&quot;&gt;사실 챗바퀴 같다면 챗바퀴 같은 이 과정을 난 너무 즐겼고 사랑&lt;/span&gt;한다.&lt;br&gt;나는 이 마음을 잃고 싶지 않고 2023년에도 이런 마음으로 개발을 하고자 한다.&lt;br&gt;내가 개발을 엄청 잘하는 사람이 되기는 힘들진 몰라도 &lt;b&gt;개발을 엄청 재밌어하는 사람으로 손꼽히는 게 나의 큰 목표&lt;/b&gt;이다.&lt;br&gt; &lt;br&gt; &lt;/p&gt;
&lt;hr data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; &lt;br&gt;2022년 글에 다 적지 못할 만큼 몸 고생도 마음고생도 많이 했다.&lt;br&gt;하지만 나는 여태 그랬듯이 잘해왔고, 잘할 것이기 때문에 무너지지 않고 한 해를 버틸 수 있었다.&lt;br&gt;2023년에도 늘 초심 잃지 않고 꾸준히 노력하는 사람이 되어보자!&lt;br&gt; &lt;br&gt;&lt;b&gt;은혜야 잘해왔고, 잘할 거야!&lt;/b&gt;&lt;/p&gt;</description>
      <category>Mind Control</category>
      <category>2022년</category>
      <category>2022년회고</category>
      <category>Mindcontrol</category>
      <category>Programmner</category>
      <category>개발자</category>
      <category>면접</category>
      <category>스터디</category>
      <category>우테캠</category>
      <category>이직</category>
      <category>잘해왔고잘할거야</category>
      <author>개발자 김은혜</author>
      <guid isPermaLink="true">https://eunveloper.tistory.com/42</guid>
      <comments>https://eunveloper.tistory.com/entry/%EB%8A%A6%EC%9D%80-2022%EB%85%84-%ED%9A%8C%EA%B3%A0-%EC%9E%98%ED%95%B4%EC%99%94%EA%B3%A0-%EC%9E%98%ED%95%A0-%EA%B1%B0%EC%95%BC#entry42comment</comments>
      <pubDate>Fri, 27 Jan 2023 19:10:21 +0900</pubDate>
    </item>
    <item>
      <title>우아한 테크캠프 Pro 회고록 - EnableJpaAuditing</title>
      <link>https://eunveloper.tistory.com/entry/%EC%9A%B0%EC%95%84%ED%95%9C-%ED%85%8C%ED%81%AC%EC%BA%A0%ED%94%84-Pro-%ED%9A%8C%EA%B3%A0%EB%A1%9D-EnableJpaAuditing</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;벌써 우테캠의 2주 차 순서인 JPA도 얼마 남지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간이 정말 빠른거 같다! 특히 최근 2~3주 동안 너무너무 바빴어서 더 그런 거 같다.&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignLeft&quot; data-emoticon-type=&quot;niniz&quot; data-emoticon-name=&quot;029&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/029.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/029.gif&quot; width=&quot;150&quot; /&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;b&gt;@EnableJpaAuditing&lt;/b&gt; 어노테이션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA 첫번째 과제를 하다가 배우고 느낀 것들을 정리해보고자 한다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;사건의 발단&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;created_at timestamp not null,&lt;br /&gt;updated_at timestamp,&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;여러개의 엔티티를 DDL을 참고하여 JPA 매핑 작업을 하고 있었고,&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;더불어 EnableJpaAuditing 기능을 사용하여 개발자가 직접 데이터를 설정하는 것이 아닌&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔티티의 &lt;span style=&quot;color: #ee2323;&quot;&gt;INSERT, UPDATE 쿼리가 실행될 때 JPA가 자동으로 데이터를 설정해주는 방식&lt;/span&gt;으로 구현을 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 원하는 기능으로 구현을 하려면 다음과 같이 구성을 해야 한다.&lt;/p&gt;
&lt;pre id=&quot;code_1667895643214&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@EnableJpaAuditing&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1667895606583&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseTime {

    @CreatedDate
    @Column(nullable = false)
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime updatedAt;

    public LocalDateTime getCreatedAt() {
        return createdAt;
    }

    public LocalDateTime getUpdatedAt() {
        return updatedAt;
    }

}&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;먼저 애플리케이션 메인 메서드 상단에 &lt;b&gt;@EnableJpaAuditing 어노테이션&lt;/b&gt;을 달아준다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;객체의 입장에서&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt; 공통 매핑 정보가 필요할 때 사용하는 @MappedSuperClass를 해당 엔티티에 달아준다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555;&quot;&gt;Entity에서 이벤트가 발생할 때마다 특정 로직을 실행하는 @EntityListeners를 달아준다.&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;실행할 특정 로직인 AuditingEntityListener.class를 추가&lt;/span&gt;한다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555;&quot;&gt;내가 하고자 했던 거&lt;/span&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555;&quot;&gt;위의 코드로 엔티티를 구성하면 BaseTime을 상속받은 엔티티들이 INSERT 되거나 UPDATE 될 때 자동으로 데이터를 설정해준다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #555555;&quot;&gt;그런데 문득 이런 생각이 들었다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;INSERT에는 updatedAt을 NULL로 반영하고, &lt;b&gt;UPDATE 일 때만 updatedAt을 반영&lt;/b&gt;하고 싶다!&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 어떤 블로그를 참고해서 작업을 진행해보았다. (&lt;a href=&quot;https://wakestand.tistory.com/936&quot;&gt;https://wakestand.tistory.com/936&lt;/a&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;414&quot; data-origin-height=&quot;108&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cQpKSn/btrQJN9ZWz2/poVaYkVsrBUzWRJjlzGJYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cQpKSn/btrQJN9ZWz2/poVaYkVsrBUzWRJjlzGJYK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cQpKSn/btrQJN9ZWz2/poVaYkVsrBUzWRJjlzGJYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcQpKSn%2FbtrQJN9ZWz2%2FpoVaYkVsrBUzWRJjlzGJYK%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;414&quot; height=&quot;108&quot; data-origin-width=&quot;414&quot; data-origin-height=&quot;108&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;2006&quot; data-origin-height=&quot;348&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9A6a4/btrQJpO6eeX/oW48CNi6bkWJLeKd4XBe8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9A6a4/btrQJpO6eeX/oW48CNi6bkWJLeKd4XBe8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9A6a4/btrQJpO6eeX/oW48CNi6bkWJLeKd4XBe8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9A6a4%2FbtrQJpO6eeX%2FoW48CNi6bkWJLeKd4XBe8k%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;2006&quot; height=&quot;348&quot; data-origin-width=&quot;2006&quot; data-origin-height=&quot;348&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;updateAt 컬럼에 &lt;span style=&quot;color: #ee2323;&quot;&gt;insertable = false 라는 설정값을 추가&lt;/span&gt;하였다.&lt;/li&gt;
&lt;li&gt;그러니 다음의 쿼리처럼 INSERT 할 때 값이 반영되지 않은걸 볼 수 있었다.&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&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;326&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cUQzwG/btrQKvgJEbH/5uTQKww7F2jEJMZn9XjFL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUQzwG/btrQKvgJEbH/5uTQKww7F2jEJMZn9XjFL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUQzwG/btrQKvgJEbH/5uTQKww7F2jEJMZn9XjFL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUQzwG%2FbtrQKvgJEbH%2F5uTQKww7F2jEJMZn9XjFL0%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;936&quot; height=&quot;326&quot; data-origin-width=&quot;936&quot; data-origin-height=&quot;326&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이상하게도 실제 save() 함수가 실행된 후의 객체를 디버깅해보니&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;updatedAt에 데이터가 들어가져 있는 것&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;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1378&quot; data-origin-height=&quot;664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcdpIz/btrQJvV1t1N/A8ymYByeIGxsHOwVxQfDuk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcdpIz/btrQJvV1t1N/A8ymYByeIGxsHOwVxQfDuk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcdpIz/btrQJvV1t1N/A8ymYByeIGxsHOwVxQfDuk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcdpIz%2FbtrQJvV1t1N%2FA8ymYByeIGxsHOwVxQfDuk%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;671&quot; height=&quot;323&quot; data-origin-width=&quot;1378&quot; data-origin-height=&quot;664&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;댓글이 20개&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;h2 data-ke-size=&quot;size26&quot;&gt;그래서 결론이 무엇이냐&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거두절미하고 해결했던 해결방안이 무엇이었냐면&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;@EnableJpaAuditing(modifyOnCreate = false)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이었다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 작성했던 (insertable = false) 설정을 할 필요 없이 애플리케이션 메인 메서드 상단에 작성했던&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;@EnableJpaAuditing 어노테이션에 (modifyOnCreate=false) 값을 설정&lt;/span&gt;하는 거였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(이 자리를 빌려 큰 도움을 주신 백 XX님 그리고 또 다른 많은 분들 모두 모두 다시 한번 감사합니다!)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;그렇다면 왜&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저 설정을 주어야 하는 걸까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이유를 알아보기 위해선 위에 BaseTime 엔티티에 설정해 주었던 &lt;span style=&quot;background-color: #ffffff; color: #555555;&quot;&gt;&lt;b&gt;AuditingEntityListener의 분석이 필요&lt;/b&gt;했다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;780&quot; data-origin-height=&quot;378&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cV30jY/btrQJvaFh0r/rqG5LnH3fdugXHmmowlj81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cV30jY/btrQJvaFh0r/rqG5LnH3fdugXHmmowlj81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cV30jY/btrQJvaFh0r/rqG5LnH3fdugXHmmowlj81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcV30jY%2FbtrQJvaFh0r%2FrqG5LnH3fdugXHmmowlj81%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;557&quot; height=&quot;270&quot; data-origin-width=&quot;780&quot; data-origin-height=&quot;378&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;AuditingEntityListener에는 touchForCreate와 touchForUpdate가 있다.&lt;/li&gt;
&lt;li&gt;두 개의 구성은 거의 비슷한데 마지막 줄이 &lt;b&gt;markCreated() / markModified()&lt;/b&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;하지만 두 가지 경우 모두 메서드를 타고 들어가 보면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;876&quot; data-origin-height=&quot;648&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kK9n0/btrQJuXaD7w/QkdQjFmlqNiBQ51tfWKzU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kK9n0/btrQJuXaD7w/QkdQjFmlqNiBQ51tfWKzU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kK9n0/btrQJuXaD7w/QkdQjFmlqNiBQ51tfWKzU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkK9n0%2FbtrQJuXaD7w%2FQkdQjFmlqNiBQ51tfWKzU0%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;562&quot; height=&quot;416&quot; data-origin-width=&quot;876&quot; data-origin-height=&quot;648&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;touch()라는 동일한 메서드가 실행된 결과를 리턴하게 된다!&lt;/li&gt;
&lt;li&gt;touch() 메소드 내부에는 &lt;b&gt;touchAuditor() 메서드를 또 실행하는데 이 부분이 포인트&lt;/b&gt;이다!&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;460&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mDT8P/btrQJ9Zj2nd/FaxLS2U2vWA3ybmi9xTtH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mDT8P/btrQJ9Zj2nd/FaxLS2U2vWA3ybmi9xTtH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mDT8P/btrQJ9Zj2nd/FaxLS2U2vWA3ybmi9xTtH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmDT8P%2FbtrQJ9Zj2nd%2FFaxLS2U2vWA3ybmi9xTtH1%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;769&quot; height=&quot;314&quot; data-origin-width=&quot;1128&quot; data-origin-height=&quot;460&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막 두 개의 조건을 확인해보면 isNew (created 시에는 true가, modified 시에는 false가 들어간다.)라는 값과&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;modifyOnCreation이라는 값을 가지고 무언가의 행위를 하는데,&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;즉, &lt;span style=&quot;color: #ee2323;&quot;&gt;modified 시에 modifyOnCreation 값에 따라 값을 세팅할지 말지를 결정&lt;/span&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;자체적으로 기본값이 true&lt;/b&gt;로 되어있었다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;492&quot; data-origin-height=&quot;32&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9Xc4u/btrQEIpbn3R/Pn8kqHXCM8khgvNpwGP3M0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9Xc4u/btrQEIpbn3R/Pn8kqHXCM8khgvNpwGP3M0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9Xc4u/btrQEIpbn3R/Pn8kqHXCM8khgvNpwGP3M0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9Xc4u%2FbtrQEIpbn3R%2FPn8kqHXCM8khgvNpwGP3M0%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;492&quot; height=&quot;32&quot; data-origin-width=&quot;492&quot; data-origin-height=&quot;32&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 나는 @EnableJpaAuditing(modifyOnCreate=false)를 통해서 저 값을 변경하여 사용해야 했던 것이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;그럼 안녕! (너무 급하게 포스팅이 마무리된 감이 있네)&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>Programing/Jpa</category>
      <category>auditing</category>
      <category>AuditingEntityListener</category>
      <category>EnableJpaAuditing</category>
      <category>jpa</category>
      <category>MappedSuperclass</category>
      <category>modifyOnCreate</category>
      <category>spring</category>
      <category>우테캠</category>
      <category>우테캠Pro</category>
      <author>개발자 김은혜</author>
      <guid isPermaLink="true">https://eunveloper.tistory.com/41</guid>
      <comments>https://eunveloper.tistory.com/entry/%EC%9A%B0%EC%95%84%ED%95%9C-%ED%85%8C%ED%81%AC%EC%BA%A0%ED%94%84-Pro-%ED%9A%8C%EA%B3%A0%EB%A1%9D-EnableJpaAuditing#entry41comment</comments>
      <pubDate>Tue, 8 Nov 2022 18:18:53 +0900</pubDate>
    </item>
    <item>
      <title>우아한 테크캠프 Pro 프리코스 회고록 - 객체지향 생활체조</title>
      <link>https://eunveloper.tistory.com/entry/%EC%9A%B0%EC%95%84%ED%95%9C-%ED%85%8C%ED%81%AC%EC%BA%A0%ED%94%84-Pro-%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-%ED%9A%8C%EA%B3%A0%EB%A1%9D</link>
      <description>&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;b&gt;우아한 테크 캠프 Pro 5기에 선발&lt;/b&gt;돼서 열심히 공부도 병행하는 중이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 이미 1주차에 들어섰지만 프리코스 때 배운 점들을 정리해보고자 뒤늦게나마 포스팅을 작성해본다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignLeft&quot; data-emoticon-type=&quot;niniz&quot; data-emoticon-name=&quot;001&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/001.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/001.gif&quot; width=&quot;150&quot; /&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;h2 data-ke-size=&quot;size26&quot;&gt;else 예약어를 쓰지 않는다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발자라면 조건문의 중요성을 너무나 잘 알거라고 생각한다! 나 또한 조건문을 이용한 코딩을 많이 하는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;else 예약어를 자주 썼던 나로써는 굉장히 당황스러웠다.&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;blockquote data-ke-style=&quot;style3&quot;&gt;힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;힌트를 듣고 나는 아래와 같이 코딩을 해봤다! List를 적절한 조건에 맞춰서 생성하여 리턴하는 메소드이다.&lt;/p&gt;
&lt;pre id=&quot;code_1667026982033&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private List&amp;lt;Car&amp;gt; resetWinCars(List&amp;lt;Car&amp;gt; winCars, Car car) {
    if (compareResult == MANY) {
        winCars = new ArrayList&amp;lt;&amp;gt;();
        winCars.add(car);
        return winCars;
    }
    if (compareResult == SAME) {
        winCars.add(car);
        return winCars;
    }
    return winCars;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;if문으로 임의의 조건을 건다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;조건에 해당될 때 바로 리스트를 생성해서 리턴&lt;/span&gt;해준다.&lt;/li&gt;
&lt;li&gt;즉, else문 없이 원하는 리스트를 받을 수 있다.&lt;/li&gt;
&lt;/ul&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;b&gt;일급 콜렉션&lt;/b&gt;이란 무엇일까?&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;List, Map 같은 Collection을 Class로 포장하면서, 그 외 다른 멤버 변수가 없는 상태를 뜻한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;일급 콜렉션에 대해서 잘 정리되어있는 내가 개인적으로 존경하는 개발자분의 포스팅 링크이다.&lt;br /&gt;이번 포스팅은 개념적인 정리보다는 내가 사용하면서 느꼈던 점을 작성하는 회고이기 때문에 개념은 링크로 대체한다!&lt;br /&gt;https://jojoldu.tistory.com/412&lt;/blockquote&gt;
&lt;pre id=&quot;code_1667323105862&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Lottos {

    private List&amp;lt;Lotto&amp;gt; lottos;
    
    public Lottos(List&amp;lt;Lotto&amp;gt; lottos) {
    	this.lottos = lottos;
    }

    public void generate(int generatorCount) {
        for (int i = 0; i &amp;lt; generatorCount; i++) {
            Lotto lotto = new Lotto();
            lotto.generateRandomNumber();
            lottos.add(lotto);
        }
    }

    public LottoResult calculatorLotto(Lotto lastWeekLotto, LottoNumber bonusNumber) {
        LottoResult lottoResult = new LottoResult();
        for (Lotto lotto : lottos) {
            int sameCount = lotto.sameNumberCount(lastWeekLotto);
            boolean containBonus = lotto.containBonusNumber(bonusNumber);
            lottoResult.addResult(sameCount, containBonus);
        }
        return lottoResult;
    }

    public void addedLotto(Lotto lotto) {
        this.lottos.add(lotto);
    }

    public void addedLottos(Lottos lottos) {
        this.lottos.addAll(lottos.lottos);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;포장된 클래스의 이름만 보더라도 해당 Collection이 어떤 의미를 가지고 있는지 알 수 있다.&lt;/li&gt;
&lt;li&gt;Lottos 클래스가 항상 List&amp;lt;Lotto&amp;gt;의 상태를 유지하고 있기 때문에 &lt;span style=&quot;color: #ee2323;&quot;&gt;행위의 구현에 대한 파라미터가 필요 없다&lt;/span&gt;.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;List&amp;lt;Lotto&amp;gt;에 대해서 무언가의 비즈니스 로직이 이뤄져야 할 때 메서드를 호출하며 파라미터로 넘기는 방식이 아닌 Lottos 객체 자체에 메소드를 구현하면 된다.&lt;/li&gt;
&lt;li&gt;Lottos가 List&amp;lt;Lotto&amp;gt;를 Wrapper 하여 가지고 있기 때문에 &lt;b&gt;메서드에 불필요한 파라미터가 필요하지 않게&lt;/b&gt; 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;List가 제공하는 public 메서드들을 비즈니스 설계에 따라서 제한&lt;/span&gt;할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;List&amp;lt;Lotto&amp;gt;에 대한 비즈니스 로직이 Lottos 클래스에 메서드로 구현되어 있기 때문에&lt;/li&gt;
&lt;li&gt;메서드의 접근제어자에 따라서 외부에서 호출 제한을 아주 정확하게 컨트롤할 수 있다.&lt;/li&gt;
&lt;li&gt;그렇게 되면 설계상 &lt;b&gt;List&amp;lt;Lotto&amp;gt;에 가해져서는 안될 행위를 미연에 방지&lt;/b&gt;할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;List&amp;lt;Lotto&amp;gt;에 관련된 로직이 Lottos 클래스에 모두 모여있기 때문에 유지보수가 간편하다.&lt;/li&gt;
&lt;/ol&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;이 말은 int, long, String 같은 &lt;b&gt;원시 타입, 문자열 변수를 객체로 포장해서 사용&lt;/b&gt;하라는 의미이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 사용하면서 어떤 이점이 있었는지 확인해보자.&lt;/p&gt;
&lt;pre id=&quot;code_1667323456545&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class LottoNumber implements Comparable&amp;lt;LottoNumber&amp;gt; {

    private int number;

    public LottoNumber(int number) {
        this.number = number;
    }

    public LottoNumber(String number) {
        try {
            this.number = Integer.parseInt(number);
        } catch (Exception e) {
            throw new IllegalArgumentException(ONLY_NUMBER_PAST_LOTTO_INPUT);
        }
        if (this.number &amp;lt; 1 || this.number &amp;gt; 45) {
            throw new IllegalArgumentException(ONLY_NUMBER_BETWEEN_1_TO_45);
        }
    }

    public int value() {
        return number;
    }

    @Override
    public int compareTo(LottoNumber number) {
        if (this.number &amp;gt; number.number) {
            return 1;
        }
        if (this.number &amp;lt; number.number) {
            return -1;
        }
        return 0;
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;일급 콜렉션과 비슷한 의미로 Wrapping 된 원시 값과 문자열의 의도를 클래스 명으로 바로 확인할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LottoNumber i 와 int i 를 보면 (사실 변수명을 이렇게 지으면 안 되지만) 보자마자 바로 체감할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Comparable 같은 &lt;span style=&quot;color: #ee2323;&quot;&gt;인터페이스를 Override 하여 재정의&lt;/span&gt; 할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클래스의 도메인 설계에 따라서 필요한 인터페이스를 Overriding 하여 재정의 하면&lt;/li&gt;
&lt;li&gt;필요할 때마다 &lt;b&gt;비즈니스 로직을 구현할 필요 없이 바로 사용&lt;/b&gt;할 수 있으며&lt;/li&gt;
&lt;li&gt;LottoNumber 클래스 안에 종속되어 있기 때문에 &lt;b&gt;유지보수가 간편&lt;/b&gt;하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;생성자를 적극 활용&lt;/span&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;하지만 Class의 &lt;b&gt;생성자를 활용하면 중복 코드도 없앨 수 있고 Class 내에 모든 로직을 개발&lt;/b&gt;할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 내가 우테캠 Pro 프리코스를 경험하며 느꼈던 많은 내용 중에 &lt;span style=&quot;color: #ee2323;&quot;&gt;'크게 와닿았던'&lt;/span&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;b&gt;실제 수강생이 돼서 1주 차를 수강&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;942&quot; data-origin-height=&quot;102&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pRwRo/btrP6gNsVUh/tlhMYG1NxIuC9q5fbGZB40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pRwRo/btrP6gNsVUh/tlhMYG1NxIuC9q5fbGZB40/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pRwRo/btrP6gNsVUh/tlhMYG1NxIuC9q5fbGZB40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpRwRo%2FbtrP6gNsVUh%2FtlhMYG1NxIuC9q5fbGZB40%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;942&quot; height=&quot;102&quot; data-origin-width=&quot;942&quot; data-origin-height=&quot;102&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 기회로 우테캠 Pro의 수강생이 되어 수강을 하고 있으니 최선을 다해서 수료해보고자 한다!&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>Programing/Java</category>
      <category>java</category>
      <category>객체지향</category>
      <category>객체지향 생활체조</category>
      <category>우테캠</category>
      <category>우테캠Pro</category>
      <category>우테캠프리코스</category>
      <category>우테캠회고</category>
      <category>일급콜렉션</category>
      <author>개발자 김은혜</author>
      <guid isPermaLink="true">https://eunveloper.tistory.com/40</guid>
      <comments>https://eunveloper.tistory.com/entry/%EC%9A%B0%EC%95%84%ED%95%9C-%ED%85%8C%ED%81%AC%EC%BA%A0%ED%94%84-Pro-%ED%94%84%EB%A6%AC%EC%BD%94%EC%8A%A4-%ED%9A%8C%EA%B3%A0%EB%A1%9D#entry40comment</comments>
      <pubDate>Wed, 2 Nov 2022 02:38:31 +0900</pubDate>
    </item>
    <item>
      <title>Java Stream 모르고(?) 쓰면 일어나는 일들</title>
      <link>https://eunveloper.tistory.com/entry/Java-Stream-%EB%AA%A8%EB%A5%B4%EA%B3%A0-%EC%93%B0%EB%A9%B4-%EC%9D%BC%EC%96%B4%EB%82%98%EB%8A%94-%EC%9D%BC%EB%93%A4</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 포스팅은 다소 어이없을(?) 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘의 주제는 Java Stream의 일부인데, 모르고 쓴다기보단 막 쓰면 일어나는 일이라고 해야 할 수도 있겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;더 나아가자면 Stream의 문제가 아닌 그냥 &lt;span style=&quot;color: #ee2323;&quot;&gt;개발자의 태도의 관한 포스팅&lt;/span&gt;이라고 생각하면 편할거 같다.&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignLeft&quot; data-emoticon-type=&quot;niniz&quot; data-emoticon-name=&quot;016&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/016.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/016.gif&quot; width=&quot;150&quot; /&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;이전 회사에서는 Stream을 자주 사용하지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분을 향상된 For문과 조건문을 활용해서 각종 필터링 작업을 진행하고, 그룹핑 해야하는 부분에서만 간혹 사용하곤 했다.&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;그런데 이번 회사는 신기할정도로 Stream을 자주 쓰고 거의 대부분이 Stream을 통해 구현되어 있다.&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;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;똑같은 코드는 아니고 객체명이나 변수명들은 대충 치환하였다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1663217430445&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;User&amp;gt; Users = userDtos.stream()
    .peek(x -&amp;gt; x.setXxx(userDtoMap.get(String.valueOf(x.getXxx()))))
    .peek(x -&amp;gt; x.setYyy(userDtoMap.get(String.valueOf(x.getYyy))))
    .peek(x -&amp;gt; x.setZzz(UserType.getType(x.getType(), String.valueOf(x.getZzz()))))
    .filter(x -&amp;gt; x.getPage() == page).collect(Collectors.toList());&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;이걸 보면서 여러 가지 생각이 들었다.&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; peek() 메소드는 무슨 역할을 하는 걸까? &quot;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot; 변수를 세팅해주기 위해 한 개의 변수당 한개의 peek() 메소드를 호출할 필요가 있을까? &quot;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&quot; filter() 처리를 하고 peek() 메소드를 호출하는 게 낫지 않을까? &quot;&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;pre id=&quot;code_1663301299446&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;String&amp;gt; ids = Arrays.asList(&quot;첫번째(ok)&quot;, &quot;두번째(ok)&quot;, &quot;세번째(ok)&quot;, &quot;네번째(no)&quot;);
List&amp;lt;String&amp;gt; newIds = ids.stream()
                        .peek(x -&amp;gt; System.out.println(&quot;first peek() : &quot; + x))
                        .peek(x -&amp;gt; System.out.println(&quot;second peek() : &quot; + x))
                        .peek(x -&amp;gt; System.out.println(&quot;third peek() : &quot; + x))
                        .filter(x -&amp;gt; x.contains(&quot;no&quot;))
                        .collect(Collectors.toList());
System.out.println(&quot;newIds size : &quot; + newIds.size());&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;peek() 메소드는 무언가의 작업을 한다.&lt;/li&gt;
&lt;li&gt;peek() 메소드를 1번 이상 호출한다.&lt;/li&gt;
&lt;li&gt;filter() 메소드를 통해 원하는 값만 추출한다.&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;결과는 다음과 같았다.&lt;/p&gt;
&lt;pre id=&quot;code_1663301517661&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;first peek() : 첫번째(ok)
second peek() : 첫번째(ok)
third peek() : 첫번째(ok)
first peek() : 두번째(ok)
second peek() : 두번째(ok)
third peek() : 두번째(ok)
first peek() : 세번째(ok)
second peek() : 세번째(ok)
third peek() : 세번째(ok)
first peek() : 네번째(no)
second peek() : 네번째(no)
third peek() : 네번째(no)
newIds size : 1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Wow 놀라운 일이 일어나고 있었다.&lt;/b&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 data-ke-size=&quot;size16&quot;&gt;하지만 &lt;span style=&quot;color: #ee2323;&quot;&gt;filter()를 선행하지 않아서 불필요한 행위들이 진행되었고, peek() 메서드를 매번 부르면서 리스트를 계속 순회&lt;/span&gt;하고 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트한 리스트의 사이즈가 작아서 별 문제가 안 되겠지만,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 백건 천 건, 더 나아가 만 건 이상의 데이터가 있는 리스트를 세 번 순회하며 무언가의 행위를 한다면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 로직은 &lt;b&gt;상당히 비효율적이고 느린 로직&lt;/b&gt;이 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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 id=&quot;code_1663303586747&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;List&amp;lt;String&amp;gt; ids = Arrays.asList(&quot;첫번째(ok)&quot;, &quot;두번째(ok)&quot;, &quot;세번째(ok)&quot;, &quot;네번째(no)&quot;);
List&amp;lt;String&amp;gt; newIds = ids.stream()
                        .filter(x -&amp;gt; x.contains(&quot;no&quot;))
                        .peek(x -&amp;gt; System.out.println(&quot;first + second + third peek() : &quot; + x))
                        .collect(Collectors.toList());
System.out.println(&quot;newIds size : &quot; + newIds.size());&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1663303607640&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;first + second + third peek() : 네번째(no)
newIds size : 1&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;span style=&quot;color: #ee2323;&quot;&gt;선 필터링하여 추출한 후, 내가 하고자 하는 연산을 한 번에 처리&lt;/span&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignLeft&quot; data-emoticon-type=&quot;niniz&quot; data-emoticon-name=&quot;035&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/035.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/035.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;파이팅!&lt;/p&gt;</description>
      <category>Programing/Java</category>
      <category>filter()</category>
      <category>for</category>
      <category>foreach</category>
      <category>java</category>
      <category>Java Stream</category>
      <category>Java8</category>
      <category>peek()</category>
      <category>Programming</category>
      <category>spring</category>
      <category>stream</category>
      <author>개발자 김은혜</author>
      <guid isPermaLink="true">https://eunveloper.tistory.com/39</guid>
      <comments>https://eunveloper.tistory.com/entry/Java-Stream-%EB%AA%A8%EB%A5%B4%EA%B3%A0-%EC%93%B0%EB%A9%B4-%EC%9D%BC%EC%96%B4%EB%82%98%EB%8A%94-%EC%9D%BC%EB%93%A4#entry39comment</comments>
      <pubDate>Fri, 16 Sep 2022 13:54:49 +0900</pubDate>
    </item>
    <item>
      <title>Spring MVC 톺아보기 (1) - Controller</title>
      <link>https://eunveloper.tistory.com/entry/Spring-MVC-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-1-Controller</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요! 해피 추석입니다  &lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignLeft&quot; data-emoticon-type=&quot;niniz&quot; data-emoticon-name=&quot;002&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/002.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/002.gif&quot; width=&quot;150&quot; /&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 style=&quot;color: #ee2323;&quot;&gt;Swagger UI를 도입&lt;/span&gt;하여 사용하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swagger UI를 사용하면 컨트롤러에 설정한 모든 Endpoint나 Parameter들이 개발자가 세팅하지 않아도&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면으로 구성된 칸에 작성만 해주면 알아서 적절하게 API Request를 날려준다.&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;하지만 지금 다니는 회사에서 한 서버에 Swagger UI를 사용하지 않고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swagger UI 물론 굉장히 쉽고 간편하지만, 너무 편하게 사용한 나머지 어떻게 사용되는지를 생각하지 않고 개발을 하게 되어서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Swagger UI를 사용하지 않는 서버에서 API 테스트를 하려고 하니 앞이 막막했다.&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을 사용하여 API를 테스트하는데, 자동으로 세팅해주던 Swagger UI와는 다르게&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;추석 연휴를 맞이하여 Spring MVC Docs를 읽으면서 개념 공부&lt;/b&gt;를 할 예정이다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #333333;&quot;&gt;굉장히 많은 부분이 있지만 컨트롤러를 위주로 공부해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-controller&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.spring.io/spring-framework/docs/current/reference/html/web.html#mvc-controller&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;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;@Controller vs @RestController&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨트롤러 행위를 하고자 하는 클래스에 Annotation을 달아서 컨트롤러임을 명시하는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 어노테이션의 차이는 간단하게 설명하자면 두 가지의 차이는 반환하는 데이터 형식의 차이이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Controller는 &lt;span style=&quot;color: #ee2323;&quot;&gt;전통적인 Spring MVC의 컨트롤러로 View를 반환&lt;/span&gt;하기 위해 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 View가 아닌 일반 Data를 반환해야 하는 경우도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 모두가 알다시피 @RequestBody 어노테이션을 함께 달아 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 &lt;span style=&quot;color: #ee2323;&quot;&gt;두 어노테이션이 합쳐진게 @RestController&lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 어노테이션을 구조를 살펴보면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1662714708489&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Component
public @interface Controller {}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1662714738388&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Controller
@ResponseBody
public @interface RestController {}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@RestController의 어노테이션에 @Controller와 @ResponseBody 어노테이션이 모두 달려있음을 확인할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;참고!&lt;br /&gt;&lt;br /&gt;&lt;b&gt;@RequestMapping 어노테이션만 달려있어도 Spring Controller로 인식&lt;/b&gt;한다.&lt;br /&gt;일반 @Controller와 같은 매핑 역할을 해준다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Mapping Reqeust&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@RequestMapping 어노테이션을 사용하면 Request를 컨트롤러 메소드에 매핑할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;URL, HTTP Method, Reqeust Parameter, Header 등 여러 값을 매핑&lt;/span&gt;할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1662739423726&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public @interface RequestMapping {
    String name() default &quot;&quot;;
    String[] value() default {};
    String[] path() default {};
    RequestMethod[] method() default {};
    String[] params() default {};
    String[] headers() default {};
    String[] consumes() default {};
    String[] produces() default {};
}&lt;/code&gt;&lt;/pre&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;b&gt;HTTP Method : &lt;/b&gt;method
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;HTTP 요청 메소드를 명시하며 RequestMethod (Enum) 값을 적어준다.&lt;/li&gt;
&lt;li&gt;그 외에 어노테이션 방식으로 작성할 수도 있다.&amp;nbsp;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@GetMapping: GET&lt;/li&gt;
&lt;li&gt;@PostMapping : POST&lt;/li&gt;
&lt;li&gt;@PutMapping : PUT&lt;/li&gt;
&lt;li&gt;@DeleteMapping : DELETE&lt;/li&gt;
&lt;li&gt;@PatchMapping : PATCH&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 정보들은 @RequestMapping 어노테이션의 필드들과 매칭 되어 사용된다.&lt;/p&gt;
&lt;pre id=&quot;code_1662735773066&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@AliasFor(annotation = RequestMapping.class)
String name() default &quot;&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 같은 기능을 하지만 HTTP Method를 추가 매핑한다는 점이 추가된 것이다.&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;b&gt;Endpoint : &lt;/b&gt;path / value&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무 필드 이름을 명시하지 않고 값만 작성하면 기본적으로 path / value 값으로 명시된다&lt;/p&gt;
&lt;pre id=&quot;code_1662735998628&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@GetMapping(&quot;/{id}&quot;)
public Person getPerson(@PathVariable Long id) {
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;참고!&lt;br /&gt;&lt;br /&gt;@RequestMapping 에는 &lt;b&gt;path와 value 값이 존재하는데 두 개는 같은 의미&lt;/b&gt;를 뜻한다!&lt;br /&gt;코드를 살펴보면 두 가지가 서로 @AliasFor 어노테이션을 통해서 서로의 이름을 향하고 있다.&lt;br /&gt;&lt;br /&gt;@AliasFor(&quot;path&quot;) String [] value() default {};&lt;br /&gt;@AliasFor(&quot;value&quot;) String [] path() default {};&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;URI 패턴
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;PathPattern&lt;/li&gt;
&lt;li&gt;AntPathMatcher&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공식 문서에 따르면 &lt;span style=&quot;color: #ee2323;&quot;&gt;PathPattern은 웹 애플리케이션에 권장되며 Spring WebFlux에서 유일하게 선택&lt;/span&gt;했다고 명시되어 있으며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring 버전 5.3 이전에는 AntPathMatcher 뿐이었고 기본값이지만 그 후 PathPattern을 사용할 수 있다.&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;Stack Overflow 답변에 따르면 &lt;span style=&quot;color: #000000;&quot;&gt;PathPattern의 처리량이 6-8배와 할당 비율에 30-40% 감소&lt;/span&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;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;URI 변수로 계산&lt;/li&gt;
&lt;li&gt;단일 와일드카드로 계산&lt;/li&gt;
&lt;li&gt;이중 와일드카드로 계산&lt;/li&gt;
&lt;li&gt;동일한 점수 시, 긴 패턴이 선택&lt;/li&gt;
&lt;li&gt;동일한 점수와 동일한 길이의 패턴 일시 와일드카드보다 URI 변수가 더 많은 패턴이 선택&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Spring MVC 5.3 이상부터 .* 패턴을 지원하지 않는다고 한다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;브라우저가 일관되게 해석하기 어려운 Header를 보낼 때 위와 같은 방식을 사용했지만&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;현재로서는 더 이상 필요하지 않기 때문에 &lt;span style=&quot;color: #ee2323;&quot;&gt;Accept Header를 사용하는 것이 선호&lt;/span&gt;된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1662737578900&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RequestMapping(value = &quot;/user*&quot;, method = RequestMethod.GET)&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/x7Pfd/btrLOmCyBjd/QRUKT25lKs5d7DnDEzx0K0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/x7Pfd/btrLOmCyBjd/QRUKT25lKs5d7DnDEzx0K0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/x7Pfd/btrLOmCyBjd/QRUKT25lKs5d7DnDEzx0K0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fx7Pfd%2FbtrLOmCyBjd%2FQRUKT25lKs5d7DnDEzx0K0%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;622&quot; height=&quot;130&quot; data-origin-width=&quot;746&quot; data-origin-height=&quot;156&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;pre id=&quot;code_1662737586586&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RequestMapping(value = &quot;/user.*&quot;, method = RequestMethod.GET)&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 alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1178&quot; data-origin-height=&quot;376&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkSgpJ/btrLK6BeIsx/khzAjJmyPo6s5qjvSlZFdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkSgpJ/btrLK6BeIsx/khzAjJmyPo6s5qjvSlZFdK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkSgpJ/btrLK6BeIsx/khzAjJmyPo6s5qjvSlZFdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkSgpJ%2FbtrLK6BeIsx%2FkhzAjJmyPo6s5qjvSlZFdK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;669&quot; height=&quot;214&quot; data-origin-width=&quot;1178&quot; data-origin-height=&quot;376&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Media Type :&amp;nbsp;&lt;/b&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;consumes / &lt;span style=&quot;background-color: #ffffff;&quot;&gt;produces&lt;/span&gt;&amp;nbsp;&lt;/span&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333;&quot;&gt;consumes : 요청이 들어올 때의 데이터 타입&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;produces : 요청이 나갈 때의 데이터 타입&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;부정표현도 지원 ex) consumes = &quot;!test/plain&quot;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1662738268084&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@PostMapping(path = &quot;/pets&quot;, consumes = &quot;application/json&quot;)
@GetMapping(path = &quot;/pets/{petId}&quot;, produces = &quot;application/json&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;Parameter / Header :&amp;nbsp;&lt;/b&gt;params / headers
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;정해진 값이 Parameter와 Header로 들어왔을 때 요청을 허용&lt;/li&gt;
&lt;li&gt;params : 정해진 파라미터만 허용&lt;/li&gt;
&lt;li&gt;headers : 정해진 헤더 값만 허용&lt;/li&gt;
&lt;li&gt;마찬가지로 부정 표현도 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1662738829839&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@GetMapping(path = &quot;/user&quot;, params = &quot;userId=ehkim&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;388&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d5QkEn/btrLLgQ6Sm5/VxuelNzF8tOmLn2pkvlEV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d5QkEn/btrLLgQ6Sm5/VxuelNzF8tOmLn2pkvlEV0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d5QkEn/btrLLgQ6Sm5/VxuelNzF8tOmLn2pkvlEV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd5QkEn%2FbtrLLgQ6Sm5%2FVxuelNzF8tOmLn2pkvlEV0%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;671&quot; height=&quot;210&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;388&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Handler Method&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;&lt;b&gt;요청&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;@RequestParam
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;서블릿 요청 매개변수 (쿼리 매개변수)를 바인딩&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;@RequestParam(&quot;petId&quot;)&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt; &lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;int&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt; petId&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;localhost:8080?petId=value&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;기본적으로 아무 어노테이션이 없다면 @RequestParam 타입이다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;필수 매개변수임을 명시하지만 required 플래그를 false로 지정하거나 Optional 인수로 선언하여 선택사항으로 지정할 수 있다.&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;Map&amp;lt;String, String&amp;gt; 또는 MultiValueMap&amp;lt;String, String&amp;gt; 이용하여 적절한 key 값으로 파라미터를 조회할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;@RequestHeader&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px; color: #ee2323;&quot;&gt;요청 헤더를 바인딩 &lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;Host, Accept, Accept-Language, Accept-Encoding, Accept-Charset, Keep-Alive&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px; color: #333333;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;@RequestHeader(&quot;Accept-Encoding&quot;)&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt; String encoding&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;Map&amp;lt;String, String&amp;gt; 또는 MultiValueMap&amp;lt;String, String&amp;gt; 이용하여 적절한 key 값으로 파라미터를 조회할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;쉼표로 구분된 문자열은 Stirng[] 또는 List&amp;lt;String&amp;gt;으로 변환하여 사용할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;@CookieValue&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;HTTP 쿠키 값을 바인딩&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;@CookieValue(&quot;JSESSIONID&quot;)&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt; String cookie&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;@ModelAttribute&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;모델의 속성에 액세스 하거나 존재하지 않는 경우 인스턴스화&lt;/span&gt; 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;@ModelAttribute로 명시된 인스턴스는 아래의 방법 중 한 가지로 소싱된다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;@ModelAttribute 매개변수로 넘어왔을 모델을 통해 검색&lt;/li&gt;
&lt;li&gt;@SessionAttributes가 클래스 수준에 명시되어 있을 경우 HTTP 세션에서 검색&lt;/li&gt;
&lt;li&gt;경로 변수나 요청 매개변수에서 이름이 일치하는 곳에서 검색&lt;/li&gt;
&lt;li&gt;기본 생성자를 통해 인스턴스화&lt;/li&gt;
&lt;li&gt;서블릿 요청 매개변수와 일치하는 인수가 있는 기본 생성자를 통해 인스턴스화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;@SessionAttributes &lt;b&gt;(Controller 내 속성)&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;HTTP 요청 시 서블릿 세션에 모델 속성을 저장하는 데 사용된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;@SessiontAttributes(&quot;pet&quot;) : 서블릿 세션에 값 저장&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;SessionSatus.setComplete() : 서블릿 세션에서 값 삭제&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;@SessionAttribute &lt;b&gt;(Controller 외 속성)&lt;/b&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;Controller 외 (인터셉터나 필터)에서 생성된 세션 데이터에 접근하는 데 사용된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;@RequestAttribute&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;서블릿 필터나 스프링 인터셉터 이전에 생성된 요청 속성 값에 바인딩한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;@RequestAttribute&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt; Client client&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;리디렉션&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;어떤 요청의 결과로 페이지를 포워딩해야 하는 경우 새로고침 등의 행위를 방지하고자 결과를 redirect 처리하기 위한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;이를 통해 &lt;span style=&quot;color: #ee2323;&quot;&gt;데이터를 리디렉션 대상으로 전달&lt;/span&gt;할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;return &quot;redirect:{url}&quot;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;하지만 위 방식이면 데이터의 정보를 숨길 수 없고 인코딩 등의 문제가 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;그때 사용하는 것이 RedirectAttributes 객체이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;플래시&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;데이터를 리디렉션 대상을 전달할 수 있는 다른 방법&lt;/span&gt;이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;Post - Redirect - Get 패턴에 주로 쓰인다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;플래시 속성 값들은 리디렉션 후 요청에 사용할 수 있도록 세션에 임시로 저장되고 즉시 제거된다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;@RequestMapping 어노테이션이 달린 컨트롤러에서는 직접 작업할 필요는 없다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span&gt;RedirectAttributes를 사용하면 자동으로 FlashMap에 전파된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;멀티파트&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;MultipartResolver가 활성화되면 &lt;span style=&quot;color: #ee2323;&quot;&gt;multipart/form-data가 포함된 POST의 요청에 액세스&lt;/span&gt; 할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;List&amp;lt;Multipartfile&amp;gt; : 동일한 이름에 대한 여러 개의 파일&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;@RequestParam으로도 액세스가 가능하지만 설정에 따라 Json으로 역직렬화 역할이 실행될 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;기본적으로는 @MultiPart와 함께 사용할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;@RequestBody&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;요청 데이터를 직렬화&lt;/span&gt; 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;@Validate 어노테이션과 함께 유효성 검사를 제공한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;@ResponseBody는 직렬화를 담당하는 거고 데이터 포맷팅 (json, xml) 행위는 별개로 HttpMessageConverter가 처리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span&gt;HttpEntity&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;@RequestBody + Content + Header&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;String 기반의 요청 입력을 담당하는 어노테이션인 @RequestParam, @RequestHeader, @PathVariable, @MatrixVariable, @CookieValue는 다른 데이터 타입으로 선언되어있을 시, &lt;b&gt;구성한 변환기에 따라서 자동으로 형식 변환&lt;/b&gt;이 적용된다. (아래에 정리된 DataBinder의 역할)&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;응답&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;@ResponseBody
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;응답 데이터를 직렬화&lt;/span&gt; 한다.&lt;/li&gt;
&lt;li&gt;클래스 레벨과 메서드 레벨을 모두 지원한다.&lt;/li&gt;
&lt;li&gt;@ResponseBody는 직렬화를 담당하는거고 데이터 포맷팅 (json, xml) 행위는 별개로 HttpMessageConverter가 처리한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ResponseEntity
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;@ResponseBody + Status + Header&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;유형에 따라서 비동기 처리 등을 지원한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&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;b&gt;HttpMessageConverter&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;@RequestBody, @ResponseBody로 요청이나 응답의 데이터 변환 처리 시 사용하는 컨버터 인터페이스&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;변환을 하고자 하는 데이터 타입에 맞춰 실제 구현체 클래스&lt;/span&gt;를 사용한다.
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;StringHttpMessageConverter
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;text/*&lt;/li&gt;
&lt;li&gt;Content-Type : text/plain&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;FormHttpMessageConverter
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;application/x-www-form-urlencoded&lt;/li&gt;
&lt;li&gt;MultiValueMap&amp;lt;String, Object&amp;gt;&lt;/li&gt;
&lt;li&gt;multipart/form-data&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ByteArrayHttpMessageConverter
&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;Content-Type : application/octet-stream&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;MarshallingHttpMessageConverter (org.springframework.oxm.Marshaller)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;text/xml&lt;/li&gt;
&lt;li&gt;applicatioin/xml&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;MappingJackson2HttpMessageConverter
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;application/json&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SourceHttpMessageConverter (javax.xml.transform.Source)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;text/xml&lt;/li&gt;
&lt;li&gt;application/xml&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;BufferedImageHttpMessageConverter
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;java.awt.image.BufferedImage&lt;/li&gt;
&lt;li&gt;Java I/O API&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;DataBinder&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Controller 또는 @ControllerAdvice가 명시된 클래스는 @InitBinder를 통해 WebDataBinder를 초기화하고 다음과 같은 기능을 수행한다.&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;문자열 기반의 요청 값(Parameter, Path, Header, Cookie 등)을 컨트롤러 대상 유형으로 변환&lt;/li&gt;
&lt;li&gt;문자열 양식의 데이터를 HTML 양식으로 렌더링&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 클라이언트가 서버에게 요청을 할 때 &lt;span style=&quot;color: #ee2323;&quot;&gt;클라이언트가 명시한 값들을 서버(Spring Web MVC)에서 사용되는 각종 객체들로 바인딩&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;b&gt;Exception&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;@ExceptionHandler를 이용하여 예외를 처리&lt;/span&gt;할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Controller, @RestController가 적용된 Bean 내에서 발생하는 예외를 처리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇기 때문에 @Service나 @Repository가 적용된 @Bean에서는 사용할 수 없다.&lt;/p&gt;
&lt;pre id=&quot;code_1662742246242&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@ExceptionHandler({FileSystemException.class, RemoteException.class})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예외처리를 하고자 하는 예외 클래스의 이름들을 명시하여 특정 예외 유형 목록을 사용할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1662743750664&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@ExceptionHandler(NullPointerException.class)
public String handle(NullPointerException ex) {
    return &quot;NullPointerException&quot;;
}

@ExceptionHandler(RuntimeException.class)
public String handle(RuntimeException ex) {
    return &quot;RuntimeException&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 상황처럼 예외처리가 핸들링되는 경우에는 좀 더 하위 예외가 우선순위를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 위의 @ExceptionHandler가 명시된 컨트롤러에서 NullPointException이 발생하면 상위의 handler 메서드가 실행된다.&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;b&gt;최대한 구체적으로 명시하는 것이 좋다.&lt;/b&gt;&lt;br /&gt;즉, 전파되는 최상위 예외나 래퍼 예외 같은 예외를 명시하는 것보다 구체적으로 정의된 예외를 명시하는 것이 좋다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@ExceptionHandler의 여러 인수들과 반환 값들은 굉장히 다양하니 공식 문서를 확인하자!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;REST 서비스는 일반적으로 예외를 그대로 응답하는 것이 아닌, 응답 본문에 오류 세부 정보를 포함하는 것이다.&lt;br /&gt;하지만 Spring Framework는 이를 자동으로 수행하지 않으니, 이러한 핸들러를 활용하여 예외를 적절하게 재정의 후 응답하는 것이다.&lt;/blockquote&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;b&gt;Controller Advice&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@ExceptionHandler와 @InitBinder 어노테이션은 @Controller가 가 적용된 클래스에만 적용되지만&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;@ControllerAdvice와 @RestControllerAdvice는 모든 컨트롤러에 적용&lt;/span&gt;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 @Component 어노테이션이 달려있어 &lt;b&gt;스프링 빈으로 등록하여 사용&lt;/b&gt;할&amp;nbsp; 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1662741754514&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@ControllerAdvice
@ResponseBody
public @interface RestControllerAdvice&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@ResponseBody 어노테이션이 달려있기 때문에 HTML이 아닌&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&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;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;background-color: #ffffff;&quot;&gt;내부 속성 값들을 통해서 일부 컨트롤러에 매핑하여 사용할 수도 있다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1662741889247&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@ControllerAdvice(annotations = RestController.class)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;작성된 값들은 런타임에 평가가 되고 광범위하게 사용하게 되면 성능의 부정적인 영향을 미칠 수 있으니 주의해야 한다!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;/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;Spring Web MVC 공식문서를 한번 훑어보며 공부&lt;/b&gt;를 해보고자 한다!&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignLeft&quot; data-emoticon-type=&quot;niniz&quot; data-emoticon-name=&quot;030&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/030.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/030.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가보자고!&lt;/p&gt;</description>
      <category>API</category>
      <category>Controller</category>
      <category>http</category>
      <category>http2</category>
      <category>MVC</category>
      <category>REST API</category>
      <category>spring</category>
      <category>spring mvc</category>
      <category>Spring Web MVC</category>
      <category>web</category>
      <author>개발자 김은혜</author>
      <guid isPermaLink="true">https://eunveloper.tistory.com/38</guid>
      <comments>https://eunveloper.tistory.com/entry/Spring-MVC-%ED%86%BA%EC%95%84%EB%B3%B4%EA%B8%B0-1-Controller#entry38comment</comments>
      <pubDate>Wed, 14 Sep 2022 14:54:36 +0900</pubDate>
    </item>
    <item>
      <title>Concurrency Problem 올바른 대응방법 (1) - Lock</title>
      <link>https://eunveloper.tistory.com/entry/Concurrency-Problem-%EC%98%AC%EB%B0%94%EB%A5%B8-%EB%8C%80%EC%9D%91%EB%B0%A9%EB%B2%95-1-Lock</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;동시성 이슈 문제를 해결했던 업무 회고를 작성했던 지난 포스팅에 이어서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시성 이슈 문제의 해결 방법 중 하나인 Lock이라는 거에 대해서 좀 더 자세히 알아보고자 한다.&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;지난 포스팅에서 나는 동시성 이슈를 JPA의 Lock Annotation을 이용하여 대응을 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시성 이슈에 대해서 공부 하다보면 대응을 위해서 Redis 데이터베이스를 썼다는 이야기가 많이 나온다.&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;나는 그래서 Lock을 사용하고자 했던 거고 이번 기회에 Lock에 대해 더 공부를 하게 되었다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Lock?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;Lock이란 트랜잭션 처리의 순차성을 보장하기 위한 방법&lt;/span&gt;으로,&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;DBMS마다 Lock을 구현하는 방식이 다르기 때문에 원하는 DB별 Lock에 대한 이해가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 Application 레벨에서도 설정할 수 있다.&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;style3&quot;&gt;개인적으로는 접근하고자 하는 데이터나 비지니스 상황에 따라 Lock의 설정을&lt;br /&gt;다르게 하고자 할 수도 있기 때문에 Application 레벨이 좀 더 유연하게 대처할 수 있지 않을까 라는 생각을 한다.&lt;br /&gt;또한 데이터베이스 레벨에서 Lock을 설정하여 구현하게 되면 성능 저하를 야기할 수도 있지 않을까? 싶기도 하다.&lt;/blockquote&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;b&gt;Database (Postgresql)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 DB의 Lock는 &lt;span style=&quot;color: #ee2323;&quot;&gt;공유(Shared) Lock과 베타(Exclusive) Lock&lt;/span&gt;이 있으며&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공유 Lock은 Read Lock 이라고도 불리고 배타 Lock은 Write Lock 이라고도 불린다.&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;공유(Shared) Lock&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;데이터를 읽을때 사용되는 Lock&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;같은 공유 Lock 끼리는 동시에 접근이 가능&lt;/li&gt;
&lt;li&gt;즉, 하나의 데이터를 읽는 것은 여러 사용자가 동시에 할 수 있다는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;베타(Exclusive) Lock&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;데이터를 변경하고자 할때 사용되는 Lock&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;트랜잭션이 완료될 때까지 유지&lt;/li&gt;
&lt;li&gt;Lock이 해제될 때까지 다른 트랜잭션(Read 포함)은 데이터에 접근할 수 없음&lt;/li&gt;
&lt;/ul&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;DB별로 Lock에 대한 세부설정이 다르기 때문에 일단 내가 동시성 이슈를 대응했던 Postgresql 기준으로 알아보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lock의 종류가 위의 두가지를 기준으로 좀 더 세부적으로 존재하는 듯했다.&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;Read Lock - AccessSharedLock&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;AccessSharedLock은 SEELCT 명령문으로 잡히는 Lock이다.&lt;/li&gt;
&lt;li&gt;첫 번째 세션이 트랜잭션을 생성하여 계정 정보를 조회하고,&lt;/li&gt;
&lt;li&gt;두 번째 세션이 다른 트랜잭션으로 동일한 계정 정보를 조회하더라고 &lt;span style=&quot;color: #ee2323;&quot;&gt;이상 없이 동일한 정보를 조회&lt;/span&gt;한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1662701464711&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;session1&amp;gt; begin; 
session1&amp;gt; select user_id, company from user_info where email = 'admin';
email | company
-----------------
admin | [NULL]


session2&amp;gt; begin; 
session2&amp;gt; select email, company from user_info where email = 'admin';
email | company
-----------------
admin | [NULL]&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1662701577457&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;select locktype, relation::regclass, mode, transactionid tid, pid, granted 
from pg_catalog.pg_locks where not pid=pg_backend_pid();

relation	user_info	AccessShareLock		6322	true&lt;/code&gt;&lt;/pre&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;b&gt;Write Lock - RowExclusiveLock&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;RowExclusiveLock은 UPDATE, DELETE, INSERT 명령문으로 잡고 있는 Lock이다.&lt;/li&gt;
&lt;li&gt;첫 번째 세션이 트랜잭션을 시작하고 데이터를 수정한 후, 수정된 데이터를 조회하면 수정된 데이터가 정상적으로 조회된다.&lt;/li&gt;
&lt;li&gt;하지만 두 번째 세션이 트랜잭션을 새로 시작하고 첫 번째 세션이 &lt;span style=&quot;color: #ee2323;&quot;&gt;트랜잭션을 종료하기 전에 동일 데이터를 조회&lt;/span&gt;하면&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;수정되기 전에 데이터로 조회&lt;/span&gt;되는것을 확인할 수 있다.&lt;/li&gt;
&lt;li&gt;이는 데이터의 정합성과 무결성을 정하는 격리수준인 Isolation level이 &lt;b&gt;대부분의 DB에서는 Read Committed&lt;/b&gt;로 되어있기 때문이다.&lt;/li&gt;
&lt;li&gt;이것은 &lt;span style=&quot;color: #ee2323;&quot;&gt;Commit을 한 정보&lt;/span&gt;만 다른 세션 또는 트랜잭션에서 확인할 수 있음을 의미한다.&lt;/li&gt;
&lt;li&gt;RowExclusiveLock은 ExclusiveLock 중에 하나로 데이터를 변경할 때 다른 사람이 동시에 바꿀수 없도록 쓰기 잠금을 걸고, 쓰기를 마치면 잠금을 해제한다.&lt;/li&gt;
&lt;li&gt;물론 쓰기 잠금이 걸려있을 때는 다른 잠금을 걸 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1662701867140&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;session1&amp;gt; begin; 
session1&amp;gt; select email, company from user_info where email = 'admin';
email | company
-----------------
admin | [NULL]
session1&amp;gt; update user_info set company = 'company' where email = 'admin';
session1&amp;gt; select email, company from user_info where email = 'admin';
email | company
-----------------
admin | company&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1662701901788&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;session2&amp;gt; begin; 
session2&amp;gt; select email, company from user_info where email = 'admin';
email | company
-----------------
admin | [NULL]&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1662701912987&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;select locktype, relation::regclass, mode, transactionid tid, pid, granted 
from pg_catalog.pg_locks where not pid=pg_backend_pid();

virtualxid	[NULL]	ExclusiveLock		6417	true&lt;/code&gt;&lt;/pre&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;b&gt;Race Condition - SharedLock&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;첫번째 세션이 트랜잭션을 생성하고 데이터를 수정한 후, 트랜잭션이 종료되지 않은 상황에서&lt;/li&gt;
&lt;li&gt;두번째 세션이 트랜잭션을 생성하여 &lt;span style=&quot;color: #ee2323;&quot;&gt;동일 데이터에 수정 작업을 진행하게 되면 쿼리가 정상적으로 작동하지 않고 대기&lt;/span&gt;를 한다.&lt;/li&gt;
&lt;li&gt;그 후에 첫번째 세션의 트랜잭션이 &lt;b&gt;정상적으로 종료된 후&lt;/b&gt;에야 두 번째 세션의 데이터 수정 작업이 완료된다.&lt;/li&gt;
&lt;li&gt;Lock 정보를 조회해보면 Shared Lock이 추가되었음을 확인할 수 있다.&lt;/li&gt;
&lt;li&gt;Shared Lock은 공유 잠금 또는 읽기 잠금이라는 뜻으로&lt;/li&gt;
&lt;li&gt;동시에 데이터를 변경할 때 생기는 문제를 보호하기 위하여 먼저 Lock을 잡은 TransactionId에 공유를 요청하는 Lock으로&lt;/li&gt;
&lt;li&gt;첫 번째 세션이 먼저 UPDATE 행위를 통해 ExclusiveLock을 잡고 있었기 때문에 SharedLock과 Conflict 되어 Lock이 Granted 되지 않았던 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1662702433333&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;session1&amp;gt; begin; 
session1&amp;gt; update user_info set company = 'company' where email = 'admin';
UPDATE 1

session2&amp;gt; begin; 
session2&amp;gt; update user_info set company = 'company2' where email = 'admin';&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;283&quot; data-origin-height=&quot;132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/K3xUN/btrLK5ILOfi/5e48pXNz327P1YMxzMC6K1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/K3xUN/btrLK5ILOfi/5e48pXNz327P1YMxzMC6K1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/K3xUN/btrLK5ILOfi/5e48pXNz327P1YMxzMC6K1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FK3xUN%2FbtrLK5ILOfi%2F5e48pXNz327P1YMxzMC6K1%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;283&quot; height=&quot;132&quot; data-origin-width=&quot;283&quot; data-origin-height=&quot;132&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1662702457690&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;session1&amp;gt; commit;&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1662702470842&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;select locktype, relation::regclass, mode, transactionid tid, pid, granted 
from pg_catalog.pg_locks where not pid=pg_backend_pid();

transactionid	[NULL]	ShareLock	1129	8902	false&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;등등 그 외에도 굉장히 다양한 종류의 Lock이 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;739&quot; data-origin-height=&quot;501&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWlpDV/btrLKJTvT1J/cK3mvKw4Eemy4clp2PokS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWlpDV/btrLKJTvT1J/cK3mvKw4Eemy4clp2PokS1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWlpDV/btrLKJTvT1J/cK3mvKw4Eemy4clp2PokS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWlpDV%2FbtrLKJTvT1J%2FcK3mvKw4Eemy4clp2PokS1%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;543&quot; height=&quot;368&quot; data-origin-width=&quot;739&quot; data-origin-height=&quot;501&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;b&gt;Application&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 실제로 사용한 방식인 JPA를 이용한 방식으로 Lock mode를 설정하는 방식을 알아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot; data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;JPA에서는 Optimistic Lock (낙관적 락)과 Pessimistic Lock (비관적 락)을 제공한다.&lt;/span&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;Optimistic Lock&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;데이터 갱신 시 충돌이 발생하지 않을 것이라고 낙관적으로 보고 잠금을 거는 기법&lt;/span&gt;이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;동시에 동일한 데이터에 대한 여러 업데이트가 서로 간섭하지 않도록 방지하는 version이라는 속성을 확인하여 Entity의 변경사항을 감지하는 메커니즘이다.&lt;/li&gt;
&lt;li&gt;JPA에서 낙관적 잠금을 사용하기 위해 &lt;span data-token-index=&quot;1&quot; data-reactroot=&quot;&quot;&gt;Entity 내부에 @Version 어노테이션이 붙은 변수를 구현&lt;/span&gt;하여 줌으로써 간단하게 구현이 가능하다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;@Version 명시에 주의사항&lt;/b&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;각 Entity 클래스에는 하나의 버전 속성만 있어야 한다.&lt;/li&gt;
&lt;li&gt;여러 테이블에 매핑된 Entity의 경우 기본 테이블에 배치되어야 한다.&lt;/li&gt;
&lt;li&gt;버전에 명시할 타입은 int, Integer, long, Long, short, Short, java.sql.Timestamp 중 하나여야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;JPA는 Select시에 트랜잭션 내부에 버전 속성의 값을 보유하고 트랜잭션이 업데이트를 하기 전에 버전 속성을 다시 확인한다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #d44c47;&quot; data-token-index=&quot;1&quot; data-reactroot=&quot;&quot;&gt;그동안에 버전 정보가 변경이 되면 OptimisticLockException이 발생&lt;/span&gt;하고 변경되지 않으면 트랜잭션은 버전 속성을 증가하는 업데이트를 하게 된다.&lt;/li&gt;
&lt;li&gt;LockMode Type
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;NONE&lt;/b&gt; : 별도의 옵션을 사용하지 않아도 Entity에 @Version이 적용된 필드만 있으면 낙관적 잠금이 적용된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;OPTIMISTIC&lt;/b&gt; (Read) : Entity 수정 시에만 발생하는 낙관적 잠금이 읽기 시에도 발생하도록 설정한다. 읽기 시에도 버전을 치크하고 트랜잭션이 종료될 때까지 다른 트랜잭션에서 변경하지 않음을 보장한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;OPTIMISTIC_FORCE_INCREMENT&lt;/b&gt; (Write) : 낙관적 잠금을 사용하면서 버전 정보를 강제로 증가시키는 옵션이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;READ, WRITE&lt;/b&gt; : READ는 OPTIMISTIC과 같은 역할, WRITE는 OPTIMISTIC_FORCE_INCREMENT와 같은 역할을 하며 JPA 1.0호 환성을 위한 옵션이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;OptimisticLockException
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;권장되는 예외처리 방법에서는 Entity를 다시 로드하거나 새로고침하여 업데이트를 재 시도하는 방법이다.&lt;/li&gt;
&lt;li&gt;Entity에서 낙관적 잠금 충돌을 감지하면 OptimisticLockException을 발생시키게 되고 트랜잭션은 롤백 처리를 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1662702698630&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
public class Student {
    @Id
    private Long id;
 
    private String name;
 
    private String lastName;
 
    @Version
    private Integer version;
}&lt;/code&gt;&lt;/pre&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;b&gt;&lt;span style=&quot;color: #000000;&quot; data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;Pessimistic Lock&lt;/span&gt;&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #000000;&quot; data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;트랜잭션의 충돌이 발생한다고 가정하고 우선 락을 걸고 보는 방법&lt;/span&gt;으로 트랜잭션 안에서 서비스 로직이 진행되어야 한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;LockMode Type
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;PESSIMISTIC_READ&lt;/b&gt; : dirty read가 발생하지 않을 때마다 공유 잠금을 획득하고 데이터가 UPDATE, DELETE 되는 것을 방지할 수 있다.&lt;/li&gt;
&lt;li&gt;데이터베이스에 따라 PESSIMISTIC_READ를 지원하지 않는 경우엔 PESSIMISTIC_WRITE로 대체된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;PESSIMISTIC_WRITE&lt;/b&gt; : 배타적 잠금을 획득하고 데이터를 다른 트랜잭션에서 READ, UPDATE, DELETE 하는 것을 방지할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;PESSIMISTIC_FORCE_INCREMENT&lt;/b&gt; : 이 잠금은 PESSIMISTIC_WRITE와 유사하게 작동하지만 @Version이 지정된 Entity와 협력하기 위해 도입되어 PESSIMISTIC_FORCE_INCREMEMT 잠금을 획득할 시 버전이 업데이트된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Exception
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;PessimisticLockException : 잠금은 ShareLock 또는 Exclusive Lock 둘 중에 하나만 얻을 수 있으며 그 락을 얻는데 실패하면 발생하는 예외&lt;/li&gt;
&lt;li&gt;LockTimeoutException : 락을 대기하다 설정해놓은 wait time이 초과되었을 때 발생하는 예외&lt;/li&gt;
&lt;li&gt;PersistenceException&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;Lock Scope
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;NORMAL&lt;/b&gt; : 기본값으로 해당 entity 만 잠금이 설정되고, @Inheritance(strategy = InheritanceThype.JOINED)와 같이 조인 상속을 사용하면 부모도 함께 잠금이 설정된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;EXTENDED&lt;/b&gt; : @ElememtCollection, @OneToOne, @OneToMany 등 연관된 entitye들도 잠금이 설정된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 내가 동시 결제견에 대한 처리를 이렇게 했다고 정의할 수 있을 것 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1662702915063&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Lock(LockModeType.PESSIMISTIC_WRITE)&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;결제 건에 대해서는 항상 한 트랜잭션으로 관리를 한다.&lt;/li&gt;
&lt;li&gt;즉, 결제 관련 데이터를 조회하고 결제를 요청하고 결제가 완료되면 데이터를 수정해서 다시 저장하는 일련의 과정을 하나의 트랜잭션으로 관리하는 것이다.&lt;/li&gt;
&lt;li&gt;하나의 트랜잭션이 일정 결제 건을 조회한다면 Dirty Read를 방지하고자 PESSIMISTIC_WRITE의 LockMode로 데이터를 조회해온다.&lt;/li&gt;
&lt;li&gt;LockScope는 결제정보 해당 Entity에만 걸고 연관된 Entity에는 걸지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;추가로 더 해줘야 하는 것?&lt;br /&gt;&lt;br /&gt;데이터에 Lock을 설정하다 보면 (특히 Exclusive Lock) 일정 트랜잭션이 일정 데이터를 점유하고 있는 동안 다른 트랜잭션에 접근이 거부되기 때문에 무한정 대기를 하게 되거나 예외를 내게 되는데 이런 경우의 적절한 처리가 필요할 것이다!&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;&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;특히 결제는 돈이 걸려있는 문제다 보니 더 신중하게 여러 경우의 수를 생각하며 개발하는 것이 좋을 것 같다!&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignLeft&quot; data-emoticon-type=&quot;niniz&quot; data-emoticon-name=&quot;022&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/022.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/022.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(오늘도 성장하는 나)&lt;/p&gt;</description>
      <category>Programing/Java</category>
      <category>concurrency</category>
      <category>database</category>
      <category>jpa</category>
      <category>lock</category>
      <category>LockMode</category>
      <category>postgresql</category>
      <category>spring</category>
      <category>springboot</category>
      <category>동시성</category>
      <category>동시성이슈</category>
      <author>개발자 김은혜</author>
      <guid isPermaLink="true">https://eunveloper.tistory.com/37</guid>
      <comments>https://eunveloper.tistory.com/entry/Concurrency-Problem-%EC%98%AC%EB%B0%94%EB%A5%B8-%EB%8C%80%EC%9D%91%EB%B0%A9%EB%B2%95-1-Lock#entry37comment</comments>
      <pubDate>Fri, 9 Sep 2022 15:09:34 +0900</pubDate>
    </item>
    <item>
      <title>CloudFront와 S3를 이용한 정적 데이터 조회 (2)</title>
      <link>https://eunveloper.tistory.com/entry/CloudFront%EC%99%80-S3%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%A0%95%EC%A0%81-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A1%B0%ED%9A%8C-2</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;포스팅 1탄에 이어서 2탄을 들고 왔다.&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;&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;span style=&quot;color: #ee2323;&quot;&gt;원본 데이터가 저장된 데이터베이스와 캐시 데이터를 가지고 있는 캐시 서버의&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;데이터 동기화 문제&lt;/span&gt;를 많이들 경험하게 될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 기능도 마찬가지로 원본 데이터가 저장되어 있는 데이터베이스(S3)와 캐싱 처리를 하고 있는 캐시 서버(CloudFront) 사이의 데이터 동기화를 항상 신경 써 주어야 한다.&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;S3에서 원본 데이터가 변경되었음을 CloudFront에서는 알 수 있는 방법이 없기 때문에 동기화 처리는 직접 해주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 1탄에서 CloudFront를 구성할 때 설정한 TTL값 만큼의 시간이 흐르고 난 뒤에는 다시 S3에서 데이터를 조회해 오기 때문에 설정한 시간만큼의 여유를 감안할 데이터라면 굳이 임의의 처리를 하지 않고 그저 TTL값 만큼의 시간을 기다리면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 데이터 별로 데이터가 변경됨을 바로 감지하고 캐싱 서버에 바로 반영해야 한다면 하염없이 그 시간을 기다릴 순 없을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럴 때 사용할 수 있는 방법을 소개한다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;630&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JGRaY/btrLDSQT8lQ/NLw3lMkNtmkRoVQSi8D61K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JGRaY/btrLDSQT8lQ/NLw3lMkNtmkRoVQSi8D61K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JGRaY/btrLDSQT8lQ/NLw3lMkNtmkRoVQSi8D61K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJGRaY%2FbtrLDSQT8lQ%2FNLw3lMkNtmkRoVQSi8D61K%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;567&quot; height=&quot;298&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;630&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;span style=&quot;color: #ee2323;&quot;&gt;엣지 캐시에서 파일을 무효화&lt;/span&gt;한다. 그러면 다음에 파일을 요청하면 CloudFront는 최신 버전의 파일을 가져오도록 오리진에 반환한다.&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;/images/image1.jpg&lt;/li&gt;
&lt;li&gt;/images/image*&lt;/li&gt;
&lt;li&gt;/images/*&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CloudFront API 방식으로 파일 무효화를 할 수 있다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;파일 무효화:&amp;nbsp;&lt;a href=&quot;https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_CreateInvalidation.html&quot;&gt;CreateInvalidation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;무효화 목록 가져오기:&amp;nbsp;&lt;a href=&quot;https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_ListInvalidations.html&quot;&gt;ListInvalidations&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;특정 무효화 관련 정보 가져오기:&amp;nbsp;&lt;a href=&quot;https://docs.aws.amazon.com/cloudfront/latest/APIReference/API_GetInvalidation.html&quot;&gt;GetInvalidation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포당 한 번에 최대 3,000개의 파일에 대해 진행 중인 무효화 요청이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와일드카드를 사용하는 경우 한 번에 최대 15개의 무효화 경로에 대해 진행 중인 요청이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;월별로 제출한 최초 1,000개의 무효화 경로에 대해서는 무료&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;b&gt;CloudFront 배포를 사용하여 기존 콘텐츠 업데이트&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CloudFront가 제공하는 콘텐츠 관리에 대한 제어를 강화하기 위해 파일 이름이나 폴더 이름에 버전 식별자를 사용하여 파일을 제거한다.&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;image.jpg 대신 image_1.jpg로 초기 저장&lt;/li&gt;
&lt;li&gt;image_2.jpg로 변경 후 &lt;span style=&quot;color: #ee2323;&quot;&gt;웹 앱 사이트의 접근 링크 변경&lt;/span&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;파일의 버전을 관리하는 경우라도 만료 날짜는 설정하는 것이 좋다.&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;b&gt;참고&lt;/b&gt;&lt;/h3&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;파일을 자주 업데이트하려는 경우에는 기본적으로 파일 버전 관리를 사용하는 것이 좋다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 데이터를 조회하는 클라이언트는 업데이트가 되어 변경된 접근 링크 endpoint를 알 수 없는 구조였기 때문이다.&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;Java + Spring Boot 환경이며 기본적으로 제공하는 AWS SDK를 사용하였다.&lt;/p&gt;
&lt;pre id=&quot;code_1662100879358&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void cacheReload(String path) {
    AmazonCloudFront cfClient =
            AmazonCloudFrontClientBuilder.standard()
            .withRegion(region).build();

    String[] paths = {addSeparatorPrefix(path)};
    Paths invalidationPaths = new Paths().withItems(paths).withQuantity(paths.length);
    InvalidationBatch batch = new InvalidationBatch(invalidationPaths, Long.toString(System.currentTimeMillis()));

    cfClient.createInvalidation(new CreateInvalidationRequest(distributionId, batch));
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;캐시를 무효화시키는 부분이다.&lt;/li&gt;
&lt;li&gt;CloudFront SDK를 이용하여 원하는 경로에 캐시 데이터를 무효화해준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1662100911623&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public String uploadObject(String bucketId, File uploadFile, MultipartFile attachment, String keyName) {
    AmazonS3 s3Client = AmazonS3ClientBuilder.standard()
            .withRegion(region)
            .build();
    String bucketName = getBucketName(bucketId);

    try{
        ObjectMetadata metadata = new ObjectMetadata();
        metadata.setContentType(attachment.getContentType());
        metadata.setContentLength(attachment.getSize());

        PutObjectRequest request = new PutObjectRequest(bucketName, keyName, uploadFile);
        request.setMetadata(metadata);

        log.info(&quot;upload s3 object: bucket[{}], key[{}]&quot;, bucketName, keyName);
        s3Client.putObject(request);

        return s3Client.getUrl(bucketName,keyName).toString();
    } catch (Exception e) {
        log.error(&quot;uploadObject Error Message : {}&quot;, e.getMessage());
    }

    return &quot;&quot;;
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원본 데이터를 업로드하는 부분이다.&lt;/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;&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;기본적인 SDK가 너무 잘 구성하여 제공되어 있기 때문에, &lt;span style=&quot;color: #ee2323;&quot;&gt;방식과 정책만 잘 정한다면&lt;/span&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;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignLeft&quot; data-emoticon-type=&quot;niniz&quot; data-emoticon-name=&quot;002&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/002.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/002.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 업무 어서 오고!&lt;/p&gt;</description>
      <category>Infra &amp;amp; Network/AWS</category>
      <category>AWS</category>
      <category>Cache</category>
      <category>Cache Evit</category>
      <category>cloudfront</category>
      <category>CreateInvalidation</category>
      <category>S3</category>
      <category>sdk</category>
      <category>spring</category>
      <category>spring boot</category>
      <author>개발자 김은혜</author>
      <guid isPermaLink="true">https://eunveloper.tistory.com/36</guid>
      <comments>https://eunveloper.tistory.com/entry/CloudFront%EC%99%80-S3%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%A0%95%EC%A0%81-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A1%B0%ED%9A%8C-2#entry36comment</comments>
      <pubDate>Fri, 2 Sep 2022 16:04:15 +0900</pubDate>
    </item>
    <item>
      <title>Proxy Pattern 으로 구현해보는 다이나믹 서비스 적용기</title>
      <link>https://eunveloper.tistory.com/entry/Proxy-Pattern-%EC%9C%BC%EB%A1%9C-%EA%B5%AC%ED%98%84%ED%95%B4%EB%B3%B4%EB%8A%94-%EB%8B%A4%EC%9D%B4%EB%82%98%EB%AF%B9-%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%A0%81%EC%9A%A9%EA%B8%B0</link>
      <description>&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;B2C 서비스를 개발하다 보니 이전 회사에서는 경험해보기 어려운 업무들을 할 수 있어서 재미있는 것 같다.&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;h3 data-ke-size=&quot;size23&quot;&gt;&quot;Proxy Pattern 적용기&quot;이다.&lt;/h3&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;672&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lEV3Z/btrKZCm9J3o/C19ypRXmK3TW4tcrMYWE10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lEV3Z/btrKZCm9J3o/C19ypRXmK3TW4tcrMYWE10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lEV3Z/btrKZCm9J3o/C19ypRXmK3TW4tcrMYWE10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlEV3Z%2FbtrKZCm9J3o%2FC19ypRXmK3TW4tcrMYWE10%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;672&quot; data-origin-width=&quot;1416&quot; data-origin-height=&quot;672&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저번에 인프런 김영한 님의 스프링강의를 들으면서 여러 가지 디자인 패턴을 학습하게 되었는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그중에 하나가 오늘 포스팅 주제인 &lt;b&gt;Proxy Pattern&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;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;- 기존 : 앱 OS (Android / IOS)에 따른 Messaging 라이브러리로 직접 푸시 로직 구현&lt;br /&gt;- 개선 : NHN Cloud Push 서비스를 연동하는 방식으로 구현&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;기존 서비스를 제공하는 앱을 사용중인 사용자도 있을 거고 개선된 서비스를 제공하는 앱을 사용하는 사용자도 있을 것이다.&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;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&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;이렇게 제공 서비스가 변경될 때마다 클라이언트 비지니스 로직을 변경한다는 것은&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;굉장히 &lt;span style=&quot;color: #ee2323;&quot;&gt;번거로운 일이고 자칫 잘못하다 휴먼에러를 발생하여 서비스에 장애를 일으킬 수도 있을 것&lt;/span&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 내가 떠올린 것이 &lt;b&gt;Proxy Pattern&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트와 서비스 부분의 소스 변경을 최소화하고, 대신 &lt;span style=&quot;color: #ee2323;&quot;&gt;내가 원하는 서비스를 다이나믹하게 사용할 수 있는 프록시&lt;/span&gt;를 중간에 둔다면 편하지 않을까? 라는 생각을 하게 되었기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 업무에서는 처음 도입해보는 거라 잘 될까 걱정이 앞섰지만, 걱정과는 다르게 간편하게 구현이 되었다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;팩토리 패턴을 고려하지 않은것은 아니다. &lt;br /&gt;두 패턴을 고민했었고, 각 패턴의 정의와 활용방안을 고려해 봤을 때 Proxy Pattern이 더 적절하다고 판단했다.&lt;br /&gt;- &lt;b&gt;Factory Pattern&lt;/b&gt; : 객체를 생성하는 클래스 (Factory) 를 따로 구성하는 방식&lt;br /&gt;- &lt;b&gt;Proxy Pattern&lt;/b&gt;&amp;nbsp;: 가상의 객체 (Proxy) 를 사용해 로직의 흐름을 제어하는 방식&lt;br /&gt;&lt;br /&gt;내가 해야하는 일은 서비스 구현체를 필요에 따라 다르게 &lt;span style=&quot;color: #ee2323;&quot;&gt;생성&lt;/span&gt; 하는것이 아닌, 컨트롤러에서 서비스를 호출할 때 상황에 따른 서비스들만 호출될 수 있도록 &lt;span style=&quot;color: #ee2323;&quot;&gt;제어&lt;/span&gt;하는 역할이 필요했기 때문에, 최종적으로 Proxy Pattern을 사용하게 되었다.&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;&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;pre id=&quot;code_1661815662840&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface PushService {
    List send();
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;PushService&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;서비스 비지니스 로직들을 모아놓은 인터페이스이다.&lt;/li&gt;
&lt;li&gt;실제로는 더 많은 기능 메서드들이 있지만, 일단 한 개만 추출해왔다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1661815750415&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class LivePushServiceImpl implements PushService {}
public class NHNPushServiceImpl implements PushService {}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;LivePushServiceImp / NHNPushServiceImpl&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;인터페이스를 상속받아 실제 로직을 구현할 서비스 클래스이다.&lt;/li&gt;
&lt;li&gt;기획에 따라서 더 추가가 될 수도, 없어질 수도 있을 것이다.&lt;/li&gt;
&lt;/ul&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;이제 실제 Proxy Pattern을 사용하여 구성한 클래스 파일들이다.&lt;/p&gt;
&lt;pre id=&quot;code_1661815889817&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
@RequiredArgsConstructor
public class PushServiceProxyConfiguration {

    @Bean
    public PushService pushService() {
        PushService livePushService = new LivePushServiceImpl();
        PushService nhnPushService = new NHNPushServiceImpl();
        // 실행에 사용하고 하는 서비스 구현체를 리스트 형식으로 담아 프록시를 생성
        PushServiceInvocationHandler handler = new PushServiceInvocationHandler(new LivePushService[]{nhnPushService, livePushService});
        return (PushService) Proxy.newProxyInstance(LivePushService.class.getClassLoader(), new Class[]{LivePushService.class}, handler);
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;PushServiceProxyConfiguration&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;기획이나 배포 상황에 맞춰서 비지니스 로직 실행에 &lt;span style=&quot;color: #ee2323;&quot;&gt;사용할 푸시 서비스의 구현체들을 생성하여 실행 프록시&lt;/span&gt;에 담아준다.&lt;/li&gt;
&lt;li&gt;이때 프록시의 형상은 서비스 인터페이스이고, 빈으로 생성한다.&lt;/li&gt;
&lt;li&gt;구현체를 DI 하여 사용하는 컨트롤러나 서비스 클래스에서는 어떤 푸시 서비스를 실행할 건지에 대해서 &lt;span style=&quot;color: #ee2323;&quot;&gt;소스를 수정할 필요가 없다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1661730552896&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RequiredArgsConstructor
public class PushServiceInvocationHandler implements InvocationHandler {

    private final Object[] services;

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Object result = null;

        ArrayList pushResults = new ArrayList();
        if (services != null) {
            for (Object service : services) {
                if (service != null) {
                    try {
                        result = method.invoke(service, args);
                        if (result != null) {
                            if (result instanceof List) {
                                pushResults.addAll((ArrayList) result);
                                result = pushResults;
                            }
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                        log.error(String.format(&quot;알람 푸시중 에러가 발생하였습니다. 타겟 서비스 [%s], 메세지 [%s]&quot;,
                                service.getClass().getName(), e.getMessage()));
                    }
                }
            }
        }

        return result;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;PushServiceInvocationHandler&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;푸시 서비스의 프록시 클래스이다.&lt;/li&gt;
&lt;li&gt;생성자로 주입된 필드(실행할 서비스 클래스들)를 반복문으로 돌아가며 하나의 서비스씩 접근한다.&lt;/li&gt;
&lt;li&gt;서비스 별로 &lt;span style=&quot;color: #ee2323;&quot;&gt;method.invoke() 함수를 호출하여 실제 서비스의 메서드를 호출&lt;/span&gt;한다.&lt;/li&gt;
&lt;/ul&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;즉, 컨트롤러에서 서비스를 호출할 때 사용될 서비스 구현체를 &lt;span style=&quot;color: #ee2323;&quot;&gt;프록시가 제어하여 대신 호출&lt;/span&gt;해주는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프록시에서 설정한대로 제어하여 호출해주기 때문에 컨트롤러나 서비스레벨의 소스코드를 수정해주지 않아도 된다!&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 Proxy Pattern이 적용된 클래스들을 추가한 후 생성한 Bean을 주입받아 원하는 메서드를 실행하면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PushServiceProxyConfiguration에서 리스트로 추가된 모든 서비스 구현체의 메서드가 실행된다!&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;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;/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;이번에 실제로 Proxy Pattern을 &lt;span style=&quot;color: #ee2323;&quot;&gt;실무에 도입하여 사용하면서 이러한 부분을 몸소 체감&lt;/span&gt;할 수 있어서 좋았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;김은혜 패턴을 만드는 그날까지... 열심히 하자! (엥?)&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignLeft&quot; data-emoticon-type=&quot;niniz&quot; data-emoticon-name=&quot;020&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/020.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/020.gif&quot; width=&quot;150&quot; /&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Programing/Spring</category>
      <category>clean code</category>
      <category>It</category>
      <category>java</category>
      <category>Programming</category>
      <category>proxy</category>
      <category>proxy pattern</category>
      <category>spring</category>
      <category>spring boot</category>
      <category>디자인패턴</category>
      <category>패턴</category>
      <author>개발자 김은혜</author>
      <guid isPermaLink="true">https://eunveloper.tistory.com/35</guid>
      <comments>https://eunveloper.tistory.com/entry/Proxy-Pattern-%EC%9C%BC%EB%A1%9C-%EA%B5%AC%ED%98%84%ED%95%B4%EB%B3%B4%EB%8A%94-%EB%8B%A4%EC%9D%B4%EB%82%98%EB%AF%B9-%EC%84%9C%EB%B9%84%EC%8A%A4-%EC%A0%81%EC%9A%A9%EA%B8%B0#entry35comment</comments>
      <pubDate>Wed, 31 Aug 2022 11:00:25 +0900</pubDate>
    </item>
    <item>
      <title>CloudFront와 S3를 이용한 정적 데이터 조회 (1)</title>
      <link>https://eunveloper.tistory.com/entry/CloudFront%EC%99%80-S3%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%A0%95%EC%A0%81-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A1%B0%ED%9A%8C-1</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;개발을 하다 보면 캐시라는 단어를 여기저기서 많이 들어보게 되는데&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;캐시란 자주 사용하는 데이터나 값을 미리 복사해 놓는 임시 장소&lt;/span&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;(내 지식 안에서) 크게 세 가지 정도라고 생각되는데 간단하게만 살펴보자면&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;b&gt;데이터베이스 shared buffer 사이즈 확보&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자주 사용되는 데이터가 rdbms에 있다면 &lt;span style=&quot;color: #ee2323;&quot;&gt;매번 쿼리 하여 디스크에서 데이터를 조회하는 것은 성능에 좋지 않기 때문&lt;/span&gt;에&lt;br /&gt;shared buffer라는 공간을 사용하여 데이터를 캐시 할 수 있다.&lt;/li&gt;
&lt;li&gt;데이터베이스에 액세스를 시도하면 먼저 디스크에서 필요한 데이터를 공유 버퍼로 먼저 읽어 드리고,&lt;br /&gt;공유 버퍼에서 데이터를 읽고 쓰기를 처리한다.&lt;/li&gt;
&lt;li&gt;이후, &lt;span style=&quot;color: #333333;&quot;&gt;동일한 데이터 액세스에 대해서는 공유 버퍼에서 읽어 들여 느린 디스크 액세스를 줄여 성능을 확보&lt;/span&gt;할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&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;말 그대로 자주 사용하는 데이터를 &lt;span style=&quot;color: #333333;&quot;&gt;웹 어플리케이션 서버가 가지고 있는 메모리에 저장&lt;/span&gt;하는 것이다.&lt;/li&gt;
&lt;li&gt;디스크가 아닌 메모리로 비지니스 로직을 처리하는 어플리케이션 내에 저장하기 때문에 굉장히 속도가 빠르지만 어플리케이션이 재기동되면 사라지는 &lt;span style=&quot;color: #ee2323;&quot;&gt;휘발성 데이터이기 때문에 유실이 되면 안 되는 데이터에는 적합하지 않다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;캐시 관리용 데이터베이스(Redis)에 저장&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;빠른 응답속도를 보장하는 메모리 데이터베이스에 데이터를 저장&lt;/span&gt;하는 것이다.&lt;/li&gt;
&lt;li&gt;많은 기업에서 캐시 용도로 메모리 데이터베이스를 많이 사용하고 있다.&lt;/li&gt;
&lt;li&gt;나 또한 재직했던 모든 회사에서 그런 이유로 메모리 데이터베이스인 Redis를 많이 사용하였다.&lt;/li&gt;
&lt;/ul&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;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;
&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;b&gt;&quot; AWS CloudFront + S3 연동방식 &quot;&lt;/b&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;사실 내가 캐시 성으로 관리하고자 했던 건 &lt;span style=&quot;color: #ee2323;&quot;&gt;정규화되어있거나 규격이 있는 &amp;lsquo;데이터'의 형식보다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&amp;lsquo;파일'의 형식&lt;/span&gt;이었고 현재 재직 중인 회사가 AWS를 사용하고 있기 때문에 위의 방식을 사용하게 되었다.&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;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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;S3?&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;928&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbZFTl/btrJ690zJOj/kC7wi3RcubOTMC4d8073I1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbZFTl/btrJ690zJOj/kC7wi3RcubOTMC4d8073I1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbZFTl/btrJ690zJOj/kC7wi3RcubOTMC4d8073I1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbZFTl%2FbtrJ690zJOj%2FkC7wi3RcubOTMC4d8073I1%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;504&quot; height=&quot;366&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;928&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;span style=&quot;color: #ee2323;&quot;&gt;Simple Storage Service의 줄임말로 인터넷용 스토리지 서비스&lt;/span&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;단독 스토리지로도 사용 가능하며 EC2, EBS와 같은 다른 AWS 서비스와도 함께 사용할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;나 같은 경우는 CloudFront와 활용하여 캐싱 서비스를 할 예정이다.&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;기본개념&lt;/b&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;객체 (Object)&lt;/b&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터가 저장되는 기본 단위로써 파일과 메타데이터로 이루어져 있다.&lt;/li&gt;
&lt;li&gt;크기는 1Byte ~ 5TB까지 가능하다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;버킷 (Bucket)&lt;/b&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;/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;99.9999999%의 내구성을 보장하며 99.99%의 가용성을 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;RRS (Reduces Redundancy Storage)&lt;/b&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;/ol&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;CloudFront?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;정적 및 동적 웹 콘텐츠를 사용자에게 더 빨리 배포하도록 지원하는 웹 서비스&lt;/span&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;Origin Server&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;실제 데이터를 보관하고 있는 서버로 Amazon S3 버킷 또는 HTTP 서버를 사용할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Edge Server (= Edge Location)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;CloudFront를 지칭하며 오리진 서버에 저장된 데이터를 캐싱한다.&lt;/li&gt;
&lt;/ul&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;클라이언트가 데이터를 요청하면 &lt;span style=&quot;color: #ee2323;&quot;&gt;가장 가까운 (지연 시간이 가장 낮은) 엣지 로테이션으로 라우팅&lt;/span&gt; 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;986&quot; data-origin-height=&quot;854&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xv79l/btrJ71VzK2B/w5cw4nMXuwulKmqJlq4tOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xv79l/btrJ71VzK2B/w5cw4nMXuwulKmqJlq4tOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xv79l/btrJ71VzK2B/w5cw4nMXuwulKmqJlq4tOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fxv79l%2FbtrJ71VzK2B%2Fw5cw4nMXuwulKmqJlq4tOK%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;854&quot; data-origin-width=&quot;986&quot; data-origin-height=&quot;854&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리는 오리진 서버로 Amazon S3 Bucket을 사용한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CloudFront는 오리진 서버로부터 파일을 가져와 모든 CloudFront 엣지 로케이션에 배포한다.&lt;/li&gt;
&lt;/ul&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;클라이언트가 CloudFront에게 파일을 요청한다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;캐싱 여부 확인&lt;/span&gt;한다.
&lt;ol 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;/ol&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;&amp;lsquo;정적 웹 사이트 콘텐츠 전송 속도 향상&amp;rsquo;을&lt;/b&gt; 근거로 사용한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;정적 콘텐츠를 저장하고 전송하기 위한 간단한 방법은 Amazon S3 버킷을 사용하는 것이다. &lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;S3와 CloudFront를 함께 사용&lt;/span&gt;하면 OAI(원본 액세스 ID)를 통해 S3 콘텐츠에 대한 액세스를 쉽게 제한할 수 있으며 이외에도 다양한 장점이 있다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 이 두 가지 서비스를 연동하여 S3 스토리지의 파일을 CloudFront에 캐시 처리하는 방법을 알아보자!&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;S3 버킷 생성&lt;/b&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;AWS Console에서 S3 서비스에 접속한다.&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;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&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;버킷 이름에 버킷의 DNS 호환 이름을 입력한다.&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;모든 Amazon S3에서 고유해야 한다.&lt;/li&gt;
&lt;li&gt;3~63자 이내여야 한다.&lt;/li&gt;
&lt;li&gt;대문자가 없어야 한다.&lt;/li&gt;
&lt;li&gt;소문자 또는 숫자로 시작해야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;버킷을 생성한 후에는 해당 이름을 변경할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;AWS 리전 : 서비스 리전 선택한다.&lt;/li&gt;
&lt;li&gt;기존 버킷에서 설정 복사 : 기존 버킷에 설정을 동일하게 사용한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;객체 소유권 : 외부에서 데이터에 접근할 수 있는지에 대한 여부를 설정한다.&lt;/li&gt;
&lt;li&gt;이 버킷의 퍼블릭 액세스 차단 설정
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;기본적으로 객체 소유권을 비활성화하면 모든 퍼블릭 액세스가 차단된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;버킷 버전 관리&lt;/li&gt;
&lt;li&gt;태그&lt;/li&gt;
&lt;li&gt;기본 암호화&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;CloudFront 연동&lt;/b&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AWS Console에서 CloudFront 서비스에 접속한다.&lt;/li&gt;
&lt;li&gt;배포를 생성한다.&lt;/li&gt;
&lt;li&gt;배포 방식을 선택한다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;b&gt;Web&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;HTTP(S)를 통해 동적, 정적 파일( .html, .css, .php 등)을 제공하는 방식이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;RTMP&amp;nbsp;(Real Time Messaging Protocol)&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Adobe Flash 멀티미디어는 HTTP 또는 HTTPS를 통해 제공할 수 없지만, CloudFront RTMP 배포를 사용하여 제공하는 방식이다.&lt;/li&gt;
&lt;li&gt;현재 VOD 같은 영상 제작물들은 이 방식으로 사용하는 중?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;배포 설정을 한다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.aws.amazon.com/ko_kr/AmazonCloudFront/latest/DeveloperGuide/distribution-web-values-specify.html&quot;&gt;https://docs.aws.amazon.com/ko_kr/AmazonCloudFront/latest/DeveloperGuide/distribution-web-values-specify.html&lt;/a&gt; (설정 값이 굉장히 많기 때문에 문서를 꼭 참고하자!)&lt;/li&gt;
&lt;li&gt;Origin
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Origin Domain Name
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;S3 사용하게 되면 input box에 사용 가능한 목록들이 뜨기 때문에 선택하면 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Origin ID
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;오리진 또는 오리진 그룹을 고유하게 구별하는 문자열이다.&lt;/li&gt;
&lt;li&gt;Origin Domain Name을 목록으로 선택했다면 자동으로 생성된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Restrict Bucket Access (Origin이 S3 버킷일 때만 노출)&amp;nbsp;
&lt;table style=&quot;border-collapse: collapse; width: 75.6544%; height: 120px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 20.2879%; height: 25px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 38.2404%; height: 25px;&quot;&gt;Access S3 URL&lt;/td&gt;
&lt;td style=&quot;width: 41.3408%; height: 25px;&quot;&gt;Access CloudFront URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 20.2879%; height: 25px;&quot;&gt;YES (권장)&lt;/td&gt;
&lt;td style=&quot;width: 38.2404%; height: 25px;&quot;&gt;X&lt;/td&gt;
&lt;td style=&quot;width: 41.3408%; height: 25px;&quot;&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 25px;&quot;&gt;
&lt;td style=&quot;width: 20.2879%; height: 25px;&quot;&gt;NO&lt;/td&gt;
&lt;td style=&quot;width: 38.2404%; height: 25px;&quot;&gt;O&lt;/td&gt;
&lt;td style=&quot;width: 41.3408%; height: 25px;&quot;&gt;O&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;YES 일 때 추가 항목
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Origin Access Identity&lt;/li&gt;
&lt;li&gt;Comment&lt;/li&gt;
&lt;li&gt;Grant Read Permissions On Bucket
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;CloudFront에서 S3에 접근하기 위해 버킷의 read 권한을 부여하기 위해 설정한다.&lt;/li&gt;
&lt;li&gt;Yes, Update Bucket Policy : S3 버킷에 대한 읽기 권한 자동 부여&lt;/li&gt;
&lt;li&gt;No, I will Update Permissions : 버킷 권한을 업데이트하는 대신 권한을 수동으로 업데이트하려는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Origin Custom Headers&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Cache
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Path Pattern : CloudFront로 파일을 가져올 규칙으로 기본값은 *(모든 파일)이고, 배포 후 추가 가능하다.&lt;/li&gt;
&lt;li&gt;Viewer Protocol Policy : Client가 CloudFront 콘텐츠를 액세스 할 때의 프로토콜을 설정한다.&lt;/li&gt;
&lt;li&gt;Allowed HTTP Methods : Origin 처리를 하기 위해 허용할 HTTP 메서드를 지정한다.&lt;/li&gt;
&lt;li&gt;Field-level Encryption Config : 중요한 보안 데이터를 강화하기 위해 필드 레벨로 암호화를 적용한다.&lt;/li&gt;
&lt;li&gt;Cached HTTP Methods
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;OPTIONS 메서드 요청 시 Origin의 응답 데이터 캐싱 여부를 설정한다.&lt;/li&gt;
&lt;li&gt;GET, HEAD 요청은 기본적으로 항상 캐싱한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Cache Based on Selected Request Headers : CloudFront에서 지정된 헤더의 값을 기반으로 객체를 캐싱할지 여부를 지정한다.
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;None (improves caching) 없음(캐싱 향상)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CloudFront가 헤더 값을 기반으로 객체를 캐시 하지 않는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Whitelist(화이트리스트)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CloudFront가 지정된 헤더의 값(Whitelist)만을 기반으로 객체를 캐시 한다.&lt;/li&gt;
&lt;li&gt;즉, Whitelist Headers(화이트리스트 헤더)를 사용하여 CloudFront에서 캐시의 기준으로 설정할 헤더를 선택한다.&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;CloudFront가 이 캐시 동작과 연결된 객체를 캐싱하지 않는다.&lt;/li&gt;
&lt;li&gt;그 대신 CloudFront가 모든 요청을 Origin에 전송한다. (Amazon S3 Origin에는 권장하지 않음)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Object Caching
&lt;ul style=&quot;list-style-type: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;Origin&amp;nbsp;서버에서 객체에 Cache-Control 헤더를 추가하여 파일의 캐시 유지 시간을 설정합니다.&lt;/li&gt;
&lt;li&gt;유지 시간이 지나면 CloudFront에서 파일이 삭제됩니다&lt;/li&gt;
&lt;li&gt;Use Origin Cache Headers를 선택하면&amp;nbsp;Default TTL이 적용되며, 1일(86400초)이 기본값입니다.&lt;/li&gt;
&lt;li&gt;Customize를 선택하면&amp;nbsp;Minimum TTL, Maximum TTL를 작성해줘야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Forward Cookies&lt;/li&gt;
&lt;li&gt;Query String Forwarding and Caching&lt;/li&gt;
&lt;li&gt;Smooth Streaming&lt;/li&gt;
&lt;li&gt;Restrict Viewer Access&lt;/li&gt;
&lt;li&gt;Compress Object Automatically&lt;/li&gt;
&lt;li&gt;Lambda Function Associations&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Distribution
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Price Class&lt;/li&gt;
&lt;li&gt;AWS WAF Web ACL&lt;/li&gt;
&lt;li&gt;Alternate Domain Names&lt;/li&gt;
&lt;li&gt;SSL Certificate&lt;/li&gt;
&lt;li&gt;Supported HTTP Versions&lt;/li&gt;
&lt;li&gt;Default Root Object&lt;/li&gt;
&lt;li&gt;Logging&lt;/li&gt;
&lt;li&gt;Enable IPv6&lt;/li&gt;
&lt;li&gt;Comment&lt;/li&gt;
&lt;li&gt;Distribution State&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 S3 버킷을 적절하게 생성하고, CloudFront에 생성한 S3 버킷을 연동한 후, S3 일정 디렉터리에&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시 처리를 하고자 하는 파일을 업로드하면 &lt;span style=&quot;color: #ee2323;&quot;&gt;CloudFront 도메인 + 저장된 디렉터리 경로로 접근&lt;/span&gt;하면 해당 파일을 볼 수 있다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;캐시의 특징처럼 최초 조회 시에는 CloudFront가 S3에서 데이터를 조회하여 보여주고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 후 &lt;span style=&quot;color: #ee2323;&quot;&gt;동일 조회 요청에서는 CloudFront의 캐시 처리가 된 데이터&lt;/span&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;다만 캐시 특성상 위에서 등록해준 데이터 TTL이 지나지 않으면 원본 데이터가 변경되었다 하더라도&lt;br /&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;CloudFront에서는 데이터 변경의 여부를 알 방법이 없다!&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음 포스팅에서는 이러한 캐시 무효화의 방식들의 소개와 실제로 어떻게 반영하여 개발하였는지를 포스팅하겠다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure contenteditable=&quot;false&quot; data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignLeft&quot; data-emoticon-type=&quot;niniz&quot; data-emoticon-name=&quot;017&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/017.gif&quot;&gt;&lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/017.gif&quot; width=&quot;150&quot; /&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;</description>
      <category>Infra &amp;amp; Network/AWS</category>
      <category>Amazon</category>
      <category>Application</category>
      <category>AWS</category>
      <category>Cache</category>
      <category>cloudfront</category>
      <category>developer</category>
      <category>It</category>
      <category>S3</category>
      <category>개발</category>
      <category>웹개발</category>
      <author>개발자 김은혜</author>
      <guid isPermaLink="true">https://eunveloper.tistory.com/34</guid>
      <comments>https://eunveloper.tistory.com/entry/CloudFront%EC%99%80-S3%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%A0%95%EC%A0%81-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%A1%B0%ED%9A%8C-1#entry34comment</comments>
      <pubDate>Sat, 20 Aug 2022 21:28:15 +0900</pubDate>
    </item>
    <item>
      <title>첫 회고 (취업 준비부터 첫 회사 퇴사까지)</title>
      <link>https://eunveloper.tistory.com/entry/%EC%B2%AB-%ED%9A%8C%EA%B3%A0-%EC%B7%A8%EC%97%85-%EC%A4%80%EB%B9%84%EB%B6%80%ED%84%B0-%EC%B2%AB-%ED%9A%8C%EC%82%AC-%ED%87%B4%EC%82%AC%EA%B9%8C%EC%A7%80</link>
      <description>&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;첫 이직을 한지 한 달이 다 되어간다.&lt;br&gt; &lt;/p&gt;
&lt;figure data-ke-type=&quot;emoticon&quot; data-ke-align=&quot;alignLeft&quot; data-emoticon-type=&quot;niniz&quot; data-emoticon-name=&quot;018&quot; data-emoticon-isanimation=&quot;false&quot; data-emoticon-src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/018.gif&quot;&gt;
 &lt;img src=&quot;https://t1.daumcdn.net/keditor/emoticon/niniz/large/018.gif&quot; width=&quot;150&quot;&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt;이직한 회사 적응도 하고 최근 운동에 흥미가 생겨서 운동도 하고 못 만났던 친구들도 만나다보니,&lt;br&gt;써야지 써야지 마음만 먹고 미루고 있었던 첫 회사 회고를 이제야 쓰게 되었다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&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; style=&quot;text-align: left;&quot;&gt;입사 전 학창 시절부터 돌이켜 본다면 나는 유사 전공자이다. (정보통신공학과 재학)&lt;br&gt;고등학생 시절 공부에 흥미가 없어서 공부를 잘하지 않았고 &lt;br&gt;수능점수 맞춰서 간 학과다 보니 대학시절도 딱히 공부를 열심히 하지 않았다.&lt;br&gt;&lt;br&gt;친구들이랑 노는 게 좋았고 학교 가는 게 싫어서 한 학기는 학사경고도 받을 정도로 공부를 안 했다.&lt;br&gt;&lt;br&gt;그래도 먹고 살길은 찾아봐야 하지 않겠나 싶어서 1학년을 마치고 대뜸 휴학을 했고&lt;br&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;돈 벌어보겠다며 이런저런 알바를 했고 나름 &lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;공부도 해보겠다고 뜬금없이 세무,회계 책을 왕창 사다가&lt;/span&gt;&lt;br&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;하루이틀 공부해 보다가 결국엔 &lt;/span&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;이도 아닌 거 같아 수능 준비를 하게&lt;/span&gt; 되었다.&lt;br&gt;&lt;br&gt;하지만 모든 것은 뜻대로 잘 되지 않았고 (끈기 있게 한 게 없으니 당연하다.)&lt;br&gt;다시 재학 중이던 대학교에 복학하게 되었다.&lt;br&gt;이 때문에 부모님도 이런 나를 보면서 정말 걱정이 많으셨다. &lt;br&gt;학창 시절에는 사춘기도 없이 자라던 내가 다 커서 놀러만 다니니 걱정이 많으실 법했다.&lt;br&gt; &lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;취업준비&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;3,4학년이 되니 동기들은 한명 두명씩 자격증을 따고, 본인이 원하는 학과로 전과를 하거나&lt;br&gt;인턴으로 취업을 하면서 본인들의 미래를 설계해 나가고 있었다.&lt;br&gt;분명 같이 놀았던거 같은데 나만 뒤쳐지고 친구들은 점점 앞서나가는거 같았다.&lt;br&gt;&lt;br&gt; &lt;br&gt;이대론 안 되겠다 싶어서 여기저기 수소문하였고 &lt;span style=&quot;color: #ee2323;&quot;&gt;국비지원이라는 교육 프로그램&lt;/span&gt;을 알게 되었다.&lt;br&gt;교육비도 무료고 학기 중에도 취업계를 내고 다닐 수 있다 하여 옳다쿠나 하며 상담을 받았고 &lt;br&gt;무엇을 하는 곳인지, 무엇을 배우는 곳인지도 모른채 무지성으로 다니기 시작하였다.&lt;br&gt;지금처럼 개발자 붐이 일어난 시기도 아니었고, 국비지원이 유명하지 않았기 때문이다.&lt;br&gt;&lt;br&gt;Java 웹 개발자 양성 프로그램이었는데, &lt;br&gt;나는 여태 그랬왔던 것처럼 열정이나 포부, 간절함도 없이 그냥 학원을 다니기 시작했다.&lt;br&gt;하지만 정말 운 좋게도 열정 넘치는 강사님과 학원 여러 동기들을 만났고,&lt;br&gt;(이 시절 에피소드는 다 쓰자니 너무 길어질 것 같아서 생략하겠다. 다음에 한번 포스팅을 써봐야지)&lt;br&gt;매일같이 수업 듣고 공부하고 복습하고 스터디하고를 반복하였다.&lt;br&gt; &lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;429&quot; data-origin-height=&quot;624&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJAYiV/btrJSXy5jB5/KsTnaZzTPFskPEpradXzx0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJAYiV/btrJSXy5jB5/KsTnaZzTPFskPEpradXzx0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJAYiV/btrJSXy5jB5/KsTnaZzTPFskPEpradXzx0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJAYiV%2FbtrJSXy5jB5%2FKsTnaZzTPFskPEpradXzx0%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;372&quot; height=&quot;624&quot; data-origin-width=&quot;429&quot; data-origin-height=&quot;624&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;br&gt;나는 점점 개발에 흥미가 생겼고, 나도 몰랐던 약간의 재능(?)을 발견하면서 하루하루 빠르게 성장했다.&lt;br&gt;강사님과 많은 동기들의 칭찬과 기대를 받았고, 이런 관심을 처음 받아본 나는 &lt;br&gt;그런 기대에 부흥하고자 더더욱 열심히 했던 거 같다.&lt;br&gt;&lt;br&gt;7개월 과정중에 두달정도는 안양-강남을 통학하며 지냈지만, 그 시간조차 아까웠던 &lt;br&gt;나는 학원 근처에 창문하나 없는 작은 고시텔을 구했고 &lt;br&gt;남은 다섯달 동안 오전에 수업을 듣고, 오후에는 스터디와 복습을 하고 새벽까지 프로젝트를 했다.&lt;br&gt;&lt;br&gt;나는 결국 &lt;span style=&quot;color: #ee2323;&quot;&gt;모든 프로젝트에서 1등을 하게 되었고, 모범상도 타며 수료&lt;/span&gt;를 하게 되었다.&lt;br&gt; &lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;1430&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ddfdTy/btrJSmsjWJa/MSCP67syf6lqkXblMZ5Nt0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ddfdTy/btrJSmsjWJa/MSCP67syf6lqkXblMZ5Nt0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ddfdTy/btrJSmsjWJa/MSCP67syf6lqkXblMZ5Nt0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FddfdTy%2FbtrJSmsjWJa%2FMSCP67syf6lqkXblMZ5Nt0%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;497&quot; height=&quot;1430&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;1430&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;&lt;br&gt;수료하고 나서도 계속 공부하며 친한 동기들과 취업전선에 달려들었고,&lt;br&gt;이렇게 나는 수료한 지 한 달 차쯤에 첫 번째 회사에 입사하게 되었다.&lt;br&gt; &lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;첫 직장&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;첫 회사는 나름 크고 이름 알려진 중소기업이었고 나는 새로운 프로젝트로 꾸려진 팀에 소속되었다.&lt;br&gt;&lt;br&gt;이 회사를 다니면서 정말 희노애락의 감정을 다양하게 느꼈고, &lt;br&gt;성격도 많이 바뀐 거 같다. &lt;span style=&quot;color: #000000;&quot;&gt;&lt;s&gt;(물론 안 좋게^^)&lt;/s&gt;&lt;/span&gt;&lt;br&gt;그리고 일적으로도 일 외적으로도 좋은 사람들과 좋은 경험도 정말 많이 했다.&lt;br&gt;첫 취업, 첫 직장, 첫 업무, 첫 회의, 첫 야근...&lt;br&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;모든 게 처음이다 보니 모든 경험이 지금 생각해보면 소중하고 특별한 기억&lt;/span&gt;이 될 것 같다.&lt;br&gt; &lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;520&quot; data-origin-height=&quot;649&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eyXLKH/btrJSmeN8nW/KzD0s4VevIy7b0mpl27vkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eyXLKH/btrJSmeN8nW/KzD0s4VevIy7b0mpl27vkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eyXLKH/btrJSmeN8nW/KzD0s4VevIy7b0mpl27vkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeyXLKH%2FbtrJSmeN8nW%2FKzD0s4VevIy7b0mpl27vkK%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;422&quot; height=&quot;649&quot; data-origin-width=&quot;520&quot; data-origin-height=&quot;649&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt;점심시간이 다 끝나지도 않았지만 커피를 4잔씩 마시기도 하고&lt;br&gt; &lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;426&quot; data-origin-height=&quot;525&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pv1H5/btrJRXM6t5C/FRw5gP9kP8yCoakUbR7YB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pv1H5/btrJRXM6t5C/FRw5gP9kP8yCoakUbR7YB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pv1H5/btrJRXM6t5C/FRw5gP9kP8yCoakUbR7YB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpv1H5%2FbtrJRXM6t5C%2FFRw5gP9kP8yCoakUbR7YB1%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;426&quot; height=&quot;525&quot; data-origin-width=&quot;426&quot; data-origin-height=&quot;525&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;517&quot; data-origin-height=&quot;478&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1UFsi/btrJSltwP60/d1F8gBBZEXKMyF1lELrQvK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1UFsi/btrJSltwP60/d1F8gBBZEXKMyF1lELrQvK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1UFsi/btrJSltwP60/d1F8gBBZEXKMyF1lELrQvK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1UFsi%2FbtrJSltwP60%2Fd1F8gBBZEXKMyF1lELrQvK%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;427&quot; height=&quot;395&quot; data-origin-width=&quot;517&quot; data-origin-height=&quot;478&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;1129&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cc3wyc/btrJV7udMGW/Oi59vKikkWuLCrsAry0uik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cc3wyc/btrJV7udMGW/Oi59vKikkWuLCrsAry0uik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cc3wyc/btrJV7udMGW/Oi59vKikkWuLCrsAry0uik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcc3wyc%2FbtrJV7udMGW%2FOi59vKikkWuLCrsAry0uik%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;426&quot; height=&quot;428&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;1129&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt;야근에 철야에 귀에 이명이 들릴 정도로 무리하면서 일했던 기간도 있었다.&lt;br&gt;이 시즌에는 번아웃이 정말 제대로 와서 힘들었던 거 같다.&lt;br&gt; &lt;br&gt;아침에 일 하기 위해서 눈뜨는게 힘들었고, 재택 근무 시절에는 울뻔 한적도 여럿 있었다.&lt;br&gt;지금 생각해보면 뭐가 그렇게 힘들었을까 싶을만큼 무뎌졌지만&lt;br&gt;그만큼 열정적으로 일했고 최선을 다했기 때문에 힘들었지 않았나 싶다.&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;하지만 그런 와중에도 ‘개발이 재밌다'라는 생각을 하지 않았던 적이 없다. &lt;/span&gt;&lt;br&gt;사람 때문에 힘들고 야근과 철야, 답이 없는 긴 회의에 아무리 지쳐도 &lt;br&gt;개발을 하고 더 나은 성능을 위해 이야기를 나누는 순간은 항상 즐거웠다.&lt;br&gt; &lt;br&gt;무뎌진건지 적응을 한건지는 모르겠지만 힘들다는 감정의 소비가 점차 줄게 되었고&lt;br&gt;그냥 그렇게 꾸준히 열심히 하다 보니 주위 사람들의 칭찬과 인정을 받게 되었고&lt;br&gt;또 그런 상황을 영양분으로 삼아 더더욱 성장하게 되었다.&lt;br&gt;이런 힘든 나날들을 보낸 전우애(?) 넘치는 팀원들이 있어서 더더욱 열심히 할 수 있었다.&lt;br&gt; &lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;519&quot; data-origin-height=&quot;571&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCpzhv/btrJSlGXoJv/KZeYZYNkeq03UJP4euPdSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCpzhv/btrJSlGXoJv/KZeYZYNkeq03UJP4euPdSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCpzhv/btrJSlGXoJv/KZeYZYNkeq03UJP4euPdSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCpzhv%2FbtrJSlGXoJv%2FKZeYZYNkeq03UJP4euPdSk%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;432&quot; height=&quot;571&quot; data-origin-width=&quot;519&quot; data-origin-height=&quot;571&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;428&quot; data-origin-height=&quot;516&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKIccU/btrJWmEGI7O/q6Twha8gAzceZsMHykwHX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKIccU/btrJWmEGI7O/q6Twha8gAzceZsMHykwHX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKIccU/btrJWmEGI7O/q6Twha8gAzceZsMHykwHX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKIccU%2FbtrJWmEGI7O%2Fq6Twha8gAzceZsMHykwHX0%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;431&quot; height=&quot;520&quot; data-origin-width=&quot;428&quot; data-origin-height=&quot;516&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt;회사 다니는 기간 동안 감사 할만큼에 관심과 생일 축하도 받고&lt;br&gt; &lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;521&quot; data-origin-height=&quot;499&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CrgZP/btrJT6I1wOx/TxpR700ToFkKFuIpR8tVT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CrgZP/btrJT6I1wOx/TxpR700ToFkKFuIpR8tVT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CrgZP/btrJT6I1wOx/TxpR700ToFkKFuIpR8tVT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCrgZP%2FbtrJT6I1wOx%2FTxpR700ToFkKFuIpR8tVT0%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;439&quot; height=&quot;499&quot; data-origin-width=&quot;521&quot; data-origin-height=&quot;499&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt;특히 정말 열심히 했던 2021년을 보낸 후에,&lt;br&gt;2022년에는 핵심 인재로 인정받아 &lt;span style=&quot;color: #ee2323;&quot;&gt;연봉도 많이 오르고 우수 사원상&lt;/span&gt;도 받았다!&lt;br&gt; &lt;/p&gt;
&lt;h3 style=&quot;text-align: left;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;이직&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt;하지만 사람이란 게 편해지며 현 상황에 적응을 하다 보니 안일해지고 나태해지게 되었다.&lt;br&gt;그래서 나는 다시 초심을 찾고자 하는 마음으로 2022년 초부터 이직을 준비하게 되었고&lt;br&gt;수많은 면접을 보면서 신입과 경력직 면접은 정말 다르구나를 느끼며 &lt;br&gt;내가 3년간 겪었던 경험들을 부정하게 되며 현타도 오고 멘탈도 많이 나갔었다.&lt;br&gt; &lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;525&quot; data-origin-height=&quot;810&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cTukOI/btrJSWtokSW/8KKYZtyejEKfoCICc3DNzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cTukOI/btrJSWtokSW/8KKYZtyejEKfoCICc3DNzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cTukOI/btrJSWtokSW/8KKYZtyejEKfoCICc3DNzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcTukOI%2FbtrJSWtokSW%2F8KKYZtyejEKfoCICc3DNzk%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;354&quot; height=&quot;810&quot; data-origin-width=&quot;525&quot; data-origin-height=&quot;810&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;하지만 ‘멘탈은 하루만 놓자'라는 생각&lt;/span&gt;을 가지고 &lt;br&gt;멘탈을 놓을 때는 시원하게 정말 놔버리고 그다음 날엔 다시 정신줄 챙겨서 회사일도 하고 이직 준비도 열심히 했다.&lt;br&gt; &lt;/p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;430&quot; data-origin-height=&quot;486&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OitcJ/btrJUZDgGnv/32ada72XoSFEUxK6C0cBFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OitcJ/btrJUZDgGnv/32ada72XoSFEUxK6C0cBFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OitcJ/btrJUZDgGnv/32ada72XoSFEUxK6C0cBFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOitcJ%2FbtrJUZDgGnv%2F32ada72XoSFEUxK6C0cBFk%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;365&quot; height=&quot;486&quot; data-origin-width=&quot;430&quot; data-origin-height=&quot;486&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p data-ke-size=&quot;size16&quot; style=&quot;text-align: left;&quot;&gt; &lt;br&gt;&lt;span style=&quot;color: #000000;&quot;&gt;결국 나는 이직에 성공하였고 많은 이들의 인사를 받으면서 첫 회사를 나올 수 있게 되었다.&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;u&gt;&lt;br&gt;&lt;/u&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이렇게 2년 9개월간의 첫 회사가 마무리 되었고, &lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다음 걸음을 걸어가고 있다!&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;br&gt;&lt;/span&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;u&gt; &lt;/u&gt;&lt;/span&gt;&lt;br&gt;&lt;br&gt;&lt;br&gt;첫 회사를 너무 즐겁게 다닌 나머지 이직을 한 지금 적응을 하기가 조금 힘들지만, &lt;br&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;새로운 환경 새로운 사람 모든 새로운 것에 난 또 나아가고 있다!&lt;/span&gt;&lt;/p&gt;</description>
      <category>Mind Control</category>
      <category>IT회사</category>
      <category>Mindcontrol</category>
      <category>개발자</category>
      <category>이직준비</category>
      <category>이직회고</category>
      <category>첫이직</category>
      <category>첫회사</category>
      <category>취업준비</category>
      <category>취준</category>
      <category>회고</category>
      <author>개발자 김은혜</author>
      <guid isPermaLink="true">https://eunveloper.tistory.com/31</guid>
      <comments>https://eunveloper.tistory.com/entry/%EC%B2%AB-%ED%9A%8C%EA%B3%A0-%EC%B7%A8%EC%97%85-%EC%A4%80%EB%B9%84%EB%B6%80%ED%84%B0-%EC%B2%AB-%ED%9A%8C%EC%82%AC-%ED%87%B4%EC%82%AC%EA%B9%8C%EC%A7%80#entry31comment</comments>
      <pubDate>Wed, 17 Aug 2022 15:56:59 +0900</pubDate>
    </item>
  </channel>
</rss>