<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>ch4njun's blog</title>
    <link>https://ch4njun.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Sun, 21 Jun 2026 04:52:37 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>ch4njun</managingEditor>
    <item>
      <title>[NCloud 후기] 포텐데이 프로젝트 후기</title>
      <link>https://ch4njun.tistory.com/277</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Q1. 프로젝트를 소개해 주세요.&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9zVO8/btsQRHJjxR4/ZvLShc6MONWioWfDMzE7Tk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9zVO8/btsQRHJjxR4/ZvLShc6MONWioWfDMzE7Tk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9zVO8/btsQRHJjxR4/ZvLShc6MONWioWfDMzE7Tk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9zVO8%2FbtsQRHJjxR4%2FZvLShc6MONWioWfDMzE7Tk%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;1920&quot; height=&quot;1080&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포텐데이에서 AI 기반 다이어트 레시피 플랫폼 Dayum 을 개발하는데 참여했다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dayum 은 사진과 텍스트를 활용해 음식의 재료와 영양소를 자동으로 분석해 칼로리 및 단백질, 탄수화물, 지방 등 레시피 속 영양 정보를 자동으로 계산해주는 서비스다.&lt;/p&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;lsquo;칼로리 계산기&amp;rsquo; 를 넘어, 20~30대가 꾸준히 기록하며 자연스럽게 건강한 식습관을 형성하도록 돕고자 했다. 또한 SNS 친화적인 디자인과 공유 기능을 고려해, 기록이 일기처럼 쌓이고 경험이 콘텐츠로 확장될 수 있도록 기획된 종합 다이어트 플랫폼이라고 할 수 있다.&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;Q2. NCloud 에서 어떤 서비스를 활용하셨나요?&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. Server&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dayum 의 API 서버와 AI Agent 를 실행시키기 위한 Server 로써 활용&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. Object Storage&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 업로드한 사진, 릴스 영상 등의 정적 데이터를 보관하기 위한 Storage 로써 활용&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. CLOVA OCR &amp;amp; Speech&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용자가 다이어트 레시피 영상을 업로드시 해당 영상에 포함된 자막 및 대본으로부터 식재료를 추출하기 위해 활용&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xsrwK/btsQSGC3kDw/ZW6pEUi1uKnsNKJn9h4YF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xsrwK/btsQSGC3kDw/ZW6pEUi1uKnsNKJn9h4YF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xsrwK/btsQSGC3kDw/ZW6pEUi1uKnsNKJn9h4YF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxsrwK%2FbtsQSGC3kDw%2FZW6pEUi1uKnsNKJn9h4YF1%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;1920&quot; height=&quot;1080&quot; data-origin-width=&quot;1920&quot; data-origin-height=&quot;1080&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;h4 data-ke-size=&quot;size20&quot;&gt;4. CLOVA Studio&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Dayum 에서 제공하는 대화형 AI Agent 속 LLM 으로써 활용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CLOVA Studio 의 Function Calling, Structured Output 와 같은 기능을 활용해 다양한 Sub-Agent 를 아래와 같이 구성해 활용&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Planner(Host) Agent : 사용자 메시지를 분석해 가지고있는 Sub-Agent 를 어떤 순서로 사용해야할지 계획을 세움&lt;/li&gt;
&lt;li&gt;DietRecipe Recommendation Agent :&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Extract Ingredient Agent :&amp;nbsp;&lt;/li&gt;
&lt;li&gt;Guardrail Agent :&amp;nbsp;&lt;/li&gt;
&lt;li&gt;SmallTalk Agent :&amp;nbsp;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Multi-Agent 기반의 AI Agent 로써 사용자 요청을 처리하기 위한 계획을 세우고, 처리해나가는 모든 과정에서의 LLM 을 CLOVA Studio 의 HCX-005, HCX-007 모델을 활용&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;Q3. Ncloud 사용 중 특히 만족했던 점과, 아쉬웠던 점은 무엇인가요?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한국어 친화적인 서비스일 뿐만 아니라, 사용자 입장에서 높은 편의성을 제공하기위한 UX 가 만들어져있어 모든 서비스를 처음 사용함에도 공식문서만으로 어려움없이 사용할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AWS, GCP 등에 비해 비용적인 측면에서 조금 더 부담되는 아쉬움이 있었지만, 높은 편의성을 기반으로 빠르게 개발/운영해야하는 상황에서 활용하기 매우 좋은 클라우드 서비스라고 생각한다.&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;Q4. Green Developers 프로그램 참여 소감 말씀 부탁 드립니다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;포텐데이를 통해 해커톤 형식의 프로젝트를 진행했고, 그 과정에서 NCloud 의 다양한 서비스를 사용할 수 있는 계기가 되어서 좋았다. 국내 AI Service 에 대한 인식 개선에도 매우 큰 계기가 되었고 앞으로 업무에 있어서도 다양한 아이디어에서 활용해볼수 있을거라 기대한다. 특히 현재 AI Agent 기반 어플리케이션이 계속해서 쏟아지고있는 지금 Green Developers 프로그램을 통해 좋은 경험을 싿고가는 점에 있어 감사를 표하고싶다!&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;Q5. 마지막 한 말씀 부탁 드립니다.&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지속적으로 AI 기반 서비스에 대한 경험을 쌓아가고싶은 지금 좋은 기회를 통해 다양한 경험을 하게되어 좋았다!&lt;/p&gt;</description>
      <author>ch4njun</author>
      <guid isPermaLink="true">https://ch4njun.tistory.com/277</guid>
      <comments>https://ch4njun.tistory.com/277#entry277comment</comments>
      <pubDate>Sat, 27 Sep 2025 17:24:35 +0900</pubDate>
    </item>
    <item>
      <title>[Concept] Composition, Aggregation, Delegation 개념정리</title>
      <link>https://ch4njun.tistory.com/275</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;정말 오랜만에 포스팅인데, 이번 포스팅에서는 Effective JAVA 와 같은 책에서 정말 많이 언급되는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Composition, Aggregation, Delegation&lt;/b&gt;&lt;/span&gt; 에 대해서 정리해보려고 한다. 이 세가지를 함께 정리하는 이유는 굉장히 비슷하면서도 다르면서도.. 그런 개념들이기 때문이다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;개념에 대해서 설명하기에 앞서 내가 프로젝트에서 Delegation 을 활용한 사례에 대해서 소개하려고 한다.&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;font-family: 'Noto Serif KR';&quot;&gt;최근에 프로젝트에서 외부 업체로부터 SDK 를 제공받아 개발을 하게 되었는데, &lt;b&gt;해당 SDK 의 기능중에서 한 가지는 byte[] 타입의 인증서를 EckaCertificate 객체로 파싱해주고 이를 통해 인증서 정보를 읽어올 수 있도록 하는 것&lt;/b&gt;이었다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;하지만 &lt;u&gt;&lt;b&gt;해당 객체의 메소드중 하나인&lt;/b&gt; &lt;b&gt;eckaCertificate.getPublicKeyData().getKey() 에서 에러가 발생하는 이슈가 있었는데,&amp;nbsp; SDK 를 제공해주는 업체에서 해당 이슈가 내부적으로 해결하기 어려우니 직접 파싱해서 사용해줄수 없겠냐는 요구사항&lt;/b&gt;&lt;/u&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;font-family: 'Noto Serif KR';&quot;&gt;처음에는 아무생각없이 &lt;i&gt;&lt;b&gt;'그럼 EckaCertificate 클래스를 활용하지말고 나만의 클래스를 만들어서 기능을 모두 구현하자.'&lt;/b&gt; &lt;/i&gt;로 접근했다. 그래서 직접 byte[] 타입의 인증서를 직접 일일히 파싱하고 필요한 메소드를 일일히 구현했다. 하지만 이렇게 구현을 하면서 정말 개발 코스트가 높다는것을 느꼈고, EckaCertificate 클래스의 모든 메소드중에 하나때문에 이렇게 한다는게 조금 비효율적이라고 생각했다.&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;font-family: 'Noto Serif KR';&quot;&gt;그러다 문득 깨달음을 얻는데... 그게 &lt;span style=&quot;color: #ef5369; background-color: #f6e199;&quot;&gt;&lt;b&gt;Composition 과 Delegation 을 사용하자는 것&lt;/b&gt;&lt;/span&gt;이었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1647063321922&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SeAttestationCert {
    private final EckaCertificate eckaCertificate;

    public SeAttestationCert(byte[] seAttestationCert) {
        this.eckaCertificate = new EckaCertificate(seAttestationCert);
    }

    public byte[] getCaIdentifier() {
        return eckaCertificate.getCAIdentifier();
    }

    public byte[] getSubjectIdentifier() {
        return eckaCertificate.getSubjectIdentifier();
    }

    public byte[] getMessage() {
        return eckaCertificate.getMessage();
    }

    public byte[] getPublicKey() {
        byte[] eckaPublicKey = eckaCertificate.getEckaPublicKey().toByteArray();
        byte[] publicKey = new byte[eckaPublicKey[4]];
        System.arraycopy(eckaPublicKey, 5, publicKey, 0, publicKey.length);
        return publicKey;
    }

    public byte[] getSignature() {
        return eckaCertificate.getSignature();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;위 코드와 같이 Composition &amp;amp; Delegation 을 통해서 이슈가 발생했던 getPublicKey() 메소드에 대해서만 내가 원하는 형태로 파싱을 해서 이슈를 해결할 수 있었다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;물론 상속을 통해서도 구현할 수 있었을 것 같지만 EckaCertificate 클래스에서 사용되지 않는 메소드들까지 모두 노출되는 것이 싫었고 내가 사용할 메소드들만 제한하여 노출시킬 수 있다는 장점이 있었기 때문에 Composition &amp;amp; Delegation 방식을 사용했다.&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;span style=&quot;color: #ef5369; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Composition&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Composition 은 Has-a 관계를 구현하기 위해 사용되는 설계기술&lt;/b&gt;&lt;/span&gt;이다. 다음 포스팅은 Effective Java 에 소개된 상속과 Composition 을 비교한 내용을 정리한 포스팅이다.&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;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://ch4njun.tistory.com/247?category=871177&quot;&gt;https://ch4njun.tistory.com/247?category=871177&lt;/a&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1647066426677&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[아이템 18] 상속보다는 컴포지션을 사용하라&quot; data-og-description=&quot;알다시피 상속은 코드 재사용을 구현하기 위한 강력한 방법이다. 하지만 잘못 사용할 경우 오류를 내기 쉬운 프로그램을 만들게 된다. 이러한 문제는 상위 클래스와 하위 클래스를 동일한 개발&quot; data-og-host=&quot;ch4njun.tistory.com&quot; data-og-source-url=&quot;https://ch4njun.tistory.com/247?category=871177&quot; data-og-url=&quot;https://ch4njun.tistory.com/247&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/Uq0rH/hyNGCbx5Np/VRhWAJhKT6JIm8xKJviU4k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bOupkX/hyNFGfyEwl/d9FQz7SWMpZ6wOBHKFm6PK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/wdVM9/hyNFDpEIER/17cVc0kiKcTKKUdfsnQlj1/img.png?width=1280&amp;amp;height=531&amp;amp;face=0_0_1280_531&quot;&gt;&lt;a href=&quot;https://ch4njun.tistory.com/247?category=871177&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ch4njun.tistory.com/247?category=871177&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/Uq0rH/hyNGCbx5Np/VRhWAJhKT6JIm8xKJviU4k/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bOupkX/hyNFGfyEwl/d9FQz7SWMpZ6wOBHKFm6PK/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/wdVM9/hyNFDpEIER/17cVc0kiKcTKKUdfsnQlj1/img.png?width=1280&amp;amp;height=531&amp;amp;face=0_0_1280_531');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[아이템 18] 상속보다는 컴포지션을 사용하라&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;알다시피 상속은 코드 재사용을 구현하기 위한 강력한 방법이다. 하지만 잘못 사용할 경우 오류를 내기 쉬운 프로그램을 만들게 된다. 이러한 문제는 상위 클래스와 하위 클래스를 동일한 개발&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ch4njun.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;간단하게 정리하면 상속과 비교해 다음과 같은 장점이 있다.&lt;/span&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: disc;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;상속으로 구현하게 되면 상위 클래스의 모든 public 메서드가 클라이언트에게 공개&lt;/b&gt;된다. 하지만,&amp;nbsp;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;컴포지션을 사용해 구현하게되면 개발자가 원하는 메서드만 클라이언트에게 공개&lt;/b&gt;&lt;/span&gt;할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ef5369; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;상위 클래스의 내부구현을 숨길 수 있다.&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;JAVA 에서 지원하지 않는&amp;nbsp;&lt;b&gt;다중상속의 목적을 달성&lt;/b&gt;할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;상위 클래스에서 제공하는 메서드를 더 나은 버전으로 개선&lt;/b&gt;&lt;/span&gt;할 수 있다. (충분히 유연하다)&lt;br /&gt;Override 는 아니지만 동일한 기능을 제공할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;참조하고 있는&amp;nbsp;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;인스턴스 변수를 변경해 프로그램을 동적으로 변경&lt;/b&gt;&lt;/span&gt;할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;상위 클래스의 메서드 형태와 관계없이 유연하게 하위 클래스의 메서드를 정의&lt;/b&gt;&lt;/span&gt;할 수 있다.&lt;br /&gt;상위 클래스에서는 String 반환 값을 가지지만 하위 클래스에서는 Integer 를 가지도록 할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #ef5369; background-color: #f6e199;&quot;&gt;&lt;b&gt;상속을 쓰지말고 Composition 을 쓰라는게 아니라 반드시 상황에 맞게 적합한 설계방법을 사용해야한다는 것&lt;/b&gt;&lt;/span&gt;이다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;엄연히 두 설계방법은 목적이 다르다. &lt;b&gt;상속은 Is-a 관계를 구현하기 위해서 사용되고 Composition 은 Has-a 관계를 구현하기 위해서 사용된다.&lt;/b&gt; 하지만 Composition 을 사용해야 하는 상황에서도 상속을 사용하고 있는 사례가 많기 때문에 항상 상속을 사용하기에 앞서 고민을 하는 것이 좋다고 생각한다.&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;font-family: 'Noto Serif KR';&quot;&gt;앞서 소개한 프로젝트에서의 사례에서도 Composition 이 활용됐다.&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;font-family: 'Noto Serif KR';&quot;&gt;참고로 Composition 과 Delegation 은 대립된 개념이 아니다. 위 사례에서는 Delegation 을 구현하는데 있어서 Composition 이 사용된 것이다. 항상은 아니어도 대부분의 경우 Delegation 을 구현하기 위해 Composition 이 사용된다. 해당 내용은 마지막 정리부분에서 다시한번 이야기한다.&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;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Aggregation&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Aggregation 은 Composition 과 비슷한 개념이다. 하지만 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Composition 은 A, B 관계에서 A 가 B 를 포함하고 있고, A 가 사라진다면 포함하고 있는 B 도 같이 사라지는것에 반해, Aggregation 은 A 가 B 의 집합의 개념이기 때문에 A 가 사라지더라도 B 는 사라지지 않는다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, Composition 은 Has-a 관계이지만, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Aggregation 은 Is-Part-Of 관계&lt;/b&gt;&lt;/span&gt;라고 할수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;예를들면, 레고로 만들어진 집이 있다고할 때 이 집이 없어진다고해서 레고까지 없어지는게 아니기 때문에 이 상황을 Aggregation 으로 구현하기 적합하다고 할 수 있다.&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_1647070846195&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class ToyHouse{
    private List&amp;lt;Block&amp;gt; blocks;

    public ToyHouse(Block... blocks) throws Exception{
       	if (blocks == null) {
       	    throw new Exception(&quot;Toy house needs blocks&quot;);
       	}
        blocks = new ArrayList&amp;lt;Block&amp;gt;();
        for(Block b : blocks){
            blocks.add(b);
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;위 예제가 Aggregation 을 구현한 예제이고, &lt;b&gt;Block 객체들이 ToyHouse 내부에서 생성되고 포함되는 것이 아니라, 이미 외부에서 존재하고 그 것들을 인자로 받아 구성하는 것&lt;/b&gt;을 확인할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Delegation&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Delegation 은 위임이라는 뜻으로 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;특정 클래스의 역할을 또 다른 클래스에게 위임하는 구조&lt;/b&gt;&lt;/span&gt;를 말한다. 먼저 위에서 소개한 예시 코드를 다시한번 살펴보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1647071790436&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class SeAttestationCert {
    private final EckaCertificate eckaCertificate;

    public SeAttestationCert(byte[] seAttestationCert) {
        this.eckaCertificate = new EckaCertificate(seAttestationCert);
    }

    public byte[] getCaIdentifier() {
        return eckaCertificate.getCAIdentifier();
    }

    public byte[] getSubjectIdentifier() {
        return eckaCertificate.getSubjectIdentifier();
    }

    public byte[] getMessage() {
        return eckaCertificate.getMessage();
    }

    public byte[] getPublicKey() {
        byte[] eckaPublicKey = eckaCertificate.getEckaPublicKey().toByteArray();
        byte[] publicKey = new byte[eckaPublicKey[4]];
        System.arraycopy(eckaPublicKey, 5, publicKey, 0, publicKey.length);
        return publicKey;
    }

    public byte[] getSignature() {
        return eckaCertificate.getSignature();
    }
}&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;font-family: 'Noto Serif KR';&quot;&gt;EckaCertificate 클래스의 역할을 SeAttestationCert 에 위임한 것을 볼 수 있다. 이렇게 위임함으로써 얻을 수 있는 장점은 &lt;b&gt;EckaCertificate 클래스에 포함된 메소드의 결과를 통해 또 다른 결과를 도출해내거나 부가적인 기능을 수행&lt;/b&gt;할 수 있다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369; background-color: #f6e199;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이 과정에서 당연히 EckaCertificate 클래스의 코드에 영향을 받거나 영향을 주지 않는다는 것도 기억하자.&lt;/span&gt;&lt;/b&gt;&lt;/span&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;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;정리하자면 Composition 과 Aggregation 은 비슷한 개념으로 각각 Has-a, Is-Part-Of 를 구현하기 위해 사용되는 설계방법이다. &lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그리고 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;이들과 Delegation 은 밀접한 관계&lt;/b&gt;&lt;/span&gt;를 가진다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Composition 은 클래스간에 Has-a 관계를 구현하고, 그 속에서 Overriding 과 같은 효과를 구현하기 위해서는 일반적으로 Delegation 을 활용하게 된다. &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;font-family: 'Noto Serif KR';&quot;&gt;반대로 생각해보면 Delegation 을 하기 위해서는 Has-a 관계를 가지고 있어야 해당 클래스에게 위임이 가능하기 때문에 Composition 이 구현된 상황에서 주로 사용된다.&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;style1&quot;&gt;&lt;i&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;해당 포스팅에서 소개한 개념이 개념적으로 복잡하거나 어려운 내용은 아니라고 생각한다. 하지만 이러한 개념을 인지하고 개발하느냐 그렇지 않느냐에 따라서 전혀 다른 결과를 만들어내는 것 같다. 앞으로 무언가 공부할때 '이게 의미있는 개념일까..? 안쓸거같은데..' 라고 생각하는 것이 아니라 어느 상황에서 사용될 수 있을지 고민하면서 공부해야겠다고 다시한번 다짐한다..!&lt;/span&gt;&lt;/i&gt;&lt;/blockquote&gt;</description>
      <category>개발/Concept</category>
      <author>ch4njun</author>
      <guid isPermaLink="true">https://ch4njun.tistory.com/275</guid>
      <comments>https://ch4njun.tistory.com/275#entry275comment</comments>
      <pubDate>Sat, 12 Mar 2022 17:08:44 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] JPA 에서의 연관관계</title>
      <link>https://ch4njun.tistory.com/274</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;JPA 에서 가장 중요한 개념이라고 하면 연관관계 매핑과 영속성 컨텍스트가 있다. 이번 포스팅에서는 이 중에서 &lt;b&gt;연관관계 매핑&lt;/b&gt;에 대해서 이야기해보려고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;객체지향 프로그램에서의 객체와 RDB 에서의 테이블이 서로 연관관계를 맺는 방법이 다르다.&lt;/b&gt;&lt;/span&gt; 그렇기 때문에 &lt;b&gt;이 둘의 차이를 채우기 위한 매핑과정이 필요하고 이를 ORM 인 JPA 가 수행&lt;/b&gt;하게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;연관관계에서는 아래와 같은 용어들이 등장한다.&lt;/span&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;방향(Direction) :&lt;/b&gt; 단방향 연관관계, 양방향 연관관계&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;다중성(Multiplicity) :&lt;/b&gt; 다대일(N:1), 일대다(1:N), 일대일(1:1), 다대다(N:M)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;연관관계의 주인(Owner)&lt;/span&gt;&lt;/b&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;color: #ef5369; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;데이터 중심의 모델링&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;JPA 에서 해주는 연관관계 매핑에 대해서 알아보기에 앞서 잘못된 연관관계 방법에 대해서 살펴보려고 한다. &lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1640071478952&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Member {
    private long id;
    private long teamId;
    private String userName;
}

class Team {
    private long id;
    private String teamName;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;위 예시는 &lt;b&gt;객체를 RDB 의 테이블에 맞춰 &lt;u&gt;데이터 중심적으로 모델링&lt;/u&gt; 한 것&lt;/b&gt;이다. 테이블은 외래키를 통해 또 다른 테이블과의 연관관계를 가지는데 &lt;b&gt;teamId 가 이 외래키&lt;/b&gt;에 해당하는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;하지만 이렇게 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;테이블에 맞춰 연관관계를 가지면 객체지향 프로그래밍에서의 객체를 제대로 활용할 수 없다.&lt;/b&gt;&lt;/span&gt; &lt;b&gt;즉, 객체 사이의 협력관계를 만들 수 없고 굉장히 부자연스러워진다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;또한 해당 객체를 테이블에 저장하고 읽어오는 과정에서 여러가지 단점들이 있는데 예시코드로 살펴보자.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1640073073228&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RequiredArgsConstructor
public class Jpa {
    private final EntityManagerFactory emf;
    
    public void save() {
    	EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        
        tx.begin();
        try {
            Team team = new Team();
            team.setTeam(&quot;teamA&quot;);
            em.persist(team);
            
            Member member = new Member();
            member.setName(&quot;member1&quot;);
            member.setTeamId(team.getId());
            
            em.persist(member);
            tx.commit();
        } catch(Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
    }
    
    public void find(Long memberId) {
    	EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        
        tx.begin();
        try {
            // 불필요한 과정이 굉장히 많이 포함된다.
            Member findMember = em.find(Member.class, memberId);
            Long findTeamId = findMember.getTeamId();
            Team findTeam = em.find(Team.class, findTeamId);
            
            tx.commit();
        } catch(Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;특정 member 가 속한 팀 정보를 조회하기 위해서는 Member 를 조회한 뒤 외래키로 가지고 있는 teamId 를 통해서 Team 을 조회하는 과정을 계속해서 반복&lt;/b&gt;&lt;/span&gt;해야 한다. &lt;b&gt;즉, Member, Team 을 조회하는 2개의 쿼리를 따로 작성&lt;/b&gt;해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;물론 이 과정을 하면되지 않나? 라고 생각할 수도 있다. 하지만 연관관계가 많아진다면 어떻게 될지 생각해보자.&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;단방향 매핑&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;단방향 연관관계에서 객체와 데이터베이스의 테이블이 각각 어떻게 연관관계를 가지는지 살펴보자&lt;/span&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: circle;&quot; data-ke-list-type=&quot;circle&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt; 객체 :&lt;/b&gt; 참조를 통해 연관된 객체를 찾는다. A가 B를 참조할 때 B -&amp;gt; A 는 불가능하다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;테이블 :&lt;/b&gt; 외래키로 Join 해 연관된 테이블을 조회한다. 양방향으로 A -&amp;gt; B, B -&amp;gt; A 모두 가능하다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이러한 차이가 있고 이러한 차이를 극복하기 위해 매핑이 필요한 것이다. 위에서 설명한 데이터 중심의 모델링은 이러한 차이를 극복하기 위해 객체를 데이터에 맞춘 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;545&quot; data-origin-height=&quot;324&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCPdJo/btroFK6UABG/GA4xyApnDeE1kXABDisPOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCPdJo/btroFK6UABG/GA4xyApnDeE1kXABDisPOk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCPdJo/btroFK6UABG/GA4xyApnDeE1kXABDisPOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCPdJo%2FbtroFK6UABG%2FGA4xyApnDeE1kXABDisPOk%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;451&quot; height=&quot;268&quot; data-origin-width=&quot;545&quot; data-origin-height=&quot;324&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;위 그림과 같이 연관관계를 맺는 것을 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;객체 중심의 모델링&lt;/b&gt;&lt;/span&gt;이라고 한다. 코드를 살펴보자.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1640184563347&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
public class Memeber {
    @Id @GeneratedValue
    private Long id;
    
    private String userName;
    
    @ManyToOne
    @JoniColumn(name = &quot;team_id&quot;)
    private Team team;
}&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;font-family: 'Noto Serif KR';&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;span style=&quot;font-family: 'Noto Serif KR';&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;아니다. 이러한 객체는 테이블과 연관관계를 맺는 구조가 다르다. 그렇기 때문에 &lt;b&gt;데이터를 테이블에 삽입하고 읽어올 때 다음과 같은 불필요한 과정들을 거쳐야한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1640184669841&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public Member findMember(String memberId) {
    // SELECT * FROM members WHERE memberId = :memberId;
    Member member = new Member();  // 쿼리 결과로 객체 생성
    
    // SELECT * FROM teams WHERE teamId = :teamId;
    // 위에서 쿼리한 member 결과에는 외래키인 teamId 가 존재한다.
    Team team = new Team();
    member.setTeam(team);
    return member;
}&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;font-family: 'Noto Serif KR';&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;또한 member.getTeam() 을 믿고 사용하려면 이렇게 제대로 매핑이 되어있는지 확인하는 단계를 거쳐야 한다. 서비스 계층과 데이터 액세스 계층이 명확하게 분리가 안되는 것이다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;이러한 매핑을 ORM 이 해주겠다는 것&lt;/b&gt;&lt;/span&gt;이다. EntityManager 를 이용한 코드를 살펴보자.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1640185087053&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@RequiredArgsConstructor
public class Jpa {
    private final EntityManagerFactory emf;
    
    public void save() {
    	EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        
        tx.begin();
        try {
            Team team = new Team();
            team.setTeam(&quot;teamA&quot;);
            em.persist(team);
            
            Member member = new Member();
            member.setName(&quot;member1&quot;);
            member.setTeam(team);
            
            em.persist(member);
            tx.commit();
        } catch(Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
    }
    
    public void find(Long memberId) {
    	EntityManager em = emf.createEntityManager();
        EntityTransaction tx = em.getTransaction();
        
        tx.begin();
        try {
            // findMember 를 가져올 때 연관관계를 가지는 Team 까지 가져와서 객체 참조형태로 매핑까지 해준다.
            Member findMember = em.find(Member.class, memberId);
            Team findTeam = findMember.getTeam();
            
            tx.commit();
        } catch(Exception e) {
            tx.rollback();
        } finally {
            em.close();
        }
    }
}&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;font-family: 'Noto Serif KR';&quot;&gt;여기서 잠시 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;N+1 문제&lt;/b&gt;&lt;/span&gt;에 대해서 살펴보고 넘어가자. &lt;b&gt;단방향 연관관계를 사용한다면 아주 흔하게 발생하는 문제이고 인식하지 않고 있다면 발생하는지 알지 못하고 넘어가버리게 된다.&lt;/b&gt; 개인이 공부 목적으로 프로젝트를 진행할 때 N+1 문제가 성능상의 이슈로 식별되기 쉽지 않기 때문이다. 나도 그랬다...!&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;span style=&quot;color: #ef5369; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;N + 1 문제&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 포스팅에서는 N + 1 문제에 대해서 간단하게 소개만하고 넘어갈 예정이다.&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;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;N + 1 문제는 JPA 를 사용한다면 굉장히 흔하게 접할 수 있는 문제이기 때문에 더욱 중요&lt;/b&gt;하다. &lt;b&gt;하지만 트래픽이 많지 않은 프로젝트에서는 N + 1 문제를 체감하기 어렵기 때문에 크게 의식하지 않을 수 있다는 문제가 있다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그럼 N + 1 문제란 무엇일까?&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1640187195604&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
public class Memeber {
    @Id @GeneratedValue
    private Long id;
    
    private String userName;
}

@Entity
public class Team {
    @Id @GeneratedValue
    private Long id;
    
    private teamName;
    
    @OneToMany
    @JoniColumn(name = &quot;member_id&quot;)
    private List&amp;lt;Member&amp;gt; members;
}&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;font-family: 'Noto Serif KR';&quot;&gt;위와 같은 단방향 연관관계를 가지는 두 엔티티가 있다. 현재 &lt;b&gt;TeamA 라는 팀에는 총 12명의 Member 가 포함&lt;/b&gt;되어 있다고 했을 때 TeamA 를 find() 하면 무슨일이 벌어질까?&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_1640188005028&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;SELECT * FROM Team WHERE teamId = 1; // TeamA 의 teamId 는 1 이라고 가정한다.
SELECT * FROM Member WHERE memberId = &quot;member1&quot;;
SELECT * FROM Member WHERE memberId = &quot;member2&quot;;
SELECT * FROM Member WHERE memberId = &quot;member3&quot;;
SELECT * FROM Member WHERE memberId = &quot;member4&quot;;
SELECT * FROM Member WHERE memberId = &quot;member5&quot;;
SELECT * FROM Member WHERE memberId = &quot;member6&quot;;
SELECT * FROM Member WHERE memberId = &quot;member7&quot;;
SELECT * FROM Member WHERE memberId = &quot;member8&quot;;
SELECT * FROM Member WHERE memberId = &quot;member9&quot;;
SELECT * FROM Member WHERE memberId = &quot;member10&quot;;
SELECT * FROM Member WHERE memberId = &quot;member11&quot;;
SELECT * FROM Member WHERE memberId = &quot;member12&quot;;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;조회 대상이었던 &lt;span style=&quot;color: #ef5369; background-color: #f6e199;&quot;&gt;&lt;b&gt;Team 을 조회하기 위한 한 번의 쿼리와 해당 팀과 연관관계를 맺고 있는 Member 들을 조회하기 위한 N 번의 쿼리(총 N + 1 번의 쿼리)가 발생&lt;/b&gt;&lt;/span&gt;한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;만약 하나의 Team 에 속한 Member 가 10만명이라면 무슨일이 벌어질까?&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;한 번의 요청에 10만 1번의 SQL 쿼리가 발생할 것이다.&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그러면 이런 요청이 10만번 발생하면 무슨일이 벌어질까?&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;셀 수 없을만큼 많은 수의 SQL 쿼리가 발생할 것이다.&lt;/span&gt;&lt;/b&gt;&lt;/i&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;font-family: 'Noto Serif KR';&quot;&gt;정리하자면 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;N + 1 문제가 발생할 때 트래픽이 증가함에 따라 SQL 쿼리의 수는 기하 급수적으로 늘어나게 된다.&lt;/b&gt;&lt;/span&gt; 이는 서비스에 심각한 성능저하를 유발할 수 있고 따라서 반드시 해결해야하는 문제다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이러한&lt;span style=&quot;color: #ef5369;&quot;&gt; &lt;b&gt;N + 1 문제를 해결하기 위한 방법으로는 Fetch Join, @EntityGraph&lt;/b&gt;&lt;/span&gt; 가 있는데 이는 별도의 포스팅을 통해서 살펴볼 예정이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;양방향 연관관계&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;사실 객체에서는 양방향 연관관계라는건 없다. 그냥 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;두 객체가 서로 참조하는 단방향 연관관계가 2개인 것을 양방향 연관관계라고 하는 것&lt;/b&gt;&lt;/span&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;데이터베이스의 테이블은 애초에 단방향 연관관계가 없었고 양방향 연관관계만 존재했다. 외래키를 통해 Join 하고 그 결과를 통해 A-&amp;gt;B, B-&amp;gt;A 모두 가능하기 때문이다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2158&quot; data-origin-height=&quot;630&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/SOec8/btroJhRAIOi/T1SF4g6CwZkJ8A5wupqfH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/SOec8/btroJhRAIOi/T1SF4g6CwZkJ8A5wupqfH1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/SOec8/btroJhRAIOi/T1SF4g6CwZkJ8A5wupqfH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FSOec8%2FbtroJhRAIOi%2FT1SF4g6CwZkJ8A5wupqfH1%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;685&quot; height=&quot;200&quot; data-origin-width=&quot;2158&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;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;객체에서 양방향 연관관계가 없고 단방향 연관관계 2개를 양방향 연관관계라고 말하기 때문에 데이터베이스 테이블과의 차이점이 생기게되고 이러한 차이점을 채우는 것이 양방향 연관관계 매핑이다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1130&quot; data-origin-height=&quot;287&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rgpSw/btroFMq77ZK/Z3mfZbDBawuS7852kwvUkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rgpSw/btroFMq77ZK/Z3mfZbDBawuS7852kwvUkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rgpSw/btroFMq77ZK/Z3mfZbDBawuS7852kwvUkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrgpSw%2FbtroFMq77ZK%2FZ3mfZbDBawuS7852kwvUkk%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;1130&quot; height=&quot;287&quot; data-origin-width=&quot;1130&quot; data-origin-height=&quot;287&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;객체에는 두 개의 단방향 연관관계가 있고 테이블에는 하나의 양방향 연관관계가 있다. &lt;b&gt;그럼 Member 객체의 team 을 수정해도 외래키인 TEAM_ID 가 수정되어야 하고, Team 의 members 를 수정해도 외래키인 TEAM_ID 가 수정되어야 한다.&lt;/b&gt; 다시 말해 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;MEMBER 테이블의 외래키 TEAM_ID 는 두 개의 연관관계 매핑에 엮여있다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이러한 문제를 해결하기 위해 &lt;span style=&quot;color: #ef5369; background-color: #f6e199;&quot;&gt;&lt;b&gt;양방향 연관관계 매핑에서는 해당 연관관계의 주인을 결정&lt;/b&gt;&lt;/span&gt;해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 객체의 두 개의 단방향 연관관계 중에서 &lt;b&gt;외래키 TEAM_ID 에 영향을 줄 하나의 단방향 연관관계를 지정해줘야 한다는 것&lt;/b&gt;이다.&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_1640240887842&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;
    
    private String memberName;
    
    @ManyToOne
    @JoinColumn(&quot;team_id&quot;)
    private Team team;
}

@Entity
public class Team {
    @Id @GeneratedValue
    private Long id;
    
    private String teamName;
    
    @OneToMany(mappedBy = &quot;team&quot;)
    List&amp;lt;Member&amp;gt; members = new ArrayList&amp;lt;&amp;gt;();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;여기서 중요하게 봐야할 것은 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;@OneToMany 어노테이션에 mappedBy 값&lt;/b&gt;&lt;/span&gt;이다. &lt;b&gt;이 값이 하는 역할이 두 개의 단바향 연관관계 중에서 연관관계 매핑의 주인을 정하는 것&lt;/b&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;위에서 @OneToMany(mappedBy = &quot;team&quot;) 의 의미는 team 이라는거에 매핑한다는 것이다. 따라서 연관관계 매핑의 주인은 team 을 가지고 있는 Member 클래스가 된다.&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;font-family: 'Noto Serif KR';&quot;&gt;이렇게 연관관계 매핑의 주인이 정해지면 다음과 같은 규칙이 생긴다.&lt;/span&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;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;연관관계 매핑의 주인만이 외래키를 관리한다. (등록 &amp;amp; 수정)&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;주인이 아닌 쪽에서는 오직 읽기만 가능하다.&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;주인은 mappedBy 속성을 사용하지 않는다.&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;주인이 아닌 쪽에서는 mappedBy 속성을 통해 주인을 지정해줘야 한다.&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이런 규칙이 필요한 이유는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&quot;난 members 를 수정했는데 왜 테이블에 반영이 안되지&quot; 와 같은 문제가 발생할 수 있기 때문&lt;/b&gt;&lt;/span&gt;이다. 테이블에 반영하려면 반드시 연관관계 매핑의 주인에 값을 변경해줘야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;i&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그럼 여기서 이러한 의문이 들 수 있다.&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;i&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그래서 두 객체중 어떤걸로 연관관계 매핑의 주인을 지정해야되는데?&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;i&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그 답은, &lt;span style=&quot;color: #ef5369;&quot;&gt;외래키를 가지고 있는 테이블을 연관관계 매핑의 주인으로 설정&lt;/span&gt;하면 된다. 물론 정해진 것은 아니다. 하지만 이렇게 하지 않으면 Team 객체의 members 를 수정했을 때 MEMBER 테이블이 수정되는 불일치 문제가 발생할 수 있다. 이러한 혼동을 방지하기 위해 외래키를 가지고 있는 테이블에 해당하는 객체를 연관관계 매핑의 주인으로 설정하는 것을 권장한다.&lt;/span&gt;&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;양방향 연관관계에서 주의점&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;양방향 연관관계에서 주의해야 할 점은 &lt;span style=&quot;color: #ef5369; background-color: #f6e199;&quot;&gt;&lt;b&gt;연관관계 매핑의 주인에 값을 입력하지 않으면 데이터베이스 테이블에 데이터가 반영되지 않는다는 점이다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1640242458628&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Member member = new Member();
member.setName(&quot;member1&quot;);
em.persist(member);

Team team = new Team();
team.setName(&quot;teamA&quot;);
team.getMembers().add(member);
em.persist(team);&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;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;이렇게 했을 때 외래키에는 null 값이 저장되는 문제&lt;/b&gt;가 생긴다. &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;연관관계 매핑의 주인이 Member 인데 member.setTeam() 메소드를 통해 Team 을 지정해주지 않았기 때문이다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이러한 문제를 해결하기 위해 &lt;u&gt;연관관계 매핑의 주인만 수정&lt;/u&gt;해도 되지만, 순수한 객체 참조관계를 고려한다면 &lt;u&gt;그냥 양쪽 다 입력&lt;/u&gt;해주는게 바람직하다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;더 나아가서 Team 을 셋팅하는 메소드를 만들어서 처리하면 더 깔끔하다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1640242601819&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void changeTeam(Team team) {
    this.team = team;
    team.getMembers().add(this);
}&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;font-family: 'Noto Serif KR';&quot;&gt;또 한가지 주의해야할 점은 &lt;span style=&quot;color: #ef5369; background-color: #f6e199;&quot;&gt;&lt;b&gt;순환참조문제가 발생할 수 있다는 것&lt;/b&gt;&lt;/span&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;우리가 굉장히 흔하게 사용하는 &lt;b&gt;toString(), lombok, JSON 생성 라이브러리&lt;/b&gt;를 쓰게되면 순환참조문제가 발생할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1640242699980&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* 회원(Member) 엔티티*/
@Entity
public class Member {
    @Override
    public String toString() {
        return &quot;Member{&quot; +
                &quot;id=&quot; + id +
                &quot;, name='&quot; + name + '\'' +
                &quot;, team=&quot; + team +
                '}';
    }
}

/* 팀(Team) 엔티티 */
@Entity
public class Team{
    @Override
    public String toString() {
        return &quot;Team{&quot; +
                &quot;id=&quot; + id +
                &quot;, name='&quot; + name + '\'' +
                &quot;, members=&quot; + members +
                '}';
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;그럼 이러한 양방향 연관관계가 필요한 이유는 무엇일까?&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;span style=&quot;font-family: 'Noto Serif KR';&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;가장 쉽게 생각할 수 있는 이유는 &lt;span style=&quot;color: #ef5369; background-color: #f6e199;&quot;&gt;&lt;b&gt;&quot;반대 방향으로의 참조가 가능하기 때문(반대 방향으로의 객체 그래프 탐색 기능 추가)&quot;&lt;/b&gt; &lt;/span&gt;이다. 반대 방향으로의 참조가 필요한 경우는 &lt;b&gt;단순히 비즈니스 로직에서 필요한 경우도 있을 것&lt;/b&gt;이다. 하지만 &lt;b&gt;JPQL 에서 역방향 참조가 필요한 경우가 많다는&lt;/b&gt; 점이 중요하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;기본적으로 단방향 연관관계를 사용하고 필요에 따라서 양방향 연관관계를 사용하는 것은 옳다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;하지만, &lt;b&gt;단방향 연관관계가 양방향 연관관계보다 좋다고 말하는 것은 잘못된 말&lt;/b&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;일대다(1:N) 단방향 연관관게 매핑에서 영속성 전이&lt;span style=&quot;background-color: #f6e199;&quot;&gt;(@OneToMany(cascade = CascadeType.ALL))&lt;/span&gt; 를 통한 Insert 시&lt;/b&gt;&lt;/span&gt;에 외래키 지정을 위한 &lt;b&gt;추가적인 Update 쿼리가 발생&lt;/b&gt;할 수 있다. 이 경우에는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;오히려 양방향 연관관계로 변경함으로써 이러한 불필요한 Update 쿼리를 없앨 수 있다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1640245040623&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Transactional
public void createTeamWithMembers() {
    Team team = new Team(&quot;teamA&quot;);
    
    Member member1 = new Member(&quot;member1&quot;);
    Member member2 = new Member(&quot;member2&quot;);
    
    team.getMembers().add(member1);
    team.getMembers().add(member2);
    
    Team savedTeam = teamRepository.save(team);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이렇게 하게 되면 하나의 트랜잭션으로 묶여있기 때문에 쓰기지연으로 인해서 최종적으로 총 3번의 Insert 쿼리가 발생할 것이라고 예상하게 된다.&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;font-family: 'Noto Serif KR';&quot;&gt;하지만, 실제로 발생하는 쿼리는 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1231&quot; data-origin-height=&quot;223&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lxfYH/btroITKo8q1/0bZkqIKsWSSSqy8L8ukWOK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lxfYH/btroITKo8q1/0bZkqIKsWSSSqy8L8ukWOK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lxfYH/btroITKo8q1/0bZkqIKsWSSSqy8L8ukWOK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlxfYH%2FbtroITKo8q1%2F0bZkqIKsWSSSqy8L8ukWOK%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;1231&quot; height=&quot;223&quot; data-origin-width=&quot;1231&quot; data-origin-height=&quot;223&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉 왜래키 지정을 위해 추가적인 Update 쿼리가 발생하게 된다. 만약 Insert 가 10만번 발생한다면 10만번의 Update 쿼리가 추가적으로 발생하게 된다.&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;font-family: 'Noto Serif KR';&quot;&gt;따라서 이러한 경우에 대해서는 양방향 연관관계가 필요한 경우가 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;(일반적인 상황에서는 단방향 연관관계를 사용하는 것이 좋다)&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;연관관계 예시&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;먼저 &lt;span style=&quot;color: #ef5369; background-color: #f6e199;&quot;&gt;&lt;b&gt;다대일(N:1) 연관관계&lt;/b&gt;&lt;/span&gt;에 대해서 살펴보자.&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1408&quot; data-origin-height=&quot;732&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mQW1U/btroKES5gvQ/0hid5rG6Z6Gi0bVeMw3Wzk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mQW1U/btroKES5gvQ/0hid5rG6Z6Gi0bVeMw3Wzk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mQW1U/btroKES5gvQ/0hid5rG6Z6Gi0bVeMw3Wzk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmQW1U%2FbtroKES5gvQ%2F0hid5rG6Z6Gi0bVeMw3Wzk%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;577&quot; height=&quot;300&quot; data-origin-width=&quot;1408&quot; data-origin-height=&quot;732&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;font-family: 'Noto Serif KR';&quot;&gt;이러한 &lt;b&gt;다대일 연관관계를 가장 많이 사용&lt;/b&gt;하게 되고, &lt;b&gt;객체가 서로 참조가 필요한 상황이라면 양방향 연관관계를 사용&lt;/b&gt;하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #ef5369; background-color: #f6e199;&quot;&gt;&lt;b&gt;일대다(1:N) 연관관계&lt;/b&gt;&lt;/span&gt;에 대해서 살펴보자. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1414&quot; data-origin-height=&quot;742&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rtA1M/btroHbxXru7/U4vSsdWREksPCAw2F2eUNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rtA1M/btroHbxXru7/U4vSsdWREksPCAw2F2eUNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rtA1M/btroHbxXru7/U4vSsdWREksPCAw2F2eUNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrtA1M%2FbtroHbxXru7%2FU4vSsdWREksPCAw2F2eUNK%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;605&quot; height=&quot;317&quot; data-origin-width=&quot;1414&quot; data-origin-height=&quot;742&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;일대다 관계에서는 '다' 쪽에 항상 외래키가 존재&lt;/b&gt;&lt;/span&gt;하게 된다. &lt;b&gt;객체에는 Team 에 있지만 테이블에는 MEMBER 에 외래키가 있기 때문에 혼동&lt;/b&gt;이 올 수 있다.&lt;i&gt;&lt;b&gt; (양방향 연관관계에서 연관관계 매핑의 주인을 결정할 때 나왔던 내용과 유사하다)&lt;/b&gt;&lt;/i&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;font-family: 'Noto Serif KR';&quot;&gt;그리고 이러한 일대다(1:N) 연관관계에서는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;@JoinColumn 을 반드시 사용&lt;/b&gt;&lt;/span&gt;해야한다. &lt;b&gt;사용하지 않으면 중간에 Join 테이블을 하나 추가하게 된다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;u&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;따라서 기본적으로 다대일(N:1) 연관관계를 사용하다가 반대 방향의 참조가 필요하면 양방향 연관관계를 사용하는 것을 권장한다.&lt;/span&gt;&lt;/b&gt;&lt;/u&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;물론 일대다(1:N) 연관관계에서도 양방향 연관관계가 존재한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1640249000320&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/* 팀(Team) */
public class Team{
		...
    @OneToMany
    @JoinColumn(name=&quot;TEAM_ID&quot;)
    private List&amp;lt;Member&amp;gt; members = new ArrayList&amp;lt;&amp;gt;();
		...
}

/* 멤버(Member) */
public class Member{
		...
    @ManyToOne
    @JoinColumn(name=&quot;TEAM_ID&quot;, insertable=false, updatable=false)
    private Team team;
		...
}&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;font-family: 'Noto Serif KR';&quot;&gt;이 경우에 insertable, updateable 옵션을 임의로 해제해 연관관계 매핑의 주인이 아닌쪽에서는 등록, 수정이 불가능하도록 임의로 만들어줘야 한다. 공식적으로 이러한 양방향 연관관계 매핑은 지원하지 않는다.&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;font-family: 'Noto Serif KR';&quot;&gt;마지막으로 &lt;span style=&quot;color: #ef5369; background-color: #f6e199;&quot;&gt;&lt;b&gt;다대다(N:M) 연관관계&lt;/b&gt;&lt;/span&gt;에 대해서 살펴보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;다대다 연관관계는 실무에서 거의 사용하지 않고 추천도 하지 않는다고 한다. 다대다 연관관계가 필요한 경우에는 중간에 하나의 엔티티를 추가해 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;@ManyToMany 를 @OneToMany, @ManyToOne 두 개로 분리하는 것을 권장&lt;/b&gt;&lt;/span&gt;한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2506&quot; data-origin-height=&quot;994&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cRmgJF/btroK0n9Hx2/Zl9OLZgfOkXNfyxvP10qBk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cRmgJF/btroK0n9Hx2/Zl9OLZgfOkXNfyxvP10qBk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cRmgJF/btroK0n9Hx2/Zl9OLZgfOkXNfyxvP10qBk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcRmgJF%2FbtroK0n9Hx2%2FZl9OLZgfOkXNfyxvP10qBk%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;709&quot; height=&quot;281&quot; data-origin-width=&quot;2506&quot; data-origin-height=&quot;994&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;다대다 연관관계에 대한 내용은 추후에 더 정리를할 예정이다.&lt;/span&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;참고한 자료는 아래와 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=WfrSN9Z7MiA&amp;amp;list=PL9mhQYIlKEhfpMVndI23RwWTL9-VL-B7U&quot;&gt;https://www.youtube.com/watch?v=WfrSN9Z7MiA&amp;amp;list=PL9mhQYIlKEhfpMVndI23RwWTL9-VL-B7U&lt;/a&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=WfrSN9Z7MiA&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/cVwjas/hyMMXg3umg/SuFLWn83czwKjaK69dwkw1/img.jpg?width=480&amp;amp;height=360&amp;amp;face=0_0_480_360&quot; data-video-width=&quot;480&quot; data-video-height=&quot;360&quot; data-video-origin-width=&quot;480&quot; data-video-origin-height=&quot;360&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/WfrSN9Z7MiA&quot; width=&quot;480&quot; height=&quot;360&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://catsbi.oopy.io/e5ab2f18-321c-4ac5-a7bb-df739964d5c2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://catsbi.oopy.io/e5ab2f18-321c-4ac5-a7bb-df739964d5c2&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1640070497651&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Spring&quot; data-og-description=&quot; &quot; data-og-host=&quot;catsbi.oopy.io&quot; data-og-source-url=&quot;https://catsbi.oopy.io/e5ab2f18-321c-4ac5-a7bb-df739964d5c2&quot; data-og-url=&quot;https://catsbi.oopy.io/e5ab2f18-321c-4ac5-a7bb-df739964d5c2&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://catsbi.oopy.io/e5ab2f18-321c-4ac5-a7bb-df739964d5c2&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://catsbi.oopy.io/e5ab2f18-321c-4ac5-a7bb-df739964d5c2&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;catsbi.oopy.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt; &lt;a href=&quot;https://www.youtube.com/watch?v=rYj8PLIE6-k&quot;&gt;https://www.youtube.com/watch?v=rYj8PLIE6-k&lt;/a&gt;&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;figure data-ke-type=&quot;video&quot; data-ke-style=&quot;alignCenter&quot; data-video-host=&quot;youtube&quot; data-video-url=&quot;https://www.youtube.com/watch?v=rYj8PLIE6-k&quot; data-video-thumbnail=&quot;https://scrap.kakaocdn.net/dn/mglHl/hyMMYNM3V0/wR5QvLtRsfhduVe9w8ZOok/img.jpg?width=480&amp;amp;height=360&amp;amp;face=0_0_480_360&quot; data-video-width=&quot;480&quot; data-video-height=&quot;360&quot; data-video-origin-width=&quot;480&quot; data-video-origin-height=&quot;360&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;iframe src=&quot;https://www.youtube.com/embed/rYj8PLIE6-k&quot; width=&quot;480&quot; height=&quot;360&quot; frameborder=&quot;&quot; allowfullscreen=&quot;true&quot;&gt;&lt;/iframe&gt;
&lt;figcaption&gt;&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Back-End/Spring Boot</category>
      <author>ch4njun</author>
      <guid isPermaLink="true">https://ch4njun.tistory.com/274</guid>
      <comments>https://ch4njun.tistory.com/274#entry274comment</comments>
      <pubDate>Tue, 21 Dec 2021 16:54:57 +0900</pubDate>
    </item>
    <item>
      <title>[JAVA] Exception vs RuntimeException</title>
      <link>https://ch4njun.tistory.com/272</link>
      <description>&lt;pre id=&quot;code_1637475556433&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CompletableFuture&amp;lt;String&amp;gt; future = asyncRestTemplate.exchange(...)
    .handleAsync((responseEntity, throwable) -&amp;gt; {
    	if (throwable instanceof HttpClientErrorException) {
            throw new CustomException(((HttpClientErrorException) throwable).getStatusCode());
        } else if (throwable instanceof RestClientException) {
            throw new CustomException(500);
        }
        return responseEntity;
    });&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;font-family: 'Noto Serif KR';&quot;&gt;실제 프로젝트에서 위와같은 코드를 사용했는데 throw new 라인에서 다음과 같은 컴파일 에러가 발생한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;/span&gt;&lt;i&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&quot;Unhandled exception: com.wemakeprice.rms.common.exception.RequestResourceException&quot;&lt;/b&gt; &lt;/span&gt;&lt;/i&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;font-family: 'Noto Serif KR';&quot;&gt;처음에는 CompletableFuture 내부에서는 Custom Exception 을 throw 를 못하는 줄 알았다. 하지만 작성되어 있던 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Custom Exception 이 RuntimeException 을 상속받는 것이 아니라 Exception 을 상속받는다는 것&lt;/b&gt;&lt;/span&gt;을 뒤늦게 깨달았고, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;CompletableFuture 내부에서는 RuntimeException 만 throw 할 수 있다는 것&lt;/b&gt;&lt;/span&gt;을 알게되었다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Exception 과 RuntimeException 의 차이를 정확하게 모르겠어서 해당 포스팅을 통해서 공부하려고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2264&quot; data-origin-height=&quot;1508&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c240g5/btrlEJknZox/In1GKR563BMmQfksjlKkmK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c240g5/btrlEJknZox/In1GKR563BMmQfksjlKkmK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c240g5/btrlEJknZox/In1GKR563BMmQfksjlKkmK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc240g5%2FbtrlEJknZox%2FIn1GKR563BMmQfksjlKkmK%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;645&quot; height=&quot;430&quot; data-origin-width=&quot;2264&quot; data-origin-height=&quot;1508&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;font-family: 'Noto Serif KR';&quot;&gt;Java 에서의 예외는 크게 Error 와 Exception 로 나눠진다.&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;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Error 는 시스템에 비정상적인 상황이 생겼을 때 발생하는 예외&lt;/b&gt;&lt;/span&gt;를 말한다. 이는 시스템 레벨에서 발생하기 때문에 심각한 오류이다. 이러한 &lt;b&gt;Error 는 개발자가 미리 예측하여 처리할 수 없기 때문에 애플리케이션을 개발할 때 오류 처리에 대한 신경을 쓰지 않아도 된다.&lt;/b&gt; 이렇게 개발자가 예외처리를 하지 않아도 되는 예외를 Unchecked Exception 이라고 한다.&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;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Exception 은 개발자가 구현한 로직에서 발생&lt;/b&gt;&lt;/span&gt;한다. 즉, &lt;b&gt;개발자가 Exception 을 미리 예측할 수 있으며 별도의 로직을 추가해 회복과 같은 처리를 해줘야 한다.&lt;/b&gt;&lt;br /&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;font-family: 'Noto Serif KR';&quot;&gt;Exception 중에서도 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;RuntimeException 과 이를 상속받는 것들은 Unchecked Exception&lt;/b&gt;&lt;/span&gt; 이고, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;이를 제외한 나머지 것들은 Checked Exception&lt;/b&gt;&lt;/span&gt; 이다. Checked Exception 이란 코드상에서 반드시 예외처리를 해야하는 Exception 을 의미한다. &lt;b&gt;제일 위에서 소개한 Unhandled Exception 에러는 Checked Exception 에 대한 예외처리를 하지 않았기 때문에 발생하는 것이다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Checked Exception vs UnChecked Exception&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;898&quot; data-origin-height=&quot;411&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4lTst/btrlLvEUvJz/JujKjVdli6OCfrYu8NBSbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4lTst/btrlLvEUvJz/JujKjVdli6OCfrYu8NBSbK/img.png&quot; data-alt=&quot;https://www.nextree.co.kr/p3239/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4lTst/btrlLvEUvJz/JujKjVdli6OCfrYu8NBSbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4lTst%2FbtrlLvEUvJz%2FJujKjVdli6OCfrYu8NBSbK%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;771&quot; height=&quot;353&quot; data-origin-width=&quot;898&quot; data-origin-height=&quot;411&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.nextree.co.kr/p3239/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;처음에 소개한 &quot;Unhandled exception: com.wemakeprice.rms.common.exception.RequestResourceException&quot; 예외는 Checked Exception 임에도 불구하고 예외처리를 하지 않았고, 컴파일 단계에서 발견된 것이다.&lt;br /&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;i&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이를 해결하기 위한 방법으로는 예외를 처리해주거나 해당 Custom Exception 을 RuntimeException 클래스를 상속받도록 수정하는 것이 있다. 나는 후자를 선택했다!&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;예외처리방법&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;span style=&quot;font-family: 'Noto Serif KR';&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;예외를 회복하는 방법은 try - catch 구문이나 CompletableFuture 의 exceptionally, handle 과 같은 메소드에서 수행할 수 있으며 다른 작업 흐름으로 이어지도록 로직을 작성&lt;/b&gt;하면 된다. 예를들면 예외가 발생했을 때 준비해놓은 더미 데이터를 Fallback 하도록 하는 작업이 있을 것 같다.&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;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;예외를 회피하는 방법은 throws 를 통해서 직접 예외를 처리하지 않고 해당 메소드를 호출한 쪽으로 예외를 전달하는 것&lt;/b&gt;을 의미한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;예외를 전환하는 방법은 예외가 발생했을 때 또 다른 예외로 throw 하는 것을 의미&lt;/b&gt;하는데, 주로 Custom Exception 을 사용하기 위해서 이러한 방법을 사용하는 것 같다. Custom Exception 을 사용하면 예외를 조금 더 명확한 의미를 가지도록 할 수 있고 Handler 를 통해 세부적으로 처리할 수 있다는 장점이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Back-End/Java</category>
      <author>ch4njun</author>
      <guid isPermaLink="true">https://ch4njun.tistory.com/272</guid>
      <comments>https://ch4njun.tistory.com/272#entry272comment</comments>
      <pubDate>Sun, 21 Nov 2021 14:22:30 +0900</pubDate>
    </item>
    <item>
      <title>[Design Pattern] Proxy Pattern</title>
      <link>https://ch4njun.tistory.com/271</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 포스팅에서는 Proxy Pattern 에 대한 개념과 Spring Boot 에서 이러한 Proxy Pattern 을 사용한 예시에 대해서 살펴보려고 한다.&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;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Proxy 패턴이란?&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #ef5369; background-color: #f6e199;&quot;&gt;&lt;b&gt;Proxy 패턴은 특정 객체가 호출되기 전에 그에 대한 접근을 제어하기 위해 사용되는 디자인 패턴이다. 즉 특정 객체가 호출될 때 해당 접근을 가로채 Proxy 객체가 그 역할을 대신하는 것을 의미한다.&lt;/b&gt;&lt;/span&gt; &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;제어권을 가져와 부가적인 기능을 수행한 후 기존 객체를 다시 &lt;u&gt;Delegate 방식&lt;/u&gt;으로 호출할 수도 있다.&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;670&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c4hJiZ/btrlDX31qIw/dQqYAVERw5KJqJwpubHYNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c4hJiZ/btrlDX31qIw/dQqYAVERw5KJqJwpubHYNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c4hJiZ/btrlDX31qIw/dQqYAVERw5KJqJwpubHYNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc4hJiZ%2FbtrlDX31qIw%2FdQqYAVERw5KJqJwpubHYNK%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;548&quot; height=&quot;306&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;670&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그림을 통해서 설명하면 기존에 클라이언트가 RealSubject.DoAction() 메소드를 호출하는 흐름을 Proxy 객체의 DoAction() 메소드로 가져와서 처리하고, 거기서 Delegate 하게 RealSubject 의 DoAction() 메소드를 호출하는 것이다.&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;font-family: 'Noto Serif KR';&quot;&gt;매우 간단하지만 이러한 Proxy 패턴을 이용하면 굉장히 다양한 기능을 구현할 수 있다.&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;style1&quot;&gt;&lt;i&gt;&lt;b&gt;&lt;span style=&quot;font-family: Noto Serif KR;&quot;&gt;인터페이스를 이용한 프록시 패턴의 구현방식에 대해서 간략하게 설명해보자면, 타겟 클래스를 인터페이스로 추상화한 뒤에 해당 인터페이스를 구현하는 프록시 클래스를 생성한다. &lt;br /&gt;그리고 해당 프록시 클래스에서는 타겟 클래스를 필드로 의존성 주입받는다. 컴포지션 방식을 이용해 가져온 타겟 클래스를 내부적으로 Delegate 방식으로 호출하도록 구현한다. &lt;br /&gt;이제 이렇게 생성한 프록시 클래스의 객체를 Bean 으로 생성해 IoC 컨테이너에 등록하고 클라이언트가 타겟 객체에 대한 호출을 하면 타겟 Bean 이 아니라 프록시 Bean 을 대신 사용해 Proxy 패턴을 구현한다.&lt;/span&gt;&lt;/b&gt;&lt;/i&gt;&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;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Proxy 패턴 활용예시&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Proxy 패턴을 활용한 예시로는&lt;b&gt; 가상 프록시, 스마트 프록시, 원격 프록시, 보호 프록시 등&lt;/b&gt; 다양하다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;가상 프록시란 꼭 필요로 하는 시점까지 객체의 생성을 연기하고 해당 객체를 Proxy 객체로 대체시키는 것&lt;/b&gt;&lt;/span&gt;을 의미한다. &lt;b&gt;JPA 의 지연로딩&lt;/b&gt;이 이러한 가상 프록시 방식을 사용 하고 있으며, 이와 같이 &lt;b&gt;객체를 생성할 때 리소스가 많이 필요한 경우 이러한 가상 프록시 방식을 이용&lt;/b&gt;할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;스마트 프록시란 특정 객체의 메소드가 호출될 때 제어권을 가져와 호출 전후로 부가적인 기능을 수행하는 것&lt;/b&gt;&lt;/span&gt;을 의미한다. 이러한 스마트 프록시는 &lt;b&gt;Spring AOP 를 구현하는데 사용한 방식&lt;/b&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&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;span style=&quot;font-family: 'Noto Serif KR';&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;결과적으로 모두 특정 객체의 메소드로 가는 호출의 제어권을 가져와 무언가 수행한다는 공통점을 가진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Proxy 패턴의 장점&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;사이즈가 큰 객체의 로딩을 실제로 사용되는 시점으로 미룰 수 있다. (가상 프록시 방식)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;실제 객체의 public, protected 메소드를 숨기고 인터페이스를 통해 노출시킬 수 있다. (보호 프록시 방식)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;로컬에 있지 않고 원격지에 존재하는 객체를 사용할 수 있다. (원격 프록시 방식)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;실제 객체의 접근에 대해서 사전, 사후 작업을 수행할 수 있다. (스마트 프록시 방식)&lt;/span&gt;&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;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Proxy 패턴의 단점&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;객체를 생성할 때 프록시 객체도 함께 생성해줘야 하기 때문에 객체 생성이 빈번한 경우 성능이 저하될 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;로직이 난해해져 가독성이 떨어질 수 있다.&lt;/span&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;&amp;nbsp;&lt;/p&gt;
&lt;div class=&quot;notranslate&quot; style=&quot;all: initial;&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;notranslate&quot; style=&quot;all: initial;&quot;&gt;&amp;nbsp;&lt;/div&gt;</description>
      <category>개발/Design Pattern</category>
      <author>ch4njun</author>
      <guid isPermaLink="true">https://ch4njun.tistory.com/271</guid>
      <comments>https://ch4njun.tistory.com/271#entry271comment</comments>
      <pubDate>Sat, 20 Nov 2021 21:41:47 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] Spring PSA</title>
      <link>https://ch4njun.tistory.com/270</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이번 포스팅에선 IoC &amp;amp; DI, Spring AOP 와 함께 Spring 의 3대 특징인 Spring PSA 에 대해서 살펴보려고 한다.&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;span style=&quot;color: #ef5369; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Spring PSA 란?&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Spring PSA 는 Portable Service Abstraction 의 약자로 휴대용 서비스 추상화라는 의미&lt;/b&gt;&lt;/span&gt;를 가진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;우선, &lt;b&gt;서비스 추상화&lt;/b&gt;란 무엇일까? &lt;b&gt;특정 서비스가 추상화되어있다는 것은 서비스의 내용을 모르더라도 해당 서비스를 이용할 수 있다는 것을 의미&lt;/b&gt;한다. &lt;u&gt;예를 들어, 우리는 JDBC Driver 를 사용해 데이터베이스에 접근하지만 JDBC Driver 가 어떻게 구현되어 있는지는 관심이 없다. 실제 구현부를 추상화 계층으로 숨기고 핵심적인 요소만 개발자에게 제공함으로써 실제 구현부를 모르더라도 해당 서비스를 이용할 수 있도록 하는 것이다.&lt;/u&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;font-family: 'Noto Serif KR';&quot;&gt;그러면 이러한 서비스 추상화에 Portable 이 붙으면 무엇을 의미하는지 이야기해보자.&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;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Portable 은 휴대용이라는 의미로 JDBC Driver 의 종류를 &lt;span style=&quot;color: #ef5369;&quot;&gt;비즈니스 로직의 수정없이 언제든지 변경할 수 있는 것&lt;/span&gt;을 의미&lt;/b&gt;한다. 즉, MySQL Driver 를 사용하다가 어느순간 Oracle Driver 로 변경한다고 해서 프로젝트의 비즈니스 로직에 변화가 없다는 것이다.&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;font-family: 'Noto Serif KR';&quot;&gt;이런 기능이 가능한 것은 추상화 계층이 존재하기 때문이다. &lt;u&gt;모든 JDBC Driver 는 공통적인 인터페이스를 가지고 있기 때문에 해당 인터페이스를 구현하는 어떤 것으로 대체되든 프로젝트에 영향이 없어지는 것이다.&lt;/u&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: #ef5369; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;이러한 점에서 Spring PSA 는 확장에는 열려있고 수정에는 닫혀있어야 한다는 OCP 에 대표적인 예시라고 할 수 있다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Spring PSA 를 왜 사용할까?&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;위에서도 언급하긴 했지만 &lt;span style=&quot;color: #ef5369; background-color: #f6e199;&quot;&gt;&lt;b&gt;서비스를 추상화함으로써 개발자가 실제 구현부를 알지 못하더라도 해당 기능을 사용할 수 있게&lt;/b&gt;&lt;/span&gt;된다. &lt;b&gt;즉, 추상화 계층인 인터페이스 API 의 정보를 활용해 해당 서비스의 모든 기능을 이용하면 되는 것이다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;또한, &lt;span style=&quot;color: #ef5369; background-color: #f6e199;&quot;&gt;&lt;b&gt;PSA 는 해당 추상화 계층을 구현하는 또 다른 서비스로 언제든지 교체할 수 있게&lt;/b&gt;&lt;/span&gt; 해준다. 비즈니스 로직의 수정도 없이 말이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;위에서 잠깐 예시를 든 JDBC Driver 이외의 또 다른 예시를 들어보자면 @Cacheable 어노테이션을 들 수 있다.&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;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;비즈니스 로직의 특정 메소드의 결과를 캐싱하고 싶을 때 @Cacheable 어노테이션을 사용한다. 현재 EhCache 를 사용해 로컬캐시를 사용하고 있다고 했을 때 이를 Redis 로 바꾸고 싶다. 그러면 해당 비즈니스 로직을 모두 수정해야할까?&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;아니다. 단순히 CacheManager 만 EhCacheCacheManager 에서 RedisCacheManager 로 교체해주면 된다.&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;span style=&quot;color: #ef5369; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Spring PSA 의 구현방법&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Spring PSA 는 추상화 계층을 추가해 서비스를 추상화하고 여러 서비스를 비즈니스 로직을 수정하지 않고 교체할 수 있도록 하는 것을 의미&lt;/b&gt;한다. 즉, 추상화 계층의 핵심사항이다. 그림으로 살펴보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;451&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bniKMG/btrlEnOSRss/yWBnG0EKrlIvYNpFXT1S2K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bniKMG/btrlEnOSRss/yWBnG0EKrlIvYNpFXT1S2K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bniKMG/btrlEnOSRss/yWBnG0EKrlIvYNpFXT1S2K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbniKMG%2FbtrlEnOSRss%2FyWBnG0EKrlIvYNpFXT1S2K%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;909&quot; height=&quot;380&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;451&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;font-family: 'Noto Serif KR';&quot;&gt;핵심은 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;추상화 계층의 인터페이스인 FlatformTransactionManager 인터페이스를 두고 이를 구현하는 다양한 서비스의 비즈니스 로직을 추상화 해두었다는 것&lt;/b&gt;&lt;/span&gt;이다. &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;개발자는 단순히 FlatformTransactionManager 를 선언해서 이용하면 되고 언제든지 이를 구현하는 또 다른 구현체로 바꿀 수 있다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그럼 내가 JDBC Driver 의 예시에 대해서 살펴보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;950&quot; data-origin-height=&quot;648&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTSCe5/btrlES8EBeH/clqkiNhByKNIBvkEnOmJ5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTSCe5/btrlES8EBeH/clqkiNhByKNIBvkEnOmJ5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTSCe5/btrlES8EBeH/clqkiNhByKNIBvkEnOmJ5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTSCe5%2FbtrlES8EBeH%2FclqkiNhByKNIBvkEnOmJ5k%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;602&quot; height=&quot;411&quot; data-origin-width=&quot;950&quot; data-origin-height=&quot;648&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;font-family: 'Noto Serif KR';&quot;&gt;위에서 소개한 두 가지 예시를 통해서 Spring SPA 가 어떻게 맞물려 돌아가는지 이해할 수 있을 것이다.&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;font-family: 'Noto Serif KR';&quot;&gt;이외에도 Tomcat 과 Netty 를 의존성만 변경하면 사용할 수 있는 것도 Spring PSA 를 통해 추상화되어있기 때문에 가능하다. 이처럼 Spring PSA 는 복잡한 구현로직을 수정하지 않고도 손쉽게 서비스를 변경할 수 있도록 해준다.&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;blockquote data-ke-size=&quot;size16&quot; data-ke-style=&quot;style1&quot;&gt;&lt;i&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;나중에 드는 생각인데..&lt;br /&gt;가장 먼저 소개한 예시인 JDBC Driver 에 대한 것은 Spring PSA 가 아니라 Java 기술이 아닌가..? 라는 생각을 하게 되었다. 예시를 들 때는 @Transactional, @Cacheable 와 같은 예시를 드는 것이 더 적절해 보인다.&lt;br /&gt;JDBC Driver 는 뭔가 비슷하면서 묘하게.. 다른....? 좀 더 고민해봐야겠다!!&lt;/span&gt;&lt;/i&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Back-End/Spring Boot</category>
      <author>ch4njun</author>
      <guid isPermaLink="true">https://ch4njun.tistory.com/270</guid>
      <comments>https://ch4njun.tistory.com/270#entry270comment</comments>
      <pubDate>Sat, 20 Nov 2021 20:23:30 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Boot] 순환참조문제</title>
      <link>https://ch4njun.tistory.com/269</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;순환참조문제란?&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;370&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8eyIT/btrkHiuZKv7/5Khz4Rx2OIdJrHVAROIPK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8eyIT/btrkHiuZKv7/5Khz4Rx2OIdJrHVAROIPK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8eyIT/btrkHiuZKv7/5Khz4Rx2OIdJrHVAROIPK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8eyIT%2FbtrkHiuZKv7%2F5Khz4Rx2OIdJrHVAROIPK1%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;1118&quot; height=&quot;370&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;370&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;순환참조 문제란 A 클래스가 B 클래스의 Bean 을 주입받고, B 클래스가 A 클래스의 Bean 을 주입받는 상황처럼 서로 순환되어 참조할 경우 발생하는 문제를 의미&lt;/b&gt;&lt;/span&gt;한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그럼 이러한 순환참조 문제는 어떠한 상황에서 발생하는지 살펴보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;왜 발생할까?&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;특정 클래스에서 IoC 컨테이너에 있는 Bean 을 주입받기 위해서 세 가지 방법을 사용할 수 있다. 필드 주입방식, Setter 주입방식, 생성자 주입방식이 이에 해당한다.&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;font-family: 'Noto Serif KR';&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;먼저, &lt;b&gt;필드 주입방식과 Setter 주입방식에서 발생하는 순환참조문제&lt;/b&gt;에 대해서 이야기해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;필드 주입방식과 Setter 주입방식에서는 A 클래스가 B 클래스를 의존하고, B 클래스가 A 클래스를 의존하는 상황이더라도 애플리케이션 실행과정에서 예외가 발생하지 않는다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그리고 실제로 두 개의 클래스가 순환참조하고 있다고 하더라도 당장에 문제가 발생하지 않는다. 이러한 상황에서 문제가 되는 순간은 A 클래스의 메소드와 B 클래스의 메소드가 서로 순환참조하고 있는 상황에서 해당 메소드가 호출되었을 때이다.&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_1636893246379&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Slf4j
@Service
public class ServiceA {
    @Autowired
    private ServiceB serviceB;
    
    public void run() {
    	serviceB.run();
        log.info(&quot;Called ServiceA.run()&quot;);
    }
}

@Slf4j
@Service
public class ServiceB {
    @Autowired
    private ServiceA serviceA;
    
    public void run() {
    	serviceA.run();
        log.info(&quot;Called ServiceB.run()&quot;);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;정리해보면 기억해야 할 것은 두 가지이다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;스프링 애플리케이션 로딩시 예외가 발생하지 않는다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;단순히 클래스끼리 순환참조하는 것이 아니라 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;실제로 메소드가 순환호출&lt;/b&gt;&lt;/span&gt;되어야 하고, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;해당 메소드가 호출되는 시점에 예외가 발생&lt;/b&gt;&lt;/span&gt;한다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3138&quot; data-origin-height=&quot;310&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0eSCU/btrkIgwKd2U/5sfFDrZEmC0ZJCYQfvud31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0eSCU/btrkIgwKd2U/5sfFDrZEmC0ZJCYQfvud31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0eSCU/btrkIgwKd2U/5sfFDrZEmC0ZJCYQfvud31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0eSCU%2FbtrkIgwKd2U%2F5sfFDrZEmC0ZJCYQfvud31%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;3138&quot; height=&quot;310&quot; data-origin-width=&quot;3138&quot; data-origin-height=&quot;310&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: #ef5369; background-color: #f6e199; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;근데 여기까지 읽었을때 이게 순환참조문제인가..? 라고 생각이 드는게 정상이다!!&lt;br /&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369; background-color: #f6e199; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;이건 순환참조 문제가 아니라 서로다른 메소드가 서로호출할 때 발생하는 순환호출문제이다..!&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;우선 생성자 주입방식의 동작방식에 대해서 이야기해보면, &lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;스프링 애플리케이션이 로딩되는 시점에 A 클래스가 B 클래스를 의존하고 B 클래스가 C 클래스를 의존한다면 Spring Boot 는 A 클래스에 대한 Bean 을 만들기 위해서 B 클래스의 Bean 을 주입하는데 없으니까, B 클래스의 Bean 을 먼저 만든다. 근데 그 과정에서 또 C 클래스의 Bean 을 주입하는데 없으니까 C 클래스의 Bean 을 먼저 만든다.&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;결과적으로 &lt;b&gt;Spring Boot 는 C - B - A 순서로 Bean 을 생성&lt;/b&gt;하는 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그렇다면 &lt;b&gt;A 클래스가 B 클래스를 의존하고, B 클래스가 A 클래스를 의존하는 상황&lt;/b&gt;에 대해서 이야기해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;i&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;A 클래스의 Bean 을 만드는 과정에서 B 클래스의 Bean 을 주입하고, 없으니까 B 클래스의 Bean 을 먼저 생성한다. 이 때 A 클래스의 Bean 을 주입하려는데 없으니까 A 클래스의 Bean 을 먼저 생성한다. 이 때 B 클래스의 Bean 을 주입하려는데 없으니까 A 클래스의 Bean 을 먼저 생성.... 하면서 무한 반복에 빠지게된다.&lt;/b&gt;&lt;/span&gt;&lt;/i&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;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;이렇게 순환되는 과정에서 결과적으로 어떠한 Bean 도 생성하지 못하는 문제를 바로 순환참조문제&lt;/b&gt;라고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;정리해보면 기억해야 할 것이 마찬가지로 두 가지이다.&lt;/span&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;클래스가 서로 의존성 주입을 통해 순환참조하고 있을 때 발생하는 문제이다. (메소드까지는 가지도못함..!)&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;스프링 애플리케이션 로딩시점에서 예외가 발생&lt;/b&gt;&lt;/span&gt;한다!&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;370&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/8eyIT/btrkHiuZKv7/5Khz4Rx2OIdJrHVAROIPK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/8eyIT/btrkHiuZKv7/5Khz4Rx2OIdJrHVAROIPK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/8eyIT/btrkHiuZKv7/5Khz4Rx2OIdJrHVAROIPK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F8eyIT%2FbtrkHiuZKv7%2F5Khz4Rx2OIdJrHVAROIPK1%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;1118&quot; height=&quot;370&quot; data-origin-width=&quot;1118&quot; data-origin-height=&quot;370&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;span style=&quot;color: #ef5369; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;어떻게 해결할까?&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #ef5369; background-color: #f6e199;&quot;&gt;&lt;b&gt;설계상 이렇게 순환참조문제가 발생할 수 있는 구조자체를 만들지 않는 것이 가장 좋다고 한다.&lt;/b&gt;&lt;/span&gt; 따라서 순환참조가 발생하고 있는 순환의 고리를 끊는 것이 가장 좋은 해결책이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;하지만 이렇게 순환의 고리를 끊을 수 없는 경우에는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;@Lazy 어노테이션을 통해서 임의로 해결&lt;/b&gt;&lt;/span&gt;할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1636894648085&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class ServiceA {
    private final ServiceB serviceB;
    
    @Autowired
    public ServiceA(ServiceB serviceB) {
    	this.serviceB = serviceB;
    }
}

@Service
public class ServiceB {
    private final ServiceA serviceA;
    
    @Autowired
    public ServiceB(@Lazy ServiceA serviceA) {
    	this.serviceA = serviceA;
    }
}&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;font-family: 'Noto Serif KR';&quot;&gt;하지만 이러한 방식은 스프링에서 권장하지 않는 방법이다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.spring-application.lazy-initialization&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.spring-application.lazy-initialization&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1636894984015&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Spring Boot Features&quot; data-og-description=&quot;Graceful shutdown is supported with all four embedded web servers (Jetty, Reactor Netty, Tomcat, and Undertow) and with both reactive and Servlet-based web applications. It occurs as part of closing the application context and is performed in the earliest &quot; data-og-host=&quot;docs.spring.io&quot; data-og-source-url=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.spring-application.lazy-initialization&quot; data-og-url=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.spring-application.lazy-initialization&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.spring-application.lazy-initialization&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.spring-application.lazy-initialization&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring Boot Features&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Graceful shutdown is supported with all four embedded web servers (Jetty, Reactor Netty, Tomcat, and Undertow) and with both reactive and Servlet-based web applications. It occurs as part of closing the application context and is performed in the earliest&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;docs.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;i&gt;&lt;b&gt;애플리케이션 로딩시점이 아니라 해당 Bean 이 필요한 시점에 주입을 받기 때문에 특정 HTTP 요청을 받았을 때 Heap 메모리가 증가할 수 있으며 메모리가 충분하지 않을 경우 장애가 발생할 수 있다는 이유 때문이다. (흠...)&lt;/b&gt;&lt;/i&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;font-family: 'Noto Serif KR';&quot;&gt;또 다른 해결 방법은 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;필드 주입방식 혹은 Setter 를 이용한 주입방식을 이용하는 것&lt;/b&gt;&lt;/span&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;하지만, 이러한 방법도 임시방편일 뿐 앞서 말했듯이 순환참조되는 설계를 지양하는 것이 좋다. 생성자 주입방식의 장점중의 하나도 이러한 순환참조 여부를 애플리케이션 로딩 시점에서 알 수 있다는 점에서 비추어 봤을 때 아주 불가피한 상황이 아니라면 설계를 개선하는 것이 좋을 것같다.&lt;br /&gt;&lt;br /&gt;&lt;/span&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;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;마지막으로 의존성 주입방법에 따른 장단점에 대한 참고자료를 확인해보자.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1680&quot; data-origin-height=&quot;1740&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VjOeS/btrkZL2GoHa/KH3fXaL6loMPahVXpu3O8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VjOeS/btrkZL2GoHa/KH3fXaL6loMPahVXpu3O8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VjOeS/btrkZL2GoHa/KH3fXaL6loMPahVXpu3O8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVjOeS%2FbtrkZL2GoHa%2FKH3fXaL6loMPahVXpu3O8k%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;1680&quot; height=&quot;1740&quot; data-origin-width=&quot;1680&quot; data-origin-height=&quot;1740&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;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;i&gt;출처 : &lt;/i&gt;&lt;/b&gt;&lt;a href=&quot;https://yaboong.github.io/spring/2019/08/29/why-field-injection-is-bad/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;&lt;i&gt;https://yaboong.github.io/spring/2019/08/29/why-field-injection-is-bad/&lt;/i&gt;&lt;/b&gt;&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Back-End/Spring Boot</category>
      <author>ch4njun</author>
      <guid isPermaLink="true">https://ch4njun.tistory.com/269</guid>
      <comments>https://ch4njun.tistory.com/269#entry269comment</comments>
      <pubDate>Sun, 14 Nov 2021 22:09:17 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Cloud] Spring Cloud Config 정리</title>
      <link>https://ch4njun.tistory.com/268</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Spring Cloud Config 란?&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Spring Cloud Config 는 분산 시스템에서 설정 정보(application.yml) 를 외부에 보관할 수 있도록 지원해주는 서비스&lt;/b&gt;이다. 이러한 Spring Cloud Config 를 이용했을 때 얻을 수 있는 장점은 다음과 같다.&lt;/span&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;여러 서버의 설정 파일을 중앙 서버에서 관리할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;서버를 재배포 하지 않고 설정 파일의 변경사항을 반영할 수 있다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1892&quot; data-origin-height=&quot;1252&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sQrmt/btrkA7AhqfV/NorIrTxBptarjUGCIpJmhk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sQrmt/btrkA7AhqfV/NorIrTxBptarjUGCIpJmhk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sQrmt/btrkA7AhqfV/NorIrTxBptarjUGCIpJmhk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsQrmt%2FbtrkA7AhqfV%2FNorIrTxBptarjUGCIpJmhk%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;727&quot; height=&quot;481&quot; data-origin-width=&quot;1892&quot; data-origin-height=&quot;1252&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;위 도식은 Spring Cloud Config 가 동작하는 과정이다. 조금 간단하게 설명하자면 우선 세 가지 요소로 생각하면 좋다.&lt;br /&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;font-family: 'Noto Serif KR';&quot;&gt;실제로 &lt;b&gt;설정파일이 저장되는 Config 저장소(Git Repository, File System 등)&lt;/b&gt;가 있고, 이러한 저장소와 연결되어 &lt;b&gt;설정파일을 관리해주는 Spring Cloud Config Server&lt;/b&gt; 가 있다. 마지막으로 &lt;b&gt;Spring Cloud Config 를 이용하고 싶은 다양한 Client 들&lt;/b&gt;이 존재한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Client 가 Spring Cloud Config Server 에게 설정값을 요청하면 Server 는 설정파일이 저장된 Config 저장소에 접근해 조건에 맞는 영역을 탐색&lt;/b&gt;&lt;/span&gt;한다. 탐색한&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt; 결과로 가져온 최신 설정값을 가지고 Client 에게 전달&lt;/b&gt;&lt;/span&gt;하게 된다.&lt;br /&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;font-family: 'Noto Serif KR';&quot;&gt;위 과정은 최초 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Client 구동시이고 운영중인 상태에 바뀐 설정 값을 갱신하고 싶다면 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;/actuator/refresh API&lt;/span&gt; 를 호출함으로써 가능&lt;/b&gt;&lt;/span&gt;하다. &lt;b&gt;Client 가 Server 에게 설정값을 요청하고 이렇게 가져온 최신의 설정 값을 Client 에게 전달하고 이를 통해 변경된 설정정보를 갱신&lt;/b&gt;하게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, /actuator/refresh API 를 사용해 서버를 재배포하지 않고도 설정정보를 갱신하는 것이다.&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;font-family: 'Noto Serif KR';&quot;&gt;하지만 &lt;b&gt;Spring Cloud Config 를 이용하는 Client 수가 엄청나게 많다고 했을 때 /actuator/refresh API 를 일일히 호출하는 것도 일이&lt;/b&gt; 될 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;Spring Cloud Bus&lt;/span&gt; 를 사용해 이러한 문제까지 개선하는 내용&lt;/b&gt;을 뒤에서 살펴볼 예정이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Spring Cloud Config Server&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Spring Cloud Config Server 는 Config 저장소와 연결되어 중간 역할을 수행한다. 간단하게 몇 가지 Dependency 와 설정 정보만 추가하면 된다!&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_1636697332267&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.cloud&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-cloud-config-server&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;위 Dependency 를 추가하고 Spring Boot 의 Auto Configuration 을 이용해 &lt;b&gt;@EnableConfigServer&lt;/b&gt; 어노테이션을 선언해주면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그리고 application.yml 파일에 다음과 같은 설정정보를 입력하면 Spring Cloud Config Server 의 설정은 완료된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1636698039022&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;server:
    prot: 8888
spring:
    cloud:
    	config:
            server:
            	git:
                    uri: {Git Config Repository URI}
                    username: {Git Config Repository Username}
                    password: {Git Config Repository Password}
                    
                    # Spring Cloud Config 는 Remote 에 있는 Git Repository 를 클론해온다.
                    # 하지만 이전에 클론해와서 로컬에 있는 정보의 폴더구조가 변경되는 등의 문제가 발생하면 제대로 업데이트할 수 없는데,
                    # 이러한 상황에서 강제로 Git Repository 를 클론하기 위한 설정이다.
                    force-pull: true
                    timeout: 30
                    
                    # Config 저장소 상에서 설정 파일을 탐색할 경로를 지정하는 설정이다.
                    # {application} 에는 Client 의 spring.application.name 값이 사용된다.
                    searchPaths:
                    	- '{application}'
                        - '{application}/*'&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;font-family: 'Noto Serif KR';&quot;&gt;위 설정에서는 다양한 설정들을 해줬지만 간단하게 spring.cloud.config.server.git.uri 만 있어도 동작은 한다. 조금 더 디테일적인 부분을 설정해주는 코드라고 생각하면 된다.&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;font-family: 'Noto Serif KR';&quot;&gt;이외에도 굉장히 다양한 설정들이 있으니 공식 레퍼런스를 참고하자.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://cloud.spring.io/spring-cloud-config/reference/html/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://cloud.spring.io/spring-cloud-config/reference/html/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1636708374902&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Spring Cloud Config&quot; data-og-description=&quot;Many source code repository providers (such as Github, Gitlab, Gitea, Gitee, Gogs, or Bitbucket) notify you of changes in a repository through a webhook. You can configure the webhook through the provider&amp;rsquo;s user interface as a URL and a set of events in &quot; data-og-host=&quot;cloud.spring.io&quot; data-og-source-url=&quot;https://cloud.spring.io/spring-cloud-config/reference/html/&quot; data-og-url=&quot;https://cloud.spring.io/spring-cloud-config/reference/html/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://cloud.spring.io/spring-cloud-config/reference/html/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://cloud.spring.io/spring-cloud-config/reference/html/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Spring Cloud Config&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Many source code repository providers (such as Github, Gitlab, Gitea, Gitee, Gogs, or Bitbucket) notify you of changes in a repository through a webhook. You can configure the webhook through the provider&amp;rsquo;s user interface as a URL and a set of events in&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;cloud.spring.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;하지만 &lt;span style=&quot;color: #ef5369; background-color: #f6e199;&quot;&gt;&lt;b&gt;해당 Config Server 를 사용하는 Client 가 여러개라면 searchPaths 설정은 해주는게 좋다고 생각&lt;/b&gt;&lt;/span&gt;한다. 이를 설정했을 때 Config 저장소에서 &lt;b&gt;Client 별로 디렉토리를 구분지어서 설정 파일을 관리&lt;/b&gt;할 수 있기 때문이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;아니면 &lt;span style=&quot;color: #ef5369; background-color: #f6e199;&quot;&gt;&lt;b&gt;spring.cloud.config.server.git.uri&lt;/b&gt;&lt;/span&gt;&lt;span style=&quot;color: #ef5369; background-color: #f6e199;&quot;&gt;&lt;b&gt; = https://github&lt;a style=&quot;color: #ef5369; background-color: #f6e199;&quot; href=&quot;https://github.com/&quot;&gt;.&lt;/a&gt;com/{application}&lt;/b&gt;&lt;/span&gt; 와 같이 설정해 저장소 자체를 Client 별로 구분하도록 설정할 수도 있다.&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;font-family: 'Noto Serif KR';&quot;&gt;searchPaths 에서 사용된 {application} 은 뭘까..?&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;font-family: 'Noto Serif KR';&quot;&gt;Client 가 Spring Cloud Config Server 에 설정 값을 요청할 때 다양한 정보를 제공해주는데&lt;/span&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;i&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;{application}&lt;/b&gt; : Client 측의 spring.application.name 에 대한 정보&lt;/span&gt;&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;&lt;i&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;{profile}&lt;/b&gt; : Client 측의 spring.profiles.active 에 대한 정보&lt;/span&gt;&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;&lt;i&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;{label}&lt;/b&gt; : 버전을 매긴 설정파일들을 지정하는 Config Server 측의 기능&lt;/span&gt;&lt;/i&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;따라서 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Spring Cloud Config Server 는 이러한 정보들을 활용해 설정 값에 사용&lt;/b&gt;&lt;/span&gt;할 수도 있고, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;이를 통해서 적절한 설정 값을 찾아 Client 에게 보내주게 된다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;예를 들면 {application}-{profile}.yml 와 같은 설정 파일을 찾는 것이다. &lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그러면 Config Server 에서는 {application}.yml, {application}-{profile}.yml 와 같이 여러 설정 파일이 존재할 수 있는데 어떤 우선순위를 가지고 설정 파일을 선택할까?&lt;/span&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;{application}-{profile}.yml&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;{application}.yml&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;application.yml&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre class=&quot;dust&quot;&gt;&lt;code&gt;/{application}/{profile}[/{label}]
/{application}-{profile}.yml
/{label}/{application}-{profile}.yml
/{application}-{profile}.properties
/{label}/{application}-{profile}.properties&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Spring Cloud Config Client&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Spring Cloud Config Client 도 Server 못지 않게 몇 가지 Dependecny 와 설정 정보로 충분히 구현이 가능하다.&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_1636705160561&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.boot&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-boot-starter-actuator&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;
&amp;lt;dependency&amp;gt;
    &amp;lt;groupId&amp;gt;org.springframework.cloud&amp;lt;/groupId&amp;gt;
    &amp;lt;artifactId&amp;gt;spring-cloud-starter-config&amp;lt;/artifactId&amp;gt;
&amp;lt;/dependency&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;spring-cloud-starter-config 는 그렇다 치더라도 &lt;b&gt;spring-boot-starter-actuator 는 어떤 역할을 수행&lt;/b&gt;하는 걸가? &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;actuator 의 기본적인 역할은 해당 애플리케이션의 상태를 종합적으로 정리해서 Client 에게 제공해주는 역할을 수행&lt;/b&gt;&lt;/span&gt;한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;/actuator/health, /actuator/env, /actuator/info 등 다양한 API 로 애플리케이션의 정보를 제공한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1636706370012&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# actuator 기본 설정
management:
  endpoint:
    health:
      show-details: never
  endpoints:
    web:
      base-path: /servicemanager
      exposure:
        include: &quot;*&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그 중에서도 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;/actuator/refresh&lt;/span&gt; 는 애플리케이션의 설정 파일을 다시 로딩하는 역할을 수행&lt;/b&gt;&lt;/span&gt;한다. &lt;b&gt;즉, Spring Cloud Config 와 결합하여 사용한다면 중앙에 있는 Config 저장소의 값을 변경하고 해당 API 를 실행하게 된다면 재배포 없이도 변경한 설정 정보를 반영할 수 있다는 것이다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이후 bootstrap.yml 파일에 Spring Cloud Config Server 에 대한 정보를 입력하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1636706671733&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  profiles: dev

  cloud:
    config:
      uri: &quot;http://config.ch4njun.com:8888&quot;
      profile: ${spring.profiles.active}, sample-${spring.profiles.active}
      label: develop

---
spring:
  profiles: qa

  cloud:
    config:
      uri: &quot;http://config.ch4njun.com:8888&quot;
      profile: ${spring.profiles.active}, sample-${spring.profiles.active}
      label: develop&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;font-family: 'Noto Serif KR';&quot;&gt;&lt;i&gt;&lt;b&gt;spring.cloud.config.profile&lt;/b&gt;&lt;/i&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: #ef5369; background-color: #f6e199;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;spring.cloud.config.profile 은 Spring Cloud Config Server 에 전달할 Profiles 목록이다!!&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Server 는 해당 목록에 해당하는 모든 설정 파일을 Client 에게 보내준다!&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;span style=&quot;color: #ef5369; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Spring Boot 2.4 이후에 달라진점&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;http://honeymon.io/tech/2021/01/16/spring-boot-config-data-migration.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;http://honeymon.io/tech/2021/01/16/spring-boot-config-data-migration.html&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1636706895446&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;[spring-boot] 2.4 부터 변경된 구성파일 처리방식 살펴보기 - I'm honeymon(JiHeon Kim).&quot; data-og-description=&quot;스프링 부트 2.4(https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.4-Release-Notes) 출시노트를 살펴보면 인상적인 2가지가 있다. 애플케이션 구성파일(application.yml 혹은 application.properties)의 작동방식&quot; data-og-host=&quot;honeymon.io&quot; data-og-source-url=&quot;http://honeymon.io/tech/2021/01/16/spring-boot-config-data-migration.html&quot; data-og-url=&quot;http://honeymon.io/tech/2021/01/16/spring-boot-config-data-migration.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;http://honeymon.io/tech/2021/01/16/spring-boot-config-data-migration.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;http://honeymon.io/tech/2021/01/16/spring-boot-config-data-migration.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[spring-boot] 2.4 부터 변경된 구성파일 처리방식 살펴보기 - I'm honeymon(JiHeon Kim).&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;스프링 부트 2.4(https://github.com/spring-projects/spring-boot/wiki/Spring-Boot-2.4-Release-Notes) 출시노트를 살펴보면 인상적인 2가지가 있다. 애플케이션 구성파일(application.yml 혹은 application.properties)의 작동방식&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;honeymon.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Spring Boot 2.4 이후에는 bootstrap.yml 을 기본적으로 애플리케이션이 로딩될 때 Context 초기화 작업이 지원되지 않는다. 즉, 위에서 작성한 설정 정보를 application.yml 보다 먼저 초기화 되는 bootstrap.yml 에 입력할 수 없다.&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;font-family: 'Noto Serif KR';&quot;&gt;이를 해결하기 위해 Spring Boot 2.4 부터는 &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #ef5369; background-color: #f6e199;&quot;&gt;&lt;b&gt;spring.config.import = optional:configserver:http://ch4njun.com:8888&lt;/b&gt;&lt;/span&gt; 와 같은 설정 정보를 application.yml 파일에 입력하는 방식을 기본적으로 사용한다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1636704552911&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
    config:
    	import: &quot;optional:configserver:http://config.ch4njun.com:8888/&quot;
    cloud:
    	config:
            name: my-config
            profile: dev&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;font-family: 'Noto Serif KR';&quot;&gt;하지만 만약 기존에 사용하던 bootstrap.yml 파일을 통한 설정방법을 이용하고 싶다면 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;spring-cloud-starter-bootstrap 의 Dependency 를 추가&lt;/span&gt;하거나 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;spring.cloud.bootstrap.enabled = true&lt;/span&gt; 설정을 두어 bootstrap.yml 을 활성화&lt;/b&gt;&lt;/span&gt;해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그리고 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;spring.config.activate.on-profile 을 통해서 profiles 를 지정&lt;/b&gt;&lt;/span&gt;해야 한다는 점이 다르다. spring.profiles 속성은 Deprecated 되었다!&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1636706932816&quot; class=&quot;css&quot; data-ke-language=&quot;css&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring:
  config:
    activate:
      on-profile: dev

  cloud:
    config:
      uri: &quot;http://config.ch4njun.com:8888&quot;
      profile: ${spring.profiles.active}, sample-${spring.profiles.active}
      label: develop

---
spring:
  config:
    activate:
      on-profile: qa

  cloud:
    config:
      uri: &quot;http://config.ch4njun.com:8888&quot;
      profile: ${spring.profiles.active}, sample-${spring.profiles.active}
      label: develop&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Back-End/Spring Cloud</category>
      <author>ch4njun</author>
      <guid isPermaLink="true">https://ch4njun.tistory.com/268</guid>
      <comments>https://ch4njun.tistory.com/268#entry268comment</comments>
      <pubDate>Fri, 12 Nov 2021 18:16:45 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] Java &amp;amp; Spring 에서의 비동기는 어떻게 발전됐을까?</title>
      <link>https://ch4njun.tistory.com/267</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Servlet 3.0 이전&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Servlet 3.0 에는 Servlet Thread 만 존재&lt;/b&gt;&lt;/span&gt;했다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;예를들어 &lt;b&gt;Thread per Request Model 을 사용하는 Tomcat 의 경우에는 기본적으로 200개의 Servlet Thread 를 가졌고 내부적으로 Blocking 되는 코드가 있다면 서버의 Latency 가 길어지게 된다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;435&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UEODt/btrkvKdY7zy/rvMqwxz0ZXCGisqunaKtX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UEODt/btrkvKdY7zy/rvMqwxz0ZXCGisqunaKtX0/img.png&quot; data-alt=&quot;Thread per Request Model&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UEODt/btrkvKdY7zy/rvMqwxz0ZXCGisqunaKtX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUEODt%2FbtrkvKdY7zy%2FrvMqwxz0ZXCGisqunaKtX0%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;1000&quot; height=&quot;435&quot; data-origin-width=&quot;1000&quot; data-origin-height=&quot;435&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Thread per Request Model&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;왜냐하면 하나의 &lt;b&gt;Request 에 대해서 하나의 Servlet Thread 를 할당&lt;/b&gt;하게 되는데, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;해당 Servlet Thread 가 Blocking 상태에 빠지게 된다면 더 이상 Servlet Thread Pool 에는 남아있는 Thread 가 없게되고 Request 는 Servlet Thread 가 반환될 때까지 기다리게 되는 것&lt;/b&gt;&lt;/span&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이러한 문제를 해결하기 위해 &lt;b&gt;Servlet Thread 개수를 늘리면 되지 않느냐&lt;/b&gt;고 생각할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;하지만 &lt;b&gt;Servlet Thread 의 수를 무한정 늘리게 되면 CPU 는 그만큼 더 많은 Context Switching 을 해야하고 그에 따라 오버헤드&lt;/b&gt;가 붙게된다. 이러한 현상이 계속되면 &lt;b&gt;CPU 사용량이 계속해서 증가하게되고 Thread 로 인한 메모리 낭비 문제가 발생&lt;/b&gt;할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;span&gt;Servlet 3.0&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Servlet 3.0 에서는 비동기 처리를 위해서 Servlet Thread 와 Worker Thread 가 분리되었다. &lt;/span&gt;&lt;/b&gt;&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_1636639537970&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@WebServlet(urlPatterns=&quot;/welcome&quot;, asyncSupported=true)
public class AsyncWelcome extends HttpServlet {
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    	final AsyncContext asyncContext = request.startAsync();
        
        new Thread(() -&amp;gt; {
            try {
            	Thread.sleep(3000);
            } catch (InterruptedException e) {}
            
            HttpServletResponse response = (HttpServletResponse) asyncContext.getResponse();
            response.setContentType(&quot;text/plain&quot;);
            response.setCharacterEncoding(&quot;UTF-8&quot;);
            try {
                response.getWriter().println(&quot;Welcome The Ch4njun World!&quot;); 
            } catch (IOException e) {}
            
            // 여기서 asyncContext 를 통해 Worker Thread 의 작업종료를 알린다.
            asyncContext.Complete();
        }).start();
        
        // 위 Thread 를 비동기적으로 실행시키고 이 시점에서 Servlet Thread 는 반환된다.
    }
}&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;font-family: 'Noto Serif KR';&quot;&gt;위 코드는 Servlet 3.0 에서 지원하는 비동기를 이용한 코드이다. 세부적인 동작과정을 살펴보자.&lt;/span&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&amp;nbsp;Web Server 의 &lt;b&gt;NIO Connector 에 의해서 Request 가 들어오고 Connection 이 만들어진 후 Servlet Thread 를 할당&lt;/b&gt;받는다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;오래걸리는 작업을 새로운 Worker Thread 로 할당해 처리&lt;/b&gt;한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그리고 &lt;b&gt;Servlet Thread 는 Servlet Thread Pool 에 반환된&lt;/b&gt;다. &lt;br /&gt;&lt;i&gt;(이 Servlet Thread 로 다른 Request 를 처리할 수 있다)&lt;br /&gt;&lt;/i&gt;&lt;/span&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;여기까지 정리해보면 &lt;b&gt;NIO Connector 가 아직 해당 Connection 을 물고있기 때는 상태&lt;/b&gt;이다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Worker Thread 는 AsyncContext 를 통해서 Response 를 추가하고 작업이 완료됨을 알린다.&lt;/span&gt;&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;해당 &lt;b&gt;Response 를 Client 에게 전달하고 Connection 은 종료&lt;/b&gt;된다.&lt;/span&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;하지만 결국 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Worker Thread 에서 외부 API 호출, DB 쿼리 조회와 같은 Blocking I/O 작업을 수행한다면 Worker Thread 는 해당 응답을 기다리게 된다.&lt;/b&gt;&lt;/span&gt; 이렇게 될 경우 결국 Request 가 증가함에 따라서 Blocking 상태에 놓이는 Worker Thread 가 증가하게되고 위에서 언급한 문제는 동일하게 발생한다.&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;font-family: 'Noto Serif KR';&quot;&gt;이러한 원인이 계속되는 이유는 &lt;b&gt;Servlet 에서 Non-Blocking I/O 를 지원하지 않기 때문&lt;/b&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;&quot;&gt;또 다른 예시가 괜찮은 포스팅이 있어 추가로 남긴다!&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://blogs.sap.com/2013/03/01/non-blocking-java/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://blogs.sap.com/2013/03/01/non-blocking-java/&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1636650330843&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Non-blocking Java with Servlets 3.0 | SAP Blogs&quot; data-og-description=&quot;Imagine that the assistant of an executive director after sending an email does nothing else but just waits until he/she gets a response to this email. An executive director would need hundreds of &amp;ldquo;blocking&amp;rdquo; assistants to manage his daily work. Quite e&quot; data-og-host=&quot;blogs.sap.com&quot; data-og-source-url=&quot;https://blogs.sap.com/2013/03/01/non-blocking-java/&quot; data-og-url=&quot;https://blogs.sap.com/2013/03/01/non-blocking-java/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://blogs.sap.com/2013/03/01/non-blocking-java/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://blogs.sap.com/2013/03/01/non-blocking-java/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Non-blocking Java with Servlets 3.0 | SAP Blogs&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Imagine that the assistant of an executive director after sending an email does nothing else but just waits until he/she gets a response to this email. An executive director would need hundreds of &amp;ldquo;blocking&amp;rdquo; assistants to manage his daily work. Quite e&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;blogs.sap.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Servlet 3.1&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Servlet 3.1 에서 Servlet Application 에서 Non-Blocking I/O 를 지원&lt;/b&gt;&lt;/span&gt;하게 된다. ReadListener, WriteListener 인터페이스를 통해 이러한 부분을 지원한다.&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;font-family: 'Noto Serif KR';&quot;&gt;여기까지 봤을 때 &lt;b&gt;이제서야 비동기 논블로킹을 위한 기반이 만들어졌다고 볼 수 있다.&lt;/b&gt; 하지만, 이러한 기술을 쓰자고 Servlet 코드를 직접 쓸순 없으니까 이제부터 Spring 에서 어떤 방식으로 지금까지 설명한 Servlet 비동기 논블로킹 방식을 활용하는지 살펴보자.&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;span style=&quot;color: #ef5369; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Spring 3.2&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Spring 3.2 에서는 Servlet 3.0, Servlet 3.1 에서 추가된 비동기, 논블로킹에 대한 기술을 편하게 활용할 수 있도록 Controller 의 Handler 가 다양한 자료형의 객체를 반환할 수 있도록 지원&lt;/b&gt;&lt;/span&gt;한다.&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Callable, ListenableFuture, CompletableFuture, WebAsyncTask, DefferedResult&lt;/b&gt;&lt;br /&gt;&lt;br /&gt;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이러한 클래스의 객체를 반환하기만 하면 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Spring 이 알아서 위에서 설명한 비동기작업을 실행&lt;/b&gt;&lt;/span&gt;해준다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;하지만 Worker Thread 도 결국 자원&lt;/b&gt;이다. &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Servlet Thread 를 직접 사용하는 것이 아니라 Blocking 작업과 같이 오래걸리는 작업을 Worker Thread 에 할당한다고 하더라도 새로운 Request 를 계속해서 받을 수 있다는 것 빼고는 나아진게 없다.&lt;/b&gt;&lt;/span&gt; &lt;b&gt;심지어 Servlet Thread 와 Worker Thread 모두 생성해야 하기 때문에 손해&lt;/b&gt;일수도 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그래도 아예 의미없는 것은 아니다. 오래걸리는 API 와 바로 반환해줄 수 있는 API 가 공존한다면 오래걸리는 API 를 Servlet Thread 로 직접 사용하게되면 금방 반환해줄 수 있는 API 호출까지 Blocking 되게 되는데 이러한 부분은 해소할 수 있다.&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;span style=&quot;color: #ef5369; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;DefferedResult&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;위에서 이야기한 다양한 클래스들이 있지만 조금 특이한 DefferedResult 클래스에 대해서 이야기해보도록 한다. DefferedResult 는 Spring 비동기 기술의 핵심이라고 한다. DefferedResult 를 이용해 다양한 응용이 가능하기 때문이다.&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;font-family: 'Noto Serif KR';&quot;&gt;그러면 왜 DefferedResult 가 Spring 비동기 기술의 핵심이라고 하는지 살펴보자.&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;font-family: 'Noto Serif KR';&quot;&gt;기본적으로 &lt;b&gt;Callable, WebAsyncTask, ListenableFuture, CompletableFuture 를 반환하게되면 Worker Thread 가 생성되고 거기서 작업을 실행&lt;/b&gt;한다. 만약 해당 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Worker Thread 에 Blocking 작업을 동작&lt;/b&gt;&lt;/span&gt;시켰다고 가정해보자. 예를들면, 외부 API 호출, DB 쿼리요청 등이 있을 것이다.&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;font-family: 'Noto Serif KR';&quot;&gt;&lt;span&gt;그러면 &lt;b&gt;해당 Worker Thread 는 결국 Blocking 상태에 놓이고 해당 요청에 대한 응답을 받아야 이후 동작을 수행&lt;/b&gt;한다. 이런 구조에서 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;만약 많은 수의 Request 가 들어오게 된다면 Servlet Thread 는 계속 반환되기 때문에 괜찮다고 하지만.. 결국 Worker Thread 도 자원이고 많아지게되면 리소스 낭비와 CPU 사용량이 증가하는 문제가 발생&lt;/b&gt;&lt;/span&gt;하게 된다.&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span&gt;그러면 Worker Thread 가 Blocking 상태에 놓이지 않고 Non-Blocking 으로 처리될 수 있는 방법이 없을까?&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;font-family: 'Noto Serif KR';&quot;&gt;&lt;span&gt;이러한 방법을 제공하는것이 바로 &lt;b&gt;DefferedResult&lt;/b&gt; 이다!!&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1085&quot; data-origin-height=&quot;631&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bagLhl/btrkxPFSuqf/C4wRESR6fv8fEKaH7kT9B0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bagLhl/btrkxPFSuqf/C4wRESR6fv8fEKaH7kT9B0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bagLhl/btrkxPFSuqf/C4wRESR6fv8fEKaH7kT9B0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbagLhl%2FbtrkxPFSuqf%2FC4wRESR6fv8fEKaH7kT9B0%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;330&quot; data-origin-width=&quot;1085&quot; data-origin-height=&quot;631&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;DefferedResult 는 쉽게 설명하면 &lt;span style=&quot;color: #ef5369; background-color: #f6e199;&quot;&gt;&lt;b&gt;Servlet Thread 1개로 여러 Request 를 처리하면서 Worker Thread 를 계속해서 만들지 않는 방법&lt;/b&gt;&lt;/span&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span&gt;&lt;b&gt;Servlet Thread 는 별도의 Worker Thread 를 생성하는 것이 아니라 DefferedResult 큐에 Handler 를 추가하고 Servlet Thread 를 Servlet Thread Pool 에 반환&lt;/b&gt;한다.&amp;nbsp;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;span&gt;그리고나서 &lt;b&gt;외부에서 특정 Handler 에 대한 이벤트가 발생하면 해당 Request 에 대한 Response 처리 작업을 수행&lt;/b&gt;한다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;font-family: 'Noto Serif KR';&quot;&gt;&lt;span&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: #ef5369; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;span&gt;&quot;결국 외부 이벤트를 만들어주는 Worker Thread 를 만들어야 되는거 아니야!?&quot;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span&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;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Client 의 Request 가 왔을 때 DefferedResult 를 반환함으로써 Handler 를 추가하고 Servlet Thread 를 반환하는 것이다. 그리고나서 AsyncRestTemplate 이나 WebClient 와 같은 비동기 논블로킹 방식으로 외부 API 를 호출하고 그에 대한 Callback 에 dr.setResult() 를 놓는다면 어떨까?&lt;br /&gt;&lt;br /&gt;외부 API 를 호출한 결과는 ListenableFuture, CompletableFuture 로 받을텐데 여기서 별도의 Worker Thread 는 어디있는지 고민해보자.&lt;br /&gt;&lt;br /&gt;AsyncRestTemplate 에 Netty 를 주입하거나 WebClient 를 사용하게 되면 하나의(?) Worker Thread 로 Event Loop 방식으로 동작한다.&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;위 상황에서 외부 이벤트를 만들어주는 Worker Thread 가 Request 수만큼 많아지는지 생각해보자.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1127&quot; data-origin-height=&quot;621&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZKSDP/btrkBthwYK7/sDQyhskRJmnVX8w7bm9O00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZKSDP/btrkBthwYK7/sDQyhskRJmnVX8w7bm9O00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZKSDP/btrkBthwYK7/sDQyhskRJmnVX8w7bm9O00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZKSDP%2FbtrkBthwYK7%2FsDQyhskRJmnVX8w7bm9O00%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;1127&quot; height=&quot;621&quot; data-origin-width=&quot;1127&quot; data-origin-height=&quot;621&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;font-family: 'Noto Serif KR';&quot;&gt;이렇게 하나의 &lt;b&gt;Servlet Thread 로 AsyncRestTemplate 이나 WebClient 를 실행하고 DefferedResult 를 반환함으로써 Servlet Thread 를 Pool 에 반환&lt;/b&gt;하게 되면, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;AsyncRestTemplate, WebClient 는 하나의(?) Worker Thread 로 Event Loop 형태로 처리하기 때문에 결과적으로 Request 마다 새로운 Worker Thread 를 만들지 않게되는 것&lt;/b&gt;&lt;/span&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;즉, 당연히 DefferedResult 를 반환하고 dr.setResult() 하주는 작업을 단순 비동기 작업으로 실행하게된다면 외부 이벤트를 만드는 작업을 위한 Worker Thread 를 만들게 된다.&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;하지만, 적은 수의 Worker Thread 를 가지고 동작하는 Netty, WebClient 와 같이 Reactive Library 를 사용하게 된다면 Request 마다 새로운 Worker Thread 를 만들지 않는다.&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;&lt;span style=&quot;font-family: 'Noto Serif KR';&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&quot;그럼 그냥 AsyncRestTemplate 이나 WebClient 가 반환해준 ListenableFuture, CompletableFuture 를 바로 반환해주면 되는거 아니야? 어차피 바로 반환할 수 있다면서!&quot;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;하지만, &lt;b&gt;DefferedResult 와 조합해서 사용하게 되면 Callback 을 통해서 처리 결과를 가공하거나 메소드 체이닝을 통해서 여러 비동기 논블로킹 호출을 수행하는 등의 작업에 어려움&lt;/b&gt;이 있다. 따라서 이러한 경우에 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;DefferedResult 의 객체인 dr 을 우선 반환하고 ListenableFuture, CompletableFuture 의 메소드 체이닝을 통해 다양한 작업을 처리한 뒤 Callback 을 통해서 dr.setResult() 할 수 있다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이처럼 DefferedResult 는 다른 비동기 처리기술과 결합되어 사용된다.&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;i&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;물론 단독으로 사용되어 다른 API 호출이나 스케줄링에 의해서 dr.setResult() 될 수도 있겠지만, 논지를 벗어나는 상황인 것 같아서 넘어가도록 한다.&lt;/span&gt;&lt;/i&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;정리하자면 &lt;b&gt;DefferedResult 를 반환하고 이후에 Callback 에서 dr.setResult() 해주는 것도 결국 CompletableFuture 에 달린 것이고, 단순히 CompletableFuture 를 반환하는 것도 CompletableFuture 에 달린 것&lt;/b&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span&gt;즉, &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;부가적인 처리를 위해 DefferedResult 를 반환해 응답을 지연시킬 것인지 말 것인지의 차이라는 것&lt;/b&gt;&lt;/span&gt;이다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>Back-End/Spring Boot</category>
      <author>ch4njun</author>
      <guid isPermaLink="true">https://ch4njun.tistory.com/267</guid>
      <comments>https://ch4njun.tistory.com/267#entry267comment</comments>
      <pubDate>Fri, 12 Nov 2021 01:10:27 +0900</pubDate>
    </item>
    <item>
      <title>[Concept] API 성능테스트 (feat. nGrinder 를 이용한 성능 테스트)</title>
      <link>https://ch4njun.tistory.com/266</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;성능테스트란?&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;쉽게 말하면 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;내가 만든 웹 서버(API)가 얼마나 많은 사용자들을 감당할 수 있는지 테스트하는 것&lt;/b&gt;&lt;/span&gt;을 말한다. 이외에도 두&lt;b&gt; 가지 설계에 대해서 어떤 설계가 더 좋은 성능을 내는지 확인하기 위해서&lt;/b&gt;도 사용될 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;성능의 지표&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;성능을 판단하는 대표적인 지표로는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Throughput(처리량)&lt;/b&gt;&lt;/span&gt;과 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Latency(지연시간)&lt;/b&gt;&lt;/span&gt;이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Throughput 은 시간당 처리량을 의미하고 TPS(Transaction per seconds), RPS(Request per seconds) 와 같은 세부항목&lt;/b&gt;이 있다. 이러한 수치들은 해당 서비스가 1초당 어느정도의 작업을 처리할 수 있는지 나타내기 때문에 &lt;b&gt;높은 수치일수록 성능이 좋다&lt;/b&gt;고 말할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;Latency 는 지연시간을 의미하고 이는 시스템이 클라이언트로부터 Request 를 받아서 Response 를 보내주기까지 걸리는 시간을 의미&lt;/b&gt;한다. 말 그대로 특정 작업을 얼마나 빨리 처리할 수 있는지 나타내는 성능 지표이다. &lt;b&gt;Latency 는 당연히 낮은게 더 빨리 응답한 것이기 때문에 낮은 수치일수록 성능이 좋다&lt;/b&gt;고 말할 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1506&quot; data-origin-height=&quot;818&quot; width=&quot;694&quot; height=&quot;377&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c09sVr/btrjvSbmbb5/Z4lMaJ8ag3nnepLkXF0F50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c09sVr/btrjvSbmbb5/Z4lMaJ8ag3nnepLkXF0F50/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c09sVr/btrjvSbmbb5/Z4lMaJ8ag3nnepLkXF0F50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc09sVr%2FbtrjvSbmbb5%2FZ4lMaJ8ag3nnepLkXF0F50%2Fimg.png&quot; data-origin-width=&quot;1506&quot; data-origin-height=&quot;818&quot; width=&quot;694&quot; height=&quot;377&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;내가 설계한 시스템의 각 구간 TPS 와 Latency 가 위와 같을 때 전체 구간의 TPS 와 Latency 는 각각 얼마일까?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;답은 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;Throughput : 500TPS, Latency : 350ms&lt;/b&gt;&lt;/span&gt; 이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;모든 구간을 더한 Latency 와는 다르게 Throughput 즉, TPS 는 가장 낮은 구간이 전체 시스템의 성능이 되는 것을 확인할 수 있다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;766&quot; data-origin-height=&quot;322&quot; width=&quot;437&quot; height=&quot;184&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/psKCI/btrjvSoTc2C/nltGBgwN0PZdD6Bv42SLk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/psKCI/btrjvSoTc2C/nltGBgwN0PZdD6Bv42SLk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/psKCI/btrjvSoTc2C/nltGBgwN0PZdD6Bv42SLk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpsKCI%2FbtrjvSoTc2C%2FnltGBgwN0PZdD6Bv42SLk0%2Fimg.png&quot; data-origin-width=&quot;766&quot; data-origin-height=&quot;322&quot; width=&quot;437&quot; height=&quot;184&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이러한 흐름이 있을 때 중간에 좁은 통로 때문에 넓은 통로의 크기와는 상관없이 동일하게 차가 막히는 것과 동일한 이유이다. &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;이러한 좁은 통로를 병목구간&lt;/b&gt;&lt;/span&gt;이라고 하는데, &lt;b&gt;Throughput 에는 이처럼 병목구간이 생겨 전체 시스템 성능에 영향을 미친다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1512&quot; data-origin-height=&quot;776&quot; width=&quot;739&quot; height=&quot;379&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGRnZS/btrjl8GOWPW/EnSigWyLkCexJKoZB363P1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGRnZS/btrjl8GOWPW/EnSigWyLkCexJKoZB363P1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGRnZS/btrjl8GOWPW/EnSigWyLkCexJKoZB363P1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGRnZS%2Fbtrjl8GOWPW%2FEnSigWyLkCexJKoZB363P1%2Fimg.png&quot; data-origin-width=&quot;1512&quot; data-origin-height=&quot;776&quot; width=&quot;739&quot; height=&quot;379&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;그래서 &lt;b&gt;이처럼 다른 구간의 성능을 높이게 됐을 때 Latency 는 280ms 로 낮아지는 것에 반해 Throughput 은 여전히 500TPS&lt;/b&gt; 이다. 이러한 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;병목현상이 발생해 전체 시스템의 성능에 큰 영향을 미치는 구간을 Critical Path&lt;/b&gt;&lt;/span&gt; 라고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ef5369; background-color: #f6e199;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;결국, 시스템 전체의 Throughput 을 향상시키기 위해서는 병목현상이 발생하는 Critical Path 를 찾아야 한다.&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1494&quot; data-origin-height=&quot;944&quot; width=&quot;763&quot; height=&quot;482&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9KMc3/btrjqqNs9Yh/7Q0YFjjznloXt7zXilVRK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9KMc3/btrjqqNs9Yh/7Q0YFjjznloXt7zXilVRK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9KMc3/btrjqqNs9Yh/7Q0YFjjznloXt7zXilVRK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9KMc3%2FbtrjqqNs9Yh%2F7Q0YFjjznloXt7zXilVRK1%2Fimg.png&quot; data-origin-width=&quot;1494&quot; data-origin-height=&quot;944&quot; width=&quot;763&quot; height=&quot;482&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이처럼 &lt;b&gt;병목현상이 발생하는 Critical Path 를 찾아서 성능향상을 시킨다면 전체 시스템의 Throughput 이 1000TPS 로 증가&lt;/b&gt;하게 된다. 하지만 Critical Path 는 없어지는 것이 아니라 다른 구간으로 이동하게 된다. 모든 구간의 Throughput 이 동일하지 않다면 항상 병목현상이 발생하기 때문이다.&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;font-family: 'Noto Serif KR';&quot;&gt;이와 다르게 &lt;span style=&quot;color: #ef5369; background-color: #f6e199;&quot;&gt;&lt;b&gt;Latency 는 각 구간의 성능향상이 전체 시스템의 성능에 영향&lt;/b&gt;&lt;/span&gt;을 미친다. 전체 시스템의 Latency 는 각 구간의 Latency 의 합이기 때문에 당연한 결과이다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;TPS 에 대해서&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;위에서도 소개했지만 TPS 는 Transaction per second 의 약자로 초당 처리할 수 있는 Transaction 의 수를 의미한다. 즉, 처리량을 나타내는 성능지표이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1212&quot; data-origin-height=&quot;880&quot; width=&quot;407&quot; height=&quot;295&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRhzbb/btrjl9eCyGl/yFZ0kbYWSxd7qke2KbA4d0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRhzbb/btrjl9eCyGl/yFZ0kbYWSxd7qke2KbA4d0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRhzbb/btrjl9eCyGl/yFZ0kbYWSxd7qke2KbA4d0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRhzbb%2Fbtrjl9eCyGl%2FyFZ0kbYWSxd7qke2KbA4d0%2Fimg.png&quot; data-origin-width=&quot;1212&quot; data-origin-height=&quot;880&quot; width=&quot;407&quot; height=&quot;295&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;위 그래프에서 보듯이 &lt;b&gt;서비스를 이용하는 사용자가 증가하면 TPS 는 지속적으로 늘어난다.&lt;/b&gt; 하지만 &lt;b&gt;어느 시점이 되면 아무리 사용자가 늘어나더라도 TPS 는 더 이상 늘어나지 않게 된다.&lt;/b&gt; 이러한 지점을 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Saturation Point(포화지점)&lt;/b&gt;&lt;/span&gt;이라고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이러한 상황은 잘못된 것이 아니라 오히려 이상적인상황이다. 제대로 튜닝이 되지 않은 서비스에서는 포화지점을 지나면 오히려 TPS 가 떨어지기도 한다.&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;font-family: 'Noto Serif KR';&quot;&gt;그러면 포화지점에서 더 이상 TPS 가 증가하지 않는 것은 무슨 의미를 가질까?&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;초당 처리할 수 있는 Transaction 의 수가 한계에 도달했고 그때부터 사용자가 증가하면 Latency 시간이 증가&lt;/b&gt;&lt;/span&gt;한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이러한 포화지점이 중요한 이유는 &lt;span style=&quot;color: #ef5369; background-color: #f6e199;&quot;&gt;&lt;b&gt;여기를 기준으로 해당 서버가 감당할 수 있는 부하의 한계를 정의할 수 있기 때문&lt;/b&gt;&lt;/span&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #ef5369; font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;nGrinder&lt;/b&gt;&lt;/span&gt;&lt;/h2&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;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;b&gt;nGrinder 는 스트레스 테스트 도구로 네이버에 의해서 개발된 오픈소스&lt;/b&gt;이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;유명한 성능 테스트 도구로 Apache 의 JMeter 가 있지만 나는 팀장님의 권유로 nGrinder 를 사용해보았다.&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;font-family: 'Noto Serif KR';&quot;&gt;nGrinder 의 구성요소에 대해서 살펴보자.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 134px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 19px;&quot;&gt;
&lt;td style=&quot;width: 14.8837%; height: 19px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;구성&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;width: 85.1163%; height: 19px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;설명&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 39px;&quot;&gt;
&lt;td style=&quot;width: 14.8837%; height: 39px;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Controller&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 85.1163%; height: 39px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;웹 기반의 GUI 시스템으로 테스트 전반적인 작업이 이 Controller 에 의해서 작동한다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 58px;&quot;&gt;
&lt;td style=&quot;width: 14.8837%; height: 58px;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Agent&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 85.1163%; height: 58px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Controller 의 명령어를 받아 Target 머신에 프로세스와 스레드를 실행시켜 부하를 발생시킨다. 복수의 머신에 설치되어서 Controller 의 신호에 따라서 일시에 부하를 발생시킨다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 14.8837%; height: 18px;&quot;&gt;&lt;b&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;Target&lt;/span&gt;&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 85.1163%; height: 18px;&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;테스트를 하기위한 타겟 머신이다. 즉, 테스트를 하려는 서버가 Target 이다.&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;font-family: 'Noto Serif KR';&quot;&gt;nGrinder 는 Java 베이스로 동작하기 때문에 Oracle JDK 1.6 이상이 설치되어 있어야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://github.com/naver/ngrinder/releases&quot;&gt;&lt;b&gt;https://github.com/naver/ngrinder/releases&lt;/b&gt;&lt;/a&gt;&lt;b&gt;&amp;nbsp; 에서 Controller 파일을 다운로드&lt;/b&gt; 받을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;'&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이렇게 다운로드 받은 Controller 는 두 가지 방법으로 실행할 수 있다. &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;첫 번째 방법은 Docker 를 이용한 방법이고 두 번째 방법은 WAR 파일을 다운로드 받아 로컬환경에 직접 구동하는 방법이다.&lt;/b&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;먼저 Docker 를 이용한 방법을 살펴보자. docker-compose 파일은 다음과 같다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1635675537167&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: '3'
services:
  ngrinder-controller:
    image: ngrinder/controller:3.5.5-p1
    container_name: ngrinder-controller
    ports:
      - &quot;80:80&quot;
      - &quot;16001:16001&quot;
      - &quot;12000-12009:12000-12009&quot;
    volumes:
      - ./ngrinder/controller:/opt/ngrinder-controller
 
  ngrinder-agent:
    container_name: ngrinder-agent-1
    image: ngrinder/agent:3.4
    command: [&quot;ngrinder-controller:80&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;위 docker-compose 파일을 이용해 컨테이너를 띄우고 http://localhost:80 으로 접속하면 nGrinder 초기화면이 나온다.&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;font-family: 'Noto Serif KR';&quot;&gt;두 번째로 로컬환경에 직접 구동하는 방법인데, 위 링크에서 Controller 파일중 WAR 파일을 받고 아래 명령어를 실행하면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1635675656581&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;java -XX:MaxPermSize=200m -jar ngrinder-controller-3.5.5-p1.war -p 7777&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;816&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TYBp5/btrjlPOgb0J/pACvuF3QGLqZrK7SbLvmIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TYBp5/btrjlPOgb0J/pACvuF3QGLqZrK7SbLvmIK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TYBp5/btrjlPOgb0J/pACvuF3QGLqZrK7SbLvmIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTYBp5%2FbtrjlPOgb0J%2FpACvuF3QGLqZrK7SbLvmIK%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;816&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;초기화면은 위와같고 기본적으로 설정되어있는 &lt;b&gt;ID/Password 는 admin/admin&lt;/b&gt; 이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;617&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/I3CS3/btrjr17Pb55/7MKBOgHKJhInK7QdQwNTEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/I3CS3/btrjr17Pb55/7MKBOgHKJhInK7QdQwNTEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/I3CS3/btrjr17Pb55/7MKBOgHKJhInK7QdQwNTEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FI3CS3%2Fbtrjr17Pb55%2F7MKBOgHKJhInK7QdQwNTEK%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;617&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;로그인을하고 메뉴를 보면 Download Agent 가 있는데 해당 메뉴를 통해 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;Agent 파일을 다운로드받아 Agent 역할을 할 PC 에 실행&lt;/b&gt;&lt;/span&gt;시키면 된다. 이후 Agent Management 메뉴에서 인식된 Agent 의 목록을 확인할 수 있다.&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&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1110&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c8CHFT/btrjqqNwkVr/iaxWY4MKboNa7uNKKlszG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c8CHFT/btrjqqNwkVr/iaxWY4MKboNa7uNKKlszG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c8CHFT/btrjqqNwkVr/iaxWY4MKboNa7uNKKlszG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc8CHFT%2FbtrjqqNwkVr%2FiaxWY4MKboNa7uNKKlszG1%2Fimg.png&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1110&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이제 성능테스트를 진행할 수 있는데 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;기본적으로 Script 를 작성해 요청할 API 와 파라미터 등을 설정&lt;/b&gt;&lt;/span&gt;할 수 있다. 물론 이렇게 추가된 &lt;b&gt;Script 는 groovy 로 작성되어 있어 직접 수정할수도 있다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;이렇게 Script 를 통해 테스트를 진행할 API 를 설정했으면 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;vUser 수, 테스트 시간, Ramp-Up 등의 설정&lt;/b&gt;&lt;/span&gt;을 하고 테스트를 진행하면 된다. &lt;b&gt;나같은 경우엔 vUser 를 1, 10, 20, 30, 40, 50, 70, 99 로 늘려가며 테스트를 진행했고, 서버에 갑작스런 부하가 가지 않도록 Ramp-Up 을 설정한 후에 진행했다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;1424&quot; data-origin-height=&quot;1222&quot; width=&quot;684&quot; height=&quot;587&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxBYxO/btrjkEe6GBi/ncN0gQPYEZnqCGoTJqHtUK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxBYxO/btrjkEe6GBi/ncN0gQPYEZnqCGoTJqHtUK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxBYxO/btrjkEe6GBi/ncN0gQPYEZnqCGoTJqHtUK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcxBYxO%2FbtrjkEe6GBi%2FncN0gQPYEZnqCGoTJqHtUK%2Fimg.png&quot; data-origin-width=&quot;1424&quot; data-origin-height=&quot;1222&quot; width=&quot;684&quot; height=&quot;587&quot; data-ke-mobilestyle=&quot;widthOrigin&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;내가 진행한 성능테스트 결과중 하나이다. 아래 포스팅에서 소개한 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;EhCache 내부호출 문제를 개선한 후 성능 테스트를 진행한 것이고 확인할 수 있듯이 약 30프로 정도의 성능향상&lt;/b&gt;&lt;/span&gt;이 이루어진 것을 볼 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;&lt;a href=&quot;https://ch4njun.tistory.com/265&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://ch4njun.tistory.com/265&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1635676123498&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[Spring Boot] EhCache Self-Invocation 문제&quot; data-og-description=&quot;회사에서 캐시 구조에 대해서 리팩토링을 진행하는 과정에서 EhCache Self-Invocation 문제로 인해 캐시가 정상적으로 동작하지 않았던 사례에 대해서 포스팅하려고 한다. 이 문제가 무엇인지 예제를 &quot; data-og-host=&quot;ch4njun.tistory.com&quot; data-og-source-url=&quot;https://ch4njun.tistory.com/265&quot; data-og-url=&quot;https://ch4njun.tistory.com/265&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/rIh4o/hyL9JEM08a/mX2mbEh8HxHqlTeOnPe0r0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bEnEmh/hyL9Cr9h7b/6F9dvnae81xkgv9bFWrN30/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/blBYvG/hyL9Ecp1Je/3DhakwNyxT9SoynvFaOQlK/img.png?width=772&amp;amp;height=555&amp;amp;face=0_0_772_555&quot;&gt;&lt;a href=&quot;https://ch4njun.tistory.com/265&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://ch4njun.tistory.com/265&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/rIh4o/hyL9JEM08a/mX2mbEh8HxHqlTeOnPe0r0/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/bEnEmh/hyL9Cr9h7b/6F9dvnae81xkgv9bFWrN30/img.png?width=800&amp;amp;height=800&amp;amp;face=0_0_800_800,https://scrap.kakaocdn.net/dn/blBYvG/hyL9Ecp1Je/3DhakwNyxT9SoynvFaOQlK/img.png?width=772&amp;amp;height=555&amp;amp;face=0_0_772_555');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[Spring Boot] EhCache Self-Invocation 문제&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;회사에서 캐시 구조에 대해서 리팩토링을 진행하는 과정에서 EhCache Self-Invocation 문제로 인해 캐시가 정상적으로 동작하지 않았던 사례에 대해서 포스팅하려고 한다. 이 문제가 무엇인지 예제를&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;ch4njun.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: 'Noto Serif KR';&quot;&gt;아쉬웠던 점은 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;b&gt;groovy 를 이용해 테스트 Script 를 자유롭게 커스터마이징할 수 있는 것이 nGrinder 의 큰 장점&lt;/b&gt;&lt;/span&gt;이라고 하는데 groovy 의 문법도 모르고 기본적인 테스트만 진행해 이 부분을 활용하지 못한 점이다.&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;font-family: 'Noto Serif KR';&quot;&gt;다음에 공부를 더 해서 상황에 맞게 이러한 장점을 활용해보고 싶다.&lt;/span&gt;&lt;/p&gt;
&lt;div class=&quot;notranslate&quot; style=&quot;all: initial;&quot;&gt;&amp;nbsp;&lt;/div&gt;</description>
      <category>개발/Concept</category>
      <author>ch4njun</author>
      <guid isPermaLink="true">https://ch4njun.tistory.com/266</guid>
      <comments>https://ch4njun.tistory.com/266#entry266comment</comments>
      <pubDate>Sun, 31 Oct 2021 19:37:08 +0900</pubDate>
    </item>
  </channel>
</rss>