<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>eckrin.log</title>
    <link>https://eckrin.tistory.com/</link>
    <description>김영빈 (github.com/eckrin)</description>
    <language>ko</language>
    <pubDate>Wed, 19 Aug 2026 05:11:16 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>eckrin</managingEditor>
    <image>
      <title>eckrin.log</title>
      <url>https://tistory1.daumcdn.net/tistory/5040520/attach/d84670a521304c578b31d0b6ae0fed73</url>
      <link>https://eckrin.tistory.com</link>
    </image>
    <item>
      <title>[알고리즘] 세그먼트 트리 (Segment Tree)</title>
      <link>https://eckrin.tistory.com/205</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옛날부터 세그트리에 대한 궁금증이 있었는데, 코테의 당락을 결정하지는 않을 알고리즘이라 취업과는 상관없다는 생각에 미뤄두었던 기억이 나서 이번에 알아두려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세그먼트 트리는 '&lt;b&gt;여러 개의 데이터가 연속적으로 존재할 때, 특정 범위의 데이터의 합/곱/최대값/최소값 등을 구하는 방법&lt;/b&gt;'중 하나이다.&amp;nbsp;수열을 저장하는 배열 A가 존재한다고 가정했을 때, 다음과 같은 케이스를 생각해보자.&lt;/p&gt;
&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;color: #333333; text-align: start;&quot;&gt;(A)&lt;span&gt; 구간 l, r (l&amp;lt;=r)이 주어졌을 때, A[l] + A[l+1] + ... + A[r-1] + A[r]을 구하는 경우를 M회 반복&lt;/span&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;&amp;nbsp;이 경우 시간복잡도는 O(N)이다. &lt;a href=&quot;https://eckrin.tistory.com/148&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;누적합, 구간합&lt;/a&gt;을 구해놓은 후 특정 구간의 합은 O(1)에 도출할 수 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(B) &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;구간 l, r (l&amp;lt;=r)이 주어졌을 때, A[l] + A[l+1] + ... + A[r-1] + A[r]을 구하고 나서, i번째 수를 v로 바꾸는 경우를 M회 반복&lt;/span&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;&amp;nbsp;이전 케이스와 다르게, 한번 구해놓은 누적합을 재사용할 수 없다. 따라서 구간합을 사용하게 되면 O(NM)의 시간이 걸린다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1729052278120&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;S[0] = A[0];
for(int i=1; i&amp;lt;N; i++) {
    S[i] = S[i-1] + A[i];
}&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;누적합을 구하기 위해 위와 같은 연산을 해 놓게 되는데, 이 때마다 합을 저장한 배열인 S의 값이 변경되기 때문이다. 세그먼트 트리를 사용하면 A[l]부터 A[r]을 구하는 연산과, i번째 수를 v로 바꾸는 경우 각각을 O(lgN)만에 수행할 수 있다.&lt;/p&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;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;세그먼트 트리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;세그먼트 트리를 사용하면 트리 구조의 특성을 이용하여 O(logN)의 시간복잡도 안에 특정 구간의 데이터의 합, 최대값 등을 빠르게 구할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cW1TIZ/btsJ7UVmH7n/9yek5PQ0Kq0ZlHn0B43qS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cW1TIZ/btsJ7UVmH7n/9yek5PQ0Kq0ZlHn0B43qS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cW1TIZ/btsJ7UVmH7n/9yek5PQ0Kq0ZlHn0B43qS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcW1TIZ%2FbtsJ7UVmH7n%2F9yek5PQ0Kq0ZlHn0B43qS0%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;710&quot; height=&quot;355&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;640&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 트리는 특정 구간의 데이터 합 정보를 갖는 세그먼트 트리에 저장되는 데이터의 정보를 표시한 그림이다. &lt;b&gt;리프 노드는 배열의 수 자체&lt;/b&gt;를 말하고, &lt;b&gt;인터널 노드는 배열의 왼쪽 자식과 오른쪽 자식의 합&lt;/b&gt;을 저장하고 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 세그먼트 트리의 크기(높이)는 N, 즉 배열의 크기에 따라서 결정된다. 따라서 N이 2의 a배승 형태인 배열에서는 Full Binary Tree(모든 노드가 자식을 0개 또는 2개 가지는 이진 트리)가 생성되고, 리프노드가 N개인 Full Binary Tree는 lg(N)의 높이와 2N-1개의 노드 개수를 갖는다. 만약 N이 2의 배승꼴이 아니라면, &lt;span&gt;&amp;lceil;&lt;/span&gt;&lt;span&gt;&lt;span&gt;l&lt;span&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span&gt;(N)&lt;/span&gt;&lt;span&gt;&amp;rceil;&lt;/span&gt;의 높이를 가지며, 세그먼트 트리를 만드는데 2^(&amp;lceil;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;l&lt;span&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;(N)&amp;rceil;+1)-1 크기의 배열이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Tree 클래스&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1729078019647&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static class SegTree {
    long[] tree;
    int size;
    
    public SegTree(int arrSize) {
        // 트리 높이 구하기
        int h = (int)Math.ceil(Math.log(arrSize)/Math.log(2));
        // 높이를 이용한 배열 사이즈 구하기
        this.size = (int) Math.pow(2, h+1);
        tree = new long[size];
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 필드로 배열을 갖는 클래스를 사용할 수 있다. 트리의 높이(&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&amp;lceil;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;l&lt;span&gt;g&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;​&lt;/span&gt;&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;(N)&amp;rceil;)를 구하고, 높이가 h일때의 노드 크기(2^(h+1)-1)만큼 트리 배열 크기 size를 지정한다. (여기서는 인덱스를 1부터 시작하도록 했다)&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;참고) 트리를 1차원 배열로 표현하는 방법&lt;br /&gt;- 루트 인덱스 = 1&lt;br /&gt;- 왼쪽 자식노드의 인덱스 = 부모 노드의 인덱스 * 2&lt;br /&gt;- 오른쪽 자식노드의 인덱스 = 부모 노드의 인덱스 * 2 + 1&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;초기화&lt;/b&gt;&lt;/h3&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public long init(long[] arr, int node, int start, int end) {
    // 배열과 시작과 끝이 같다면 leaf노드이므로 값 저장
    if(start==end) {
        return tree[node] = arr[start];
    }

    // 재귀 - leaf노드가 아니라면 자식노드(왼쪽+오른쪽)의 합 담기
    return tree[node] = init(arr, node*2, start, (start+end)/2) + init(arr, node*2+1, (start+end)/2+1, end);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;이렇게 구간 합 트리를 초기화하기 위해서는 반복과 재귀를 사용할 수 있는데,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;재귀&lt;/b&gt;를 사용하는 것이 직관적이고 편리하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 파라미터로 받는 arr배열은 &lt;b&gt;데이터를 담은 배열 A&lt;/b&gt;를, node는 &lt;b&gt;초기화를 진행할 노드&lt;/b&gt;를 말한다. 현재 노드(node)를 기준으로 왼쪽 자식노드(2*node), 오른쪽 자식노드(2*node+1)를 탐색해나가며 재귀적으로 탐색한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- start와 end는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;b&gt;&lt;span&gt;원배열에서&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;b&gt;구간&lt;span&gt;&amp;nbsp;&lt;/span&gt;합 범위의 시작과 끝 인덱스&lt;/b&gt;를 나타낸다. 원 배열 A를 절반씩 나누어 탐색해나가는 병합 정렬과 유사하다고 볼 수 있다. start와 end가 동일하다면 리프 노드에 도달했다고 볼 수 있으므로 원 배열의 값을 트리에 저장해둔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;구간 합 구하기&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1729083004062&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public long sum(int node, int start, int end, int left, int right) {
    if(left&amp;gt;end || right&amp;lt;start) {
        return 0;
    }

    if(left&amp;lt;=start &amp;amp;&amp;amp; end&amp;lt;=right) {
        return tree[node];
    }

    return sum(node*2, start, (start+end)/2, left, right) + sum(node*2+1, (start+end)/2+1, end, left, right);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 초기화한 트리를 이용하여 구간 합을 구할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVaFA6/btsJ8bQYeoY/2rHwlnl6RKwk9KDTcHgprK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVaFA6/btsJ8bQYeoY/2rHwlnl6RKwk9KDTcHgprK/img.png&quot; data-alt=&quot;https://cano721.tistory.com/38&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVaFA6/btsJ8bQYeoY/2rHwlnl6RKwk9KDTcHgprK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVaFA6%2FbtsJ8bQYeoY%2F2rHwlnl6RKwk9KDTcHgprK%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;800&quot; height=&quot;450&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://cano721.tistory.com/38&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- start와 end는 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;b&gt;&lt;span&gt;원배열에서&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;b&gt;구간&lt;span&gt;&amp;nbsp;&lt;/span&gt;합 범위의 시작과 끝 인덱스&lt;/b&gt;를 나타내고, left와 right는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;원하는 누적합의 시작과 끝 인덱스&lt;/b&gt;를 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 모든 내부 노드는 자식으로 갖는 리프 노드의 합을 갖고 있으므로, 자식노드로 내려가며 확인해가다가 &lt;b&gt;현재 배열이 찾고자 하는 범위에서 벗어나면 0&lt;/b&gt;을 반환하고, &lt;b&gt;범위 안에 포함되면 해당 값을 반환&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 위 예시에서 3~5까지의 구간합을 구하기 위해서는 arr[3]~arr[4]의 합을 가지고 있는 5번 노드(tree[5])와 arr[5]의 값을 가지고 있는 12번 노드(tree[5])의 값을 더해서 해결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;값 업데이트하기&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1729083039798&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public void update(int node, int start, int end, int idx, long diff) {
    if(idx&amp;lt;start || end&amp;lt;idx) return;

    tree[node] += diff;

    if(start!=end) {
        update(node*2, start, (start+end)/2, idx, diff);
        update(node*2+1, (start+end)/2+1, end, idx, diff);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;450&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/crw1AD/btsJ77uwlZp/6E8J3j3htfBjGD71qCRXS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/crw1AD/btsJ77uwlZp/6E8J3j3htfBjGD71qCRXS0/img.png&quot; data-alt=&quot;https://cano721.tistory.com/38&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/crw1AD/btsJ77uwlZp/6E8J3j3htfBjGD71qCRXS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcrw1AD%2FbtsJ77uwlZp%2F6E8J3j3htfBjGD71qCRXS0%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;800&quot; height=&quot;450&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;450&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://cano721.tistory.com/38&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;값을 업데이트한다는 것은 리프 노드를 업데이트한다는 것이고, 이는 즉 값을 바꿀 리프 노드의 부모 노드들의 값을 모두 바꾸어주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;- start와 end는&lt;b&gt;&lt;span&gt; 원배열에서&amp;nbsp;&lt;/span&gt;&lt;/b&gt;&lt;/span&gt;&lt;b&gt;구간 합 범위의 시작과 끝 인덱스&lt;/b&gt;를 나타낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 파라미터로 들어오는 idx는 &lt;b&gt;값을 변경할 원 배열의 인덱스&lt;/b&gt;, diff는 &lt;b&gt;원 배열값의 원래 값과 새로운 값의 차이&lt;/b&gt;를 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 만약 현재 범위 내에 idx가 포함되지 않는다면, 아무것도 할 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 그것이 아니라면, 현재 tree의 노드에 diff만큼을 더해주고, 재귀적으로 반복한다.&lt;/p&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;pre id=&quot;code_1729250686491&quot; class=&quot;pgsql&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;static class SegTree {
    long[] tree;
    int size;

    public SegTree(int arrSize) {
        // 트리 높이 구하기
        int h = (int)Math.ceil(Math.log(arrSize)/Math.log(2));
        // 높이를 이용한 배열 사이즈 구하기
        this.size = (int) Math.pow(2, h+1);
        tree = new long[size];
    }

    public long init(long[] arr, int node, int start, int end) {
        // 배열과 시작과 끝이 같다면 leaf노드이므로 값 저장
        if(start==end) {
            return tree[node] = arr[start];
        }

        // 재귀 - leaf노드가 아니라면 자식노드(왼쪽+오른쪽)의 합 담기
        return tree[node] = init(arr, node*2, start, (start+end)/2) + init(arr, node*2+1, (start+end)/2+1, end);
    }

    public void update(int node, int start, int end, int idx, long diff) {
        if(idx&amp;lt;start || end&amp;lt;idx) return;

        tree[node] += diff;

        if(start!=end) {
            update(node*2, start, (start+end)/2, idx, diff);
            update(node*2+1, (start+end)/2+1, end, idx, diff);
        }
    }

    public long sum(int node, int start, int end, int left, int right) {
        if(left&amp;gt;end || right&amp;lt;start) {
            return 0;
        }

        if(left&amp;lt;=start &amp;amp;&amp;amp; end&amp;lt;=right) {
            return tree[node];
        }

        return sum(node*2, start, (start+end)/2, left, right) + sum(node*2+1, (start+end)/2+1, end, left, right);
    }
}&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;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 세그먼트 트리를 이용하여 특정 구간의 데이터의 합이나 곱, 최대값 등을 O(logN)에 빠르게 구할 수 있는 방법을 알아보았다. 지금까지는 특정 구간의 데이터 합을 구하는 트리를 설명했는데, '부모 노드가 자식 노드의 정보를 모두 포함하도록' 트리를 만들어주면 합 말고 다양한 정보들 또한 빠르게 구할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>[ CS기초 ]/알고리즘</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/205</guid>
      <comments>https://eckrin.tistory.com/205#entry205comment</comments>
      <pubDate>Wed, 16 Oct 2024 12:46:03 +0900</pubDate>
    </item>
    <item>
      <title>[DB] 인덱스(Index)로 조회 성능 개선하기</title>
      <link>https://eckrin.tistory.com/202</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그동안 데이터베이스 조회 성능 향상을 위해서 인덱스를 종종 사용했었는데, 따로 포스팅으로 정리한 적은 없어서 정리해보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스를 한 마디로 표현하면 &quot;&lt;b&gt;별도의 메모리를 추가로 할당하여 데이터베이스 조회 성능을 향상시킬 수 있는 방법&lt;/b&gt;&quot;이라고 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;인덱스&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인덱스에 대해서 이야기하기 전에, 데이터베이스에서 어떻게 데이터를 조회하는지 서술하도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzghin/btsJNJ0PXKH/fOuuKO9OxZxIkw0vYedjhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzghin/btsJNJ0PXKH/fOuuKO9OxZxIkw0vYedjhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzghin/btsJNJ0PXKH/fOuuKO9OxZxIkw0vYedjhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbzghin%2FbtsJNJ0PXKH%2FfOuuKO9OxZxIkw0vYedjhK%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;603&quot; height=&quot;195&quot; data-origin-width=&quot;1050&quot; data-origin-height=&quot;340&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우리가 흔히 말하는 Oracle이나 Mysql을 '데이터베이스 시스템' 이라고 부르는데, 데이터베이스 시스템은 데이터가 저장되는 '&lt;b&gt;데이터베이스&lt;/b&gt;'와 데이터베이스를 관리하는 소프트웨어인 '&lt;b&gt;DBMS&lt;/b&gt;(DataBase Management System)'으로 구성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DBMS에서 데이터베이스에 있는 데이터를 조회하기 위해서는 DBMS 내의 '&lt;b&gt;질의 처리기&lt;/b&gt;(Query Processor)'와 '&lt;b&gt;저장 데이터 관리자&lt;/b&gt;(Stored Data Manager)'를 사용하는데, 일반적으로 다음과 같은 순서를 따르게 된다.&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;1. 구문 분석: 애플리케이션에서 SQL 쿼리가 데이터베이스 시스템으로 전달되면, DBMS의 질의 처리기는 쿼리를 받아서 구문 분석을 수행한다. 이 단계에서 문법 오류를 확인하게 된다.&lt;br /&gt;&lt;br /&gt;2. 최적화: 파싱이 완료되고 나면, 쿼리를 실행하기 위한 최적의 수행 계획을 수립하여 캐시에 저장해둔다. &lt;br /&gt;(p.s 이 부분을 '&lt;b&gt;옵티마이저&lt;/b&gt;'라고 부르기도 한다)&lt;br /&gt;이는 테이블을 full-scan을 통해 조회할지, 인덱스를 사용할지, 조인을 어떠한 순서로 수행할지와 같은 정보들을 포함한다. 또한, 캐싱된 데이터를 활용하여 추후 동일 쿼리가 다시 실행되면 속도를 향상시킬 수 있다.&lt;br /&gt;&lt;br /&gt;3. 실행: 옵티마이저가 수행한 계획에 따라서 쿼리를 실행한다. 이 때 저장 데이터 관리자를 사용하여 데이터베이스에서 데이터를 실제로 조회하게 된다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 최적화 단계에서, 인덱스가 존재하지 않으면 모든 데이터를 순차 탐색을 통해서 읽게 되는데, 이를 &lt;b&gt;Table Full Scan&lt;/b&gt; 방식이라고 한다. 이러한 방법은 테이블의 모든 행을 차례대로 읽어야 하므로, 높은 I/O cost(O(N) in worst case)로 인해 속도가 매우 느려지게 된다. 따라서 &lt;b&gt;B+Tree를 사용하여 조회 속도를 O(logN)으로 줄일 수 있는 방법&lt;/b&gt;이 인덱스이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Mysql의 인덱스&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Mysql은 5.5버전부터 InnoDB를 기본 스토리지 엔진으로 사용하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 사용하던, 테이블 수준의 락을 지원하던 MyISAM과 InnoDB의 가장 큰 차이점 중에 하나는 InnoDB가 레코드 수준의 락을 지원한다는 것이다. 즉, 하나의 레코드에 대한 변경 작업이 발생하면 해당 테이블 전체에 락이 걸려서 테이블의 데이터를 수정할 수 없게 되는 것이다. 이는 다수의 트랜잭션이 동시에 작동하는 상황에서 성능 저하를 일으키게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 InnoDB는 레코드 수준의 잠금을 지원한다. 이는 한 번에 여러 트랜잭션이 하나의 테이블에 여러 레코드를 수정할 수도 있음을 의미한다. 따라서 인덱스를 사용할 경우 레코드를 찾기 위해서 검색한 인덱스의 레코드가 모두 잠기기 때문에, 인덱스를 잘 설정해주면 성능을 향상시킬 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;예시&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 orders 테이블이 존재하고, 클러스터링 인덱스 외에 아무런 인덱스가 설정되지 않았다고 가정하자.&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;1300&quot; data-origin-height=&quot;312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBIQqm/btsJO90DLK4/PdQHeg7xKBK3wB7DEvhqS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBIQqm/btsJO90DLK4/PdQHeg7xKBK3wB7DEvhqS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBIQqm/btsJO90DLK4/PdQHeg7xKBK3wB7DEvhqS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBIQqm%2FbtsJO90DLK4%2FPdQHeg7xKBK3wB7DEvhqS0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;764&quot; height=&quot;183&quot; data-origin-width=&quot;1300&quot; data-origin-height=&quot;312&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1727442979571&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;UPDATE orders SET total_amount = total_amount + 50 WHERE customer_id = 101;&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;위와 같은 update쿼리를 날리게 되면, customer_id가 101인 레코드를 탐색하기 위해서 테이블 전체에 대해서 순차 탐색을 해야 하기 때문에 결국 테이블 전체에 대한 락을 필요로 하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때 외래키인 customer_id 컬럼에 인덱스가 설정되어 있다면, order_id가 1, 3인 두개의 레코드만 락이 걸리게 된다. 이는 order_id가 2,4인 레코드에 대해서는 동시에 트랜잭션이 실행될 수 있음을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(이러한 이유에서인지 InnoDB는 자동으로 외래 키에 대해서 인덱스를 생성하도록 설정되어 있다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;인덱스 종류&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;클러스터링 인덱스&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;682&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdqnsH/btsJM3Z8pC1/MNFXqjT0Kjgdl24Q89wmxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdqnsH/btsJM3Z8pC1/MNFXqjT0Kjgdl24Q89wmxK/img.png&quot; data-alt=&quot;https://golf-dev.tistory.com/67&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdqnsH/btsJM3Z8pC1/MNFXqjT0Kjgdl24Q89wmxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdqnsH%2FbtsJM3Z8pC1%2FMNFXqjT0Kjgdl24Q89wmxK%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;684&quot; height=&quot;364&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;682&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://golf-dev.tistory.com/67&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1727340034652&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE TABLE users(
    id int primary key,
    name varchar(255),
    email varchar(255) unique
)&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;관계형 데이터베이스는 '기본키 제약 조건'에 의해서 반드시 unique한 pk가 존재하는데, 데이터베이스는 이를 기준으로 B+Tree를 사용하여 데이터를 저장한다. 이렇게 pk를 기준으로 하는 기본적으로 존재하는 B+Tree를 &lt;b&gt;클러스터링 인덱스&lt;/b&gt;라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;논클러스터링 인덱스&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;719&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ISjwh/btsJMdPUxuZ/8DbnKLXf4pwcsGZmej44C1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ISjwh/btsJMdPUxuZ/8DbnKLXf4pwcsGZmej44C1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ISjwh/btsJMdPUxuZ/8DbnKLXf4pwcsGZmej44C1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FISjwh%2FbtsJMdPUxuZ%2F8DbnKLXf4pwcsGZmej44C1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1280&quot; height=&quot;719&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;719&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1727340104492&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE TABLE users(
    id int primary key, # 클러스터링 인덱스
    name varchar(255),
    email varchar(255) unique # 논클러스터링 인덱스
)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1727340166584&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;CREATE [UNIQUE] INDEX 인덱스_이름 ON 테이블_이름 (열 이름) [ASC | DESC]&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;반면 우리가 unique조건 등을 사용하거나, CREATE INDEX 명령어를 통해서 인덱스를 생성하게 되면 기본적으로 존재하는 클러스터링 인덱스 외에 추가로 B+Tree를 사용한 인덱스를 생성할 수 있다. 이렇게 생성된 인덱스를 &lt;b&gt;논클러스터링 인덱스&lt;/b&gt;라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그림에서 볼 수 있듯이, 논클러스터링 인덱스는 클러스터링 인덱스와 다르게 실제 데이터값을 저장하고 있지 않고, 대신 &lt;b&gt;데이터가 존재하는 페이지 번호, 즉 참조값을&lt;/b&gt;&amp;nbsp;가지고 있다는 차이가 있다. 따라서 인덱스를 조회한 후, 해당 페이지 번호로 이동하여 실제 데이터를 조회하는 과정을 거치게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;간단한 성능 테스트&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 인덱스를 설정해주는 것이 얼마나 효율성을 가져오는지 확인해보자. 아래 실습은&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MySQL 8.1.0(InnoDB 8.1.0)&lt;/li&gt;
&lt;li&gt;MacOS Sonoma 14.5&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;에서 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 데이터베이스 테이블 생성&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1727348794794&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mysql&amp;gt; create table bulk (
    -&amp;gt;  id int primary key,
    -&amp;gt;  name varchar(255),
    -&amp;gt;  email varchar(255)
    -&amp;gt; );&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 대용량 데이터 정보를 담는 .csv 파일 생성&lt;/b&gt;&lt;/h3&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class Main {
    public static void main(String[] args) {
        String fileName = &quot;data.csv&quot;;
        int totalRecords = 1_000_000;

        try(FileWriter writer = new FileWriter(fileName)) {
            writer.append(&quot;id,name,email\n&quot;);  // 헤더 추가

            Set&amp;lt;String&amp;gt; emails = new HashSet&amp;lt;&amp;gt;();
            Random random = new Random();

            for (int i=1; i&amp;lt;=totalRecords; i++) {
                String name = &quot;name_&quot;+UUID.randomUUID().toString().substring(0, 8);

                String email;
                do {
                    email = &quot;user&quot;+random.nextInt(1_000_000)+&quot;@example.com&quot;;
                } while (!emails.add(email));

                writer.append(String.format(&quot;%d,%s,%s\n&quot;, i, name, email));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;312&quot; data-origin-height=&quot;204&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bol90T/btsJOGPSUlg/3CyU00BUJiCqJaCQQXlVxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bol90T/btsJOGPSUlg/3CyU00BUJiCqJaCQQXlVxk/img.png&quot; data-alt=&quot;data.csv&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bol90T/btsJOGPSUlg/3CyU00BUJiCqJaCQQXlVxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbol90T%2FbtsJOGPSUlg%2F3CyU00BUJiCqJaCQQXlVxk%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;312&quot; height=&quot;204&quot; data-origin-width=&quot;312&quot; data-origin-height=&quot;204&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;data.csv&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;100만건의 더미 데이터를 만들어서 넣어주면, 위와 같은 형태로 파일이 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. MySQL의 LOAD DATA INFILE을 통해서 .csv파일을 데이터베이스 테이블에 삽입하기&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1727349504532&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;LOAD DATA INFILE '.csv 파일 절대경로'
INTO TABLE bulk
FIELDS TERMINATED BY ','
ENCLOSED BY '&quot;'
LINES TERMINATED BY '\n'
IGNORE 1 LINES(id, name, email);&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-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;953&quot; data-origin-height=&quot;23&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ds2HT8/btsJNsZQmQL/ybc3zga50O7jtXKCsZhOkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ds2HT8/btsJNsZQmQL/ybc3zga50O7jtXKCsZhOkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ds2HT8/btsJNsZQmQL/ybc3zga50O7jtXKCsZhOkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fds2HT8%2FbtsJNsZQmQL%2Fybc3zga50O7jtXKCsZhOkk%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;953&quot; height=&quot;23&quot; data-origin-width=&quot;953&quot; data-origin-height=&quot;23&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 위와 같은 오류가 발생한다면 MySQL이 특정 경로에서만 파일을 읽도록 설정되어 있는 것인데, MySQL 설정파일인 my.cnf 파일을 찾아서 이 설정값을 바꾸어 주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;my.cnf파일의 위치를 찾으려면&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;708&quot; data-origin-height=&quot;356&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GW5mh/btsJMDOkn7q/GWkpJRfkHt2FIvEw2zksHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GW5mh/btsJMDOkn7q/GWkpJRfkHt2FIvEw2zksHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GW5mh/btsJMDOkn7q/GWkpJRfkHt2FIvEw2zksHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGW5mh%2FbtsJMDOkn7q%2FGWkpJRfkHt2FIvEw2zksHK%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;708&quot; height=&quot;356&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;356&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 명령어를 사용해서 basedir 하위에 위치한다고 하는데, 나는 저 위치에 없고 /opt/homebrew/etc 아래에 있었다. homebrew로 MySQL을 깔아서 그런가?&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;346&quot; data-origin-height=&quot;125&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dYY1Ek/btsJN9rufUY/Wjtb1nKmhm1JQKlLhkFVo0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dYY1Ek/btsJN9rufUY/Wjtb1nKmhm1JQKlLhkFVo0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dYY1Ek/btsJN9rufUY/Wjtb1nKmhm1JQKlLhkFVo0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdYY1Ek%2FbtsJN9rufUY%2FWjtb1nKmhm1JQKlLhkFVo0%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;346&quot; height=&quot;125&quot; data-origin-width=&quot;346&quot; data-origin-height=&quot;125&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1727350638541&quot; class=&quot;sql&quot; data-ke-language=&quot;sql&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;mysql.server restart&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어쨌든 해당 파일을 찾고 나서 이렇게 'secure-file-priv' 한 줄을 추가해주고, MySQL을 재시작해주고 나면 LOAD DATA INFILE명령어 실행시 오류가 발생하지 않는다.&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;439&quot; data-origin-height=&quot;162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XRjLK/btsJOm5dEfG/Ngdw6tei69DZRf9fFnHs40/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XRjLK/btsJOm5dEfG/Ngdw6tei69DZRf9fFnHs40/img.png&quot; data-alt=&quot;성공&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XRjLK/btsJOm5dEfG/Ngdw6tei69DZRf9fFnHs40/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXRjLK%2FbtsJOm5dEfG%2FNgdw6tei69DZRf9fFnHs40%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;439&quot; height=&quot;162&quot; data-origin-width=&quot;439&quot; data-origin-height=&quot;162&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;성공&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 쿼리 수행시간 측정 - 인덱스 적용 전&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 MySQL에서 제공하는 Profile 기능을 통해서 쿼리 수행 시간을 측정해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;666&quot; data-origin-height=&quot;37&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dt3aXR/btsJODTal7F/NVfuyd6Qk0RpUMtSpa8zCk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dt3aXR/btsJODTal7F/NVfuyd6Qk0RpUMtSpa8zCk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dt3aXR/btsJODTal7F/NVfuyd6Qk0RpUMtSpa8zCk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdt3aXR%2FbtsJODTal7F%2FNVfuyd6Qk0RpUMtSpa8zCk%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;666&quot; height=&quot;37&quot; data-origin-width=&quot;666&quot; data-origin-height=&quot;37&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;366&quot; data-origin-height=&quot;415&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6vCFN/btsJOjOmK0P/bqEHYIoTbdVukAQglXsxe0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6vCFN/btsJOjOmK0P/bqEHYIoTbdVukAQglXsxe0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6vCFN/btsJOjOmK0P/bqEHYIoTbdVukAQglXsxe0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6vCFN%2FbtsJOjOmK0P%2FbqEHYIoTbdVukAQglXsxe0%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;366&quot; height=&quot;415&quot; data-origin-width=&quot;366&quot; data-origin-height=&quot;415&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;특정 이메일 값을 갖는 데이터를 찾기 위한 select쿼리 수행에는 0.19초 가량이 소요된 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. 쿼리 수행시간 측정 - 인덱스 적용 후&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같이 조회할 컬럼에 인덱스를 적용하였다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dOfew7/btsJNApQioR/V5xx1zqftIvhtoqHKfw451/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dOfew7/btsJNApQioR/V5xx1zqftIvhtoqHKfw451/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dOfew7/btsJNApQioR/V5xx1zqftIvhtoqHKfw451/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdOfew7%2FbtsJNApQioR%2FV5xx1zqftIvhtoqHKfw451%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;374&quot; height=&quot;68&quot; data-origin-width=&quot;726&quot; data-origin-height=&quot;132&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다시 쿼리 성능을 측정해보자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1328&quot; data-origin-height=&quot;76&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzNJqx/btsJM2mRcHH/Gfooi6euKykC8Ebs6PvXl1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzNJqx/btsJM2mRcHH/Gfooi6euKykC8Ebs6PvXl1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzNJqx/btsJM2mRcHH/Gfooi6euKykC8Ebs6PvXl1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzNJqx%2FbtsJM2mRcHH%2FGfooi6euKykC8Ebs6PvXl1%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;684&quot; height=&quot;39&quot; data-origin-width=&quot;1328&quot; data-origin-height=&quot;76&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;736&quot; data-origin-height=&quot;880&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WtbTn/btsJNB99mng/kQx8s41WfNswyKbLkJFoh1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WtbTn/btsJNB99mng/kQx8s41WfNswyKbLkJFoh1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WtbTn/btsJNB99mng/kQx8s41WfNswyKbLkJFoh1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWtbTn%2FbtsJNB99mng%2FkQx8s41WfNswyKbLkJFoh1%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;363&quot; height=&quot;434&quot; data-origin-width=&quot;736&quot; data-origin-height=&quot;880&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;인덱스를 적용했을 때 전체 쿼리 수행시간은 0.187s에서 0.002s로 감소하였고, 특히 executing 단계가 0.187378s에서 0.0012s로 단순 조회시 대략 &lt;b&gt;150배 가량의 조회 성능이 향상&lt;/b&gt;됨을 확인할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이렇게 보면 인덱스가 조회 속도를 향상시켜주므로, 반드시 사용하는 것이 좋아 보일 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 &lt;span style=&quot;background-color: #fafafa; color: #222222; text-align: start;&quot;&gt;인덱스 자체도&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;메모리&lt;span style=&quot;background-color: #fafafa; color: #222222; text-align: start;&quot;&gt;를 차지하며, 데이터를 수정할 경우 인덱스를 구성하는 B+Tree 또한 수정해주어야 하며, 인덱스의 크기가 매우 커질 수도 있다. 따라서 &lt;/span&gt;&lt;b&gt;수정, 삭제가 드물고 조회가 잦은 컬럼&lt;/b&gt;&lt;span style=&quot;background-color: #fafafa; color: #222222; text-align: start;&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;(언젠가 조회:수정 비율이 최소 8:2는 되어야 인덱스를 적용해볼 만 하다고 들은 적이 있는데, 구글링을 아무리 해봐도 그러한 자료는 아직 못 찾음 - 추가 예정)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>[ CS기초 ]/데이터베이스</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/202</guid>
      <comments>https://eckrin.tistory.com/202#entry202comment</comments>
      <pubDate>Thu, 26 Sep 2024 16:57:40 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] @Valid와 @Validated를 이용한 순차 검증</title>
      <link>https://eckrin.tistory.com/201</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHHxer/btsJIO1vxUE/nW8w3kkUX0F5akZWbdxx7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHHxer/btsJIO1vxUE/nW8w3kkUX0F5akZWbdxx7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHHxer/btsJIO1vxUE/nW8w3kkUX0F5akZWbdxx7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHHxer%2FbtsJIO1vxUE%2FnW8w3kkUX0F5akZWbdxx7K%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;512&quot; height=&quot;268&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;268&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Spring에서 유효성 검증을 위해서 사용되는 어노테이션에는 흔히 &lt;b&gt;@Valid&lt;/b&gt;와 &lt;b&gt;@Validated&lt;/b&gt;가 있다. 필자는 평소에는 @Valid 위주로 사용했는데, 얼마 전 진행했던 프로젝트에서 @Validated를 사용한 좋은 검증 예시를 보아서 정리해보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;@Valid&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;261&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DkZHf/btsJG4LsleJ/pkRvzcKfEoNlN3TXDZKkQk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DkZHf/btsJG4LsleJ/pkRvzcKfEoNlN3TXDZKkQk/img.png&quot; data-alt=&quot;https://jakarta.ee/specifications/bean-validation/3.0/apidocs/jakarta/validation/valid&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DkZHf/btsJG4LsleJ/pkRvzcKfEoNlN3TXDZKkQk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDkZHf%2FbtsJG4LsleJ%2FpkRvzcKfEoNlN3TXDZKkQk%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;750&quot; height=&quot;246&quot; data-origin-width=&quot;796&quot; data-origin-height=&quot;261&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://jakarta.ee/specifications/bean-validation/3.0/apidocs/jakarta/validation/valid&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 @Valid는 jakarta.validation(이전에는 javax.validation), 즉 Java 라이브러리 안에 속해있는 어노테이션이다.&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;@Valid 어노테이션은 Bean Validation API와의 통합을 통해서 &lt;b&gt;데이터 무결성을 유지하는 데에 필요한 유효성 검사를 수행&lt;/b&gt;할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Bean Validation API란?&lt;br /&gt;&lt;br /&gt;@NotBlank, @Min, @Email과 같이 도메인 모델의 필드에 유효성 제약을 위한 어노테이션을 제공하는 API를 말한다. 표준으로는 JSR 303, JSR 380 등이 존재하며, 이를 구현한 대표적인 구현체가 Hibernate Validator이다.&lt;br /&gt;&lt;br /&gt;(JPA의 구현체인 Hibernate 프레임워크와는 독립적인 프로젝트이다 - Hibernate ORM은 Hibernate Validator를 포함하기는 하지만, Hibernate ORM을 사용하지 않아도 Hibernate Validator를 사용할 수 있다)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;@Validated&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;249&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcGOAV/btsJIgxqa7r/SqwyHUpyPkv599DrwXx711/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcGOAV/btsJIgxqa7r/SqwyHUpyPkv599DrwXx711/img.png&quot; data-alt=&quot;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/validation/annotation/Validated.html&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcGOAV/btsJIgxqa7r/SqwyHUpyPkv599DrwXx711/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcGOAV%2FbtsJIgxqa7r%2FSqwyHUpyPkv599DrwXx711%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;1242&quot; height=&quot;249&quot; data-origin-width=&quot;1242&quot; data-origin-height=&quot;249&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/validation/annotation/Validated.html&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 @Validated 어노테이션은 org.springframework.validation 안에 있는 어노테이션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설명을 보면 '&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;Variant of JSR-303's&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a style=&quot;background-color: #ffffff; color: #4a6782; text-align: start;&quot; href=&quot;https://jakarta.ee/specifications/platform/9/apidocs/jakarta/validation/Valid.html&quot;&gt;Valid&lt;/a&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt;, supporting the specification of validation groups.'라고 되어있는 것을 확인할 수 있는데, 즉, &lt;b&gt;@Validated 어노테이션은 @Valid의 확장&lt;/b&gt;이며, &quot;&lt;b&gt;Validation Group&lt;/b&gt;&quot;을 지원한다는 사실을 알 수 있다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #474747; text-align: start;&quot;&gt; Validation Group&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그렇다면&amp;nbsp; &quot;Validation Group&quot;이란 무엇일까? 흔히 Validation API를 사용하여 검증을 진행할 때, 위와 같이 Dto나 엔티티, 도메인 엔티티 등에 @NotNull, @NotBlank 등의 어노테이션을 사용하고, 컨트롤러에 @Valid 어노테이션을 사용해서 값을 검증한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;539&quot; data-origin-height=&quot;602&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Gc67F/btsJIfFeoDl/j4SKLiGWsyHDeOulchH8p1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Gc67F/btsJIfFeoDl/j4SKLiGWsyHDeOulchH8p1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Gc67F/btsJIfFeoDl/j4SKLiGWsyHDeOulchH8p1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGc67F%2FbtsJIfFeoDl%2Fj4SKLiGWsyHDeOulchH8p1%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;539&quot; height=&quot;602&quot; data-origin-width=&quot;539&quot; data-origin-height=&quot;602&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;588&quot; data-origin-height=&quot;87&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d7CLcJ/btsJIh4cFWS/Yr2Ts69S800mvMR3Mp65V1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d7CLcJ/btsJIh4cFWS/Yr2Ts69S800mvMR3Mp65V1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d7CLcJ/btsJIh4cFWS/Yr2Ts69S800mvMR3Mp65V1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd7CLcJ%2FbtsJIh4cFWS%2FYr2Ts69S800mvMR3Mp65V1%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;588&quot; height=&quot;87&quot; data-origin-width=&quot;588&quot; data-origin-height=&quot;87&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그런데 만약  Dto의 각 필드에 대해서 검증을 순서대로 진행해서 사용자에게 반환해주고 싶다면, 각 Validation 실패 시에 터지는 예외를 다르게 잡아주어야 한다. 문제는 유효성 검증은 한번에 이루어지기 때문에, 검증 순서를 지정하기는 어렵다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 &lt;b&gt;Group Validation&lt;/b&gt;과 &lt;b&gt;Group Sequence&lt;/b&gt;를 사용하여 해결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Group Validation&lt;/b&gt;은 검증 규칙을 논리적인 그룹에 따라서 특정 상황에서만 적용할 수 있도록 하며, &lt;b&gt;Group Sequence&lt;/b&gt;는 여러 검증 그룹을 순차적으로 검증하도록 하는 어노테이션이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 마커 인터페이스 생성&lt;/b&gt;&lt;/h3&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class ValidationGroups {
    public interface LotsRewardValidation {};
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;가장 먼저 검증 그룹을 분류할 마커 인터페이스를 생성해준다. 해당 인터페이스는 말 그대로 '마킹'하는 역할만을 하며, Bean Validation API에 명시해서 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. @GroupSequence 어노테이션을 통해서 검증 그룹의 검증 순서 정의&lt;/b&gt;&lt;/h3&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;@GroupSequence({Default.class, ValidationGroups.LotsRewardValidation.class})
public interface LotsValidationSequence {
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;@GroupSequence 어노테이션은 검증 그룹의 검증 순서를 정의&lt;/b&gt;한다. 즉, 지정된 @GroupSequence 어노테이션은 검증 그룹의 검증 순서를 정의하며, 이를 통해 의존성 있는 검증 규칙을 체계적으로 적용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Default.class란 &lt;b&gt;기본 검증 그룹&lt;/b&gt;을 말하며,  여기서 기본 검증이 완료되었다면 추가로 ValidationGroups.LotsRewardValidation.class라는 검증 그룹이 동작하게 된다. 이렇게 하면 Default 그룹의 검증이 성공한 후에 LotsRewardValidation 그룹의 검증을 수행하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 Default 그룹의 검증 중에 하나라도 실패하면, 이후 그룹의 검증은 수행되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. DTO에 그룹 지정&lt;/b&gt;&lt;/h3&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class ConfigEventRewardRequestDto {

    @NotEmpty(message = &quot;상품 리스트가 비어있습니다.&quot;)
    @Valid
    List&amp;lt;@Valid EventRewardDto&amp;gt; eventRewardList;

    @Valid
    @NotNull(message = &quot;가중치가 비어있습니다.&quot;, groups = ValidationGroups.LotsRewardValidation.class)
    EventWeightDto eventWeight;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Dto의 검증 어노테이션의 'groups' 옵션에 마커 인터페이스를 설정해준다. 여기서 eventRewardList는 Default 검증 그룹에, eventWeight는 LotsRewardValidation 검증 그룹에 속한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. 컨트롤러에서 @Validated 사용&lt;/b&gt;&lt;/h3&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@PutMapping(&quot;/lots/rewardconfig&quot;)
@Operation(summary = &quot;랜덤추첨 이벤트 상품 수정 Api&quot;)
@ApiResponses(value = {
        @ApiResponse(responseCode = &quot;200&quot;, description = &quot;성공적인 응답&quot;),
        @ApiResponse(responseCode = &quot;400&quot;, description = &quot;요청 형식 에러 - 형식이 잘못되었습니다.&quot;),
        @ApiResponse(responseCode = &quot;500&quot;, description = &quot;이벤트를 찾을 수 없습니다.&quot;),
})
public CommonResponse&amp;lt;ConfigEventRewardResponseDto&amp;gt; configLotsEventReward(@RequestBody @Validated(LotsValidationSequence.class) ConfigEventRewardRequestDto dto){
    return new CommonResponse&amp;lt;&amp;gt;(adminService.configLotsEventReward(dto));
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Validated에 그룹 시퀀스를 설정해주어 검증 그룹이 순차적으로 검증되도록 할 수 있다. 즉, ConfigEventRewardRequestDto에 대해서&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. eventRewardList의 @NotEmpty여부를 검증한 후, 리스트를 구성하는 EventRewardDto의 @Min, @NotBlank를 검사&lt;br /&gt;2. eventWeight의 @NotNull여부를 검사하고, EventWeightDto 내부의 @Min과 @NotBlank 여부를 검사&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;의 순서로 검증이 이루어지게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;5. 검증 결과 확인&lt;/b&gt;&lt;/h3&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;359&quot; data-origin-height=&quot;132&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EdoOD/btsJGW7YM11/kQp9uZ5an1saVQXY8QqWc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EdoOD/btsJGW7YM11/kQp9uZ5an1saVQXY8QqWc1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EdoOD/btsJGW7YM11/kQp9uZ5an1saVQXY8QqWc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEdoOD%2FbtsJGW7YM11%2FkQp9uZ5an1saVQXY8QqWc1%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;359&quot; height=&quot;132&quot; data-origin-width=&quot;359&quot; data-origin-height=&quot;132&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1254&quot; data-origin-height=&quot;237&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tu5fC/btsJIThqJvU/JRS7Ytpuv8cE9Qy5Gfz7l0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tu5fC/btsJIThqJvU/JRS7Ytpuv8cE9Qy5Gfz7l0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tu5fC/btsJIThqJvU/JRS7Ytpuv8cE9Qy5Gfz7l0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Ftu5fC%2FbtsJIThqJvU%2FJRS7Ytpuv8cE9Qy5Gfz7l0%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;1254&quot; height=&quot;237&quot; data-origin-width=&quot;1254&quot; data-origin-height=&quot;237&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;eventRewardList과 eventWeight를 모두 null로 해서 보내도, Default 검증 과정에서 오류가 발생했으므로 eventRewardList에 대한 검증만 이루어지고, LotsRewardValidation 그룹에 대한 검증은 이루어지지 않는 것을 확인할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 - &lt;a href=&quot;https://dncjf64.tistory.com/302&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://dncjf64.tistory.com/302&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가 예정 - &lt;a href=&quot;https://medium.com/sjk5766/valid-vs-validated-%EC%A0%95%EB%A6%AC-5665043cd64b&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/sjk5766/valid-vs-validated-%EC%A0%95%EB%A6%AC-5665043cd64b&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1726921786462&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;@Valid vs @Validated 정리&quot; data-og-description=&quot;아래 결론부터 정리하고 내용을 보도록 하겠다.&quot; data-og-host=&quot;medium.com&quot; data-og-source-url=&quot;https://medium.com/sjk5766/valid-vs-validated-%EC%A0%95%EB%A6%AC-5665043cd64b&quot; data-og-url=&quot;https://medium.com/sjk5766/valid-vs-validated-%EC%A0%95%EB%A6%AC-5665043cd64b&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bDhXzn/hyW2TcyAMX/O5ArmxopaFZXElw48CBbV1/img.png?width=490&amp;amp;height=257&amp;amp;face=18_50_156_200,https://scrap.kakaocdn.net/dn/jP7Gc/hyW6Mv3az6/k1ehi4Lk1Ka38QkrNGipH0/img.png?width=660&amp;amp;height=322&amp;amp;face=0_0_660_322,https://scrap.kakaocdn.net/dn/eVKET/hyW2Xst98p/R8P0ihYa32HJRHKXiNBCH1/img.png?width=490&amp;amp;height=257&amp;amp;face=18_50_156_200&quot;&gt;&lt;a href=&quot;https://medium.com/sjk5766/valid-vs-validated-%EC%A0%95%EB%A6%AC-5665043cd64b&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://medium.com/sjk5766/valid-vs-validated-%EC%A0%95%EB%A6%AC-5665043cd64b&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bDhXzn/hyW2TcyAMX/O5ArmxopaFZXElw48CBbV1/img.png?width=490&amp;amp;height=257&amp;amp;face=18_50_156_200,https://scrap.kakaocdn.net/dn/jP7Gc/hyW6Mv3az6/k1ehi4Lk1Ka38QkrNGipH0/img.png?width=660&amp;amp;height=322&amp;amp;face=0_0_660_322,https://scrap.kakaocdn.net/dn/eVKET/hyW2Xst98p/R8P0ihYa32HJRHKXiNBCH1/img.png?width=490&amp;amp;height=257&amp;amp;face=18_50_156_200');&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;@Valid vs @Validated 정리&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;medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>[ Backend ]/Spring</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/201</guid>
      <comments>https://eckrin.tistory.com/201#entry201comment</comments>
      <pubDate>Sat, 21 Sep 2024 16:23:04 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 서블릿 분석 - Spring은 어떻게 multipart/form-data를 처리할까</title>
      <link>https://eckrin.tistory.com/200</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;부제) 필터에서 multipart/form-data 읽기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;갑자기 뜬금없는 주제로 글을 쓰게 됐는데, 최근에 진행했던 프로젝트에서 겪었던 문제를 해결하는 과정에서 알게 된 내용이라 까먹기 전에 정리해두려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;첫 번째 문제 - request body&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1518&quot; data-origin-height=&quot;599&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c9jp3K/btsJkRFwjXg/TdtJ4OPfDbNkXYtCV9t8yk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c9jp3K/btsJkRFwjXg/TdtJ4OPfDbNkXYtCV9t8yk/img.png&quot; data-alt=&quot;참고했던 타 동아리 레퍼런스&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c9jp3K/btsJkRFwjXg/TdtJ4OPfDbNkXYtCV9t8yk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc9jp3K%2FbtsJkRFwjXg%2FTdtJ4OPfDbNkXYtCV9t8yk%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;1518&quot; height=&quot;599&quot; data-origin-width=&quot;1518&quot; data-origin-height=&quot;599&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;참고했던 타 동아리 레퍼런스&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(사실 피할 수 있던 문제였기는 했지만 내가 해보고싶어서 시작해버림)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시작은 &lt;a href=&quot;https://eckrin.tistory.com/184&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Spring AOP&lt;/a&gt;를 사용한 로깅 시스템의 도입이었다. 프로젝트에 타 동아리에서 봤던 Admin페이지에 요청 정보를 로깅하는 시스템을 그대로 도입해보려고 했는데, 나는 요청을 인터셉터하는 위치를 필터로 정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;114&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6TLTx/btsJltYlavr/IWH6p1OtjPSIaCIRl6J7kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6TLTx/btsJltYlavr/IWH6p1OtjPSIaCIRl6J7kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6TLTx/btsJltYlavr/IWH6p1OtjPSIaCIRl6J7kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6TLTx%2FbtsJltYlavr%2FIWH6p1OtjPSIaCIRl6J7kk%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;1240&quot; height=&quot;114&quot; data-origin-width=&quot;1240&quot; data-origin-height=&quot;114&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;이를 위해서 Filter에서 HttpServletRequest를 인터셉트하여 정보를 Mdc에 저장할 계획이었다.&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;1204&quot; data-origin-height=&quot;247&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5WMvA/btsJkTQRnfk/E9QHltJjp8VQwNm2vRuVA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5WMvA/btsJkTQRnfk/E9QHltJjp8VQwNm2vRuVA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5WMvA/btsJkTQRnfk/E9QHltJjp8VQwNm2vRuVA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5WMvA%2FbtsJkTQRnfk%2FE9QHltJjp8VQwNm2vRuVA0%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;1204&quot; height=&quot;247&quot; data-origin-width=&quot;1204&quot; data-origin-height=&quot;247&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;OncePerRequestFilter를 통해서 매 요청이 들어올때마다 Mdc에 request의 관련 정보를 로깅하고, 컨트롤러 메서드에 Advice 범위를 걸어주어 상황에 맞게 로깅해주면 될 것이라고 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Request의 Body를 읽기 위해서는 request의 getInputStream() 함수를 사용해야 한다는 문제가 있었다. 파싱이 귀찮다는 문제는 둘째치고, InputStream 자체에 문제가 있었다.&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;768&quot; data-origin-height=&quot;34&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zst4P/btsJk2z216F/Kvkk0QrgfVReyipKktekk0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zst4P/btsJk2z216F/Kvkk0QrgfVReyipKktekk0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zst4P/btsJk2z216F/Kvkk0QrgfVReyipKktekk0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fzst4P%2FbtsJk2z216F%2FKvkk0QrgfVReyipKktekk0%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;768&quot; height=&quot;34&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;34&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Java의 Inputstream은 '한 방향으로만' 데이터를 읽도록 되어 있다.&lt;/b&gt; 따라서 현재의 경우처럼 중간에서 요청 body를 읽고, 컨트롤러에서도 body를 읽게 하려면 다른 방법이 필요했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 나는 Request를 Wrapping하는 방법을 사용했다. 찾아보니 이러한 목적으로 제공하는 '&lt;b&gt;HttpServletRequestWrapper&lt;/b&gt;'라는 래퍼 클래스가 존재했고, 나는 기존 inputsream을 읽어서 다른 outputstream에 저장한다음, getInputStream()을 해당 outputstream에서 읽도록 하는 방법을 사용했다.&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;1205&quot; data-origin-height=&quot;224&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cmHivG/btsJlubXfXh/PHgzdKkuKpRFhgDn1KpJ1k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cmHivG/btsJlubXfXh/PHgzdKkuKpRFhgDn1KpJ1k/img.png&quot; data-alt=&quot;이런 식으로&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cmHivG/btsJlubXfXh/PHgzdKkuKpRFhgDn1KpJ1k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcmHivG%2FbtsJlubXfXh%2FPHgzdKkuKpRFhgDn1KpJ1k%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;1205&quot; height=&quot;224&quot; data-origin-width=&quot;1205&quot; data-origin-height=&quot;224&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이런 식으로&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;440&quot; data-origin-height=&quot;328&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bm6W0Z/btsJkYLijVm/k00fYmWkrDtJwshusD6rv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bm6W0Z/btsJkYLijVm/k00fYmWkrDtJwshusD6rv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bm6W0Z/btsJkYLijVm/k00fYmWkrDtJwshusD6rv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbm6W0Z%2FbtsJkYLijVm%2Fk00fYmWkrDtJwshusD6rv1%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;440&quot; height=&quot;328&quot; data-origin-width=&quot;440&quot; data-origin-height=&quot;328&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 해주면 필터에서 inputstream을 읽어서 Mdc에 저장해둘 수 있을 뿐만 아니라, Controller에서 body를 읽을 수도 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;s&gt;해치웠나&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;두 번째 문제 - multipart/form-data&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;나는 위 방법으로 모든 문제가 해결되었을 것이라고 생각했다. 서블릿 내부에서도 getInputStream() 메서드를 사용해서 request body를 읽을 것이라고 확신했고, 얼마 전에 순수 자바로만 WAS를 구현했을때 내 코드와 비슷하게 스프링에서도 getInputStream으로 body를 쭉 읽은 다음 delimiter(Content-Type 헤더의 boundary 정보)로 split하여 문자열 or byte 파싱했을 것이라고 예측했기 때문이었다.&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;1690&quot; data-origin-height=&quot;634&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beVeUl/btsJlIHKriK/Uf748nFDADHAd7eMzSesEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beVeUl/btsJlIHKriK/Uf748nFDADHAd7eMzSesEk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beVeUl/btsJlIHKriK/Uf748nFDADHAd7eMzSesEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeVeUl%2FbtsJlIHKriK%2FUf748nFDADHAd7eMzSesEk%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;780&quot; height=&quot;293&quot; data-origin-width=&quot;1690&quot; data-origin-height=&quot;634&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;하지만 시간이 좀 지나서, 이미지 파일과 json dto를 동시에 받아야 할 일이 생기면서 문제가 시작되었다. 나는 예전에 해왔듯이 @RequestPart 어노테이션을 통해서 form-data 형태로 데이터를 읽으려고 시도했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;3614&quot; data-origin-height=&quot;70&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EJJks/btsJkGxpKTw/0sPcOaGtIOjatDBK3atMsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EJJks/btsJkGxpKTw/0sPcOaGtIOjatDBK3atMsk/img.png&quot; data-alt=&quot;'Required part 'dto' is not present'&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EJJks/btsJkGxpKTw/0sPcOaGtIOjatDBK3atMsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEJJks%2FbtsJkGxpKTw%2F0sPcOaGtIOjatDBK3atMsk%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;3614&quot; height=&quot;70&quot; data-origin-width=&quot;3614&quot; data-origin-height=&quot;70&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;'Required part 'dto' is not present'&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;하지만 form-data 정보를 읽을 수 없다는 예외가 발생했다. 사실 나는 처음에 도입한 필터의 문제라고 전혀 생각하지 못했다. Postman에서 dto명을 잘못 넣었던지, form-data가 아닌 form-urlencoded로 넣었든지.. 그러한 유형의 사소한 실수라고만 생각했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 디버깅을 통해 예외가 터지는 위치를 살펴보니 Filter를 통과한 이후, Controller로 들어오기 이전이었고, 이제서야 필터에서 모종의 이유로 인해서 처리가 제대로 되지 않고 있음을 눈치챘다. 그리고 디버깅 툴을 통해서 &lt;b&gt;오버라이딩한 getInputStream이 전혀 호출되지 않고 있음&lt;/b&gt;을 알 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;서블릿 분석하기&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2450&quot; data-origin-height=&quot;3724&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bpgGzY/btsJjTRNuPY/yCFTVVHbpKBQCjo0jOnE50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bpgGzY/btsJjTRNuPY/yCFTVVHbpKBQCjo0jOnE50/img.png&quot; data-alt=&quot;좀 많이 복잡한데 기념으로 남겨두기&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpgGzY/btsJjTRNuPY/yCFTVVHbpKBQCjo0jOnE50/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbpgGzY%2FbtsJjTRNuPY%2FyCFTVVHbpKBQCjo0jOnE50%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;382&quot; height=&quot;581&quot; data-origin-width=&quot;2450&quot; data-origin-height=&quot;3724&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;좀 많이 복잡한데 기념으로 남겨두기&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이 문제를 해결하기 위해서는 결국 서블릿이 multipart/form-data를 어떠한 방식으로 처리하는지를 알아야 했다. 오버라이딩해준 getInputstream 메서드가 왜 동작하지 않는지, 만약 inputStream을 사용하지 않는다면 inputStream을 읽는 다른 함수가 있는건지 등 원인을 확신할 수조차 없었기 때문이다.&lt;/p&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;서블릿에서 multipart/form-data를 어떻게 읽는지를 알아본 결과는 다음과 같았다.&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;1762&quot; data-origin-height=&quot;1028&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bep8ik/btsJlu4fjfy/qDHNoaEwJosP7cSPmdv4Y1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bep8ik/btsJlu4fjfy/qDHNoaEwJosP7cSPmdv4Y1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bep8ik/btsJlu4fjfy/qDHNoaEwJosP7cSPmdv4Y1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbep8ik%2FbtsJlu4fjfy%2FqDHNoaEwJosP7cSPmdv4Y1%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;1762&quot; height=&quot;1028&quot; data-origin-width=&quot;1762&quot; data-origin-height=&quot;1028&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;b&gt;1. DispatcherServlet이 Multipart 데이터를 받을 때, MultipartResolver를 호출하고, 그 구현체를 거쳐 request에 있는 getParts() 메서드를 호출하게 된다.&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;889&quot; data-origin-height=&quot;316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dWlNFO/btsJkbxUtPm/gKnsMGMnGEjJuWwQXocJKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dWlNFO/btsJkbxUtPm/gKnsMGMnGEjJuWwQXocJKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dWlNFO/btsJkbxUtPm/gKnsMGMnGEjJuWwQXocJKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdWlNFO%2FbtsJkbxUtPm%2FgKnsMGMnGEjJuWwQXocJKk%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;889&quot; height=&quot;316&quot; data-origin-width=&quot;889&quot; data-origin-height=&quot;316&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;위와 같이 MultipartResolver 인터페이스의 resolvemultipart를 호출하는데, 디버거로 확인해본 결과 이 해당 인터페이스에는 StandardServletMultipartResolver 클래스가 들어온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;흐름을 따라가다보면 StandeardMultipartHttpServletRequest 클래스의 parseRequest 함수를 호출하게 되고, 여기서는 요청 객체의 'getParts' 메서드를 호출한다.&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;761&quot; data-origin-height=&quot;379&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhqJIG/btsJkZwRCuZ/x6nWX0h5DhZXPmNV1bLdmk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhqJIG/btsJkZwRCuZ/x6nWX0h5DhZXPmNV1bLdmk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhqJIG/btsJkZwRCuZ/x6nWX0h5DhZXPmNV1bLdmk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhqJIG%2FbtsJkZwRCuZ%2Fx6nWX0h5DhZXPmNV1bLdmk%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;761&quot; height=&quot;379&quot; data-origin-width=&quot;761&quot; data-origin-height=&quot;379&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 하지만 필터에서 래핑한 객체(RequestWrapper)는 getInputStream()를 사용하는 메서드만을 오버라이딩했으므로, getParts()는 오버라이딩 되어있지 않다.&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;따라서 상위 클래스인 HttpServletRequestWrapper의 getParts()를 호출하는데, 여기서는 더 상위 클래스인 ServletRequestWrapper가 갖는 request 필드의 getParts() 함수를 호출한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;577&quot; data-origin-height=&quot;90&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eipMif/btsJlaZgQGX/7VF73IQ4DvzEqR6KGFXof0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eipMif/btsJlaZgQGX/7VF73IQ4DvzEqR6KGFXof0/img.png&quot; data-alt=&quot;HttpServletRequestWrapper.getParts()&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eipMif/btsJlaZgQGX/7VF73IQ4DvzEqR6KGFXof0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeipMif%2FbtsJlaZgQGX%2F7VF73IQ4DvzEqR6KGFXof0%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;90&quot; data-origin-width=&quot;577&quot; data-origin-height=&quot;90&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;HttpServletRequestWrapper.getParts()&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ServletRequestWrapperd의 request필드는 ServletRequest 타입으로 되어있는데, 어떤 구현체가 들어오는지 디버거를 돌려보면&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;650&quot; data-origin-height=&quot;90&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/661yt/btsJlxs28lv/JyBKONODahbOd3OuNHFm10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/661yt/btsJlxs28lv/JyBKONODahbOd3OuNHFm10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/661yt/btsJlxs28lv/JyBKONODahbOd3OuNHFm10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F661yt%2FbtsJlxs28lv%2FJyBKONODahbOd3OuNHFm10%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;650&quot; height=&quot;90&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;90&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;이 녀석이다. 주석에는 'wraps a Coyote request object' - coyote 패키지의 Request 객체를 갖는 래퍼 클래스라고 되어 있는데, 정확히 말하면 catalina.connector 클래스의 Request 객체를 갖고, catalina.connector 클래스의 Request 클래스 내의 request 객체가 coyote 패키지의 Request 객체를 갖는 이중 래핑이 되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(코드만 봤을 때, coyote 패키지의 Request 객체는 http header, method와 같은 좀 더 원시적인 데이터를 가지고, connector 클래스의 Request 객체는 이러한 원시 데이터를 통해서 고급화된 정보를 제공하는 것 같았다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어쨋든 중요한 것은 결론적으로 catalina.connector 패키지의 Request 객체에 있는 parseParts()가 호출된다는 것이다.&lt;/p&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: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;&lt;b&gt;3. multipart 데이터를 파일로 임시 저장한 후 읽어들이는데, 임시 저장을 위임하는 FiltItemIteratorImpl 클래스 쪽에 파라미터로 Request객체 자신을 컨텍스트에 감싸서 넘겨주게 된다.&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;506&quot; data-origin-height=&quot;50&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvR5i1/btsJl5JJRha/IH4Zz5Rmef5szsVkPCsRXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvR5i1/btsJl5JJRha/IH4Zz5Rmef5szsVkPCsRXk/img.png&quot; data-alt=&quot;FiltItemIteratorImpl.init()&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvR5i1/btsJl5JJRha/IH4Zz5Rmef5szsVkPCsRXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvR5i1%2FbtsJl5JJRha%2FIH4Zz5Rmef5szsVkPCsRXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;506&quot; height=&quot;50&quot; data-origin-width=&quot;506&quot; data-origin-height=&quot;50&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;FiltItemIteratorImpl.init()&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;ctx가 Request객체를 감싼 ServletRequestContext인데, 결국 다시 말하면 Request클래스의 getInputStream을 호출한다는 것이다. getInputStream() 메서드를 보자&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;748&quot; data-origin-height=&quot;316&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TTnMb/btsJmd1SfJ7/jZIIAOekeaoXVVDpgk7A7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TTnMb/btsJmd1SfJ7/jZIIAOekeaoXVVDpgk7A7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TTnMb/btsJmd1SfJ7/jZIIAOekeaoXVVDpgk7A7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTTnMb%2FbtsJmd1SfJ7%2FjZIIAOekeaoXVVDpgk7A7k%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;748&quot; height=&quot;316&quot; data-origin-width=&quot;748&quot; data-origin-height=&quot;316&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: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;당연하겠지만, 우리가 캐싱해놓은 OutputStream에서 읽는 것이 아니라, 원래 inputStream에서 읽고 있다. 순수 inputstream은 이미 Mdc에 저장해주느라 다 읽어버렸으니 아무것도 읽힐 리가 없고, 따라서 오류가 발생하게 된다.&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;b&gt;4. 이제 getParts()에서 필요한 부분을 오버라이딩해주면 문제가 해결된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;문제 해결을 위해서는 Request 객체의 getParts() 메서드가 호출되면 안된다. 다시 RequestWrapper로 돌아가서, getInputStream 외에 getParts(), getPart()와 같이 getParts()를 사용하는 함수를 모두 오버라이딩하여 request객체의 getParts()가 호출되지 않도록 제어를 가져오면 된다.&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;1025&quot; data-origin-height=&quot;1211&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjKvFW/btsJk9miUe8/D9cuKkMANKcu35SEnUB97k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjKvFW/btsJk9miUe8/D9cuKkMANKcu35SEnUB97k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjKvFW/btsJk9miUe8/D9cuKkMANKcu35SEnUB97k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjKvFW%2FbtsJk9miUe8%2FD9cuKkMANKcu35SEnUB97k%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;1025&quot; height=&quot;1211&quot; data-origin-width=&quot;1025&quot; data-origin-height=&quot;1211&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;파일을 임시로 저장하고, 결과물인 FileItem 컬렉션을 통해 ApplicationPart의 컬렉션을 생성하여 반환하도록 만들어 주었다. FileUpload의 parseRequest() 메서드에서는 RequestWrapper를 감싼 ServletRequestContext를 인자로 넘겨주고 있으므로, inputStream을 호출하더라도 Request객체의 그것이 아니라 RequestWrapper에서 오버라이딩해준 getInputStream()이 호출될 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;HTTP request body에 담겨서 들어온다는 점은 동일하지만,  json과 다르게 multipart/form-data의 경우 &lt;b&gt;임시로 파일로 저장했다가, 다시 읽어들인다는 점&lt;/b&gt;이 상당히 특이했다. 이것은 파일 자체를 업로드할 수 있는 multipart/form-data 특성상, 극단적인 경우 파일의 총합이 메모리보다 커지면 메모리가 터질 수 있는 문제가 있기에 파일로 읽어서 디스크에 저장하는 방식을 채택했을 확률이 높아 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>[ Backend ]/Spring</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/200</guid>
      <comments>https://eckrin.tistory.com/200#entry200comment</comments>
      <pubDate>Fri, 30 Aug 2024 00:09:42 +0900</pubDate>
    </item>
    <item>
      <title>[Java] Collection</title>
      <link>https://eckrin.tistory.com/198</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2335&quot; data-origin-height=&quot;1291&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VxjtG/btsI1FRVj6Y/reBUWkXUzu0KbsgUzc5e4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VxjtG/btsI1FRVj6Y/reBUWkXUzu0KbsgUzc5e4k/img.png&quot; data-alt=&quot;https://levelup.gitconnected.com/java-collections-framework-class-hierarchy-latest-2024-51f9154f1f57&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VxjtG/btsI1FRVj6Y/reBUWkXUzu0KbsgUzc5e4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVxjtG%2FbtsI1FRVj6Y%2FreBUWkXUzu0KbsgUzc5e4k%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;2335&quot; height=&quot;1291&quot; data-origin-width=&quot;2335&quot; data-origin-height=&quot;1291&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://levelup.gitconnected.com/java-collections-framework-class-hierarchy-latest-2024-51f9154f1f57&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;자바에는 데이터의 그룹(자료구조)을 나타내기 위한 &lt;b&gt;Collection 인터페이스&lt;/b&gt;가 존재한다. Collection 인터페이스에는 &lt;b&gt;List, Set, Queue&lt;/b&gt;가 존재하며, 해당 인터페이스들을 구현하는 다양한 구현체들이 존재한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+ Collections라는 클래스도 존재하는데, 여기에는 Collection과 그 하위 클래스들을 조작하기 위한 static 메서드들이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다양한 컬렉션 인터페이스들(+Map)을 살펴보고, 각 인터페이스를 구현하는 구현체의 특징과 자료구조, 시간복잡도 등에 대해서 정리해보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. List&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;274&quot; data-origin-height=&quot;468&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xzaUt/btsI0HpFMkR/1jj9JfX743ShAgkBQY2TL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xzaUt/btsI0HpFMkR/1jj9JfX743ShAgkBQY2TL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xzaUt/btsI0HpFMkR/1jj9JfX743ShAgkBQY2TL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxzaUt%2FbtsI0HpFMkR%2F1jj9JfX743ShAgkBQY2TL1%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;196&quot; height=&quot;335&quot; data-origin-width=&quot;274&quot; data-origin-height=&quot;468&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;List는 &lt;b&gt;순서&lt;/b&gt;를 보장하는 컬렉션 인터페이스이며, &lt;b&gt;중복을 허용&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1-1. ArrayList - [배열 기반의 리스트]&lt;/b&gt;&lt;/h3&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class ArrayList&amp;lt;E&amp;gt; extends AbstractList&amp;lt;E&amp;gt;
        implements List&amp;lt;E&amp;gt;, RandomAccess, Cloneable, java.io.Serializable
{
    transient Object[] elementData; // non-private to simplify nested class access&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;ArrayList는 &lt;b&gt;가변 크기의 배열&lt;/b&gt;로 구성된 리스트이다. 클래스를 보면 Object[] 형태로 데이터를 저장하고 있음을 알 수 있다.&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;504&quot; data-origin-height=&quot;289&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cAUqcP/btsI0qIsjwz/DyhNMUdJhF5kf9iOWNlC60/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cAUqcP/btsI0qIsjwz/DyhNMUdJhF5kf9iOWNlC60/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cAUqcP/btsI0qIsjwz/DyhNMUdJhF5kf9iOWNlC60/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcAUqcP%2FbtsI0qIsjwz%2FDyhNMUdJhF5kf9iOWNlC60%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;504&quot; height=&quot;289&quot; data-origin-width=&quot;504&quot; data-origin-height=&quot;289&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본 배열 capacity는 10이다.&lt;/li&gt;
&lt;li&gt;배열이 다 찬 상태에서 데이터를 삽입하려고 하면, 크기가 1.5배 큰 배열에 복사한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723307242139&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.*;

ArrayList&amp;lt;Integer&amp;gt; list = new ArrayList&amp;lt;Integer&amp;gt;();
list.add(element); //O(1)
list.remove(idx); //O(n)
list.get(idx); //O(1)
list.contains(element); //O(n)
list.size(); //O(1)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;조회: O(1) -&amp;gt; 배열을 이용하기 때문에 인덱스 번호를 이용하여 데이터에 직접 접근 가능하다. 단, 인덱스가 아닌 값으로 조회할 경우는 여전히 O(N)이 든다. (정렬된 자료구조가 아니므로 순차 탐색만 사용 가능하다.)&lt;/li&gt;
&lt;li&gt;삽입: O(1) -&amp;gt; 데이터의 삽입은 일반적으로 O(1)이다. 배열이 다 찼다면 크기의 재조정이 필요할 수 있으나, 크게 시간복잡도에 영향을 끼치지 않았다.&lt;/li&gt;
&lt;li&gt;삭제: O(N) -&amp;gt; 배열의 중간 원소에 접근하여 삭제하기 위해서는 배열 요소의 재조정이 필요하므로 느리다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1723311278635&quot; class=&quot;qml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;Object[] arr = new String[3]; // 실수
arr[0] = 1L; // 컴파일 에러 X

ArrayList&amp;lt;Object&amp;gt; list = new ArrayList&amp;lt;String&amp;gt;(); // 컴파일 에러&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;ArrayList는 &lt;b&gt;조회&lt;/b&gt;에 이점을 가지는 List 자료구조이다. 직접 배열을 선언하여 구현할 수도 있지만, 사용하지 않는 데이터의 참조를 해제해 줌으로써 메모리 관리 측면에서 이득이 있다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;또한 배열을 사용할 때와 다르게 제네릭을 사용하여 컴파일 시점에 에러를 잡아낼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1-2. LinkedList - [노드 기반의 리스트]&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;257&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BxpKC/btsI1grkYsD/Bgcid4NZbFdZxmL7Wq3dTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BxpKC/btsI1grkYsD/Bgcid4NZbFdZxmL7Wq3dTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BxpKC/btsI1grkYsD/Bgcid4NZbFdZxmL7Wq3dTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBxpKC%2FbtsI1grkYsD%2FBgcid4NZbFdZxmL7Wq3dTk%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;566&quot; height=&quot;257&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;257&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;LinkedList는 노드로 이루어진 리스트이다. 흔히 링크드 리스트에는 Singly Linked List와 Doubly Linked List가 존재하는데, 자바에서는 아래와 같이 양쪽에 포인터를 가지는 &lt;b&gt;Doubly Linked List&lt;/b&gt; 형태를 사용하여 역순으로 탐색이 가능하게 하여 조회 성능을 조금이라도 높이고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class LinkedList&amp;lt;E&amp;gt;
    extends AbstractSequentialList&amp;lt;E&amp;gt;
    implements List&amp;lt;E&amp;gt;, Deque&amp;lt;E&amp;gt;, Cloneable, java.io.Serializable
{
    transient int size = 0;

    transient Node&amp;lt;E&amp;gt; first;
    transient Node&amp;lt;E&amp;gt; last;
    
    ...
    
private static class Node&amp;lt;E&amp;gt; {
    E item;
    Node&amp;lt;E&amp;gt; next;
    Node&amp;lt;E&amp;gt; prev;

    Node(Node&amp;lt;E&amp;gt; prev, E element, Node&amp;lt;E&amp;gt; next) {
        this.item = element;
        this.next = next;
        this.prev = prev;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서 링크드리스트 구현체는 List뿐 아니라, 후술할 Stack이나 Queue에서도 사용되기 때문에 이를 위한 다양한 메서드들이 존재한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;초기 크기가 지정되어 있지 않고, 동적으로 노드가 추가/삭제된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723308314903&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.*;

LinkedList&amp;lt;Integer&amp;gt; list = new LinkedList&amp;lt;Integer&amp;gt;();
list.add(element); //O(1)
list.remove(); //O(1)
list.get(index); //O(n)
list.contains(element); //O(n)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;조회: O(N) -&amp;gt; Node의 포인터를 따라 탐색하면서 데이터를 찾아야 하므로, 이동 과정에 O(N)만큼의 시간복잡도가 소요된다.&lt;/li&gt;
&lt;li&gt;삽입: O(1) -&amp;gt; 새로운 Node를 기존 Node의 Pointer로 연결해주기만 하면 된다.&lt;/li&gt;
&lt;li&gt;삭제: O(N) -&amp;gt; 타겟 Node를 제거하고, 앞뒤 노드의 포인터만 조정해주면 되므로 삭제 자체는 O(1)의 시간복잡도를 갖는다. 다만 중간에 있는 데이터의 경우 조회+삭제의 과정을 필요로 하므로 O(N)이라고 하는 것이 옳다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;삽입과 삭제가 ArrayList보다 빠르고, 인덱스를 이용한 조회는 느리므로 &lt;b&gt;데이터의 변경&lt;/b&gt;이 잦은 데이터로 리스트를 구성하고 싶을 때 사용하면 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1-3. SynchronizedList - [락을 사용하는 리스트 + 동시성 제어]&lt;/b&gt;&lt;/h3&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;static class SynchronizedList&amp;lt;E&amp;gt;
    extends SynchronizedCollection&amp;lt;E&amp;gt;
    implements List&amp;lt;E&amp;gt; {
    
    final Collection&amp;lt;E&amp;gt; c;  // Backing Collection
    @SuppressWarnings(&quot;serial&quot;) // Conditionally serializable
    final Object mutex;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1723310267913&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public E get(int index) {
    synchronized (mutex) {return list.get(index);}
}
public void add(int index, E element) {
    synchronized (mutex) {list.add(index, element);}
}&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;Collections 클래스에 구현되어 있는 inner static class로, &lt;b&gt;메서드 자체에 synchronized 락&lt;/b&gt;이 걸려있다. 단순히 메서드에 synchronized 키워드를 걸었기 때문에 모든 읽기/쓰기 동작에 락이 걸린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 후술할 CopyOnWriteLinkedList는 쓰기 작업시 배열을 복제하기 때문에, &lt;b&gt;쓰기 작업이 많은 경우&lt;/b&gt; SynchronizedList를 쓰는 것도 나쁘지 않은 선택일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1-4. CopyOnWriteArrayList - [배열 기반의 리스트 + 동시성 제어]&lt;/b&gt;&lt;/h3&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class CopyOnWriteArrayList&amp;lt;E&amp;gt;
    implements List&amp;lt;E&amp;gt;, RandomAccess, Cloneable, java.io.Serializable {&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SynchronizedList와 비슷하게 동시성 제어를 위한 &lt;b&gt;ArrayList 기반 리스트&lt;/b&gt;인 CopyOnWriteArrayList도 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public E get(int index) {
    return elementAt(getArray(), index);
}

public void add(int index, E element) {
    synchronized (lock) {
        Object[] es = getArray();
        int len = es.length;
        if (index &amp;gt; len || index &amp;lt; 0)
            throw new IndexOutOfBoundsException(outOfBounds(index, len));
        Object[] newElements;
        int numMoved = len - index;
        if (numMoved == 0)
            newElements = Arrays.copyOf(es, len + 1);
        else {
            newElements = new Object[len + 1];
            System.arraycopy(es, 0, newElements, 0, index);
            System.arraycopy(es, index, newElements, index + 1,
                             numMoved);
        }
        newElements[index] = element;
        setArray(newElements);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CopyOnWriteArrayList에서 내부를 변경하는 메서드에서는 &lt;b&gt;synchronized 블록&lt;/b&gt;을 사용하여 &lt;b&gt;복사본&lt;/b&gt;을 만들어서 수행하도록 되어 있다. 대신 get()과 같이 &lt;b&gt;읽기 작업에는 락이 걸리지 않기 때문에 읽기 작업&lt;/b&gt;이 많이 필요한 경우에 좋은 성능을 낼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Map&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;624&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVpodP/btsI01OGabn/DnAG8cXtCnV6c6HrWbZLd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVpodP/btsI01OGabn/DnAG8cXtCnV6c6HrWbZLd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVpodP/btsI01OGabn/DnAG8cXtCnV6c6HrWbZLd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVpodP%2FbtsI01OGabn%2FDnAG8cXtCnV6c6HrWbZLd0%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;428&quot; height=&quot;353&quot; data-origin-width=&quot;624&quot; data-origin-height=&quot;515&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;Map 인터페이스는 &lt;b&gt;Key-Vaue 형태&lt;/b&gt;로 데이터를 저장하는 자료구조이다. Key는 중복을 허용하지 않고(중복된 Key를 넣을 경우 value가 갱신된다), Value의 경우 중복을 허용한다. key와 value 모두 &lt;b&gt;null&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;Collection 인터페이스를 직접 상속받는 인터페이스는 아니지만, 후술할 Set이 Map을 기반으로 구현되는 등 Collection과 밀접한 관계를 가지고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2-1. HashMap - [해시함수를 이용]&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;HashMap은 순서에 상관없이 데이터를 Key-Value 형태로 저장하는 자료구조이며, Key값으로 해시함수를 실행하여 반환된 결과를 통해 저장위치를 결정한다.&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;그렇다고 단순히 해시 함수만으로 value의 저장될 위치를 결정하는 단순한 구조는 아니다. HashMap은 Bucket이라고 불리는 원소들로 구성된 배열을 기반으로 저장되는데, 해시 함수를 이용하여 Key를 해싱하고, 그 값을 인덱스로 하여 버킷에 접근한다.&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;621&quot; data-origin-height=&quot;246&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/HABIV/btsI1oQp8Jz/3KIgTUg89qtEAtemSMPT30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/HABIV/btsI1oQp8Jz/3KIgTUg89qtEAtemSMPT30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/HABIV/btsI1oQp8Jz/3KIgTUg89qtEAtemSMPT30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHABIV%2FbtsI1oQp8Jz%2F3KIgTUg89qtEAtemSMPT30%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;621&quot; height=&quot;246&quot; data-origin-width=&quot;621&quot; data-origin-height=&quot;246&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 &lt;b&gt;버킷 배열의 크기(CAPACITY)&lt;/b&gt;를 관리하기 위해서 동적으로 배열 크기를 조절한다. 이를 위해 &lt;b&gt;부하율(LOAD_FACTOR)&lt;/b&gt;라는 개념을 사용하는데, 이것은 배열의 resize를 위한 상한선을 의미한다.&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;버킷의 기본 크기는 16이다.&lt;/li&gt;
&lt;li&gt;Load Factor가 0.75 이상이 되면, 버킷의 크기를 두 배로 늘린다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;433&quot; data-origin-height=&quot;384&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qXGYT/btsI0AxgKa3/hwD0PGhhX1OmGJyXWFzdsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qXGYT/btsI0AxgKa3/hwD0PGhhX1OmGJyXWFzdsk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qXGYT/btsI0AxgKa3/hwD0PGhhX1OmGJyXWFzdsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqXGYT%2FbtsI0AxgKa3%2FhwD0PGhhX1OmGJyXWFzdsk%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;433&quot; height=&quot;384&quot; data-origin-width=&quot;433&quot; data-origin-height=&quot;384&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;또한 해시 함수를 이용하는 만큼, 다른 키의 해시값이 같은 '해시 충돌'이 발생할 수 있다. 따라서 각 버킷을 연결리스트로 구성하여, 충돌이 발생하면 버킷 연결리스트에 새로운 (key-value)쌍을 추가한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723342354441&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.*;

HashMap&amp;lt;String, String&amp;gt; map = new HashMap&amp;lt;&amp;gt;();
map.put(key, value); //O(1)
map.get(key); //O(1)
map.containsKey(key); //O(1)
map.remove(key); //O(1)
map.size(); //O(1)
map.keySet(); //O(1) - key들로 이루어진 Set을 생성한다.
map.values(); //O(1) - value들로 이루어진 Collection을 생성한다.
map.getEntry(); //O(1) - Map&amp;lt;key, values&amp;gt;로 이루어진 Set을 생성한다.&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;조회/삽입/삭제: O(1) -&amp;gt; 해시함수를 이용해서 데이터를 저장/삽입/삭제할 버킷을 찾기 때문에 O(1)이다.&lt;/li&gt;
&lt;li&gt;keySet(): O(1) -&amp;gt; key들로 이루어진 Set을 반환하는데, 이 Set은 HashMap 객체의 복제본이 아니라, HashMap에 대한 Wrapper에 대한 뷰를 제공한다. 따라서 keySet().clear()를 하면 &lt;a href=&quot;https://stackoverflow.com/questions/1850802/what-is-the-time-complexity-of-java-util-hashmap-class-keyset-method&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;기존 hashMap의 데이터가 모두 삭제&lt;/a&gt;되므로 유의하자&lt;/li&gt;
&lt;li&gt;values(): O(1) -&amp;gt; value들로 이루어진 Collection을 생성해주는 함수인데, 이 또한 keySet()과 같이 복제본이 아닌 뷰를 제공한다.&lt;/li&gt;
&lt;li&gt;getEntry(): O(1) -&amp;gt; Key-Value로 이루어진 Map의 집합(Set&amp;lt;Map&amp;lt;Key, Value&amp;gt;&amp;gt;)을 반환하는데, 이 메서드도 복제본이 아닌 뷰를 제공한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;207&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2SKfD/btsI1uwaJFb/epV88PZTquGvCUJkbicMGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2SKfD/btsI1uwaJFb/epV88PZTquGvCUJkbicMGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2SKfD/btsI1uwaJFb/epV88PZTquGvCUJkbicMGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2SKfD%2FbtsI1uwaJFb%2FepV88PZTquGvCUJkbicMGk%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;450&quot; height=&quot;207&quot; data-origin-width=&quot;450&quot; data-origin-height=&quot;207&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;442&quot; data-origin-height=&quot;46&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cGejjQ/btsIZYTjrYC/8yCUcIrkBOkO4BBVMB35c1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cGejjQ/btsIZYTjrYC/8yCUcIrkBOkO4BBVMB35c1/img.png&quot; data-alt=&quot;원본 HashMap이 변경되는 것을 볼 수 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cGejjQ/btsIZYTjrYC/8yCUcIrkBOkO4BBVMB35c1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcGejjQ%2FbtsIZYTjrYC%2F8yCUcIrkBOkO4BBVMB35c1%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;442&quot; height=&quot;46&quot; data-origin-width=&quot;442&quot; data-origin-height=&quot;46&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;원본 HashMap이 변경되는 것을 볼 수 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2-2. LinkedHashMap - [삽입 순서에 따른 정렬을 보장]&lt;/b&gt;&lt;/h3&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public class LinkedHashMap&amp;lt;K,V&amp;gt;
    extends HashMap&amp;lt;K,V&amp;gt;
    implements Map&amp;lt;K,V&amp;gt;
{&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;앞서 설명한 HashMap은 순서를 보장하지 않는다. 별도의 정렬을 지원하지 않을 뿐 아니라, 로직상으로 들어간 순서대로 정렬되지도 않는다. 이를 해결하려면 LinkedHashMap을 사용하여 데이터의 &lt;b&gt;삽입 순서를 보장&lt;/b&gt;할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/util/LinkedHashMap.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;공식 문서&lt;/a&gt;를 보면&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;This implementation differs from HashMap in that &lt;span style=&quot;color: #ee2323;&quot;&gt;it maintains a doubly-linked list&lt;/span&gt; running through all of its entries.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라고 나와있는 것을 확인할 수 있는데, 내부적으로는&lt;b&gt; Doubly-linked list&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;+ LinkedHashMap 또한 List처럼 Comparator 등을 이용한 자유로운 정렬 기준을 보장하지는 않는 것처럼 보인다(대신 생성자 파라미터로 accessOrder를 지정해주면 접근 순서에 따라서 정렬(LRU)할 수는 있다). 대신 삽입한 순서대로 데이터가 정렬되기에 List&amp;lt;Entry&amp;lt;K, V&amp;gt;&amp;gt;로 변환하여 정렬한 후 Map에 삽입하는 두 번의 과정을 거치면 원하는 순서대로 정렬된 Map을 얻을 수 있다.&lt;/p&gt;
&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;LinkedHashMap을 사용하여 정렬하기&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public static void main(String[] args) {
    Map&amp;lt;String, Integer&amp;gt; linkedMap = new LinkedHashMap&amp;lt;&amp;gt;();
    linkedMap.put(&quot;some type of love&quot;, 5);
    linkedMap.put(&quot;dangerously&quot;, 10);
    linkedMap.put(&quot;up all night&quot;, 2);
    linkedMap.put(&quot;that's not how this works&quot;, 7);

    System.out.println(&quot;Before Sorting: &quot; + linkedMap);

    // LinkedHashMap을 List로 변환
    List&amp;lt;Map.Entry&amp;lt;String, Integer&amp;gt;&amp;gt; list = new ArrayList&amp;lt;&amp;gt;(linkedMap.entrySet());

    // List를 키(key) 기준으로 정렬
    list.sort(Map.Entry.comparingByKey());

    // 정렬된 List를 LinkedHashMap에 다시 삽입
    LinkedHashMap&amp;lt;String, Integer&amp;gt; sortedMap = new LinkedHashMap&amp;lt;&amp;gt;();
    for (Map.Entry&amp;lt;String, Integer&amp;gt; entry : list) {
        sortedMap.put(entry.getKey(), entry.getValue());
    }

    System.out.println(&quot;After Sorting by Key: &quot; + sortedMap);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;44&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mpuPE/btsI6AbN9JC/UtWRScV0LPKKYcpIDLuQM0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mpuPE/btsI6AbN9JC/UtWRScV0LPKKYcpIDLuQM0/img.png&quot; data-alt=&quot;key 기준 map 정렬&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mpuPE/btsI6AbN9JC/UtWRScV0LPKKYcpIDLuQM0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmpuPE%2FbtsI6AbN9JC%2FUtWRScV0LPKKYcpIDLuQM0%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;826&quot; height=&quot;44&quot; data-origin-width=&quot;826&quot; data-origin-height=&quot;44&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;key 기준 map 정렬&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2-3. TreeMap - [데이터의 정렬을 보장]&lt;/b&gt;&lt;/h3&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;actionscript&quot;&gt;&lt;code&gt;public class TreeMap&amp;lt;K,V&amp;gt;
    extends AbstractMap&amp;lt;K,V&amp;gt;
    implements NavigableMap&amp;lt;K,V&amp;gt;, Cloneable, java.io.Serializable
{&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;TreeMap은 LinkedHashMap과 같이 데이터의 순서를 보장할 수 있는 또 다른 Map이다. TreeMap은 삽입 순서가 아니라, &lt;b&gt;key 기준 오름차순 정렬&lt;/b&gt;된다. 정렬 기준을 바꾸기 위해서는 &lt;a href=&quot;https://eckrin.tistory.com/36&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;Comparator 인터페이스&lt;/b&gt;를 구현하여 생성자에 파라미터로 넘기거나, &lt;b&gt;Comparable&lt;/b&gt;을 이용하여 객체의 정렬 기준을 커스텀&lt;/a&gt;할 수 있다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// key 내림차순 정렬 예시
public static void main(String[] args) throws InterruptedException {
    Map&amp;lt;String, Integer&amp;gt; treeMap = new TreeMap&amp;lt;&amp;gt;(new Comparator&amp;lt;String&amp;gt;() {
        @Override
        public int compare(String s1, String s2) {
            return s2.compareTo(s1);
        }
    });
    treeMap.put(&quot;some type of love&quot;, 5);
    treeMap.put(&quot;dangerously&quot;, 10);
    treeMap.put(&quot;up all night&quot;, 2);
    treeMap.put(&quot;that's not how this works&quot;, 7);

    System.out.println(&quot;TreeMap: &quot; + treeMap);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 정렬을 보장하기 때문에 HashMap보다 &lt;b&gt;삽입, 삭제의 시간복잡도가 크다&lt;/b&gt;. 대신 정렬을 이용한 조회의 경우 HashMap보다 성능이 좋을 수 있다.&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;518&quot; data-origin-height=&quot;256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c42sc3/btsI4yZAtHX/9VKXCn6n6e7Ir5Ar0YUgn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c42sc3/btsI4yZAtHX/9VKXCn6n6e7Ir5Ar0YUgn0/img.png&quot; data-alt=&quot;https://brilliant.org/wiki/red-black-tree/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c42sc3/btsI4yZAtHX/9VKXCn6n6e7Ir5Ar0YUgn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc42sc3%2FbtsI4yZAtHX%2F9VKXCn6n6e7Ir5Ar0YUgn0%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;518&quot; height=&quot;256&quot; data-origin-width=&quot;518&quot; data-origin-height=&quot;256&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://brilliant.org/wiki/red-black-tree/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TreeMap은 이진트리의 일종인 Red-Black 트리로 되어 있는데, 이것은 트리가 skewed되지 않고 일정한 depth를 가지도록 보장하는 트리로, 최악의 경우에도 O(log N)의 시간복잡도를 보장한다.&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723560238512&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.*;

TreeMap&amp;lt;String, String&amp;gt; map = new TreeMap&amp;lt;&amp;gt;();
map.put(key, value); //O(logN)
map.get(key); //O(logN)
map.containsKey(key); //O(logN)
map.remove(key); //O(logN)
map.size(); //O(1)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2-4. ConcurrentHashMap [블록 단위 락 + 동시성 제어]&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Map 자료구조에서 동시성을 제어하고 싶을 때 사용할 수 있는 대표적인 자료구조가 &lt;b&gt;ConcurrentHashMap&lt;/b&gt;이다. ConcurrentHashMap은 메서드 내부에서 필요한 부분에 &lt;b&gt;블록 단위 락&lt;/b&gt;을 걸고, 조회 메서드에는 락을 사용하지 않기 때문에 성능이 비교적 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 &lt;b&gt;HashTable&lt;/b&gt;도 동시성 제어를 지원하지만, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;모든 메서드에 synchronized 키워드를 걸어서 동시성을 제어하며,&lt;/span&gt; 읽기 작업에는 락을 사용하지 않기 때문에 ConcurrentHashMap보다 좋지 않은 성능을 보인다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;324&quot; data-origin-height=&quot;50&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/duXNjr/btsI2ft9chB/9kJHmhFQjr1gsChDjTF5D1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/duXNjr/btsI2ft9chB/9kJHmhFQjr1gsChDjTF5D1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/duXNjr/btsI2ft9chB/9kJHmhFQjr1gsChDjTF5D1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FduXNjr%2FbtsI2ft9chB%2F9kJHmhFQjr1gsChDjTF5D1%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;402&quot; height=&quot;62&quot; data-origin-width=&quot;324&quot; data-origin-height=&quot;50&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;363&quot; data-origin-height=&quot;37&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bEsI0r/btsI2Io05u7/XMck5ewi5INpmooeh5OvkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEsI0r/btsI2Io05u7/XMck5ewi5INpmooeh5OvkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEsI0r/btsI2Io05u7/XMck5ewi5INpmooeh5OvkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEsI0r%2FbtsI2Io05u7%2FXMck5ewi5INpmooeh5OvkK%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;412&quot; height=&quot;42&quot; data-origin-width=&quot;363&quot; data-origin-height=&quot;37&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;위 함수는 HashTable의 데이터 삽입을 맡는 put과 get 메서드이다. &lt;b&gt;메서드 레벨에서 synchronized 키워드&lt;/b&gt;를 통해 락을 걸고 있으며, get 메서드에도 synchronized 키워드가 적용되어 있음을 확인할 수 있다. 이러한 불필요한 설계들로 인해 현재 HashTable 클래스는 사용을 권장하지 않고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 아래는 ConcurrentHashMap에서 데이터의 삽입을 관리하는 putVal 메서드이다. &lt;b&gt;동시성 제어가 필요한 부분은 중간에 있는 synchronized 블록을 통해서 처리&lt;/b&gt;하고 있는 모습을 볼 수 있다. 또한 get 메서드에는 별도의 락이 적용되어 있지 않다.&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;300&quot; data-origin-height=&quot;32&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/53YHs/btsI2u5HwDw/CL9TnHnKE6AvKl20kAgdAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/53YHs/btsI2u5HwDw/CL9TnHnKE6AvKl20kAgdAk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/53YHs/btsI2u5HwDw/CL9TnHnKE6AvKl20kAgdAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F53YHs%2FbtsI2u5HwDw%2FCL9TnHnKE6AvKl20kAgdAk%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;300&quot; height=&quot;32&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;32&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1723561859385&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;final V putVal(K key, V value, boolean onlyIfAbsent) {
    ...
            synchronized (f) {
                ...
            }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. Set&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3JwtR/btsI3Z5EJLX/U7NHJYSbEJhteJvzxP3nck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3JwtR/btsI3Z5EJLX/U7NHJYSbEJhteJvzxP3nck/img.png&quot; data-origin-width=&quot;356&quot; data-origin-height=&quot;524&quot; data-is-animation=&quot;false&quot; width=&quot;288&quot; height=&quot;424&quot; style=&quot;width: 35.509%; margin-right: 10px;&quot; data-widthpercent=&quot;35.93&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3JwtR/btsI3Z5EJLX/U7NHJYSbEJhteJvzxP3nck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3JwtR%2FbtsI3Z5EJLX%2FU7NHJYSbEJhteJvzxP3nck%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;356&quot; height=&quot;524&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVpodP/btsI01OGabn/DnAG8cXtCnV6c6HrWbZLd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVpodP/btsI01OGabn/DnAG8cXtCnV6c6HrWbZLd0/img.png&quot; width=&quot;428&quot; height=&quot;353&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;515&quot; data-origin-width=&quot;624&quot; style=&quot;width: 63.3282%;&quot; data-widthpercent=&quot;64.07&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVpodP/btsI01OGabn/DnAG8cXtCnV6c6HrWbZLd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVpodP%2FbtsI01OGabn%2FDnAG8cXtCnV6c6HrWbZLd0%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;624&quot; height=&quot;515&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Set은&lt;b&gt; null값을 허용&lt;/b&gt;하지만, 데이터의 &lt;b&gt;중복은 허용하지 않는&lt;/b&gt; 자료구조이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 사진처럼 Set은 Map과 유사한 클래스 구조를 가지고 있는데, 이는 실제로 Set 내부에서 Map을 사용하여 구현하고 있기 때문이기도 하다. HashSet을 예로 들어서 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class HashSet&amp;lt;E&amp;gt; extends AbstractSet&amp;lt;E&amp;gt; implements Set&amp;lt;E&amp;gt;, Cloneable, Serializable {
    private transient HashMap&amp;lt;E, Object&amp;gt; map;
    private static final Object PRESENT = new Object();&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public boolean add(E e) {
    return this.map.put(e, PRESENT) == null;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;HashSet의 add 메서드인데, 내부에 존재하는 HashMap에 값(e)을 넣고 있음을 알 수 있다. HashMap의 value에 저장되는 값은 사실상 의미를 갖지는 않으며, 공간을 채우기 위해 Object타입 final 더미 객체를 하나 만들어서 넣어주는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 HashSet을 비롯한 대부분의 Set 구현 클래스들은 유사한 Map 구현 클래스를 이용하고 있으며, 따라서 사용하는 Map 구현체의 특징을 그대로 가진다.&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. Queue&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;354&quot; data-origin-height=&quot;734&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DvHs1/btsI5gZFYrr/GqjAG3UnLmfles6gO2DK0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DvHs1/btsI5gZFYrr/GqjAG3UnLmfles6gO2DK0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DvHs1/btsI5gZFYrr/GqjAG3UnLmfles6gO2DK0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDvHs1%2FbtsI5gZFYrr%2FGqjAG3UnLmfles6gO2DK0k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;246&quot; height=&quot;510&quot; data-origin-width=&quot;354&quot; data-origin-height=&quot;734&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;마지막으로 큐 자료구조를 사용하기 위한 Queue 인터페이스이다. Stack과 다르게 Queue는 별도의 전용 구체 클래스를 제공하지는 않는데, 대신 상황에 맞게 우선순위 정렬이 필요한 경우에는 &lt;b&gt;PriorityQueue&lt;/b&gt;를, 단순 FIFO 큐가 필요할 경우에는 &lt;b&gt;LinkedList&lt;/b&gt;나 &lt;b&gt;ArrayQueue&lt;/b&gt;를 선택할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4-1. LinkedList [연결 리스트로 큐 사용]&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;앞서 설명한 LinkedList를 Queue의 구현체로 사용할 수 있다. 앞서 설명했으므로 설명을 반복하지는 않겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4-2. ArrayDeque [원형 배열 기반의 Deque 구현체]&lt;/b&gt;&lt;/h3&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;public class ArrayDeque&amp;lt;E&amp;gt; extends AbstractCollection&amp;lt;E&amp;gt; implements Deque&amp;lt;E&amp;gt;, Cloneable, Serializable {
    transient Object[] elements;
    transient int head;
    transient int tail;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그런데 자바는 ArrayList 대신, 큐(덱)의 사용을 위한 ArrayDeque 이라는 덱 구현체를 별도로 제공하고 있다. 이는 ArrayList의 삽입/삭제의 비효율성을 극복하고, &lt;b&gt;원형 배열&lt;/b&gt;을 사용하여 더 나은 성능을 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4-3. PriorityQueue [우선순위 큐]&lt;/b&gt;&lt;/h3&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class PriorityQueue&amp;lt;E&amp;gt; extends AbstractQueue&amp;lt;E&amp;gt; implements Serializable {
    private static final int DEFAULT_INITIAL_CAPACITY = 11;
    transient Object[] queue;
    private final Comparator&amp;lt;? super E&amp;gt; comparator;
    
    public PriorityQueue(int initialCapacity, Comparator&amp;lt;? super E&amp;gt; comparator) {
        if (initialCapacity &amp;lt; 1) {
            throw new IllegalArgumentException();
        } else {
            this.queue = new Object[initialCapacity];
            this.comparator = comparator;
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;우선순위 큐는 &lt;b&gt;배열 기반의 힙 자료구조&lt;/b&gt;를 사용하여 구현된다. 기본적으로는 오름차순 정렬(최소 힙)이며, &lt;a href=&quot;https://eckrin.tistory.com/36&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Comparator 인터페이스의 compareTo()를 구현하여 매개변수로 넘기거나, Comparable을 사용하여 객체의 정렬 기준을 조작함&lt;/a&gt;으로써 우선순위를 정의할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;사용&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1723733829406&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.*;

PriorityQueue&amp;lt;Integer&amp;gt; queue = new PriorityQueue&amp;lt;&amp;gt;(); //오름차순
PriorityQueue&amp;lt;Integer&amp;gt; queue = new PriorityQueue&amp;lt;&amp;gt;(Collections.reverseOrder()); //내림차순
queue.add(n); //O(logn) - 힙 재배열 필요
queue.peek(); //O(1)
queue.poll(); //O(logn) - 힙 재배열 필요
queue.size(); //O(1)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;PriorityQueue는 완전 이진 트리 형태를 갖는 힙 기반으로 동작하기 때문에, O(logN)의 비선형 시간복잡도를 가진다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4-4. BlockingQueue [큐 + 동시성 제어]&lt;/b&gt;&lt;/h3&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public interface BlockingQueue&amp;lt;E&amp;gt; extends Queue&amp;lt;E&amp;gt; {
    boolean offer(E var1);
    void put(E var1) throws InterruptedException;
    E take() throws InterruptedException;
    E poll(long var1, TimeUnit var3) throws InterruptedException;
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;b&gt;BlockingQueue&lt;/b&gt;는 멀티스레드 환경에서도 동시성 문제가 발생하지 않도록 하는 큐 인터페이스의 일종으로, Producer-Consumer 패턴에서 유용하게 사용될 수 있다. 구현체로는 &lt;b&gt;ArrayBlockingQueue&lt;/b&gt;, &lt;b&gt;LinkedBlockingQueue&lt;/b&gt; 등이 존재하는데, 이름에서 알 수 있듯이 각각 배열 기반의 고정 크기 큐, 연결 리스트 기반 큐라는 특징을 갖는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;cs&quot;&gt;&lt;code&gt;public E poll() {
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
        return (count == 0) ? null : dequeue();
    } finally {
        lock.unlock();
    }
}

public E take() throws InterruptedException {
    final ReentrantLock lock = this.lock;
    lock.lockInterruptibly();
    try {
        while (count == 0)
            notEmpty.await();
        return dequeue();
    } finally {
        lock.unlock();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ArrayBlockingQueue의 poll, take 메서드를 보면 &lt;b&gt;ReentrantLock&lt;/b&gt;이라는 락을 사용하는 것을 볼 수 있다. 이것은 synchronized에 기능이 추가된 락이라고 할 수 있는데, 이것을 통해서 동시성을 제어하는 것으로 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;테스트&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public static void main(String[] args) {
    // 크기가 5인 ArrayBlockingQueue 생성
    BlockingQueue&amp;lt;Integer&amp;gt; queue = new ArrayBlockingQueue&amp;lt;&amp;gt;(5);

    // 생산자 스레드
    Thread producer = new Thread(() -&amp;gt; {
        try {
            for (int i=0; i&amp;lt;10; i++) {
                System.out.println(&quot;[Produce]: &quot; + i);
                queue.put(i);  // 큐가 가득 차면 대기
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    });

    // 소비자 스레드
    Thread consumer = new Thread(() -&amp;gt; {
        try {
            for (int i=0; i&amp;lt;10; i++) {
                Integer value = queue.take();  // 큐가 비어 있으면 대기
                System.out.println(&quot;[Consumed]: &quot; + value);
            }
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    });

    producer.start();
    consumer.start();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ArrayBlockingQueue를 사용하여 간단하게 Producer-Consumer 패턴을 구현한 예시이다. 두 개의 스레드를 생성하고, 하나의 스레드에서는 queue.put()을 실행하고, 다른 하나의 스레드에서는 queue.take()를 실행한다. ArrayBlockingQueue이기 때문에 &lt;b&gt;큐에 size의 제한&lt;/b&gt;이 있으므로, 큐가 가득 찬 상태에서 queue.put()을 통해서 데이터를 넣으려고 시도할 경우 더 이상 데이터를 추가하지 못하고 대기하게 된다.&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;252&quot; data-origin-height=&quot;886&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkl8P4/btsI5ytCKOJ/FmqUtwvfm0NlZKXlSt3Am0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkl8P4/btsI5ytCKOJ/FmqUtwvfm0NlZKXlSt3Am0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkl8P4/btsI5ytCKOJ/FmqUtwvfm0NlZKXlSt3Am0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbkl8P4%2FbtsI5ytCKOJ%2FFmqUtwvfm0NlZKXlSt3Am0%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;109&quot; height=&quot;383&quot; data-origin-width=&quot;252&quot; data-origin-height=&quot;886&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;실행 결과이다.&amp;nbsp;Producer가 0~6까지 7개가 들어간 것 처럼 보일 수 있지만, 출력 시점의 차이일 뿐 큐에 들어가 있는 데이터의 수는 5를 넘지 않음을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 put() 시점에 queue.size()를 찍어보아도 알 수 있고, ArrayBlockingQueue 대신 capacity 제한이 없는 &lt;b&gt;LinkedBlockingQueue&lt;/b&gt;를 사용해보면 데이터 10개로는 Produce와 Consumer의 출력이 겹치지 않는 것을 확인할 수 있다. Array먼저 시작된 Producer 스레드가 시작되고 나서 Consumer 스레드가 시작되는 텀 사이에 10개의 put()메서드는 실행을 마칠 정도로 빠른 작업이지만, ArrayBlockingQueue의 capacity로 인해서 put() 작업이 블록된 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실습 github: &lt;a href=&quot;https://github.com/eckrin/various-tests&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/eckrin/various-tests&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>[ Languages ]/Java</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/198</guid>
      <comments>https://eckrin.tistory.com/198#entry198comment</comments>
      <pubDate>Sat, 10 Aug 2024 19:31:17 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 자바 비동기와 스프링 @Async</title>
      <link>https://eckrin.tistory.com/196</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;자바의 비동기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;자바에서 비동기 처리를 위해서는 쓰레드를 생성한 후 작업을 할당하는 방식을 주로 사용한다. 첫번째로 &lt;b&gt;Thread 클래스를 상속받고, 상속받은 클래스에서 run()메서드를 오버라이딩&lt;/b&gt;해주는 방법이 있고, 두번째로는 &lt;b&gt;Runnable 인터페이스를 구현하는 클래스를 정의하고 해당 클래스를 Thread의 생성자 파라미터로 전달&lt;/b&gt;하는 방법이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;방법 1 - Thread 상속하기&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1730790253833&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;static class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println(&quot;Thread: &quot; + Thread.currentThread().getName());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1730790693222&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;void threadStart() {
    Thread thread = new MyThread();
    thread.start();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 Thread 클래스를 상속받는 클래스를 만들고, run 메서드를 오버라이딩해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;방법 2 - Runnable 인터페이스 이용하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Runnable 인터페이스를 구현하는 클래스를 정의하고, 해당 클래스를 Thread 클래스의 생성자 파라미터로 넘겨주었다.&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;659&quot; data-origin-height=&quot;192&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcSvku/btsKyBmMBdn/K7off1rjchyHtpBUufXBQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcSvku/btsKyBmMBdn/K7off1rjchyHtpBUufXBQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcSvku/btsKyBmMBdn/K7off1rjchyHtpBUufXBQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcSvku%2FbtsKyBmMBdn%2FK7off1rjchyHtpBUufXBQK%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;659&quot; height=&quot;192&quot; data-origin-width=&quot;659&quot; data-origin-height=&quot;192&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Runnable 인터페이스는 위와 같이 생겼는데, 1개의 추상 메서드만을 가지는 함수형 인터페이스이므로 아래와 같이 람다식을 이용할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1730790469035&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;void main() {
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            System.out.println(&quot;Thread: &quot; + Thread.currentThread().getName());
        }
    };

    Thread thread = new Thread(runnable);
    thread.start();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이러한 방법들은 각 쓰레드의 작업 결과를 반환받을 수는 없다는 제약이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;441&quot; data-origin-height=&quot;171&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bbR4is/btsKwlfdhLI/nRUyPsroxdrXNFYr2BKKDK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bbR4is/btsKwlfdhLI/nRUyPsroxdrXNFYr2BKKDK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bbR4is/btsKwlfdhLI/nRUyPsroxdrXNFYr2BKKDK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbbR4is%2FbtsKwlfdhLI%2FnRUyPsroxdrXNFYr2BKKDK%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;508&quot; height=&quot;197&quot; data-origin-width=&quot;441&quot; data-origin-height=&quot;171&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 Java는 5버전부터(Java 1.5) Callable이라는 인터페이스를 통해 작업 결과를 반환할 수 있게 만들었다. 이는 &lt;b&gt;Future&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-filename=&quot;blob&quot; data-origin-width=&quot;1209&quot; data-origin-height=&quot;609&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dlFHUL/btsKwMC9tl6/gxV7QSGy6ovotMK5MrbO7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dlFHUL/btsKwMC9tl6/gxV7QSGy6ovotMK5MrbO7k/img.png&quot; data-alt=&quot;https://www.youtube.com/watch?v=AzGhzqfRhig&amp;amp;amp;ab_channel=%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dlFHUL/btsKwMC9tl6/gxV7QSGy6ovotMK5MrbO7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdlFHUL%2FbtsKwMC9tl6%2FgxV7QSGy6ovotMK5MrbO7k%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;1209&quot; height=&quot;609&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1209&quot; data-origin-height=&quot;609&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.youtube.com/watch?v=AzGhzqfRhig&amp;amp;ab_channel=%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Future에도 여전히 문제가 존재했다. 단순히 Future객체를 사용할 경우 메인 스레드는 생성한 스레드의 결과를 Future로 받기 위해서 &lt;b&gt;블로킹 상태로 대기&lt;/b&gt;하게 되는 것이다. 이를 해결하기 위해서 Java 8에 서는 CompletableFuture가 도입되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;CompletableFuture&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;34&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bOqORG/btsKysp6Rsw/CncGYKHaXD9oFjaKfwtYE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bOqORG/btsKysp6Rsw/CncGYKHaXD9oFjaKfwtYE0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bOqORG/btsKysp6Rsw/CncGYKHaXD9oFjaKfwtYE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbOqORG%2FbtsKysp6Rsw%2FCncGYKHaXD9oFjaKfwtYE0%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;608&quot; height=&quot;34&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;34&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;CompletableFuture는 Future 인터페이스를 구현하면서 CompletableStage 인터페이스를 구현한다. 이를 활용하여 다른 작업들과 순차적으로 실행하고, 콜백 메서드를 활용할 수 있다. (구체적인 사용법은 구글링하면 많이 나옴)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 무엇보다도 CompletableFuture가 갖는 강점은 Future.get() 호출시 &lt;b&gt;작업이 완료될때까지 블로킹되는 문제를 해결&lt;/b&gt;했다는 것이다. 비동기 메서드를 활용하여 결과를 기다리지 않고 다른 작업을 계속 수행할 수 있으며, 별도의 스레드 풀을 활용하여 결과가 나올 때까지 메인 스레드가 블로킹되지 않도록 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;스프링의 비동기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래의 코드를 보자.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;...
// execute하기 전에 다른 스레드가 다른 connection을 주입하는 것을 방지하기 위해 동기화
private static synchronized void setConnAndExecute(ExecutorService threadPool, Socket connection, RequestHandler handler) {
    logger.debug(&quot;connection = &quot; + connection);
    handler.setConnection(connection);

    Future&amp;lt;?&amp;gt; future = threadPool.submit(handler); // 쓰레드를 할당하고 비동기적으로 작업 배치
    try{
        future.get(); // Future.get()은 블로킹 방식으로 동작
    } catch (ExecutionException | InterruptedException e) {
        logger.error(e.getMessage(), e);
    }
}
...&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;360&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rtk3j/btsInMlce1S/m28A4qYJdk95UkXa0j9Tnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rtk3j/btsInMlce1S/m28A4qYJdk95UkXa0j9Tnk/img.png&quot; data-alt=&quot;ExecutorService의 submit()은 쓰레드를 할당하고 비동기적으로 Runnable task를 처리한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rtk3j/btsInMlce1S/m28A4qYJdk95UkXa0j9Tnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Frtk3j%2FbtsInMlce1S%2Fm28A4qYJdk95UkXa0j9Tnk%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;644&quot; height=&quot;181&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;360&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;ExecutorService의 submit()은 쓰레드를 할당하고 비동기적으로 Runnable task를 처리한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ExecutorService와 Future를 사용하여 비동기 요청을 보내고, 블로킹 방식으로 대기한 후 결과값을 받아서 프로세스를 처리하고 있다.&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;스프링에서 요청을 비동기로 처리하기 위해서는 @Async 어노테이션을 가장 흔하게 사용한다. 그렇다면 이 @Async 어노테이션은 어떠한 방식으로 동작하고, 순수 자바 코드로 작성한 비동기 요청 방식과 어떠한 차이가 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Executor&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1356&quot; data-origin-height=&quot;212&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QVsbB/btsInnstYMv/oGVoRZ648gqikprVsz1KzK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QVsbB/btsInnstYMv/oGVoRZ648gqikprVsz1KzK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QVsbB/btsInnstYMv/oGVoRZ648gqikprVsz1KzK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQVsbB%2FbtsInnstYMv%2FoGVoRZ648gqikprVsz1KzK%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;1356&quot; height=&quot;212&quot; data-origin-width=&quot;1356&quot; data-origin-height=&quot;212&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;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;SimpleAsyncTaskExecutor&lt;/b&gt;를 사용하는데, 이 어노테이션은 작업마다 생성/소멸되는 새로운 스레드를 할당하기 때문에 스레드를 관리하기 위한 오버헤드가 존재한다. 따라서 사용자가 직접 다른 Executor를 사용하여 성능을 향상시킬 수 있는데, 대표적인 것이 ExecutorService와 유사하게 미리 생성된 스레드 풀을 통해서 스레드를 관리하는 &lt;b&gt;ThreadPoolTaskExecutor&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;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VD1As/btsIn3UwHPA/u9PjAenXMIziV150X78UPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VD1As/btsIn3UwHPA/u9PjAenXMIziV150X78UPK/img.png&quot; style=&quot;width: 42.0918%; margin-right: 10px;&quot; width=&quot;384&quot; height=&quot;375&quot; data-widthpercent=&quot;42.59&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;1024&quot; data-origin-width=&quot;1048&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VD1As/btsIn3UwHPA/u9PjAenXMIziV150X78UPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVD1As%2FbtsIn3UwHPA%2Fu9PjAenXMIziV150X78UPK%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;1048&quot; height=&quot;1024&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DQwvk/btsIogMSZHw/k3NKiKJoVpjLyyksanrdpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DQwvk/btsIogMSZHw/k3NKiKJoVpjLyyksanrdpk/img.png&quot; style=&quot;width: 56.7454%;&quot; width=&quot;418&quot; height=&quot;303&quot; data-filename=&quot;blob&quot; data-widthpercent=&quot;57.41&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;740&quot; data-origin-width=&quot;1021&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DQwvk/btsIogMSZHw/k3NKiKJoVpjLyyksanrdpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDQwvk%2FbtsIogMSZHw%2Fk3NKiKJoVpjLyyksanrdpk%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;1021&quot; height=&quot;740&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;ThreadPoolTaskExecutor / SimpleAsyncTaskExecutor&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SimpleAsyncTaskExecutor와 ThreadPoolTaskExecutor는 모두 자바의 java.util.concurrent.Executor 인터페이스를 구현하는 클래스이다. 아까 사용한 ExecutorService도 이 Executor 클래스를 구현하고 있으며, 쓰레드 풀의 크기, 최대 풀 크기, 큐 용량과 같은 다양한 정보들을 설정할 수 있다. 따라서 이러한 설정을 통해서 애플리케이션의 상황에 맞는 적절한 스레드 풀을 구성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링에서는 @Configuration 설정 클래스를 통해서 쓰레드 풀을 커스텀할 수 있다. 스프링에서 ThreadPoolTaskExecutor 빈을 정의하고, 아래와 같이 필요한 설정을 추가해줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;angelscript&quot; style=&quot;background-color: #f8f9fa; color: #353535; text-align: left;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;    @Configuration
    public class AsyncConfig {
        @Bean
        public Executor taskExecutor() {
            ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
            executor.setCorePoolSize(10); // 풀 기본 크기 (초기 사이즈)
            executor.setMaxPoolSize(20); // 풀 최대 크기
            executor.setQueueCapacity(500); // 작업요청 대기 큐의 사이즈 (큐가 가득 차면 스레드 생성)
            executor.initialize(); // 스레드풀 생성
            return executor;
        }
    }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; 초기에는 setCorePoolSize()에 설정해놓은 값만큼 스레드 풀 크기가 설정된다. 이후에는 기존 스레드풀을 넘는 요청이 들어올 경우 setMaxPoolSize()를 통해서 설정한 값만큼 스레드 풀의 크기가 증가하는 것이&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;아니라&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;, 초기 스레드풀보다 많은 작업 요청이 들어오면 setQueueCapacity()에 설정해둔 크기의 대기열에 요청을 넣고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;대기열을 초과할 경우 max size만큼 점진적으로 스레드를 생성&lt;/b&gt;한다. 다만 setMaxPoolSize는 점진적으로 생성되는 최대 스레드 수를 제한하는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;@Async&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;240&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cORWvm/btsIoMLhuql/y7dR3xrLDG37TB5gd0SPh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cORWvm/btsIoMLhuql/y7dR3xrLDG37TB5gd0SPh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cORWvm/btsIoMLhuql/y7dR3xrLDG37TB5gd0SPh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcORWvm%2FbtsIoMLhuql%2Fy7dR3xrLDG37TB5gd0SPh0%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;413&quot; height=&quot;130&quot; data-origin-width=&quot;762&quot; data-origin-height=&quot;240&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;@Async 어노테이션은 스프링 프레임워크에서 비동기 처리를 위해서 제공하는 어노테이션이다. 이 어노테이션은 메서드와 타입에 붙일 수 있는데, 주로 메서드 레벨에 선언되어 스프링이 해당 메서드를 비동기적으로 처리하도록 한다.&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;그렇다면 스프링은 어떻게 어노테이션을 통해서 요청을 비동기로 처리할까? 원리는 앞서 ExecutorService를 사용한 자바 코드와 별반 다르지 않다. 하지만 스프링은 같이 사용되는 @EnableAsync 어노테이션을 통해서 해당 메서드의 &lt;b&gt;&lt;a href=&quot;https://eckrin.tistory.com/184&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;프록시 객체&lt;/a&gt;&lt;/b&gt;를 생성하고, 그것을 통해 실제 메서드의 호출을 비동기적으로 처리한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;112&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsM8I5/btsInNLb2wn/uu73gXrQT1li7v7hZrnvOk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsM8I5/btsInNLb2wn/uu73gXrQT1li7v7hZrnvOk/img.png&quot; data-alt=&quot;프록시 패턴(https://eckrin.tistory.com/164)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsM8I5/btsInNLb2wn/uu73gXrQT1li7v7hZrnvOk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsM8I5%2FbtsInNLb2wn%2Fuu73gXrQT1li7v7hZrnvOk%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;588&quot; height=&quot;91&quot; data-origin-width=&quot;724&quot; data-origin-height=&quot;112&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;프록시 패턴(https://eckrin.tistory.com/164)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 말해서, &lt;b&gt;@Async 어노테이션의 처리는 스프링 AOP방식으로 동작&lt;/b&gt;한다고 할 수 있다. 프로그래머가 @Async 어노테이션을 명시해주기만 한다면 메서드가 호출될 때 요청을 가로채서 프록시 패턴을 통해서 비동기 요청 처리 로직을 추가해 주는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;내부 호출&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;비동기 로직을 구현할 때 가장 쉽게 할 수 있는 실수는, @Async가 붙은 비동기 메서드를 동일한 클래스에서 호출하는 것이다. 예전에 &lt;a href=&quot;https://eckrin.tistory.com/184&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;스프링 프록시에 대해서 정리한 글&lt;/a&gt;에서, 내부 호출에 관해서 이야기한 적이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1723466180547&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Service
public class MyService {

    @Async
    public void asyncMethod() {
        System.out.println(&quot;Async method execution&quot;);
    }

    public void syncMethod() {
        // 동일 클래스 내에서 asyncMethod 호출
        // 해당 메서드는 비동기로 동작하지 않는다.
        asyncMethod();
    }
}&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;스프링 AOP는 &lt;b&gt;프록시 패턴이 적용된 클래스를 빈으로 등록&lt;/b&gt;하기 때문에 스프링 빈으로 등록된 클래스를 호출하면 프록시가 적용된 클래스가 호출된다. 하지만 동일한 클래스에서 메서드를 호출하게 되면, 스프링 빈의 프록시가 적용된 메서드가 아니라 'this' 객체를 통해서 메서드가 호출된다. 이는 프록시가 적용되지 않은 순수한 메서드이므로 비동기 처리가 이루어지지 않게 되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;트랜잭션 관리&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;a href=&quot;https://eckrin.tistory.com/176&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;예전에 테스트&lt;/a&gt;해본 적이 있는데, 트랜잭션이 적용된 func1()에서 비동기로 func2()를 호출했을 때, func2()에서 exception이 터져도 별개의 스레드에서 실행되게 되므로 func1()의 트랜잭션 롤백은 발생하지 않는다. (스프링 트랜잭션은 스레드간 전파가 이루어지지 않는다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비동기로 실행되던 메서드에서 예외를 던지자, ControllerAdvice를 통해서 설정해 놓은 ExceptionHandler가 비동기 호출된 함수에서도 발생한 예외를 처리하지 못했다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1658&quot; data-origin-height=&quot;410&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cy1AgL/btsI1SRU83N/qfPuDkDOsIhEJpeJMDSXQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cy1AgL/btsI1SRU83N/qfPuDkDOsIhEJpeJMDSXQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cy1AgL/btsI1SRU83N/qfPuDkDOsIhEJpeJMDSXQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcy1AgL%2FbtsI1SRU83N%2FqfPuDkDOsIhEJpeJMDSXQ0%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;1658&quot; height=&quot;410&quot; data-origin-width=&quot;1658&quot; data-origin-height=&quot;410&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2494&quot; data-origin-height=&quot;188&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWrxFS/btsI1dhTznS/Pt6xqpuH2Cl7eXhHTEopJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWrxFS/btsI1dhTznS/Pt6xqpuH2Cl7eXhHTEopJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWrxFS/btsI1dhTznS/Pt6xqpuH2Cl7eXhHTEopJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWrxFS%2FbtsI1dhTznS%2FPt6xqpuH2Cl7eXhHTEopJK%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;992&quot; height=&quot;75&quot; data-origin-width=&quot;2494&quot; data-origin-height=&quot;188&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: #333333; text-align: start;&quot;&gt;만약 결과를 CompletableFuture를 통해서 기다리고 있었다면, 원래의 Exception 대신&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;CompletionException이 발생해서 func1()쪽에 비동기 상황에서 예외가 발생했다는 사실만 전달하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이러한 비동기 요청은 사용자의 요청을 처리하는 동안 긴 시간이 소요되는 작업, 다시 말해 동기 방식으로 처리할 경우 main application의 리소스를 많이 소모시키는 작업들을 처리하는데 적합하다. 하지만 고려해야 할 점들이 생각보다 많으니, 이러한 점들을 모두 고려한 후 사용하는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>[ Backend ]/Spring</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/196</guid>
      <comments>https://eckrin.tistory.com/196#entry196comment</comments>
      <pubDate>Fri, 5 Jul 2024 10:31:37 +0900</pubDate>
    </item>
    <item>
      <title>[OS] 멀티쓰레드와 멀티프로세스, 멀티코어</title>
      <link>https://eckrin.tistory.com/195</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다들 알고 있을 기본적인 OS 내용이지만, 오랜만에 생각해보니 헷갈리는 부분도 있어서 기억을 되살릴 겸 정리해보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;프로그램과 프로세스, 스레드&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;713&quot; data-origin-height=&quot;336&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dgVrgq/btsIha0ZcCT/l09KlHi391Sb40ZOE2Y6r1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dgVrgq/btsIha0ZcCT/l09KlHi391Sb40ZOE2Y6r1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dgVrgq/btsIha0ZcCT/l09KlHi391Sb40ZOE2Y6r1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdgVrgq%2FbtsIha0ZcCT%2Fl09KlHi391Sb40ZOE2Y6r1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;622&quot; height=&quot;293&quot; data-origin-width=&quot;713&quot; data-origin-height=&quot;336&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로그램&lt;/b&gt;은 정적인 실행 파일을 말한다. 이러한 프로그램이 CPU에 올라와서 실행되면 그것을 &lt;b&gt;프로세스&lt;/b&gt;라고 한다. 프로그램은 Long-Term Scheduler에 의해서 CPU에 올라오고(Ready), 메모리를 할당받아 프로세스가 된다. &lt;b&gt;스레드&lt;/b&gt;는 프로세스가 할당받은 자원을 이용하는 작업의 단위를 의미한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;멀티프로세스와 멀티스레드&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CPU는 한번에 하나의 프로세스만 실행할 수 있다. 따라서 CPU를 점유(Run)하고 대기(Wait)하는 과정, 즉 Context Switching을 통해서 여러개의 프로세스를 번갈아서 실행시켜 사용자에게는 동시에 실행중인 것처럼 보이게 하는데(동시성), 이 Context Switching 과정에는 부하가 발생할 수 밖에 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 스레드를 사용하여 Context Switching 비용을 절약할 수 있는데, 스레드는 각각 별도의 메모리를 갖는 프로세스와 다르게 Stack영역을 제외한 나머지 메모리 영역들을 공유하는 실행 단위이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 Context switching 비용의 차이는 메모리의 재사용에 따라서&amp;nbsp;&lt;b&gt;가상 메모리&lt;/b&gt;의 재사용 여부에서도 차이를 보인다. 멀티스레딩은 매핑 테이블 캐시인 TLB의 재사용이 가능하다. 물론 스레드간 공유하지 않는 메모리(스택)의 경우 TLB fault가 발생할 수 있지만, 나머지 공유가 이루어지는 메모리는 그대로 TLB hit이 이루어질 것이기 때문이다. 하지만 멀티프로세싱은 독립된 주소 공간을 가지므로, 동일한 가상 메모리 주소라고 해도 전혀 다른 물리 메모리 주소를 가리킬 것이며, 이는 매핑 테이블까지 모두 교체해야 함을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(추가 - 매핑 테이블을 교체한다기보다는, 각 프로세스가 참조하는 고유한 페이지 테이블이 있는데, 이것이 바뀌는 것)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 스레드는 공유 자원을 통해서 Context Switching 비용을 절약할 수 있지만, 공유 자원으로 인한 동기화 문제가 일어날 수 있을 뿐 아니라 하나의 스레드에 문제가 발생할 경우 전체 쓰레드에 문제가 발생할 수 있다. 대신 프로세스는 독립적이기 때문에 IPC를 통해서 통신해야 한다. 따라서 자원 공유를 위한 비용이 크지만, 동기화 작업이 필요하지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 멀티쓰레드와 멀티프로세스는 하나의&lt;b&gt;&amp;nbsp;어플리케이션을 실행시키기 위해서 처리하는 방법에 대한 차이&lt;/b&gt;라고 이해하면 된다.&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 91px;&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: 20px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 20px; text-align: center;&quot;&gt;멀티 프로세스&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 20px; text-align: center;&quot;&gt;멀티 쓰레드&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 18px;&quot;&gt;프로세스간 독립적인 메모리 구조를 가짐&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 18px;&quot;&gt;쓰레드간 일부 메모리 자원을 공유&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 18px;&quot;&gt;Context Switching 비용이 크다&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 18px;&quot;&gt;Context Switching 비용 절약 가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 18px;&quot;&gt;IPC를 사용하여 통신 (high cost)&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 18px;&quot;&gt;공유 메모리를 사용하여 통신 (low cost)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&gt;동기화 작업 필요 X&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 17px;&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;멀티코어&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 코어란 하드웨어적인 차이를 이용하여 &lt;b&gt;병렬성&lt;/b&gt;을 제공하는 것을 말한다. 다시 말해 물리적으로 다수의 코어를 사용하여 동시에 하나 이상의 프로세스 또는 쓰레드가 동시에 실행되는 것을 의미하는 것이다.&amp;nbsp;&lt;/p&gt;</description>
      <category>[ CS기초 ]/운영체제</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/195</guid>
      <comments>https://eckrin.tistory.com/195#entry195comment</comments>
      <pubDate>Sun, 30 Jun 2024 14:15:57 +0900</pubDate>
    </item>
    <item>
      <title>'클린 아키텍처' 프로젝트 설계하기</title>
      <link>https://eckrin.tistory.com/193</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;작년에 로버트 마틴의 &lt;b&gt;'&lt;/b&gt;클린 아키텍처'를 읽은 적이 있다. SOLID부터 시작해서 여러 디자인 패턴이나 방법론에 대한 소개를 통해 객체지향적인 사고를 할 수 있도록 도왔고, &lt;a href=&quot;https://eckrin.tistory.com/171&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;OOP에 대한 새로운 시각&lt;/a&gt;을 제시해 주기도 했던 책이었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 그 책을 읽고 나서도, 프로젝트를 진행하면서 책에 존재하는 수많은 선택지들 중에 '그렇다면 내가 앞으로 어떤 방법을 택해야 하는가?'라는 질문에 대한 확답을 내리지는 못했다. 예를 들어 추상화를 통해 클래스간의 결합도를 줄이고  외부 인터페이스에 의존하지 않는 코드를 만들고자 했던 목표를 두고 작성한 코드가, 간단한 프로젝트임에도 기존의 몇 배에 달하는 클래스들이 복잡한 연관관계를 가지고 있어 가독성이 떨어지고 간단한 유지보수조차 어렵게 하는 trade-off를 낳는 것을 목격하기도 했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 상황에 따라서 정답에 가까운 선택지를 고르는 것이 최선의 방법이겠지만, 아직 나는 어떤 것이 정답에 가까울지에 대한 질문에 확신을 가지고 대답할 수 없었다. 그래서 이번에는 작년에 읽었던 &lt;b&gt;'클린 아키텍처'&lt;/b&gt;에 더해 &lt;b&gt;'만들면서 배우는 클린 아키텍처'&lt;/b&gt;라는 책을 읽고, 사이드 프로젝트에 적용해가면서 나만의 결론을 내려보려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;계층형 아키텍처의 문제&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;516&quot; data-origin-height=&quot;432&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bTnHrL/btsHuMZKN2v/mbtBQBkbKkykhMpdqC1xak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bTnHrL/btsHuMZKN2v/mbtBQBkbKkykhMpdqC1xak/img.png&quot; data-alt=&quot;Controller-Service-Entity(ORM)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bTnHrL/btsHuMZKN2v/mbtBQBkbKkykhMpdqC1xak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbTnHrL%2FbtsHuMZKN2v%2FmbtBQBkbKkykhMpdqC1xak%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;345&quot; height=&quot;289&quot; data-origin-width=&quot;516&quot; data-origin-height=&quot;432&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Controller-Service-Entity(ORM)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;아마 웹 개발을 시작한 많은 사람들이 맨 처음 이러한 계층형 아키텍처를 접했으리라고 생각한다. 이러한 &lt;b&gt;계층형 아키텍처&lt;/b&gt;는 가장 많이 사용하는 아키텍처로서, 요청을 처리하고 응답하는 프로세스를 계층화한 가장 기초적이고 견고한 구조이기도 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 이 단순한 3계층 아키텍처는 소프트웨어 크기가 커지고 유지보수 기간이 길어질수록 클래스간 결합도가 높아지고, 그 결과 변경에 취약한 코드가 된다는 문제를 낳는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제점의 가장 큰 원인은 3계층 구조가 &lt;b&gt;영속성 계층, 즉 데이터베이스에 의존적&lt;/b&gt;이라는 것이다. 위 그림에서 알 수 있듯이, 웹 계층과 도메인 계층은 모두 영속성 계층에 의존성을 가지고 있다. 결국 &lt;b&gt;비즈니스 로직을 갖는 도메인 계층이 영속계층과 강하게 결합&lt;/b&gt;하게 되고, 유지보수를 계속할수록 비즈니스 로직을 수행하는 다양한 퍼사드 클래스나 헬퍼 클래스까지 영속성 계층에 의존성을 갖게 될 수도 있다. 또한 한쪽 방향으로 의존성을 갖는 아키텍처 특성상 웹 계층에서 영속성 계층에 직접 접근하는 일이 생길 수도 있다. 이는 분명 계층간의 역할을 분리하는 기존 의도와 어긋남에도, 이러한 &lt;b&gt;개발자의 일탈을 막는 장치는 어디에도 존재하지 않는다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 기본적인 3계층 아키텍처는 다음과 같은 문제점들도 가지고 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;- 테스트를 어렵게 만든다. 간단한 단위 테스트조차 웹, 도메인, 영속성 계층에 대한 검증을 요구하게 되고, 웹 계층 테스트에서 도메인뿐 아니라 영속성 계층에 대한 테스트까지 mocking하게 만든다.&lt;br /&gt;&lt;br /&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;b&gt;계층형 아키텍처 자체가 잘못된 것이 아니다&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;일부 문제들은 계층형 아키텍처에 대한 높은 이해를 가지고 설계한다면 생기지 않을 문제점들일 수도 있다. 하지만 모든 개발자가 아키텍처에 대한 높은 이해를 가지고 있는 것은 아니며, 계층형 아키텍처는 개발자의 잘못된 구현을 막지 못한다는 것이 서비스 크기가 커질수록 문제점으로 다가올 확률이 높다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;의존성 역전으로 도메인 중심 구조 만들기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;앞서 보았듯이, 계층형 아키텍쳐는 웹 -&amp;gt; 도메인 -&amp;gt; 영속성이라는  단방향 의존성을 가지고 있다. 결국 하위 계층이 변경될 직접적인 이유는 클라이언트의 새로운 요구사항 등과 같은 필수적인 요소여야 함에도, 상위 계층의 경우 하위 계층에 의해 불필요하게 변경될 가능성이 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계층화 구조와 상관 없이 &quot;&lt;b&gt;프로그램의 핵심은 비즈니스 로직을 갖는 도메인 계층&lt;/b&gt;&quot;이라는 사실을 고려했을 때, 도메인 계층이 영속성 계층에 강한 의존성을 가지고 있다는 사실은 충분히 불편하게 느껴질 것이다. 그래서 책에서는 &lt;b&gt;계층간 의존성을 역전&lt;/b&gt;시켜 도메인 중심의 개발이 이루어질 수 있도록 하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;1088&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3UoZy/btsHvLsTSGc/Jjk0dC9sITv5Bc90VDYcz1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3UoZy/btsHvLsTSGc/Jjk0dC9sITv5Bc90VDYcz1/img.png&quot; data-alt=&quot;https://eckrin.tistory.com/171&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3UoZy/btsHvLsTSGc/Jjk0dC9sITv5Bc90VDYcz1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3UoZy%2FbtsHvLsTSGc%2FJjk0dC9sITv5Bc90VDYcz1%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;565&quot; data-origin-width=&quot;1320&quot; data-origin-height=&quot;1088&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://eckrin.tistory.com/171&lt;/figcaption&gt;
&lt;/figure&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;개발자는 다형성을 이용하여 DIP를 구현할 수 있으며, 이를 통해 간접적으로 제어의 흐름을 역전시킬 수 있다. 우리는 이를 3계층 아키텍처에도 동일하게 적용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로젝트 설계&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1772&quot; data-origin-height=&quot;1020&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b6Y9k7/btsHz1CVgjw/upBtL4UBWR6DaCwRu9kT4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b6Y9k7/btsHz1CVgjw/upBtL4UBWR6DaCwRu9kT4k/img.png&quot; data-alt=&quot;단방향 의존성을 가지는 3계층 구조&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b6Y9k7/btsHz1CVgjw/upBtL4UBWR6DaCwRu9kT4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb6Y9k7%2FbtsHz1CVgjw%2FupBtL4UBWR6DaCwRu9kT4k%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;860&quot; height=&quot;495&quot; data-origin-width=&quot;1772&quot; data-origin-height=&quot;1020&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;단방향 의존성을 가지는 3계층 구조&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1768&quot; data-origin-height=&quot;1022&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzkFda/btsHApKglEe/0epQpGRWT92JFwYdWigt4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzkFda/btsHApKglEe/0epQpGRWT92JFwYdWigt4K/img.png&quot; data-alt=&quot;인터페이스를 통해 영속계층이 도메인에 의존하도록 변경&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzkFda/btsHApKglEe/0epQpGRWT92JFwYdWigt4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzkFda%2FbtsHApKglEe%2F0epQpGRWT92JFwYdWigt4K%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;1768&quot; height=&quot;1022&quot; data-origin-width=&quot;1768&quot; data-origin-height=&quot;1022&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;인터페이스를 통해 영속계층이 도메인에 의존하도록 변경&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 원리를 프로젝트에 적용한 예시이다. 기존 계층형 아키텍처에서 비즈니스(서비스) 로직은 도메인 계층에, 엔티티와 리포지토리는 영속성 계층에 존재했다면, 위에서는 리포지토리 인터페이스를 만들고 이를 도메인 계층에 위치시켰다. 여기서 웹과 도메인 계층의 결합도를 더 낮추고 싶다고 하면, BannerService에 대한 인터페이스를 추가하고 웹 계층에서는 해당 인터페이스에 대한 의존성만 갖도록 하면 된다.&lt;/p&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;특이한 점은 엔티티가 도메인 계층에서 관리하는 'Banner' 클래스와 영속계층에서 관리하는 'BannerEntity' 클래스로 나누어졌다는 것이다. 도메인 계층이 영속성 계층에 대해서 알 수 없도록 분리했기 때문에 각 계층에서 엔티티를 만들게 된 것이다(물론 도메인 계층의 엔티티는 의미상으로만 엔티티이다). 도메인 코드를 특정 외부 프레임워크에 종속적이지 않도록 하기 위해서 필연적으로 나타나게 되는 결과이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;패키지 구성하기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 위와 같은 아키텍처에 대한 패키지를 어떻게 구성하면 좋을까?&amp;nbsp; 책에서는 도메인 중심적인 해결책을 제시하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;348&quot; data-origin-height=&quot;557&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sTAKn/btsHx9tYlot/0j5jp3tKPWM5tvqz9qKe6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sTAKn/btsHx9tYlot/0j5jp3tKPWM5tvqz9qKe6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sTAKn/btsHx9tYlot/0j5jp3tKPWM5tvqz9qKe6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsTAKn%2FbtsHx9tYlot%2F0j5jp3tKPWM5tvqz9qKe6k%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;348&quot; height=&quot;557&quot; data-origin-width=&quot;348&quot; data-origin-height=&quot;557&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;최상위 패키지로 domain과 application, adapter를 두고, domain에는 엔티티, application에는 서비스 클래스를 위치시킨다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;adapter &lt;/b&gt;패키지에는 웹 계층과 영속계층에 관련된 클래스가 존재한다. 도메인 관점에서 해석하는 것이 목표므로, adapter.in에는 도메인으로 제어가 들어오는 web계층에 해당하는 Controller 클래스가, adapter.out에는 도메인으로부터 제어가 나가는 persistence계층에 해당하는 Repository가 위치한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;application&lt;/b&gt;은 도메인 계층의 핵심 비즈니스 로직을 담은 서비스 클래스와 함께 결합도를 낮추고 의존성을 역전시켜줄 &lt;b&gt;포트&amp;nbsp;인터페이스&lt;/b&gt;가 존재한다. port.in에는 웹 계층 인터페이스가, port.out에는 영속계층의 인터페이스가 존재하여 웹 계층과 영속 계층이 모두 도메인 계층에 의존하도록 하여 도메인 중심적인 구조를 만든다.&lt;/li&gt;
&lt;li&gt;도메인 계층에 웹 계층 또는 영속 계층에서 접근 가능해야하는 인터페이스(port 하위 등)를 제외한 클래스들은 접근지정자를 조절하여 불필요한 의존성이 생기는 것을 방지할 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;유효성 검증은 어느 계층의 책임인가&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;위 예시에서는 서비스 코드에 대한 인터페이스를 유스케이스라고 명명하고 있다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;유스케이스&lt;/b&gt;란 시스템에서 수행되는 일련의 활동에 대한 시나리오를 의미하는데, 일반적으로 입력-&amp;gt;비즈니스 규칙 검증-&amp;gt;상태 변경-&amp;gt;출력의 과정을 거친다. 이 때 중요한 점은 입력값에 대한 검증과, 비즈니스 규칙을 검증하는 로직을 구분해야 한다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;입력값 검증&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;입력값에 대한 유효성을 검증하는 것은 사실 유스케이스의 책임은 아니라고 할 수 있다. 하지만 이는 여전히 application 계층의 책임인데, web이나 persistence 계층에서 입력 유효성을 검증하려고 하면 어댑터가&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt; &lt;/span&gt;유스케이스 각각에 대한 흐름을 분기시켜&lt;span&gt; &lt;/span&gt;&lt;/span&gt;입력 유효성을 검증해야 할 것이기 때문이다. 하나의 유스케이스가 여러개의 어댑터를 통해 호출될 수도 있다는 점을 고려하면 이는 adapter 계층의 복잡도를 증가시키고 도메인 계층의 결합도를 증가시키는 원인이 될 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;비즈니스 규칙 검증&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그렇다면 비즈니스 규칙을 검증하는 것은 어떨까? 비즈니스 규칙은 분명 유스케이스 로직의 일부이며, 애플리케이션의 핵심이기에 application 계층에서 검증하는 것이 당연하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;두 검증의 차이&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력값 검증과 비즈니스 규칙 검증의 가장 큰 차이는&lt;b&gt; 도메인 모델의 '현재' 상태에 의존적인가의 여부&lt;/b&gt;라고 할 수 있다. 따라서 검증 과정에 현재 도메인 모델의 상태를 고려해야 하는지 여부를 확인해서 입력값 검증과 비즈니스 규칙 검증을 구분할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 이와 같은 검증 코드를 application 계층의 어디에 위치시킬 것인가에 대해서는 각자의 코드 스타일에 달려있다. 책에서는 '&lt;b&gt;풍부한 도메인 모델'&lt;/b&gt;과 '&lt;b&gt;빈약한 도메인 모델&lt;/b&gt;'이라는 두 케이스를 소개하고 있는데, 전자는 &lt;b&gt;도메인 엔티티&lt;/b&gt;(영속 엔티티와 다르다 - 도메인 계층에 위치함)&amp;nbsp;안에 비즈니스 규칙을 검증하는 로직을 포함하지만, 후자는 엔티티에 필드와 getter, setter를 제외한 도메인 로직을 두지 않으며 이를 &lt;b&gt;유스케이스, 즉 서비스 클래스&lt;/b&gt;에서 해결한다. 무엇이 옳은가에 대한 정답은 존재하지 않으며, 개발자가 적당한 스타일을 선택해서 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;책에서는 이러한 두 가지 케이스를 소개만 할 뿐, 어떤 방법이 나은지에 대해서는 설명하지 않고 있다. 그래서 내 경험을 통해 얻은 (아마 많은 개발자들이 공감할 것이다) 점만 적어보자면, '빈약한' 도메인 모델만 사용하면&lt;b&gt; 엔티티에 대한 검증 로직이 모조리 서비스 클래스에 추가&lt;/b&gt;되며, &lt;b&gt;결국 코드의 응집도가 낮아지고 가독성이 저해된다&lt;/b&gt;라는 것이다. 따라서 엔티티의 상태를 업데이트하는 등의 단순한 작업들은 엔티티 내의 도메인 로직을 사용하고 적절한 명명규칙을 통해서 가독성을 올리고 코드의 불필요한 반복을 최소화하는 것이 좋아 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다고 모든 비즈니스 로직을 도메인 엔티티에 넣을 생각은 하지 않는 것이 좋아 보인다. 도메인 엔티티도 결국 '엔티티'이지, 퍼사드나 유틸리티 클래스가 아니기 때문이다. 도메인 엔티티에 대한 변경이 주된 기능을 하는 부분만 도메인 엔티티에 별도 로직으로 분리하는 것이 좋지 않을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;의존성 역전으로 도메인 중심 구조 만들기 (1) - 웹 어댑터&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;앞서 LoadApplicationPort 인터페이스에서 확인할 수 있듯이, 도메인 계층과 영속 계층의 의존성을 역전시키기 위해서 도메인 계층에 영속성 계층에 대한 인터페이스를 위치시키는 방법을 사용하였다. 그런데 웹 계층(컨트롤러)와 도메인(서비스) 계층은 제어의 흐름을 역전시킬 필요가 없는데도 인터페이스를 두는 이유가 무엇일까?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;414&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4ATWT/btsHCVhGg8F/5VYTI1aV1jqC32NLkmAZHk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4ATWT/btsHCVhGg8F/5VYTI1aV1jqC32NLkmAZHk/img.png&quot; data-alt=&quot;incoming, outgoing 포트 인터페이스 추가&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4ATWT/btsHCVhGg8F/5VYTI1aV1jqC32NLkmAZHk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4ATWT%2FbtsHCVhGg8F%2F5VYTI1aV1jqC32NLkmAZHk%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;724&quot; height=&quot;234&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;414&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;incoming, outgoing 포트 인터페이스 추가&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 포트 인터페이스는 애플리케이션 코어가 외부 세계와 통신할 수 있는 명세이기 때문이다. 도메인 계층만 보고도 포트 인터페이스를 통해서 웹 컨트롤러와 어떤 통신이 이루어지고 있는지 알 수 있으며, 이는 도메인 계층에 대한 유지보수를 보다 용이하게 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.서비스 계층에서 웹 컨트롤러를 호출해야 하는 케이스가 존재한다. 애플리케이션 코어에서 실시간 데이터를 사용자의 브라우저로 보내기 위해서는 애플리케이션 계층에서 능동적으로 웹 컨트롤러에 알림을 보낼 수 있어야 한다. 그러한 과정을 위해서는 위와 같은 아웃고잉 포트 인터페이스가 필요하다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;의존성 역전으로 도메인 중심 구조 만들기 (2) - 영속성 어댑터&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9OP5G/btsHDfhwL6B/UZP1QJHNQgx7XUVfaAkzY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9OP5G/btsHDfhwL6B/UZP1QJHNQgx7XUVfaAkzY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9OP5G/btsHDfhwL6B/UZP1QJHNQgx7XUVfaAkzY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9OP5G%2FbtsHDfhwL6B%2FUZP1QJHNQgx7XUVfaAkzY1%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;736&quot; height=&quot;232&quot; data-origin-width=&quot;1278&quot; data-origin-height=&quot;402&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;앞서 이야기했듯이 포트 인터페이스를 사용하여 도메인 계층과 영속계층의 의존성을 역전시킬 수 있다. 이것은 도메인 계층의 영속 계층에 대한 코드 의존성을 없애기 위한 방법이며, (런타임 의존성은 여전히 애플리케이션 서비스에서 영속성 어댑터로 향한다) 포트 인터페이스를 통해 영속성 계층 내의 변경이 서비스에까지 변경으로 이어지는 것을 방지한다는데 의미를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;영속성 계층에 대한 결합도를 낮춘다&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이렇게 분리된 영속성 어댑터는, 포트 인터페이스를 구현하는 어떠한 방식으로든 자유롭게 교체될 수 있다. 이는 사용하던 JPA가 아닌 다른 인터페이스로 교체하더라도 애플리케이션 클래스의 코드로 이어지지는 않는다는 것을 말한다. 영속 계층의 변화가 도메인 계층으로 전파되는 것을 최소화하는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;포트 인터페이스 설계하기&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1272&quot; data-origin-height=&quot;404&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xP5xB/btsHCP4vBqY/5DYbA8gZIgbNNopAHKSVG1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xP5xB/btsHCP4vBqY/5DYbA8gZIgbNNopAHKSVG1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xP5xB/btsHCP4vBqY/5DYbA8gZIgbNNopAHKSVG1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxP5xB%2FbtsHCP4vBqY%2F5DYbA8gZIgbNNopAHKSVG1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;769&quot; height=&quot;244&quot; data-origin-width=&quot;1272&quot; data-origin-height=&quot;404&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;이제 포트 인터페이스를 어떻게 구성해야 될 지에 대해서 생각해보자. 일반적으로 위 그림과 같이 'AccountRepository'와 같은 포트의 형태를 사용하여 Account에 관련된 기능들을 한 개의 클래스에 모아두는 경우가 많다. 이는 영속성 어댑터가 사용하지도 않을 메서드들에 대한 의존성을 가지게 만들며, 테스트를 어렵게 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 계좌정보가 정상적으로 불러와지는지에 대한 테스트를 한다고 하자. 우리는 애플리케이션 서비스가 호출할 메서드를 모킹해주어야 한다. 하지만 AccountRepository의 어떤 메서드가 사용되는지를 확인하고 모킹하기 위해서는 서비스 클래스에 대한 분석이 필요하고, 이는 다른 테스트시에 AccountRepository의 어떤 메서드가 모킹되었는지 구분하기 힘들게 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1270&quot; data-origin-height=&quot;546&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/clrt0d/btsHFcKcQkt/MKlMYawgHxANzEU2mIrth1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/clrt0d/btsHFcKcQkt/MKlMYawgHxANzEU2mIrth1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/clrt0d/btsHFcKcQkt/MKlMYawgHxANzEU2mIrth1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fclrt0d%2FbtsHFcKcQkt%2FMKlMYawgHxANzEU2mIrth1%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;723&quot; height=&quot;311&quot; data-origin-width=&quot;1270&quot; data-origin-height=&quot;546&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: #333333; text-align: start;&quot;&gt;ISP(인터페이스 분리 원칙)는 &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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;테스트 설계하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;설계한 시스템과 마찬가지로, 아키텍처의 경계를 넘나드는 테스트 구조는 테스트 비용과 속도에 부정적인 영향을 줄 수 있다. 따라서 테스트에 들어가는 자원을 줄이기 위해서는 커버리지 목표를 작게 잡는 것이 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;405&quot; data-origin-height=&quot;247&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZopuT/btsH150etc5/NO9jIKnjVnHKFBoqrq7qMk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZopuT/btsH150etc5/NO9jIKnjVnHKFBoqrq7qMk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZopuT/btsH150etc5/NO9jIKnjVnHKFBoqrq7qMk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZopuT%2FbtsH150etc5%2FNO9jIKnjVnHKFBoqrq7qMk%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;405&quot; height=&quot;247&quot; data-origin-width=&quot;405&quot; data-origin-height=&quot;247&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;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;- &lt;b&gt;단위 테스트&lt;/b&gt;: 클래스를 인스턴스화하고, 다른 클래스에 의존하는 부분은 &lt;b&gt;모킹&lt;/b&gt;(mocking)함으로써 &lt;span style=&quot;background-color: #fcfcfc; color: #333333; text-align: left;&quot;&gt;개별 메서드의 동작을 확인하는 등 소프트웨어의 가장 작은 단위에 대한 동작을 테스트한다.&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;- &lt;b&gt;통합 테스트&lt;/b&gt;: 모듈간 연결을 담당하는 여러 클래스를 &lt;b&gt;인스턴스화&lt;/b&gt;하고, 여러 모듈이나 컴포넌트가 서로 잘 작동하는지 모듈간의 상호작용을 테스트한다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;- &lt;b&gt;시스템 테스트&lt;/b&gt;: 전체 시스템이 사용자 관점에서 특정 유스케이스가 제대로 동작하는지 확인한다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;아키텍처 경계 강화하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;클린 아키텍처를 지키면서 프로젝트를 설계했다고 하더라도, 시간이 지나며 프로젝트의 규모가 커짐에 따라서 아키텍처의 경계가 무너질 수 있다.&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;1358&quot; data-origin-height=&quot;1282&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/phEOx/btsImcY8w8F/MK5hpd8Z7pgDj5QMXvffF1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/phEOx/btsImcY8w8F/MK5hpd8Z7pgDj5QMXvffF1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/phEOx/btsImcY8w8F/MK5hpd8Z7pgDj5QMXvffF1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FphEOx%2FbtsImcY8w8F%2FMK5hpd8Z7pgDj5QMXvffF1%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;725&quot; height=&quot;684&quot; data-origin-width=&quot;1358&quot; data-origin-height=&quot;1282&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;도메인 중심 개발(DDD)을 위해서는 도메인 중심적인 프로젝트 설계가 필요하다.&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;br /&gt;- 어댑터는 입력 포트(유스케이스) 인터페이스를 통해서 그 구현체인 서비스 코드에 접근한다.&lt;br /&gt;- 서비스는 출력 포트를 통해서 영속성 어댑터에 접근하고, 해당 어댑터를 통해서 영속 계층(레포지토리)에 접근할 수 있다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 계층 간 의존성은 바깥쪽에서 안쪽을 가리키며, 최종적으로 모든 의존성은 도메인 계층으로 이어진다. 이러한 구조 설계를 위해서는 어떤 방법을 사용해야 할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바는 패키지, 즉 모듈간에 상호 접근 가능하게 설정하는 접근 제한자인 'package-private' 즉, default 제한자가 존재한다. 이 default 제한자가 달려있으면 모듈 내의 클래스는 상호 접근하지만, 외부에서는 접근할 수 없다. 따라서 해당 모듈의 진입점으로 활용될 클래스들을 골라서 public으로 설정해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고: 만들면서 배우는 클린 아키텍처 - Tom Hombergs&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실습 github: &lt;a href=&quot;https://github.com/eckrin/clean-architecture&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/eckrin/clean-architecture&lt;/a&gt;&lt;/p&gt;</description>
      <category>[ 아키텍쳐, 방법론, 디자인패턴 등 ]</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/193</guid>
      <comments>https://eckrin.tistory.com/193#entry193comment</comments>
      <pubDate>Sun, 19 May 2024 23:44:55 +0900</pubDate>
    </item>
    <item>
      <title>[Spring DB] 트랜잭션 매니저 커스텀하기</title>
      <link>https://eckrin.tistory.com/192</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;512&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blBukE/btsGXhtkNSY/LzoirTgWRuoRKVMXCc3sqK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blBukE/btsGXhtkNSY/LzoirTgWRuoRKVMXCc3sqK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blBukE/btsGXhtkNSY/LzoirTgWRuoRKVMXCc3sqK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FblBukE%2FbtsGXhtkNSY%2FLzoirTgWRuoRKVMXCc3sqK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;552&quot; height=&quot;276&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;512&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&amp;nbsp;JPA는 내부에 하나의 &lt;b&gt;EntityManagerFactory&lt;/b&gt;가 존재하고, EntityManagerFactory가 여러개의 &lt;b&gt;EntityManager&lt;/b&gt;들을 생성할 수 있으며, 이렇게 생성된 EntityManager는 &lt;b&gt;영속성 컨텍스트&lt;/b&gt;를 생성해 엔티티의 영속성을 관리한다. EntityManager는 트랜잭션 범위 내에서 작동하는 것이 일반적이기 때문에 트랜잭션을 시작할 때 마다 새로운 EntityManger 인스턴스가 필요하며, 이는 트랜잭션 종료 시 폐기된다.&lt;br /&gt;&lt;br /&gt;스프링 컨테이너에 등록된 &lt;b&gt;트랜잭션 매니저&lt;/b&gt;는 데이터베이스 커넥션을 획득하고, 반납하는 역할을 한다. 스프링에는 데이터베이스 &lt;b&gt;커넥션 풀&lt;/b&gt;이라는 것이 존재하는데, 스프링은 빈으로 등록된 &lt;b&gt;Datasource 객체&lt;/b&gt;를 통해 커넥션 풀을 관리한다. 이 커넥션 풀에 있는 커넥션들을 트랜잭션 매니저가 필요시 획득하여 사용 후 반납할 수 있다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 내용들은 예전에 &lt;a href=&quot;https://eckrin.tistory.com/155&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;JPA에 관한 공부를 하면서 알게 된 내용&lt;/a&gt;들이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 스프링이 관리하는 트랜잭션 매니저를 우리가 커스텀할 수는 없을까? 스프링은 프로퍼티나 YAML파일을 통해서 datasource에 관한 다양한 설정을 할 수 있도록 지원한다. 하지만 오늘은 개발자가 직접 트랜잭션 매니저를 커스텀하는 방법에 대해서 알아보도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;트랜잭션 매니저 커스텀하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에 &lt;a href=&quot;https://eckrin.tistory.com/155&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;트랜잭션 매니저에 관련된 포스팅&lt;/a&gt;을 했을때, PlatformTransactionManager는 AbstractPlatformTransactionManager를 구현하는 다양한 구현체들을 가지며, 스프링은 사용하는 DB 액세스 기술에 따라서 적절한 구현체를 사용한다고 공부했던 적이 있다.&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;어떤 프로젝트에서 MyBatis와 JPA를 동시에 사용할 필요가 있다고 하자. JPA는 ORM 기술이고, MyBatis는 작성한 SQL쿼리를 자바 객체에 매핑해주는 역할을 하기 때문에 각각 별개로 사용할 수 있지만, 동일 데이터베이스에 접근하게 될 경우 JDBC 트랜잭션을 사용하는 MyBatis와 JPA 트랜잭션 매니저를 사용하는 JPA 사이에 문제가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고려해야 할 점은 크게 두 가지가 있을 것이다. &lt;b&gt;사용하는 트랜잭션 매니저의 설정을 통일&lt;/b&gt;해주어야 하고, &lt;b&gt;동일 Datasource를 사용&lt;/b&gt;해야 한다는 점이다. 이를 위해서 우리는 스프링에서 JPA와 MyBatis를 사용하기 위해 설정하는 빈들을 커스텀해줄 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. 동일 Datasource 설정&lt;/b&gt;&lt;/h3&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Primary
@Bean
@ConfigurationProperties(prefix = &quot;spring.datasource.hikari&quot;)
public DataSource dataSource() {
    return DataSourceBuilder.create().build();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;application.yml의 프로퍼티를 통해서 Datasource를 설정해주고, @Primary 어노테이션을 통해 기존에 스프링에서 사용되던 빈보다 먼저 동작하도록 설정할 수 있다. 이렇게 설정한 Datasource는 JPA와 MyBatis 설정시 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. JPA 설정&lt;/b&gt;&lt;/h3&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Primary
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
        EntityManagerFactoryBuilder builder,
        @Qualifier(&quot;dataSource&quot;) DataSource dataSource,
        Environment env) {
    Map&amp;lt;String, Object&amp;gt; properties = new HashMap&amp;lt;String, Object&amp;gt;();

    properties.put(&quot;hibernate.hbm2ddl.auto&quot;, env.getRequiredProperty(&quot;spring.jpa.hibernate.ddl-auto&quot;));
    properties.put(&quot;hibernate.dialect&quot;, env.getRequiredProperty(&quot;spring.jpa.database-platform&quot;));

    return builder.dataSource(dataSource)
            .packages(&quot;com.eckrin.test&quot;) // 엔티티 클래스 경로 지정
            .properties(properties) // env(YAML)에서 가져온 맵 형태의 프로퍼티 설정
            .build();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;스프링은 JPA 설정을 위해 EntityManagerFactory를 구성할 때 LocalContainerEntityManagerFactoryBean을 사용하는데, @Primary 어노테이션을 통해서 스프링의 기존 &lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;LocalContainerEntityManagerFactoryBean보다 우선적으로 사용할 빈으로 설정하고 커스텀할 수 있다.&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;앞서 빈으로 등록해둔 DataSource를 사용하였으며, 엔티티 클래스가 존재하는 경로를 설정하였다. 이제 JPA는 이 패키지 하위 경로를 스캔하여 엔티티 클래스를 찾아 관리하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. MyBatis 설정&lt;/b&gt;&lt;/h3&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Primary
@Bean
public SqlSessionFactory sqlSessionFactory(@Qualifier(&quot;dataSource&quot;) DataSource DataSource, ApplicationContext applicationContext) throws Exception {
    SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
    sqlSessionFactoryBean.setDataSource(DataSource);
    // mybatis 설정 파일 세팅
    sqlSessionFactoryBean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource(&quot;classpath:mybatis/mybatis-config.xml&quot;));
    // mapper.xml 패키지 주소
    sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(&quot;classpath*:mybatis/mapper/**/*.xml&quot;));
    return sqlSessionFactoryBean.getObject();
}

@Primary
@Bean
public SqlSessionTemplate sqlSessionTemplate(@Qualifier(&quot;sqlSessionFactory&quot;) SqlSessionFactory sqlSessionFactory) {
    return new SqlSessionTemplate(sqlSessionFactory);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로는 MyBatis에서 사용하는 &lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;SqlSessionFactory&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;을 설정해주자. 하지만 &lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;SqlSession을 사용하는&amp;nbsp;&lt;/span&gt;일반적인 &lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;SqlSessionFactory와 다르게 SqlSessionTemplate을 사용하여 SqlSession이 스프링 트랜잭션 내에서 사용될 수 있도록 보장한다.&amp;nbsp;&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;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;무슨 말이냐면, MyBatis는 JDBC를 기반으로 동작하며, xml로 이루어진 SQL 매핑 파일을 JDBC를 통해서 데이터베이스에 전송한다. 이때 SqlSession을 통해서 트랜잭션을 관리하는데, 여기서는 SqlSessionTemplate을 사용해서 스프링 트랜잭션에 위임하기 때문에 아래와 같이 JpaTransactionManager를 사용하면 MyBatis와 JDBC 커넥션을 모두 관리할 수 있게 된다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt;4. TransactionManager 설정&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Primary
@Bean
public JpaTransactionManager transactionManager(@Qualifier(&quot;entityManagerFactory&quot;) LocalContainerEntityManagerFactoryBean entityManagerFactory) {
    // JPA transactional
    JpaTransactionManager jpaTransactionManager = new JpaTransactionManager();
    jpaTransactionManager.setEntityManagerFactory(entityManagerFactory.getObject());

    return jpaTransactionManager;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2번에서 생성한 EntityManagerFactoryBean을 사용해서 JpaTransactionManager를 생성하고, 설정해둔 EntityMangerFactoryBean을 등록해준다.&lt;/p&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;적용 github: &lt;a href=&quot;https://github.com/eckrin/various-tests&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/eckrin/various-tests&lt;/a&gt;&lt;/p&gt;</description>
      <category>[ Backend ]/Spring DB, JPA</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/192</guid>
      <comments>https://eckrin.tistory.com/192#entry192comment</comments>
      <pubDate>Thu, 25 Apr 2024 19:11:28 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] Spring Batch 프로젝트에 적용해보기</title>
      <link>https://eckrin.tistory.com/191</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSzUUl/btsGPa7LnMI/TFdRK2sUVuHUOc5sQaZnC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSzUUl/btsGPa7LnMI/TFdRK2sUVuHUOc5sQaZnC1/img.png&quot; data-alt=&quot;https://spring.io/projects/spring-batch&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSzUUl/btsGPa7LnMI/TFdRK2sUVuHUOc5sQaZnC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSzUUl%2FbtsGPa7LnMI%2FTFdRK2sUVuHUOc5sQaZnC1%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;518&quot; height=&quot;337&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://spring.io/projects/spring-batch&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Batch와 Spring Batch&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Spring Batch는 배치 작업을 위해서 스프링에서 제공하는 &lt;b&gt;배치 프레임워크&lt;/b&gt;이며,&amp;nbsp;배치(Batch)란 데이터를 실시간으로 처리하지 않고 단발성으로 일괄처리하는 방식을 말한다.&lt;/p&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;상품 결제를 승인하거나 api요청에 따라 뷰를 띄워주는 등의 작업을 위해서는 클라이언트에게 즉각적인 응답을 제공해야 한다. 하지만 일정 시간을 기준으로 데이터를 취합하여 통계 리포트를 생성하거나, 부하가 큰 대용량의 데이터 작업을 서버 부하가 적은 특정 시간대에 처리하려 할 경우에는 실시간 처리보다는 특정 주기를 두고 처리하는 것이 바람직하다. 이러한 데이터 처리 방식을 &lt;b&gt;배치(Batch)&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;이러한 배치 어플리케이션의 특성에 Spring Framework의 3대 요소인 DI, AOP, PSA(서비스 추상화)를 지원하는 프레임워크가 &lt;b&gt;Spring Batch&lt;/b&gt;이다. Spring Batch는 Batch에 관련된 다양한 기능을 정형화시켜 제공하기에 비즈니스 코드 작성에 집중할 수 있으며, 기존 스프링 프레임워크를 사용한 프로젝트에 효과적으로 적용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring Batch는 특정 시간대의 데이터 일괄 처리를 목적으로 하기에 보통 &lt;b&gt;스케줄러&lt;/b&gt;와 같이 사용된다. 이를 위해 스프링의 @Scheduled 어노테이션을 사용하거나, 보다 복잡한 스케줄링을 위해서는 Quartz와 같은 외부 라이브러리와 조합하여 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; Spring 프로젝트에 Spring Batch와 함께 스케줄러를 적용하여 일정 주기별로 데이터를 처리해보자.&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;814&quot; data-origin-height=&quot;298&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boqRVx/btsGQ0kapPq/dowBjpQXjkAJKJ25hSTnK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boqRVx/btsGQ0kapPq/dowBjpQXjkAJKJ25hSTnK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boqRVx/btsGQ0kapPq/dowBjpQXjkAJKJ25hSTnK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboqRVx%2FbtsGQ0kapPq%2FdowBjpQXjkAJKJ25hSTnK1%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;814&quot; height=&quot;298&quot; data-origin-width=&quot;814&quot; data-origin-height=&quot;298&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;Spring Batch는 간단하게 위와 같은 구조를 지니며, 각 단계는 다음과 같은 역할을 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.4884%; text-align: center;&quot;&gt;JobLauncher&lt;/td&gt;
&lt;td style=&quot;width: 81.5116%; text-align: left;&quot;&gt;Spring Application에서 설정된 Job을 실행한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.4884%; text-align: center;&quot;&gt;Job&lt;/td&gt;
&lt;td style=&quot;width: 81.5116%; text-align: left;&quot;&gt;하나의 &lt;b&gt;배치 작업 단위&lt;/b&gt;를 말하며, 세부 내용은 Step에서 처리된다. 하나의 Job은 여러개의 Step으로 구성될 수 있다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.4884%; text-align: center;&quot;&gt;Step&lt;/td&gt;
&lt;td style=&quot;width: 81.5116%; text-align: left;&quot;&gt;Job에서 실제로 수행되는 &lt;b&gt;개별 작업 단위&lt;/b&gt;를 말하며, 각 Step은 읽기, 처리, 쓰기의 3단계로 나누어진다. 이 각각의 작업은 ItemReader, ItemProcessor, ItemWriter가 각각 담당한다.&lt;br /&gt;1개의 Job에 1개 이상의 Step이 존재할 수 있다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 18.4884%; text-align: center;&quot;&gt;JobRepository&lt;/td&gt;
&lt;td style=&quot;width: 81.5116%; text-align: left;&quot;&gt;배치가 수행될 때 메타데이터를 관리하고, 시작시간, 종료시간, Job의 상태 등 배치 수행에 관련된 데이터가 저장된다. JobRepository를 이용하여 실행 상태를 저장하여 실패 작업을 복구하거나, 관련 정보를 확인할 수 있다.&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;프로젝트 실습&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;// spring batch (latest: 5.x)
implementation 'org.springframework.boot:spring-boot-starter-batch'&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;spring:
  batch:
    jdbc:
      initialize-schema: always
    job:
      name: testJob # job 이름 설정
      
  datasource:
    hikari:
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://localhost:3306/${DB_NAME}?serverTimezone=Asia/Seoul
      username: ${DB_USERNAME}
      password: ${DB_PASSWORD}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 dependencies와 application.yml에 관련 설정을 추가해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Spring Batch 5.0 설정하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 SpringBoot 3.x버전에서는 Spring Batch 5.0을 사용하는 것을 권장하고 있는데, 기존 4.x버전과는 차이가 존재한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이전에는 &lt;b&gt;@EnableBatchProcessing&lt;/b&gt;이라는 어노테이션을 써 주어야&amp;nbsp;&lt;b&gt;BatchAutoConfiguration&lt;/b&gt;을 사용하여 @Configuration에 설정된 빈들을 자동으로 Job으로 등록해주었지만, 이제는 사용하지 않아도 된다. 대신 &lt;b&gt;DefaultBatchConfiguration&lt;/b&gt;이 존재하지 않는 경우에만 BatchAutoConfiguration이 활성화된다는 점은 기억해두자&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;971&quot; data-origin-height=&quot;268&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wgPPz/btsGOvFi3Hb/JKCqLvxpIZtF5uc9iu19UK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wgPPz/btsGOvFi3Hb/JKCqLvxpIZtF5uc9iu19UK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wgPPz/btsGOvFi3Hb/JKCqLvxpIZtF5uc9iu19UK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwgPPz%2FbtsGOvFi3Hb%2FJKCqLvxpIZtF5uc9iu19UK%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;971&quot; height=&quot;268&quot; data-origin-width=&quot;971&quot; data-origin-height=&quot;268&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;또한 JobBuilderFactory와 StepBuilderFactory를 의존성 주입받아 사용하던 기존 Spring Batch 4.0과는 다르게, 5.x부터는 아래와 같이 JobBuilder와 StepBuilder를 사용하고, JobRepository와 PlatformTransactionManager를 주입받아서 Job과 Step을 등록한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Slf4j
@RequiredArgsConstructor
@Configuration
public class BatchJobConfig {

    private final String JOB_NAME = &quot;testJob&quot;;
    private final String STEP_NAME = &quot;testStep&quot;;

    /**
     * Job 등록
     */
    @Bean
    public Job testJob(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
        return new JobBuilder(JOB_NAME, jobRepository)
                .incrementer(new RunIdIncrementer()) // sequential id
                .start(testStep(jobRepository, transactionManager)) // step 설정
                .build();
    }

    /**
     * Step 등록
     */
    @Bean
    @JobScope
    public Step testStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
        return new StepBuilder(STEP_NAME, jobRepository)
                .tasklet(testTasklet(), transactionManager) // tasklet 설정
                .build();
    }

    /**
     * Tasklet: Reader-Processor-Writer를 구분하지 않는 단일 step
     */
    @Bean
    @StepScope
    public Tasklet testTasklet() {
        return new Tasklet() {
            @Override
            public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
                log.info(&quot;Spring Batch Test Success&quot;);
                return RepeatStatus.FINISHED; // 작업에 대한 Status 명시
            }
        };
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 주입받은 Repository와 TransactionManager를 통해서 Spring Batch 작업 단계에서 필요한 트랜잭션을 관리하여 데이터 일관성을 유지할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;발생한 오류) 기존에 PlatformTransactionManager를 커스텀하여 사용하는 중 발생한 오류&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;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1748&quot; data-origin-height=&quot;678&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CdbMO/btsGSPbOpxJ/ZnEdznfLOi8beFUKckhmX0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CdbMO/btsGSPbOpxJ/ZnEdznfLOi8beFUKckhmX0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CdbMO/btsGSPbOpxJ/ZnEdznfLOi8beFUKckhmX0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCdbMO%2FbtsGSPbOpxJ%2FZnEdznfLOi8beFUKckhmX0%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;1748&quot; height=&quot;678&quot; data-origin-width=&quot;1748&quot; data-origin-height=&quot;678&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;

&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 추가로 해당 프로젝트에서 MyBatis와 JPA를 동시에 사용하기 위해서 PlatformTransactionManager를&lt;span&gt;&amp;nbsp;&lt;/span&gt;JpaTransactionManager와 DatasourceTransactionManager를 연결시킨 형태의 ChainedTransactionManager의 형태로 커스텀해서 사용하고 있었는데, 이 때 Job과 Step에 PlatformTransactionManager를 주입하려고 시도하니 isolation level이 다르다고 나오면서 실행되지 않았다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- isolation level을 설정파일에서 일치시킬 수 있는 방법도 찾지 못해서 일단 JpaTransactionManager를 커스텀하여 사용하는 방법으로 대신했는데, 나중에 더 알아보자&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 일단 Spring Batch 5.0에서 ChainedTransactionManager가 deprecated된 것과 연관이 있지 않을까 싶다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;PlatformTransactionManager&lt;/b&gt;: 모든 트랜잭션 매니저의 상위 인터페이스이다. 트랜잭션 관리를 위한 기본 인터페이스를 제공한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;ChainedTransactionManager&lt;/b&gt;: 여러개의 다른 트랜잭션 매니저를 연결해서 사용하기 위하여 사용하는 클래스이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;Abstract&lt;/b&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;&lt;b&gt;PlatformTransactionManager&lt;/b&gt;: 트랜잭션 관리자를 만들기 위한 추상 클래스이다. 이를 &lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;JpaTransactionManager, &lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;DatasourceTransactionManager, &lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;JtaTransactionManager 등으로 구현하여 JPA, JDBC, JTA와 같은 다양한 데이터 액세스 기술을 지원한다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;-  트랜잭션 매니저에 관련된 자세한 설명은 &lt;a href=&quot;https://eckrin.tistory.com/155&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://eckrin.tistory.com/155&lt;/a&gt; 에 있다.&amp;nbsp;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&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;1814&quot; data-origin-height=&quot;119&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAOh5T/btsGSjdcFSV/l4DsLCPSngLTA1jR4pb411/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAOh5T/btsGSjdcFSV/l4DsLCPSngLTA1jR4pb411/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAOh5T/btsGSjdcFSV/l4DsLCPSngLTA1jR4pb411/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAOh5T%2FbtsGSjdcFSV%2Fl4DsLCPSngLTA1jR4pb411%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;1814&quot; height=&quot;119&quot; data-origin-width=&quot;1814&quot; data-origin-height=&quot;119&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;이후 스프링 부트를 실행하면 위와 같이 Batch가 동작하는 것을 확인할 수 있다. 성공적으로 실행되었다면 datasource에 설정해 둔 데이터베이스에 몇 개의 메타 테이블들이 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;데이터 마이그레이션&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;주문(Orders) 테이블에서 정산(Accounts) 테이블로 일정 시간마다 스케줄러를 통해서 데이터를 마이그레이션하는 상황을 가정하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;/**
 * 주문(order) 테이블 -&amp;gt; 정산(accounts) 테이블 데이터 이관 가정
 */
@Slf4j
@RequiredArgsConstructor
@Configuration
public class TrMigrationConfig {

    private final String JOB_NAME = &quot;trMigrationJob&quot;;
    private final String STEP_NAME = &quot;trMigrationStep&quot;;

    private final OrderRepository orderRepository;
    private final AccountRepository accountRepository;

    /**
     * Job 등록
     */
    @Bean
    public Job trMigrationJob(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
        return new JobBuilder(JOB_NAME, jobRepository)
                .incrementer(new RunIdIncrementer()) // sequential id
                .start(trMigrationStep(jobRepository, transactionManager)) // step 설정
                .build();
    }

    /**
     * Step 등록
     */
    @Bean
    @JobScope
    public Step trMigrationStep(JobRepository jobRepository, PlatformTransactionManager transactionManager) {
        return new StepBuilder(STEP_NAME, jobRepository)
                .&amp;lt;Order, Account&amp;gt;chunk(5, transactionManager) // chunkSize: 몇 개 단위로 데이터를 처리할 것인지 지정. 참고로 파라미터로 받아서 사용할 수는 없다.
                .reader(trOrderReader())
                .processor(trOrderProcessor())
                .writer(trOrderWriter())
                .build();
    }

    /**
     * Account를 Repository의 method를 명시하여 데이터 처리
     */
    @Bean
    @StepScope
    public RepositoryItemWriter&amp;lt;Account&amp;gt; trOrderWriter() {
        return new RepositoryItemWriterBuilder&amp;lt;Account&amp;gt;()
                .repository(accountRepository)
                .methodName(&quot;save&quot;)
                .build();
    }

    /**
     * Order를 Account로 변경하는 Processor
     */
    @Bean
    @StepScope
    public ItemProcessor&amp;lt;Order, Account&amp;gt; trOrderProcessor() {
        return new ItemProcessor&amp;lt;Order, Account&amp;gt;() {
            @Override
            public Account process(Order item) throws Exception {
                return new Account(item);
            }
        };
    }

    @Bean
    @StepScope
    public RepositoryItemReader&amp;lt;Order&amp;gt; trOrderReader() {
        return new RepositoryItemReaderBuilder&amp;lt;Order&amp;gt;()
                .name(&quot;trOrderReader&quot;)
                .repository(orderRepository)
                .methodName(&quot;findAll&quot;)
                .pageSize(5) // chunkSize와 일치하게 설정
                .arguments(List.of())
                .sorts(Collections.singletonMap(&quot;id&quot;, Sort.Direction.ASC))
                .build();

    }


}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 작성한 testJob과 비슷하지만, 보다 세부적인 작업을 위해서 tasklet이 아닌 ItemReader, ItemProcessor, ItemWriter를 사용하였다.&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;reader에서 orderRepository의 findAll() 메소드를 통해서 데이터를 조회한 후 id순 정렬하고, processort에서 Order객체를 Account객체로 변환한 후, 받아온 Item 각각을 writer에서 accountRepository의 save 메소드의 인자로 넘기는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고로 Chunk란, ItemReader를 통해서 읽은 아이템을 쌓아두는 단위이며 Job 생성시점에 정의된다. Chunk size만큼 데이터가 쌓였다면 ItemWriter로 일괄 전달되며, Chunk 단위로 트랜잭션 작업(커밋)이 이루어진다. 추가로 txOrderReader를 보면 pageSize()라는 메서드가 존재하는데, 이는 읽어올 때 페이징하는 데이터 개수로 chunk와는 다른 개념이다.&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;465&quot; data-origin-height=&quot;176&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b624wa/btsGUWpOM6q/E2CoJb1HYjJsZPfbCuCs6K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b624wa/btsGUWpOM6q/E2CoJb1HYjJsZPfbCuCs6K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b624wa/btsGUWpOM6q/E2CoJb1HYjJsZPfbCuCs6K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb624wa%2FbtsGUWpOM6q%2FE2CoJb1HYjJsZPfbCuCs6K%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;465&quot; height=&quot;176&quot; data-origin-width=&quot;465&quot; data-origin-height=&quot;176&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;이렇게 설정파일을 세팅하고 스프링부트를 실행시키면 (현재 스케줄러가 없으므로) 위 사진과 같이 Order테이블의 데이터들이 Account 테이블로 넘어간 것을 확인할 수 있다. 또한 스프링부트를 반복 실행시켜서 Job을 여러 차례 동작시켜도 Accounts테이블에는 데이터가 반복 추가되지 않았는데, 아마도 Spring Batch의 메타데이터를 관리하는 JobRepository에서&amp;nbsp; 이미 실행된 Job이라고 판단하여 Job의 반복 실행을 방지하는 것이라고 추측된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;스케줄러 적용&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;스케줄링&lt;/b&gt;(Scheduling)이란 &lt;b&gt;일정한 시간 간격을 두고 반복적인 작업을 수행&lt;/b&gt;하는 것을 말한다. 앞서 말했듯이 배치 작업의 경우 실시간 작업이 아닌, 특정 시간대에 예약 작업으로 진행되는 경우가 많다. 이를 적용하기 위해서 스프링에서 제공하는 @Scheduler 어노테이션이나, spring boot quartz와 같은 스케줄링 라이브러리를 사용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;453&quot; data-origin-height=&quot;139&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FjW0j/btsG3PisebJ/pXuEeCM8xQzHg5gKVG3rkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FjW0j/btsG3PisebJ/pXuEeCM8xQzHg5gKVG3rkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FjW0j/btsG3PisebJ/pXuEeCM8xQzHg5gKVG3rkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFjW0j%2FbtsG3PisebJ%2FpXuEeCM8xQzHg5gKVG3rkk%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;453&quot; height=&quot;139&quot; data-origin-width=&quot;453&quot; data-origin-height=&quot;139&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;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;@Component
@RequiredArgsConstructor
public class SampleScheduler {

    private final Job testJob;
    private final JobLauncher jobLauncher; // 스케줄링을 활용하여 Job 실행

    @Scheduled(cron = &quot;0 */1 * * * *&quot;) // 1분마다 실행할 수 있게 함
    public void testJobRun() throws JobInstanceAlreadyCompleteException, JobExecutionAlreadyRunningException, JobParametersInvalidException, JobRestartException {

        JobParameters jobParameters = new JobParameters(
                Collections.singletonMap(&quot;requestTime&quot;, new JobParameter(System.currentTimeMillis(), Long.class))
        );

        jobLauncher.run(testJob, jobParameters);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Scheduled 어노테이션을 사용하여 Job을 실행하는 예시이다. application.yml 파일에서 batch의 enabled 속성을 false로 지정하여 Job이 스프링 부트 시작시점에 자동 실행되는 것이 아니라 스케줄러에게 위임되고, JobLauncher를 사용하여 직접 주입받은 Job을 실행한다.&lt;/p&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;실습 github: &lt;a href=&quot;https://github.com/28th-meetup/28th_Meetup_BE&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/28th-meetup/28th_Meetup_BE&lt;/a&gt;, &lt;a href=&quot;https://github.com/eckrin/various-tests/commits/master/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/eckrin/various-tests/commits/master/&lt;/a&gt;&lt;/p&gt;</description>
      <category>[ Backend ]/Spring</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/191</guid>
      <comments>https://eckrin.tistory.com/191#entry191comment</comments>
      <pubDate>Sun, 21 Apr 2024 23:13:40 +0900</pubDate>
    </item>
    <item>
      <title>BOJ 플레 달성</title>
      <link>https://eckrin.tistory.com/190</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;210&quot; data-origin-height=&quot;210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/C7cez/btsGBRVIu3h/HvK8D3KQZKtB1Fr8cHfIrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/C7cez/btsGBRVIu3h/HvK8D3KQZKtB1Fr8cHfIrk/img.png&quot; data-alt=&quot;귀엽다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/C7cez/btsGBRVIu3h/HvK8D3KQZKtB1Fr8cHfIrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FC7cez%2FbtsGBRVIu3h%2FHvK8D3KQZKtB1Fr8cHfIrk%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;210&quot; height=&quot;210&quot; data-origin-width=&quot;210&quot; data-origin-height=&quot;210&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;귀엽다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://eckrin.tistory.com/65&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://eckrin.tistory.com/65&lt;/a&gt; 이후 무려 2년 2개월만에 달성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;막타는 &lt;a href=&quot;https://www.acmicpc.net/problem/17143&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BOJ17143&lt;/a&gt;으로 쳤다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1073&quot; data-origin-height=&quot;567&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kGJR5/btsGAfpL1ia/SPwLO51QT1Rw07d2rDLQQK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kGJR5/btsGAfpL1ia/SPwLO51QT1Rw07d2rDLQQK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kGJR5/btsGAfpL1ia/SPwLO51QT1Rw07d2rDLQQK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkGJR5%2FbtsGAfpL1ia%2FSPwLO51QT1Rw07d2rDLQQK%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;1073&quot; height=&quot;567&quot; data-origin-width=&quot;1073&quot; data-origin-height=&quot;567&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나도 이제 (물)플레&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;122&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yUbDO/btsGC3nkcTT/UpO0EmvvnZTPUCi3PQw9k1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yUbDO/btsGC3nkcTT/UpO0EmvvnZTPUCi3PQw9k1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yUbDO/btsGC3nkcTT/UpO0EmvvnZTPUCi3PQw9k1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyUbDO%2FbtsGC3nkcTT%2FUpO0EmvvnZTPUCi3PQw9k1%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;1072&quot; height=&quot;122&quot; data-origin-width=&quot;1072&quot; data-origin-height=&quot;122&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기념으로 난이도 기여도 해봤다&lt;/p&gt;</description>
      <category>[ 기타 ]/잡담</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/190</guid>
      <comments>https://eckrin.tistory.com/190#entry190comment</comments>
      <pubDate>Sun, 14 Apr 2024 02:27:22 +0900</pubDate>
    </item>
    <item>
      <title>[알고리즘] Traveling Salesman Problem</title>
      <link>https://eckrin.tistory.com/189</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;283&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ty4Nx/btsGqsvmP0D/mDWuzn0fsO2rHD4zkokVH1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ty4Nx/btsGqsvmP0D/mDWuzn0fsO2rHD4zkokVH1/img.png&quot; data-alt=&quot;ㅋㅋ&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ty4Nx/btsGqsvmP0D/mDWuzn0fsO2rHD4zkokVH1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fty4Nx%2FbtsGqsvmP0D%2FmDWuzn0fsO2rHD4zkokVH1%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;640&quot; height=&quot;283&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;283&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;ㅋㅋ&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;따로 알고리즘 포스팅을 하는건 union-find 이후 1년만이다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 코테에 나올 확률이 매우 드문 알고리즘이라고 생각해 본인이 코테 대비만 한다면 굳이 알 필요는 없을 것 같지만.. 개인적인 호기심으로 작년 말부터 정리해야겠다 생각은 했는데, 그동안 놓았던 ps를 다시 감을 잡고 돌아오는데 시간이 많이 걸렸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Travelling_salesman_problem&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;위키&lt;/a&gt;를 보면 Travelling Salesman Problem(이하 TSP)를 다음과 같이 설명하고 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;&quot;Given a list of cities and the distances between each pair of cities, what is the shortest possible route that visits each city exactly once and returns to the origin city?&quot;&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 가중치가 존재하는 양방향 그래프에서, &lt;b&gt;모든 노드를 한 번씩 방문하고 출발점으로 돌아오는 경우의 수 중 가장 가중치의 합이 작은 경로&lt;/b&gt;를 찾는 문제이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;브루트 포스&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;가장 쉬운 방법은 모든 도시를 순회하는 모든 경우의 수를 구하는 것이다. 도시(정점)가 n개라고 하면, n개의 점을 순차 방문하는 경우의 수는 n!개임을 알 수 있다. 물론 팩토리얼의 시간복잡도를 가지는 만큼 매우 비효율적이다. 대신 동적 계획법을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;동적 계획법&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;아이디어&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 브루트포스 문제는 대부분의 경우 동적 계획법을 사용하여 시간복잡도를 줄일 수 있다고 알려져 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TSP에도 일반적으로 동적 계획법이 사용된다. dp의 핵심이 점화식인 이유는 n값에 상관없이 일정한 규칙을 찾는 것이 핵심이기 때문이다. 점화식을 찾아보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이해를 위해 쉽게 A~E까지 5개의 도시가 있을 때, 1, 2번째 도시를 방문하는 최적값이 A, B 순서라고 하자. 3번째 도시를 방문하려면 다음 세가지 경우의 수가 존재하며, 이 중 최소의 가중치를 가지는 경우의 수가 답이 된다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. C를 방문하고, 이후 D, E를 최소 가중치로 방문&lt;br /&gt;2. D를 방문하고, 이후 C, E를 최소 가중치로 방문&lt;br /&gt;3. E를 방문하고, 이후 C, D를 최소 가중치로 방문&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 문장을 살펴보면, *를 방문하고, 이후 남은 도시를 방문하는 최소 가중치의 합이 최종적인 답이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 말하면 &lt;b&gt;(방문하지 않은 점들 중 하나를 방문했을 때 가중치) + (그 점을 제외한 나머지 점들을 방문했을 때의 가중치)&lt;/b&gt;가 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;점화식 도출&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;dp[i][visited]를 현재 위치가 i이고, 방문 상태가 visited일때 남은 점들을 최소 거리로 돌았을 떄의 남은 이동 거리라고 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;visited는 방문 상태를 의미하므로, 비트 마스킹을 이용하여 0-1로 방문 상태를&lt;span style=&quot;color: #333333;&quot;&gt; 표시할 것이다. (당연히 리스트로도 표현할 수 있다)&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #666666;&quot;&gt;예를 들어, 현재 위치가 3이고 1,2번쨰 도시를 방문했다면 i=3, visited=00011(2) = 24가 된다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #666666;&quot;&gt;2^(n-1)번째 자리가 n번 도시를 방문했는지 여부를 나타내는 것.&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;/p&gt;
&lt;pre id=&quot;code_1712512400945&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;dp[i][visited] = min(dp[next][visited | (1 &amp;lt;&amp;lt; next)] + graph[i][j]), dp[i][visited])&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞서 말했듯이 (방문하지 않은 점들 중 하나를 방문했을 때 가중치) + (그 점을 제외한 나머지 점들을 방문했을 때의 가중치)와 기존값을 비교하여 가중치가 더 작은 값을 적용하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시작점을 어디로 잡는가는 중요하지 않다&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어떤 도시에서 시작하든, TSP의 핵심은 모든 도시의 탐색을 마치고 '재방문 없이 시작 도시로 돌아오는 것', 즉 사이클에 속해 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 구현이다. 위에서 구한 점화식에다 dfs를 통해서 다음에 방문할 도시를 검사할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 &lt;a href=&quot;https://www.acmicpc.net/problem/2098&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;백준에 있는 대표적인 TSP문제&lt;/a&gt;를 사용하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1712514627111&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private static int dfs(int cur, int visited) {
    if(visited == (1&amp;lt;&amp;lt;N)-1) { // 모든 도시를 방문한 경우
        // 출발도시로 가는 경로가 존재해야 돌아갈 수 있음 (존재하지 않으면 이 방법은 불가능)
        if(W[cur][0] == 0) return INF;
        return W[cur][0];
    }

    // 현재노드에서 탐색한 내역이 있다면, 중복탐색 방지
    if(dp[cur][visited] != -1) {
        return dp[cur][visited];
    }
    
    dp[cur][visited] = INF;
    for(int i=0; i&amp;lt;N; i++) {
        // 현재 노드에서 아직 방문하지 않는 i번 도시 가는 경로가 있는 경우
        if((visited &amp;amp; (1&amp;lt;&amp;lt;i))==0 &amp;amp;&amp;amp; W[cur][i]!=0) {
            // 방문해야하는 도시로 가는 최소 비용
            dp[cur][visited] = Math.min(dfs(i, visited | (1 &amp;lt;&amp;lt; i)) + W[cur][i], dp[cur][visited]);
        }
    }
    return dp[cur][visited];
}&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;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;dp[cur][visited] = INF;
for(int i=0; i&amp;lt;N; i++) {
    // 현재 노드에서 아직 방문하지 않는 i번 도시 가는 경로가 있는 경우
    if((visited &amp;amp; (1&amp;lt;&amp;lt;i))==0 &amp;amp;&amp;amp; W[cur][i]!=0) {
        // 방문해야하는 도시로 가는 최소 비용
        dp[cur][visited] = Math.min(dfs(i, visited | (1 &amp;lt;&amp;lt; i)) + W[cur][i], dp[cur][visited]);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 부분을 보면 dfs 실행시마다 현재 노드에서 아직 방문하지 않은 도시가 있는지(visited &amp;amp; (1&amp;lt;&amp;lt;i))==0)와 그 도시로 가는 경로가 있는지(W[cur][i]!=0)를 검사한 후, 있다면 모든 도시들에 대해서 최소 비용을 갱신한다. 이 때 dfs함수를 비용을 반환하도록 만들어서 모든 도시를 재귀탐색한 비용을 받도록 한다.&lt;/p&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>[ CS기초 ]/알고리즘</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/189</guid>
      <comments>https://eckrin.tistory.com/189#entry189comment</comments>
      <pubDate>Mon, 8 Apr 2024 02:13:32 +0900</pubDate>
    </item>
    <item>
      <title>[알고리즘] LIS로 알아보는 역추적 기법</title>
      <link>https://eckrin.tistory.com/188</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;600&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bb4O2H/btsF2D4eZPV/7HgU3pdjgJ53ZkmYp23eLK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bb4O2H/btsF2D4eZPV/7HgU3pdjgJ53ZkmYp23eLK/img.jpg&quot; data-alt=&quot;귀엽다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bb4O2H/btsF2D4eZPV/7HgU3pdjgJ53ZkmYp23eLK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbb4O2H%2FbtsF2D4eZPV%2F7HgU3pdjgJ53ZkmYp23eLK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;361&quot; height=&quot;361&quot; data-origin-width=&quot;600&quot; data-origin-height=&quot;600&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;귀엽다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고 문제: &lt;a href=&quot;https://www.acmicpc.net/problem/11053&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BOJ 11053&lt;/a&gt;, &lt;a href=&quot;https://www.acmicpc.net/problem/12015&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BOJ 12015&lt;/a&gt;, &lt;a href=&quot;https://www.acmicpc.net/problem/14003&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BOJ 14003&lt;/a&gt;, &lt;a href=&quot;https://www.acmicpc.net/problem/2568&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BOJ2568&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;LIS 개념: &lt;a href=&quot;https://eckrin.tistory.com/30&quot;&gt;https://eckrin.tistory.com/30&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;O(N^2)의 LIS 풀이:&amp;nbsp;https://www.acmicpc.net/source/37212239&lt;br /&gt;O(NlogN)의 LIS 풀이:&amp;nbsp;https://www.acmicpc.net/source/75537735&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711189006391&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for(int i=0; i&amp;lt;N; i++) //오른쪽에 위치한 수들에 대하여
    for(int j=0; j&amp;lt;N; j++) 
        if(arr[i]&amp;lt;arr[j]) //조건에 맞을경우 캐시에 저장
            dp[j] = max(dp[j], dp[i]+1);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;반복문만을 사용하는 LIS의 경우, O(N^2)의 시간복잡도를 가진다. 여기서 12015번 문제의 경우는 N이 백만으로, O(N^2)의 시간복잡도를 가지는 일반적인 LIS를 사용하면 무조건 TLE가 날 것을 예상할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1711189085444&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int end = 0;
for(int i=0; i&amp;lt;N; i++) { // 배열 각각의 원소에 대하여
    if(end==0) { // 초기화
        dp[end] = arr[i];
        continue;
    }
    if(arr[i]&amp;gt;dp[end-1]) { // dp 최대길이 갱신
        dp[end] = arr[i];
        end++;
    } else {
        int curIdx = binarySearch(0, end, arr[i]); // 내부로 들어갈 경우 0~end범위 이진탐색
        dp[curIdx] = arr[i];
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;910&quot; data-origin-height=&quot;904&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ca1PID/btsF2QCiCgv/9PYkhOjIkik0D5ngy7YtGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ca1PID/btsF2QCiCgv/9PYkhOjIkik0D5ngy7YtGk/img.png&quot; data-alt=&quot;https://loosie.tistory.com/376&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ca1PID/btsF2QCiCgv/9PYkhOjIkik0D5ngy7YtGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fca1PID%2FbtsF2QCiCgv%2F9PYkhOjIkik0D5ngy7YtGk%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;813&quot; height=&quot;808&quot; data-origin-width=&quot;910&quot; data-origin-height=&quot;904&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://loosie.tistory.com/376&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 위와 같이 binary search를 사용하면 O(N^2)인 LIS의 시간복잡도를 O(NlogN)으로 줄일 수 있다. 간단하게 요약하면, arr값을 하나하나 읽어가며 dp배열(위 그림에서는 memo)의 어느 위치에 들어가야 할지를 정하는 것이다.&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;12015번은 LIS의 길이만을 구하라고 하기 때문에 배열에 0~end 범위내에서 이진탐색하거나, 배열에 추가하여 end범위를 늘리는 선택을 반복하여 답을 구할 수 있다. 그런데 14003번은 LIS의 길이와 함께 LIS를 만족하는 배열을 출력하라는 추가조건이 있는데, 이럴 때 사용할 수 있는 방법이 &lt;b&gt;역추적&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;역추적 알아보기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;binary search를 이용한 LIS에서 arr배열을 탐색해나가면서 dp배열의 어느 위치에 들어가야 할지를 정하고(맨 끝일 경우 dp배열의 맨 마지막에, 중간일 경우 이진탐색을 사용하여 위치를 찾는다), 그 과정에서 dp배열은 계속 갱신되기 때문에 반복문이 끝나면 dp배열은 LIS를 만족시키는 배열과는 모양이 달라질 확률이 높다.&lt;/p&gt;
&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;해당 인덱스가 어떤 dp인덱스에 저장되었는지를 저장하는 배열(아래 index 배열)&lt;/b&gt;만 하나 만들어주면 된다. 예를 들자면, arr을 순회하면서 dp값을 저장할 때 arr[i]가 dp[j]에 저장되었다면 index[i]에는 j값을 넣어주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;arr = {1,5,6,2,3,4} 라고 하자. i=2까지 들어왔을때는 아래와 같은 모양을 띌 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 68px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;i&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;arr&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;6&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;dp&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;6&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;index&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&amp;nbsp;&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;i=3일때는 dp[1]에 있는 값이 덧씌워지고, index[3]에는 1(저장된 dp의 index값)이 들어간다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 68px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;i&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;arr&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;6&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;dp&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;6&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;index&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&amp;nbsp;&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;i=4일때는 dp[2]의 값이 덧씌워지고, index[4]에는 2가 들어간다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 68px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;i&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;arr&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;6&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;dp&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;index&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&amp;nbsp;&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;i=5일때는 dp[3]에 새로운 값이 들어가고, index[5]에는 3이 들어간다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 68px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;i&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;arr&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;6&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;dp&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;index&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;3&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;최종적인 배열의 형태는 위와 같다.&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;이제 LIS의 길이는 end값(4)이 되고, index에서 LIS가 만들어질 때의 배열 형태를 추출하면 되는데, 그 방법은 오른쪽부터 end-1부터 시작하며 처음 나오는 값을 출력하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3이 처음 나오는 위치는 i=5이므로 arr[5]를 출력하고, 2가 처음 나오는 위치는 i=4이므로 arr[4], 1이 처음나오는 위치는 i=3이므로 arr[3], 0이 처음나오는 위치는 i=0이므로 arr[0]을 출력하면 된다. 이때 나는 index를 i=0부터 스택에 넣고 하나씩 빼내면서 처음 나오는지 여부를 확인해나갔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(end = 4)&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 34px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;n (0&amp;lt;=n&amp;lt;end)&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 20%;&quot;&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 20%; height: 17px;&quot;&gt;n이 처음 나오는 위치&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 17px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 17px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 17px;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 17px;&quot;&gt;5&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 20%; height: 17px;&quot;&gt;arr[n이 처음 나오는 위치]&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 17px;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 20%; height: 17px;&quot;&gt;4&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;1 2 3 4가 답이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;이유?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;왜 오른쪽부터 처음 나오는 값의 인덱스 i를 찾아 arr[i]를 호출하는건지 궁금할 수도 있는데, 정확히 말하면 처음 나오는 값의 인덱스를 무조건 호출하기 때문이 아니라, &lt;b&gt;end-1부터 시작하여 오른쪽부터 하나씩 줄여가며 인덱스를 호출하기 때문&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;(end=3)&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 68px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;i&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;arr&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;7&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;dp&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;6&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;7&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;index&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;0&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 14.2857%; height: 17px;&quot;&gt;1&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;이 케이스를 보자. 현재 dp값은 1 6 7로, 주어진 arr의 LIS가 아니다. 하지만 end-1부터 오른쪽부터 탐색하면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;end-1 (2)가 처음 나오는 인덱스 : 2&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;end-2 (1)이 처음 나오는 인덱스: 1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;end-3 (0)이 처음 나오는 인덱스: 0&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로, 해당 인덱스들의 arr값을 출력하면 정답 (1 5 7)이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;end-1은 반드시 LIS의 가장 오른쪽 값이기 때문에, 그로부터 왼쪽에 위치한 end-2값은 반드시 end-1보다 작게 되는 것이다. 다시 말해서 end-1부터 시작하여 1개씩 감소하는 (end-1, end-2, ... ,0) 어떤 순서도 LIS가 된다.&lt;/p&gt;</description>
      <category>[ CS기초 ]/알고리즘</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/188</guid>
      <comments>https://eckrin.tistory.com/188#entry188comment</comments>
      <pubDate>Sat, 23 Mar 2024 16:19:03 +0900</pubDate>
    </item>
    <item>
      <title>자바에서 다양한 정렬기준 커스텀하기</title>
      <link>https://eckrin.tistory.com/186</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Comparator를 사용하려면 Array나 List Collection에 대해서 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. Primitive type array&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;391&quot; data-origin-height=&quot;160&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmH6BX/btsFZCxk03F/uz2y6A4aOVWzdhkLWlTYv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmH6BX/btsFZCxk03F/uz2y6A4aOVWzdhkLWlTYv1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmH6BX/btsFZCxk03F/uz2y6A4aOVWzdhkLWlTYv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmH6BX%2FbtsFZCxk03F%2Fuz2y6A4aOVWzdhkLWlTYv1%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;391&quot; height=&quot;160&quot; data-origin-width=&quot;391&quot; data-origin-height=&quot;160&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;361&quot; data-origin-height=&quot;46&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6QwtZ/btsFYDcq8xd/JF3q7VztZzX9OOskPKdnv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6QwtZ/btsFYDcq8xd/JF3q7VztZzX9OOskPKdnv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6QwtZ/btsFYDcq8xd/JF3q7VztZzX9OOskPKdnv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6QwtZ%2FbtsFYDcq8xd%2FJF3q7VztZzX9OOskPKdnv0%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;361&quot; height=&quot;46&quot; data-origin-width=&quot;361&quot; data-origin-height=&quot;46&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;primitive type의 경우 Comparator를 사용할 수 없다. 내림차순 정렬을 위해서 Collections.reverseOrder()를 사용하기 위해서는 아래와 같이 Wrapper Type으로 감싸서 사용해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Wrapper type array&lt;/b&gt;&lt;/h2&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;Integer[] arr = new Integer[30];
Arrays.sort(arr, new Comparator&amp;lt;Integer&amp;gt;() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1-o2;
    }
});&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. Collection&lt;/b&gt;&lt;/h2&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;ArrayList&amp;lt;Node&amp;gt; nodes = new ArrayList&amp;lt;&amp;gt;();

Collections.sort(arr, new Comparator&amp;lt;Node&amp;gt;() {
    @Override
    public int compare(Node n1, Node n2) {
        return n1.start-o2.start;
    }
});&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1711633058840&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;class Node implements Comparable&amp;lt;myObject&amp;gt; {
    int start;
    int end;

    @Override
    public int compareTo(myObject o) {
    	return this.start-o.start;
    }
}

// 사용
Collctions.sort(node);&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;Array.sort()함수의 인자로 Comparator의 compare()함수를 구현하여 넣어주거나, 대상 클래스를 Comarable&amp;lt;&amp;gt;을 구현하도록 만들고 compareTo() 함수를 오버라이딩해주면 된다.&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. Priority Queue&lt;/b&gt;&lt;/h2&gt;
&lt;pre id=&quot;code_1711004942307&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;static PriorityQueue&amp;lt;Jewel&amp;gt; pq = new PriorityQueue&amp;lt;&amp;gt;(new Comparator&amp;lt;Jewel&amp;gt;() {
    @Override
    public int compare(Jewel o1, Jewel o2) {
        return o2.v-o1.v; // value 내림차순 정렬
    }
});&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;우선순위 큐의 경우도 인자로 Comparator의 compare()함수를 구현하여 넣어주면 된다.&lt;/p&gt;</description>
      <category>[ 코딩테스트 ]</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/186</guid>
      <comments>https://eckrin.tistory.com/186#entry186comment</comments>
      <pubDate>Thu, 21 Mar 2024 15:13:18 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 프록시(Proxy)와 스프링 AOP</title>
      <link>https://eckrin.tistory.com/184</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;예전 &lt;a href=&quot;https://eckrin.tistory.com/32&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;JPA를 처음 공부하던 시절&lt;/a&gt;에 '프록시'라는 개념을 접한 적이 있고, 구조 패턴 중 하나인 &lt;a href=&quot;https://eckrin.tistory.com/164&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;프록시 패턴과 데코레이터 패턴&lt;/a&gt;에 대해서 공부했던 적이 있다. 이처럼 스프링 환경의 다양한 곳에서 프록시가 사용되고 있다는 것은 들어본 적이 있는데, 지금까지는 @Transactional 사용시 &lt;a href=&quot;https://eckrin.tistory.com/155&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;스프링이 트랜잭션 대상 코드를 프록시 형태로 감싼다&lt;/a&gt;는 정도로만 알고 있었다. 이번 포스트에서 프록시에 대한 개념부터, 스프링이 프록시를 어떻게 활용하고 있는지에 대해서 자세하게 정리해보도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;동적 프록시&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;타겟 클래스 하나마다 프록시 클래스를 직접 생성하여 적용하는 방법을 &lt;b&gt;정적 프록시&lt;/b&gt;라고 한다. 프록시의 사용 목적이 접근 제어나 기능 추가와 같이 공통 코드의 반복이라는 점을 고려하면, 프록시 적용 대상이 되는 서비스 코드의 증가에 따라서 유사한 프록시 클래스가 반복 생성되는 문제를 낳게 될 확률이 높다.&amp;nbsp;따라서 JDK나 CGLIB같은 기술을 사용하여&amp;nbsp;&lt;b&gt;동적으로 프록시를 생성 및 적용&lt;/b&gt;할 수 있다. 단, 이러한 동적 프록시 기술을 직접 사용하기보다는 원리를 이해하여 JDK프록시, CGLIB와 같은 스프링에서 제공하는 AOP 기술들을 이해하는 것을 목적으로 하자.&lt;/p&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;b&gt;핸들러&lt;/b&gt;(Handler)를 만들어 적용할 공통 로직을 작성하고, 프록시를 적용할 &lt;b&gt;타겟&lt;/b&gt;(target)과 함께 핸들러를 같이 지정해주어 타겟 클래스가 동작할 때 핸들러의 로직이 돌아가도록 하는 원리로 동작한다. 타겟의 경우 인터페이스를 이용하거나, 구체 클래스를 이용하는 방법 모두 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;리플렉션&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;리플렉션은 Java 프로그램 실행 중에 클래스나 메서드의 메타정보를 사용하여 호출하는 메소드를 동적으로 탐색하고 조작할 수 있는 기술이다.&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;Target target = new Target(); // 실제 인스턴스
// 클래스 메타정보 획득
Class classInfo = Class.forName(&quot;com.eckrin.test.Reflection$reflectionTest&quot;);
// 메서드 메타정보 획득
Method methodCall = classInfo.getMethod(&quot;execute&quot;); // 문자열을 사용한 함수 호출 -&amp;gt; 파라미터화해서 클래스 분리 가능
Object result = methodCall.invoke(target); // 실제 인스턴스의 메소드 호출&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;자바에서 제공하는 Class.forName()이나, Class.getMethod(), Method.invoke()와 같은 메소드들을 사용하여 특정 클래스나 메소드의 메타정보를 가져오고, 문자열을 이용해서 함수를 호출할 수 있다. 이렇게 리플렉션을 사용하면 함수 호출을 파라미터화해서 로직을 분기하여 동적 프록시를 사용할 수 있다. 하지만 .getMethod()를 보면 파라미터로 문자열을 받기 때문에, &lt;b&gt;파라미터 관련 에러를 컴파일 시점에 잡아낼 수 없다&lt;/b&gt;는 단점도 존재하며, 일반적인 메서드 호출보다 속도가 느리고 직관성이 떨어진다는 문제가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링에서도 &lt;b&gt;@Autowired&lt;/b&gt;, &lt;b&gt;@Component를 통한 빈 등록(Dependency Injection)&lt;/b&gt;, &lt;b&gt;Spring Data JPA에서의 메서드 명명규칙 분석&lt;/b&gt;과 같이 다양한 곳에서 리플렉션을 활용하고 있는데, &lt;b&gt;Spring AOP에서도 리플렉션을 통해서 메서드의 실행을 가로채서&lt;/b&gt; 공통 로직을 추가하여 실행한다.&lt;/p&gt;
&lt;pre id=&quot;code_1720147554293&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Aspect
public class LoggingAspect {
    @Before(&quot;execution(* com.example.service.*.*(..))&quot;)
    public void logBefore(JoinPoint joinPoint) {
        System.out.println(&quot;Before method: &quot; + joinPoint.getSignature().getName());
    }
}&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;예를 들어, Spring AOP는 위에서 execution 포인트컷 표현식을 분석한 이후, 리플렉션을 사용하여 해당하는 메서드를 찾아 요청을 가로채서 logBefore() 메서드를 실행하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;JDK 동적 프록시&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;JDK 동적 프록시는 InvocationHandler를 구현하여 적용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// InvocationHandler를 구현한 TimeInvocationHandler.java

/**
 * @param proxy 메서드가 호출될 프록시 인스턴스
 * @param method 호출할 메서드 정보
 * @param args 메서드에 들어갈 파라미터들
 */
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    /**
     * @param target 메소드를 호출할 인스턴스
     * @param args 메소드 호출 요청시 넘길 파라미터들
     */
     
    // 이부분에 공통 로직 추가
    Object result = method.invoke(target, args);
    // 이부분에 공통 로직 추가
    return result;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;위에서 InvocationHandler를 오버라이딩한 클래스(TimeInvocationHandler.java)의 invoke 메소드를 보면, method 파라미터의 invoke 메소드를 통해서 프록시를 통해서 실행할 타겟을 지정해주고 있다. 필요한 공통 로직의 경우 위에서 오버라이딩한 invoke() 메소드 안에 함께 작성해주면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1707219728869&quot; class=&quot;oxygene&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;Object result = method.invoke(target, args);&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;오버라이딩한 invoke() 메소드 안의 이 부분이 실제 로직으로 제어가 넘어가는 부분이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;TimeInvocationHandler를 사용하여 프록시 객체를 사용하는 모습을 보자.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Test
void dynamicJDK() {
    InterfaceEx target = new ExImpl();
    TimeInvocationHandler handler = new TimeInvocationHandler(target);

    /**
     * @param loader 프록시의 로딩에 사용될 클래스 로더
     * @param interfaces 프록시가 구현해야 하는 인터페이스들의 리스트
     * @param invocationHandler 프록시 객체의 메서드 호출을 처리하는 invoke 메소드를 구현한 객체
     */
    // 동적으로 프록시 객체 생성
    InterfaceEx proxy = (InterfaceEx) Proxy.newProxyInstance(
            InterfaceEx.class.getClassLoader(),
            new Class[]{InterfaceEx.class},
            handler
    );
    proxy.call();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1.&lt;/b&gt; InvocationHandler의 invoke 메소드를 구현하여 만든 TimeInvocationHandler가 newProxyInstance라는 Proxy 클래스의 static 메서드의 인자로 들어가고, &lt;b&gt;그 결과로 프록시 객체가 반환&lt;/b&gt;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2.&lt;/b&gt; 이 객체는 JDKProxy가 동적으로 만들어준 프록시 객체이며, 프록시 객체에 있는 call()메소드를 호출하면 newProxyInstance 메소드의 세 번째 인자로 들어온 TimeInvocationHandler의 invoke 메소드 전후에 있는 로직이 같이 수행된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;CGLIB&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;CGLIB(code generator library)라는 외부 라이브러리도 사용할 수 있는데, &lt;span style=&quot;background-color: #ffffff; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;CGLIB&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;를 사용하면 인터페이스가 없이 구체 클래스만 가지고도 동적 프록시를 만들어낼 수 있다&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&quot;&gt;. JDKProxy를 사용할 때는 동적 프록시 적용을 위해서 InvocationHandler를 구현했다면, CGLIB는 MethodIntercepter를 제공한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// MethodInterceptor 구현

/**
 *
 * @param obj CGLIB가 적용된 객체
 * @param method 호출된 메서드
 * @param args 메서드를 호출하면서 전달된 인수
 * @param methodProxy 프록시 적용할때 사용
 */
@Override
public Object intercept(Object obj, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
    // 이 부분에 공통 로직 추가
    Object result = methodProxy.invoke(target, args); // 프록시를 적용할 인스턴스의 메소드
    // 이 부분에 공통 로직 추가    
    return result;
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Slf4j
public class CglibTest {

    @Test
    void cglib() {
        ConcreteService target = new ConcreteService();

        // 프록시 설정
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(ConcreteService.class); // 구체 클래스를 상속받아서 프록시를 생성할 수 있다.
        enhancer.setCallback(new TimeMethodInterceptor(target));
        // 프록시 생성
        ConcreteService proxy = (ConcreteService) enhancer.create();
        proxy.call();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;&lt;b&gt;1.&lt;/b&gt; MethodInterceptor를 구현하는 구현체를 만들고 intercept() 메서드의 invoke()메서드 전후로 프록시 로직을 작성해준다&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #333333; text-align: start;&quot;&gt;2. &lt;/span&gt;&lt;/b&gt;CGLIB가 제공하는 cglib.proxy.Enhancer 클래스에 MethodInterceptor의 구현체를 넣어주면 위와 같이 프록시를 세팅, 생성해서 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인자로 제공하는 methodProxy를 이용해서 프록시를 적용하는 구체적인 로직은 JDKProxy와 차이가 존재하지만, 플로우 자체는 유사함을 알 수 있다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;스프링과 프록시&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;스프링에서 사용하는 프록시 기능들도 위에서 소개한 JDK 동적프록시나 CGLIB를 이용한다. (기본적으로 spring 2.5 이후 버전부터는 CGLIB를 이용하여 프록시를 구현한다) 스프링에서 제공하는 ProxyFactory를 사용하면 JDK 동적 프록시와 CGLIB를 같이 사용하는 것도 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Test
@DisplayName(&quot;JDKProxy를 사용하여 ServiceInterface의 구현체들에 동적 프록시 적용&quot;)
void interfaceProxy() {
    ServiceInterface target = new ServiceImpl();
    ProxyFactory proxyFactory = new ProxyFactory(target); // 프록시를 만들때 타겟 주입
    proxyFactory.addAdvice(new TimeAdvice());
    ServiceInterface proxy = (ServiceInterface) proxyFactory.getProxy(); // 프록시객체 생성
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;포인트컷(Pointcut): 프록시를 통해 적용할 로직을 적용할지 여부를 결정하는 필터링 로직이다.&amp;nbsp;&lt;br /&gt;어드바이스(Advice): 프록시를 통해 적용할 로직 자체를 말한다.&lt;br /&gt;어드바이저(Advisor): Pointcut+Advice 쌍을 의미한다.&lt;br /&gt;-&amp;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;앞에서 JDK 동적 프록시와 CGLIB를 직접 구현하여 사용할 때에도 프록시를 적용할 대상과 로직을 핸들러를 통해서 지정했는데, Pointcut을 적용 대상을 지정하는 필터, Advice이 적용하는 공통 로직이라고 할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;714&quot; data-origin-height=&quot;462&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bswsHm/btsEGj7lSem/213kyqoKk7eo3HKTTjhoxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bswsHm/btsEGj7lSem/213kyqoKk7eo3HKTTjhoxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bswsHm/btsEGj7lSem/213kyqoKk7eo3HKTTjhoxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbswsHm%2FbtsEGj7lSem%2F213kyqoKk7eo3HKTTjhoxK%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;328&quot; height=&quot;212&quot; data-origin-width=&quot;714&quot; data-origin-height=&quot;462&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;스프링은 이를 위해서 Pointcut이라는 인터페이스를 지원하는데, 위에 보이는 두 메서드를 오버라이딩하여 적용할 클래스와 메소드 조건을 걸어줄 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;빈 후처리기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;@Bean이나 @Component와 같은 어노테이션을 설정해주면 스프링은 컴포넌트 스캔을 통해서 객체를 스프링 빈으로 등록하게 되는데, 이러한 작업은 ApplicationContext가 생성될 때 이루어진다. 따라서 컴포넌트 스캔을 통해서 등록된 객체에 프록시 패턴을 적용하기 위해서는 원래 서비스 객체가 스프링 빈으로 등록되는 과정 사이에 개입이 필요한데, 이 때 사용가능한 것이 스프링에서 제공하는 &lt;b&gt;빈 후처리기&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1566&quot; data-origin-height=&quot;104&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nsia6/btsEGLil5oU/RQVHF7NagR0kiGSGkIFVd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nsia6/btsEGLil5oU/RQVHF7NagR0kiGSGkIFVd0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nsia6/btsEGLil5oU/RQVHF7NagR0kiGSGkIFVd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fnsia6%2FbtsEGLil5oU%2FRQVHF7NagR0kiGSGkIFVd0%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;1566&quot; height=&quot;104&quot; data-origin-width=&quot;1566&quot; data-origin-height=&quot;104&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이것이 스프링에서 기본 제공하는 빈 후처리기이다. 이 후처리기는 &lt;b&gt;스프링 빈으로 등록된 Advisor&lt;/b&gt;들을 자동으로 찾고, 내부의 Pointcut과 Advice들을 바탕으로 프록시가 필요한 곳에 &lt;b&gt;자동으로 프록시를 적용&lt;/b&gt;해준다. 즉, Advisor들을 스프링 빈으로 등록해주기만 하면 Advisor를 통해서 Pointcut에 부합하는 경우 Advice를 추가하여 프록시 객체를 빈으로 등록해주는 것이다.&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;1342&quot; data-origin-height=&quot;146&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RdECt/btsEGSn9hep/jgL6kGK2psO6DrU5qXpCpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RdECt/btsEGSn9hep/jgL6kGK2psO6DrU5qXpCpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RdECt/btsEGSn9hep/jgL6kGK2psO6DrU5qXpCpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRdECt%2FbtsEGSn9hep%2FjgL6kGK2psO6DrU5qXpCpK%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;787&quot; height=&quot;86&quot; data-origin-width=&quot;1342&quot; data-origin-height=&quot;146&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;일반적으로 Pointcut은 이 AspectJExpressionPointcut을 많이 사용한다.&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;추가로 하나의 프록시에 여러개의 Advisor들을 포함할 수 있다는 점을 이용하여 스프링은 임의의 스프링 빈이 여러개의 Advisor의 Pointcut을 만족한다고 할 때에도 하나의 프록시만을 생성하여 비용을 최소화한다. 물론 생성된 프록시는 Pointcut을 만족하는 여러 Advisor들이 세팅되어 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Spring AOP&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;지금까지 설명한 프록시의 목적은 공통 로직에 대한 책임을 분리하고 코드의 중복을 최소화하는데 있다. 실제 스프링으로 서비스를 구현할때도 핵심 비즈니스 로직과 공통 부가 로직이 나누어지게 되는 경우가 많은데,&lt;b&gt;&amp;nbsp;AOP(Aspect-Oriented Programming)&lt;/b&gt;를 통해 핵심 기능에서 부가 기능을 분리하여 역할과 책임의 분리, 중복 최소화를 통해 개발시 변경 지점이 하나가 될 수 있도록 모듈화할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;사실 AOP를 적용하는 시점도 컴파일 시점, 클래스 로딩 시점, 런타임 시점과 같이 여러가지 방법들이 존재하는데, 위에서 설명한 프록시와 빈 후처리기를 활용하는 예시가 런타임 시점에 AOP를 적용하는 예시이다. AspectJ와 같은 기능을 사용하면 런타임이 아닌 컴파일, 클래스 로딩 시점에 AOP를 적용하는 것도 가능하기 때문에 스프링 빈을 통해 메소드에만 AOP를 적용할 수 있다는 단점이 상쇄되지만, 여기서는 프록시를 사용하는 스프링 AOP에 대해서 서술하도록 하겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;밑에 나오는 용어들은 스프링 AOP에서 사용되는 용어들이며 기억해두도록 하자(스프링 AOP는 AspectJ의 용어들을 활용하고 있으나 AspectJ를 그대로 사용하지는 않는다.).&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 200px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style5&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 19.0698%; height: 17px; text-align: center;&quot;&gt;JoinPoint&lt;/td&gt;
&lt;td style=&quot;width: 80.9302%; height: 17px;&quot;&gt;추상적인 개념으로, Advice가 적용될 수 있는 지점을 말한다. 스프링 AOP는 프록시 기반으로 동작하므로 항상 메소드 실행 시점으로 제한된다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 19.0698%; height: 17px; text-align: center;&quot;&gt;Pointcut&lt;/td&gt;
&lt;td style=&quot;width: 80.9302%; height: 17px;&quot;&gt;JoinPoint 중에 Advice가 적용되어야 하는 지점을 말하며, 주로 AspectJ 표현식을 사용하여 지정한다. AspectJ를 직접 사용하면 메서드 호출 외에도 생성자 호출, 필드값 접근, static메소드 접근과 같은 다양한 JoinPoint들에 Pointcut을 설정할 수 있으나, 스프링 AOP를 사용하면 메소드 호출 시점으로 제한된다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 19.0698%; text-align: center; height: 18px;&quot;&gt;Target&lt;/td&gt;
&lt;td style=&quot;width: 80.9302%; height: 18px;&quot;&gt;Advice를 받는 객체 자체를 말하며, Pointcut으로 대상이 결정된다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 37px;&quot;&gt;
&lt;td style=&quot;width: 19.0698%; text-align: center; height: 37px;&quot;&gt;Advice&lt;/td&gt;
&lt;td style=&quot;width: 80.9302%; height: 37px;&quot;&gt;부가 기능 자체를 의미하며, 특정 JoinPoint에서 Aspect에 의해서 실행되는 로직을 말한다. Around, Before, After와 같은 다양한 종류의 Advice들이 존재한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 56px;&quot;&gt;
&lt;td style=&quot;width: 19.0698%; text-align: center; height: 56px;&quot;&gt;Aspect&lt;/td&gt;
&lt;td style=&quot;width: 80.9302%; height: 56px;&quot;&gt;앞서 @Aspect 어노테이션을 사용한 적이 있는데, @Aspect를 적용한 클래스 내 Pointcut과 Advice(둘이 합쳐 Advisor)가 존재했다.&amp;nbsp; 스프링 AOP의 Aspect도 Advisor와 비슷하게 부가 기능과, 해당 부가 기능을 어디에 적용할지 정의한 것을 의미한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 19.0698%; text-align: center; height: 18px;&quot;&gt;Advisor&lt;/td&gt;
&lt;td style=&quot;width: 80.9302%; height: 18px;&quot;&gt;스프링 AOP에만 사용되는 용어로, 하나의 Pointcut-Advice 쌍을 말한다.&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 37px;&quot;&gt;
&lt;td style=&quot;width: 19.0698%; text-align: center; height: 37px;&quot;&gt;Weaving&lt;/td&gt;
&lt;td style=&quot;width: 80.9302%; height: 37px;&quot;&gt;Pointcut으로 결정한 Target의 JoinPoint에 Advice를 적용하는 행위를 말한다. 다시 말하면 Aspect를 객체에 연결하여 AOP를 적용하는 것을 말한다.&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;@Aspect&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;위에서 스프링 aop가 제공하는 빈 후처리기를 사용해서 Advisor를 스프링 빈으로 등록하는 과정을 통해서 프록시를 자동 생성할 수 있었다. 하지만 @Aspect 어노테이션을 이용하면 그러한 과정을 더욱 편리하게 간편화할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Aspect
public class LogTraceAspect {

    // Advisor
    @Around(&quot;execution(* packagename..*(..))&quot;) // Pointcut
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable { 
        // Advisor 로직
        Object result = joinPoint.proceed(); // 프록시로 감싼 객체 호출
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;클래스 레벨에 @Aspect 어노테이션을 선언하고, @Around 어노테이션이 붙은 메소드를 선언하면 하나의 Advisor가 완성된다. @Around 안에 들어간 표현식이 프록시를 적용한 Pointcut을 의미하고, execute함수의 body가 Advice가 되어 함수 전체가 Advisor로 동작할 수 있게 된다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;위와 같이 설정하면 앞서 설명한 스프링 aop의 빈 후처리기(AnnotationAwareAspectJAutoProxyCreator)가 @Aspect가 적용된 클래스들을 찾아서 Advisor로 등록해준다. 즉 스프링 aop의 빈 후처리기는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;스프링 빈으로 등록된 Advisor&lt;/b&gt;&amp;nbsp;뿐 아니라,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;@Aspect 어노테이션도 찾아서 Advisor로 변환하고 프록시를 생성&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Advice 범위 설정&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Advice를 코드레벨에서 컨트롤할 수도 있지만, 어노테이션을 통해서 적용 시점을 관리할 수 있다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 85px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style5&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 20.3488%; height: 17px;&quot;&gt;@Around&lt;/td&gt;
&lt;td style=&quot;width: 79.6512%; height: 17px;&quot;&gt;메소드 호출 전후에 전반적으로 수행된다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 20.3488%; height: 17px;&quot;&gt;@Before&lt;/td&gt;
&lt;td style=&quot;width: 79.6512%; height: 17px;&quot;&gt;JoinPoint 실행 직전에 수행한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 20.3488%; height: 17px;&quot;&gt;@After&lt;/td&gt;
&lt;td style=&quot;width: 79.6512%; height: 17px;&quot;&gt;JoinPoint 실행 후 정상 혹은 예외여부에 관계없이 수행한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 20.3488%; height: 17px;&quot;&gt;@AfterReturning&lt;/td&gt;
&lt;td style=&quot;width: 79.6512%; height: 17px;&quot;&gt;JoinPoint 정상실행 직후에 수행한다.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 20.3488%; height: 17px;&quot;&gt;@AfterThrowing&lt;/td&gt;
&lt;td style=&quot;width: 79.6512%; height: 17px;&quot;&gt;JoinPoint 실행후 예외 발생시 수행한다.&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;코드로 적용 위치를 살펴보면 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1707805510051&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Aspect
public class LogTraceAspect {

    @Around(&quot;execution(* packagename..*(..))&quot;) // Pointcut
    public Object execute(ProceedingJoinPoint joinPoint) throws Throwable { 
        try {
            // @Before
            Object result = joinPoint.proceed(); // JoinPoint 실행
            // @AfterReturning
        } catch (Exception e) {
            // @AfterThrowing
        } finally {
            // @After
        }
    }
}&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;사실 모두 @Around 안에서 수행되는 작업들이고, 별도의 함수로 분리하고 싶을 때 사용할 수 있다. 또한 @Around는 ProceedingJoinPoint를 인자로 받아 proceed() 메소드를 통해서 내부 로직을 실행해주어야 하지만(실수로 proceed() 메소드를 빼먹으면 비즈니스 로직은 실행되지 않고 공통 로직만 실행되는 문제가 생긴다), 나머지 어노테이션을 적용한 메소드는 JoinPoint만을 인자로 받고, proceed()를 별도로 수행하지 않아도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Pointcut 지시자&lt;/b&gt;&lt;/h3&gt;
&lt;pre id=&quot;code_1708242120188&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Around(&quot;execution(* packagename..*(..))&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;AspectJ는 Pointcut을 표현하기 위한 여러 지시자를 제공한다. 위에서 @Around 내에 존재하는 것을 포인트컷 표현식이라고 하고, 괄호를 묶고 있는 execution을 포인트컷 지시자라고 한다. Spring AOP에서 사용할 수 있는 지시자는 execution 이외에도 여러 종류가 있지만, 가장 많이 쓰이는 지시자는 execution이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Execution&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1708244601769&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; execution({접근제어자} 반환타입 {선언타입} 메서드이름(파라미터) {예외})&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 접근제어자와 선언타입, 메서드명과 예외는 생략할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;기타 포인트컷 지시자&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;execution 이외에는 사용빈도가 높아보이지는 않는다. args, @args, @target의 경우 단독으로 사용하면 안되고, 다른 지시자와 함께 사용해야 한다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 160px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style6&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 25.5814%; height: 18px;&quot;&gt;within&lt;/td&gt;
&lt;td style=&quot;width: 74.4186%; height: 18px;&quot;&gt;특정 타입 내의 JoinPoint로 매핑&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 25.5814%; height: 18px;&quot;&gt;args&lt;/td&gt;
&lt;td style=&quot;width: 74.4186%; height: 18px;&quot;&gt;인자가 주어진 타입의 인스턴스인 JointPoint로 매핑&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 25.5814%; height: 18px;&quot;&gt;@target&lt;/td&gt;
&lt;td style=&quot;width: 74.4186%; height: 18px;&quot;&gt;인스턴스의 모든 메서드를 JoinPoint로 매핑 (부모 클래스 메서드까지 적용)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 25.5814%; height: 18px;&quot;&gt;@within&lt;/td&gt;
&lt;td style=&quot;width: 74.4186%; height: 18px;&quot;&gt;해당 타입 내의 메서드를 JoinPoint로 매핑 (부모 클래스 메서드는 적용 X)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 25.5814%; height: 18px;&quot;&gt;@annotation&lt;/td&gt;
&lt;td style=&quot;width: 74.4186%; height: 18px;&quot;&gt;메서드가 주어진 어노테이션을 가지고 있는 JoinPoint로 매핑&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 25.5814%; height: 18px;&quot;&gt;@args&lt;/td&gt;
&lt;td style=&quot;width: 74.4186%; height: 18px;&quot;&gt;전달된 실제 인수의 런타임 타입이 주어진 타입의 어노테이션을 갖는 JoinPoint로 매핑&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 18px;&quot;&gt;
&lt;td style=&quot;width: 25.5814%; height: 18px;&quot;&gt;bean&lt;/td&gt;
&lt;td style=&quot;width: 74.4186%; height: 18px;&quot;&gt;스프링 빈의 이름으로 JointPoint 매핑&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25.5814%; height: 17px;&quot;&gt;this&lt;/td&gt;
&lt;td style=&quot;width: 74.4186%; height: 17px;&quot;&gt;스프링 빈 객체(AOP 프록시)를 대상으로 하는 JoinPoint로 매핑&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25.5814%; height: 17px;&quot;&gt;target&lt;/td&gt;
&lt;td style=&quot;width: 74.4186%; height: 17px;&quot;&gt;Target 객체(AOP 프록시가 가리키는 실제객체)를 대상으로 하는 JoinPoint로 매핑&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Spring AOP 사용시 주의할 점&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;내부 호출&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;앞서 이야기했듯이, 스프링 AOP는 실제 클래스가 아닌, 프록시 패턴이 적용된 클래스를 빈으로 등록하는 방법으로 동작한다. 코드를 직접 삽입하는(AspectJ같은) 방식이 아니기 때문에, 대상 클래스를 컨테이너에서 불러서 사용할 경우 프록시 객체가 호출되게 된다. 하지만 프록시 객체는 하나의 객체가 아니라 내부 객체에 프록시 패턴이 적용된 만큼, &lt;b&gt;this를 사용한 호출이 발생하는 경우 프록시가 적용되지 않은 내부 객체를 호출&lt;/b&gt;하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Slf4j
@Aspect
public class InternalAspect {

    @Before(&quot;execution(* com.eckrin.test.aop_internalcall..*.*(..))&quot;)
    public void doLog(JoinPoint joinPoint) {
        log.info(&quot;aop 적용됨 : {}&quot;, joinPoint.getSignature());
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;less&quot;&gt;&lt;code&gt;@Slf4j
@Component
public class InternalService {

    public void outer() {
        log.info(&quot;외부 메서드 호출 로직&quot;);
        inner(); // 내부 메서드 호출 (== this.inner();) -&amp;gt; AOP가 적용되지 않음
    }

    public void inner() {
        log.info(&quot;내부 메서드 호출 로직&quot;);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 간단하게 프록시가 적용될 타겟을 잡고, outer()메서드를 호출해보면 쉽게 결과를 확인해볼 수 있다. 의도대로라면 outer()메서드를 호출할 때와 outer() 내부에서 inner() 메서드를 호출할 때 모두 AOP프록시가 적용되어야 하지만, 로그를 확인하면 inner()에는 프록시가 적용되지 않는 것을 확인할 수 있다.&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;730&quot; data-origin-height=&quot;67&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b12M7K/btsFegKkXQg/ZKCXjAaGCfKk5oyvyFYPZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b12M7K/btsFegKkXQg/ZKCXjAaGCfKk5oyvyFYPZ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b12M7K/btsFegKkXQg/ZKCXjAaGCfKk5oyvyFYPZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb12M7K%2FbtsFegKkXQg%2FZKCXjAaGCfKk5oyvyFYPZ0%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;730&quot; height=&quot;67&quot; data-origin-width=&quot;730&quot; data-origin-height=&quot;67&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;이는 outer에서 호출한 inner메서드는 프록시가 적용된 그것이 아닌, 순수한 InternalService이기 때문이다. (this.inner()로 이해하면 바로 이해가 갈 것이다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자주 발생하지는 않을 상황인 것 같기는 하지만, AOP의 프록시에 대해서 이해하지 못하고 있다면 찾기 힘들 것 같아 정리해두었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;상속으로 인한 한계&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;스프링 AOP는 기본값으로 CGLIB를 사용한다.&lt;/p&gt;
&lt;pre id=&quot;code_1708793905190&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;spring.aop.proxy-target-class=true // default&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;JDK 동적 프록시는 인터페이스만을 대상으로 프록시를 적용할 수 있기 때문에 인터페이스가 존재하지 않는 경우 프록시를 적용할 수 없고, DI시에도 구체 클래스를 대상으로는 의존관계 주입이 불가능하기 때문이다. 하지만 CGLIB도 상속을 기본 전략으로 하기에 기본 생성자가 반드시 필요하고, final 클래스나 final 메서드가 존재하는 클래스를 대상으로는 프록시가 정상적으로 생성되지 않는다는 문제가 존재한다. (당연히 final 변수는 상관 없다 - 이 부분은 자바 문법과 관련된 부분임)&lt;/p&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;스프링은 이 중에서 'objenesis'라는 특별한 라이브러리를 사용하여 기본 생성자가 필수로 필요한 문제를 해결했다. 따라서 우리는 스프링 AOP를 이용할 때, &lt;b&gt;메서드나 클래스에 적용된 final 키워드에 대해서만 주의&lt;/b&gt;하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>[ Backend ]/Spring</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/184</guid>
      <comments>https://eckrin.tistory.com/184#entry184comment</comments>
      <pubDate>Sun, 4 Feb 2024 16:15:32 +0900</pubDate>
    </item>
    <item>
      <title>다양한 디자인 패턴 - (2) 행동 패턴</title>
      <link>https://eckrin.tistory.com/182</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1259&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsyPO1/btsDGmSMQni/uq2CbHQ0u20XVMXqCedz3K/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsyPO1/btsDGmSMQni/uq2CbHQ0u20XVMXqCedz3K/img.jpg&quot; data-alt=&quot;귀엽다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsyPO1/btsDGmSMQni/uq2CbHQ0u20XVMXqCedz3K/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsyPO1%2FbtsDGmSMQni%2Fuq2CbHQ0u20XVMXqCedz3K%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;175&quot; height=&quot;172&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1259&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;귀엽다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;행동 패턴?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;행동 패턴은 '&lt;b&gt;객체 사이의 상호작용&lt;/b&gt; 또는 &lt;b&gt;객체의 책임 할당&lt;/b&gt;'과 밀접한 관련이 있는 디자인 패턴이다. 한 객체가 수행할 수 없는 작업을 여러개의 객체로 분배하는 등의 작업을 통해서 객체 사이의 결합도를 최소화하는데 중점을 두는 패턴이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;템플릿 메서드 패턴&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;부모 클래스에서 골격을 정의하면 자식 클래스가 알고리즘의 특정 메소드를 오버라이딩하여 사용할 수 있도록 한다. 다시 말하자면, 부모 클래스에 변하지 않는 &lt;b&gt;템플릿&lt;/b&gt;을 두고, 변하는 부분을 &lt;b&gt;메소드&lt;/b&gt;의 형태로 자식 클래스에 두어 상속을 사용하여 문제를 해결한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;예를 들어 다음과 같이 메소드 실행에 대한 로깅을 한다고 가정하자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;427&quot; data-origin-height=&quot;179&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/djiOOB/btsD3M3fas3/1yPslTaAwZ7YW2ypNLbFIK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/djiOOB/btsD3M3fas3/1yPslTaAwZ7YW2ypNLbFIK/img.png&quot; data-alt=&quot;이해를 위한 코드이므로 불편해하지 말자&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/djiOOB/btsD3M3fas3/1yPslTaAwZ7YW2ypNLbFIK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdjiOOB%2FbtsD3M3fas3%2F1yPslTaAwZ7YW2ypNLbFIK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;427&quot; height=&quot;179&quot; data-origin-width=&quot;427&quot; data-origin-height=&quot;179&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이해를 위한 코드이므로 불편해하지 말자&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심 비즈니스 로직의 시작과 끝에 로깅을 위한 코드가 추가되었다. 이는 개발자가 핵심 코드에 집중할 수 없게 할 뿐만 아니라, &lt;a href=&quot;https://eckrin.tistory.com/108&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;SRP원칙&lt;/a&gt;에도 위배된다. 위와 같은 방법으로 로깅 시스템을 구축했다가는 로깅 메세지 한글자를 바꾸려고 전체 서비스코드를 수정해야 하는 불상사가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;528&quot; data-origin-height=&quot;403&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/88l30/btsD6xK3DUS/3nKr0dhuNYqLNZpE2JgLh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/88l30/btsD6xK3DUS/3nKr0dhuNYqLNZpE2JgLh0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/88l30/btsD6xK3DUS/3nKr0dhuNYqLNZpE2JgLh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F88l30%2FbtsD6xK3DUS%2F3nKr0dhuNYqLNZpE2JgLh0%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;528&quot; height=&quot;403&quot; data-origin-width=&quot;528&quot; data-origin-height=&quot;403&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;477&quot; data-origin-height=&quot;200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWUXjW/btsD1aK02da/yieoj9T56h6FlGzzTqZp01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWUXjW/btsD1aK02da/yieoj9T56h6FlGzzTqZp01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWUXjW/btsD1aK02da/yieoj9T56h6FlGzzTqZp01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWUXjW%2FbtsD1aK02da%2Fyieoj9T56h6FlGzzTqZp01%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;477&quot; height=&quot;200&quot; data-origin-width=&quot;477&quot; data-origin-height=&quot;200&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그래서 대신 추상 클래스를 사용하고, call()이라는 추상 메소드는 자식 클래스 &lt;b&gt;객체 생성시 구현하여 사용&lt;/b&gt;하도록 했다. 이렇게 템플릿 메서드 패턴을 통해서 상속과 오버라이딩을 통한 다형성을 이용하여 전체 구조는 건드리지 않으면서 특정 부분만 재정의할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;하지만&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이러한 템플릿 메소드 패턴은 상속을 사용한다. &lt;a href=&quot;https://eckrin.tistory.com/115&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;상속과 합성에 대한 글&lt;/a&gt;에서도 말했듯이, 상속은 자식 클래스에서 부모 클래스의 구현법에 대한 지식을 필요로 하고, 따라서 부모 객체의 캡슐화를 약화시킨다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;상속은 재사용이 아니라 대체(is-a)관계에서만 사용해야 한다&lt;/b&gt;고 했던 적이 있다. 위 코드에서 우리는 재사용을 위해서 상속을 사용했고, 그 결과 자식 클래스는 상속받는 부모 클래스의 다른 기능을 사용하지 않음에도 부모 클래스에 강하게 의존하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이와 같은 문제를 해결하기 위해서 밑에 나올 전략 패턴을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;전략 패턴&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;공통 부분을 클래스(&lt;b&gt;Context&lt;/b&gt;)에 두고, 변하는 부분을 인터페이스(&lt;b&gt;Strategy&lt;/b&gt;)로 만들어 해당 인터페이스를 구현하도록 한다. 공통 부분을 클래스에서 직접 상속받는 템플릿 메서드 패턴과 다르게 인터페이스를 활용한다는 점에서 차이가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옛날에 &lt;a href=&quot;https://eckrin.tistory.com/115&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;상속 및 합성에 관한 포스팅&lt;/a&gt;에서 사용한 예시를 가져왔다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1706370904903&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;// 인터페이스
interface Brain {
  void work();
}

// 구현체
class BrainV1 implements Brain {
  @Override
  void work() {
    System.out.println(&quot;1번 뇌 일하는 중&quot;);
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방법 1) 생성자 주입&lt;/p&gt;
&lt;pre id=&quot;code_1706370904904&quot; class=&quot;cpp&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class Animal {
  private Brain brain;

  public Animal(Brain brain) {
    this.brain = brain; // 의존성 주입
  }
  
  public void think() {
    brain.work(); // 인터페이스에 의존
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1706370904904&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;Animal myComposedAnimal = new Animal(new BrainV1()); // 생성자를 이용한 의존성 주입

myComposedAnimal.think(); // BrainV1에 구현한 think메소드 동작
myComposedAnimal.think();
myComposedAnimal.think();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;방법 2) 파라미터 주입&lt;/p&gt;
&lt;pre id=&quot;code_1706370904904&quot; class=&quot;angelscript&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Animal {
  
  public void think(Brain brain) { // 파라미터로 구현체 주입
    brain.work(); // 인터페이스에 의존
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1706370904905&quot; class=&quot;java&quot; style=&quot;background-color: #f8f8f8; color: #383a42;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Animal myComposedAnimal = new Animal();

myComposedAnimal.think(new BrainV1()); // 파라미터를 이용한 의존성 주입
myComposedAnimal.think(new BrainV2());
myComposedAnimal.think(new BrainV3());&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;첫 번째 방법은 생성자를 이용하여 Animal 내의 Brain 인터페이스 타입 필드에 구현체를 저장해놓고 이용하는 방식이고, 아래는 메소드 실행시에 파라미터로 Brain타입 구현체를 전달하는 방법이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사용 예시에서 보이듯이 전자는 이미 생성해놓은 객체의 메소드를 반복 호출해도 동일한 동작을 하지만, 다른 구현체를 이용하려면 다른 Animal객체를 다시 만들어주어야 하는 반면, 후자는 하나의 Animal객체로도 여러개의 구현체를 받아 사용할 수 있으나 메소드 호출시마다 파라미터로 구현체를 넘겨주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아마도 개발자가 판단하여 사용하려는 의도와 상황에 따라서 적절하게 사용하면 될 것 같다. 참고로 2번째 방법의 경우 스프링에서는 '&lt;b&gt;템플릿 콜백 패턴&lt;/b&gt;'이라는 이름으로 불리기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 &lt;b&gt;팩토리&amp;nbsp;패턴&lt;/b&gt;과도 유사성을 지니는데, 둘은 목적에 있어 차이가 존재한다. 인터페이스를 활용하여 동적으로 전략을 주입하여 코드의 유연성을 증가시키는 것이 목적인 전략 패턴과 다르게, 팩토리 패턴은 객체 생성시의 복잡도를 낮추고 캡슐화하는 것이  주된 목적이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;옵저버 패턴&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;관찰자를 의미하는 옵저버(observer)에서 알 수 있듯이, 옵저버 패턴이란 관찰자가 &lt;b&gt;관찰하고 있는 대상의 상태 변화를 관찰자에게 알리는 행동 패턴&lt;/b&gt;이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고객이 새로운 상품이 입고되었는지를 확인하려고 한다고 하자. 고객은 상품을 팔고 있는 가게에 직접 접근하여 상품의 재고를 확인할 수 있지만, 상품이 존재하지 않는다면 불필요한 접근이 계속 이루어질 것이다. 혹은 가게에서 상품이 입고되면 모든 잠재 고객들에게 입고에 관련된 알림을 보낼 수도 있다. 하지만 이 경우도 상품에 구매 의향이 없는 고객들에게도 불필요한 알림이 간다는 문제가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;848&quot; data-origin-height=&quot;676&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bi76iY/btsHnqWHoZQ/7nOsAjtNGYgKXZpPoKHzVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bi76iY/btsHnqWHoZQ/7nOsAjtNGYgKXZpPoKHzVk/img.png&quot; data-alt=&quot;https://refactoring.guru/ko/design-patterns/observer&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bi76iY/btsHnqWHoZQ/7nOsAjtNGYgKXZpPoKHzVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbi76iY%2FbtsHnqWHoZQ%2F7nOsAjtNGYgKXZpPoKHzVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;468&quot; height=&quot;373&quot; data-origin-width=&quot;848&quot; data-origin-height=&quot;676&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://refactoring.guru/ko/design-patterns/observer&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이를 해결하기 위한 방법이 옵저버 패턴이다. Kafka나 Redis의 pub-sub, Azure의 Service bus등을 사용해 보았다면 그와 유사한 구조를 가진다고 생각하면 된다. (&lt;a href=&quot;https://jistol.github.io/software%20engineering/2018/04/11/observer-pubsub-pattern/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;차이&lt;/a&gt;가 존재하기는 한다. 사실 위와 같이 메시지 브로커를 사용하는 비동기방식의 처리 프로세스는 옵저버 패턴보다는 Pub-Sub 패턴에 가깝다.)&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;938&quot; data-origin-height=&quot;814&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WDTLc/btsHmzAjQKa/KPJtMkMGqW64wRAYS1Otk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WDTLc/btsHmzAjQKa/KPJtMkMGqW64wRAYS1Otk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WDTLc/btsHmzAjQKa/KPJtMkMGqW64wRAYS1Otk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWDTLc%2FbtsHmzAjQKa%2FKPJtMkMGqW64wRAYS1Otk1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;564&quot; height=&quot;489&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;814&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;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class StoreImpl implements Store {

    private List&amp;lt;Client&amp;gt; subscribers = new LinkedList&amp;lt;&amp;gt;();

    @Override
    public void registerClient(Client client) {
        subscribers.add(client);
    }

    @Override
    public void removeClient(Client client) {
        subscribers.remove(client);
    }

    @Override
    public void informClients() {
        for (Client subscriber : subscribers) {
            subscriber.inform();
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;arduino&quot;&gt;&lt;code&gt;public class Client {

    private String name;

    public Client(String name) {
        this.name = name;
    }

    public void inform() {
        System.out.println(&quot;notice to &quot;+name);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Store는 Client를 등록할 수 있고, informClients 메서드를 실행시 등록된 클라이언트에게만 메시지가 간다.&amp;nbsp;&lt;/p&gt;</description>
      <category>[ 아키텍쳐, 방법론, 디자인패턴 등 ]</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/182</guid>
      <comments>https://eckrin.tistory.com/182#entry182comment</comments>
      <pubDate>Sun, 28 Jan 2024 14:30:03 +0900</pubDate>
    </item>
    <item>
      <title>인텔리제이 자동 빌드 해제하기</title>
      <link>https://eckrin.tistory.com/183</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;이런 코딩 외적인 문제들은 잘 기록하지 않는편인데, 꽤 오랜시간 해결을 못하고있어서 기록해두려고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;발생한 문제&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;how to disable auto build in intellij&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 돌아가고 있는 스프링 프로젝트 창에서 포커스를 해제하면, 자동으로 빌드가 된다. 다시말해서 현재 사용중인 인텔리제이 창에서 alt-tab을 하거나, 다른 창에 마우스클릭을 해서 인텔리제이의 포커스가 해제되면&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;418&quot; data-origin-height=&quot;51&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/UYJfN/btsDIx0o2Nr/gDnxXKzuKXaKxPLzw3kx31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/UYJfN/btsDIx0o2Nr/gDnxXKzuKXaKxPLzw3kx31/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/UYJfN/btsDIx0o2Nr/gDnxXKzuKXaKxPLzw3kx31/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FUYJfN%2FbtsDIx0o2Nr%2FgDnxXKzuKXaKxPLzw3kx31%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;418&quot; height=&quot;51&quot; data-origin-width=&quot;418&quot; data-origin-height=&quot;51&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;위와 같이 Build [project-name]이라고 뜨면서 자동으로 빌드가 되어버린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 큰 문제가 아니라고 느껴질수도 있으나, 코드를 작성하면서 구글링 등을 동시에 진행할 경우, 코드가 완성되지 않은 대부분의 상태에서 창을 옮길 때마다 빌드가 진행되면서&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1834&quot; data-origin-height=&quot;165&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GCMmG/btsDGXr0zqq/k5eajGXKqLRS6ir6k3Mqc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GCMmG/btsDGXr0zqq/k5eajGXKqLRS6ir6k3Mqc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GCMmG/btsDGXr0zqq/k5eajGXKqLRS6ir6k3Mqc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGCMmG%2FbtsDGXr0zqq%2Fk5eajGXKqLRS6ir6k3Mqc0%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;1834&quot; height=&quot;165&quot; data-origin-width=&quot;1834&quot; data-origin-height=&quot;165&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 에러 창이 올라오거나,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1835&quot; data-origin-height=&quot;146&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/CWkcr/btsDIuXbajp/hgsVbDiKrSZzp0vTYntsqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/CWkcr/btsDIuXbajp/hgsVbDiKrSZzp0vTYntsqk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/CWkcr/btsDIuXbajp/hgsVbDiKrSZzp0vTYntsqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FCWkcr%2FbtsDIuXbajp%2FhgsVbDiKrSZzp0vTYntsqk%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;1835&quot; height=&quot;146&quot; data-origin-width=&quot;1835&quot; data-origin-height=&quot;146&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 성공창이 올라오기 때문에, 창을 옮기는 동작마다 위 창들을 반복적으로 계속 닫아주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1838&quot; data-origin-height=&quot;142&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dg3Lsm/btsDHmkvGae/Px9ADnzGG2idANMX1DcqrK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dg3Lsm/btsDHmkvGae/Px9ADnzGG2idANMX1DcqrK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dg3Lsm/btsDHmkvGae/Px9ADnzGG2idANMX1DcqrK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdg3Lsm%2FbtsDHmkvGae%2FPx9ADnzGG2idANMX1DcqrK%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;1838&quot; height=&quot;142&quot; data-origin-width=&quot;1838&quot; data-origin-height=&quot;142&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;위 사진은 cmd+F9을 통해서 수동 빌드했을때의 모습이다. 나타나는 메세지의 양에 약간의 차이가 있는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;분석&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;- &lt;b&gt;현재 이러한 문제가 발생하는 스프링 프로젝트는 현재 이 프로젝트 단 &quot;1개&quot;이며, 다른 프로젝트들은 문제가 발생하지 않는다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;span style=&quot;text-align: start;&quot;&gt;-&amp;nbsp;&lt;/span&gt;&lt;b&gt;동일 프로젝트를 윈도우 10에서 실행했을 경우 문제가 발생하지 않았다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; -&amp;gt; 현재 인텔리제이 세팅과 프로젝트 세팅 모두에 문제가 있어야 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 현재 프로젝트는 &lt;b&gt;Kotlin 프로젝트&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 인텔리제이에서 Build and run과 Run tests 모두 IDEA가 아닌 Gradle로 진행하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- devtools가 기존 gradle에 포함되어 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- jrebel 이 activate되어있으나, 프로젝트에는 세팅되어 있지 않다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 문제가 발생한 컴퓨터는 Mac Pro M2(2023)이며, 사용중인 os는 MacOS Sonoma 14.2.1이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;nbsp; 현재 사용중인 IDE는 Intellij IDEA 2023.2.3 (Ultimate Edition)이며, Runtime Version: 17.0.8.1+7-b1000.32 aarch64, VM은 OpenJDK 64-Bit Server VM by JetBrains s.r.o이다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;시도한 방법&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Settings -&amp;gt; Build, Execution, Deployment -&amp;gt; Compiler -&amp;gt; Build project automatically 체크박스 해제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. Settings -&amp;gt; Advanced Settings -&amp;gt; Allow auto-make to start even if developed application is currently running 체크박스 해제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. gradle에서 devtools dependency 삭제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. Run/Debug Configuration -&amp;gt; Modify options -&amp;gt; on update action에 do nothing 선택 (&lt;a href=&quot;https://www.jetbrains.com/help/idea/updating-applications-on-application-servers.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.jetbrains.com/help/idea/updating-applications-on-application-servers.html&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. .idea 파일 전체 삭제 후 rebuild&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. Invalid caches and restart&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. 기존 프로젝트 삭제후 git clone으로 다시 원격 저장소에서 복제해오기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. jrebel 비활성화하기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9. veracode greenlight plugin 설치되어있으면 제거&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10. 사용하지 않는 플러그인 있으면 제거 (&lt;a href=&quot;https://intellij-support.jetbrains.com/hc/en-us/community/posts/360000148510-Project-keeps-automatically-building-in-IntelliJ-2018-1-&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://intellij-support.jetbrains.com/hc/en-us/community/posts/360000148510-Project-keeps-automatically-building-in-IntelliJ-2018-1-&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;11. Settings-&amp;gt; Build, Execution, Deployment -&amp;gt; Build Tools -&amp;gt; Reload project after changes in the build scripts 체크박스 해제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;해결&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Run/Debug Configurations에서 아래 On frame deactivation: 이라고 되어있는 옵션을 'Do nothing'으로 설정해주면 된다.&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;721&quot; data-origin-height=&quot;137&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcZSrL/btsDJiBVMpC/MNlXIULTxf2uqASsCKfjl0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcZSrL/btsDJiBVMpC/MNlXIULTxf2uqASsCKfjl0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcZSrL/btsDJiBVMpC/MNlXIULTxf2uqASsCKfjl0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcZSrL%2FbtsDJiBVMpC%2FMNlXIULTxf2uqASsCKfjl0%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;721&quot; height=&quot;137&quot; data-origin-width=&quot;721&quot; data-origin-height=&quot;137&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;s&gt;위에 있는 4번과 똑같은 항목같은데&lt;/s&gt;, &lt;b&gt;위 4번과 똑같은 항목인데, 인텔리제이 버그인지 몰라도 탭을 열어서 설정해주면 값이 바뀌지 않는다!&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;1099&quot; data-origin-height=&quot;289&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/boKI2h/btsDGsMi5Wx/bqocF9tUJkrVEYkQxAEIR1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/boKI2h/btsDGsMi5Wx/bqocF9tUJkrVEYkQxAEIR1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/boKI2h/btsDGsMi5Wx/bqocF9tUJkrVEYkQxAEIR1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FboKI2h%2FbtsDGsMi5Wx%2FbqocF9tUJkrVEYkQxAEIR1%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;1099&quot; height=&quot;289&quot; data-origin-width=&quot;1099&quot; data-origin-height=&quot;289&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;탭을 보면 분명 'Do nothing'으로 체크되어 있지만, 뒷 배경화면의 On frame deactivation값은 Update classes and resources로 설정되어있다. 탭에서 Do nothing을 아무리 눌러도 변경되지 않고, 뒷 배경의 On frame deactivation을 직접 눌러 변경/해제해 주어야 값이 정상적으로 반영된다.&lt;/p&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;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;추가적인 문제&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 문제와의 연관성은 알 수 없으나, 발생한 추가적인 문제는 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. Run/Debug Configuration -&amp;gt; Build and run -&amp;gt; Spring Boot Class 설정시 생기는 오류&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 main 클래스는 *Application.kt의 형태로, 하단 오른쪽 사진과 같이 Spring Boot Class가 정상적으로 탐색된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0BhXe/btsDKyDZY14/678T27zsYM2bhj3ym9rQKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0BhXe/btsDKyDZY14/678T27zsYM2bhj3ym9rQKk/img.png&quot; style=&quot;width: 32.578476639148256%;&quot; data-widthpercent=&quot;32.96&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;160&quot; data-origin-width=&quot;350&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0BhXe/btsDKyDZY14/678T27zsYM2bhj3ym9rQKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0BhXe%2FbtsDKyDZY14%2F678T27zsYM2bhj3ym9rQKk%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;350&quot; height=&quot;160&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mA586/btsDHrfk5yI/38K8UWyZ7KPaAlWQA9YWGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mA586/btsDHrfk5yI/38K8UWyZ7KPaAlWQA9YWGk/img.png&quot; style=&quot;width: 66.25873266317733%;&quot; data-widthpercent=&quot;67.04&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;147&quot; data-origin-width=&quot;654&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mA586/btsDHrfk5yI/38K8UWyZ7KPaAlWQA9YWGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmA586%2FbtsDHrfk5yI%2F38K8UWyZ7KPaAlWQA9YWGk%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;654&quot; height=&quot;147&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;807&quot; data-origin-height=&quot;116&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vxpJ4/btsDKQkmyow/Xmt6KFlJSsf52skOFvwKPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vxpJ4/btsDKQkmyow/Xmt6KFlJSsf52skOFvwKPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vxpJ4/btsDKQkmyow/Xmt6KFlJSsf52skOFvwKPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvxpJ4%2FbtsDKQkmyow%2FXmt6KFlJSsf52skOFvwKPK%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;807&quot; height=&quot;116&quot; data-origin-width=&quot;807&quot; data-origin-height=&quot;116&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;*Application 클래스를 Spring Boot Class로 지정해주고, Run/Debug Configuration 창을 빠져나온 후, 다시 들어가면 Spring Boot Class가 자동으로 바뀌면서 오류가 뜬다.&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;787&quot; data-origin-height=&quot;103&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwpr63/btsDHo3KRTx/i2YCv4deGMmkJCRkF9CCU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwpr63/btsDHo3KRTx/i2YCv4deGMmkJCRkF9CCU1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwpr63/btsDHo3KRTx/i2YCv4deGMmkJCRkF9CCU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcwpr63%2FbtsDHo3KRTx%2Fi2YCv4deGMmkJCRkF9CCU1%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;787&quot; height=&quot;103&quot; data-origin-width=&quot;787&quot; data-origin-height=&quot;103&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;분명히 *Application으로 Spring Boot Class를 지정해주었음에도, *ApplicationKt라는 클래스로 자동 변경되며 오류가 뜨고 있음을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;그런데 이렇게 에러가 뜬 상태에서 프로젝트를 돌려도 돌아간다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;인텔리제이 버그였다. 근데 왜 해당 프로젝트의 Run/Debug Configurations에만 'On Update Action'의 기본세팅이 'Update Classes and Resources'였는지는 모르겠다.&lt;/p&gt;</description>
      <category>[ 기타 ]/잡담</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/183</guid>
      <comments>https://eckrin.tistory.com/183#entry183comment</comments>
      <pubDate>Sun, 21 Jan 2024 17:53:15 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] @JsonCreator 없이 immutable하게 역직렬화하기</title>
      <link>https://eckrin.tistory.com/181</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;배경지식&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Spring Boot에서 json으로 rest api 통신을 하기 위해서 DTO를 이용하는데, http통신시에 직렬화, 역직렬화되는 과정에 대해서 원래 다음과 같은 사실들을 알고 있었다.&lt;/p&gt;
&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;Jackson&lt;/b&gt;이라는 라이브러리가 많이 사용되며, Jackson 내부에서는 ObjectMapper가 Java Reflection을 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Jackson이 직렬화, 역직렬화를 하기 위해서는 DTO에 default constructor와 getter, setter가 필요하다. Default constructor를 통해서 객체 접근/생성이 이루어지고, getter와 setter를 통해서 field들에 대한 접근이 이루어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- Setter를 사용하게 되면 객체의 불변성을 유지할 수 없다는 문제가 있다. 객체의 불변성을 유지하면서 역직렬화가 정상적으로 동작하기 위해서는 다른 생성자로 대체하여 접근하는 과정이 필요한데, 이 과정에서 @JsonCreator와 @JsonProperty 어노테이션을 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- @JsonCreator를 파싱을 원하는 생성자에 붙여주고, @JsonProperty(name=&quot;var&quot;)와 같이 파라미터에 매핑할 필드명을 세팅해주면 Jackson이 파싱한 데이터를 적절한 위치에 넣어줄 수 있다.&lt;/p&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;그런데 프로젝트 진행 중에 DTO에 기본 생성자가 없어도 역직렬화 과정에서 오류가 나지 않는 것을 발견했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;기본 생성자가 없어도 오류가 나지 않는데?&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/uG9Lw/btsDAGwrHeC/2JlolKIu1BohNbt8cmigbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/uG9Lw/btsDAGwrHeC/2JlolKIu1BohNbt8cmigbk/img.png&quot; data-origin-width=&quot;259&quot; data-origin-height=&quot;288&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;31.1&quot; style=&quot;width: 170px; margin-right: 10px;&quot; height=&quot;189&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/uG9Lw/btsDAGwrHeC/2JlolKIu1BohNbt8cmigbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FuG9Lw%2FbtsDAGwrHeC%2F2JlolKIu1BohNbt8cmigbk%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;259&quot; height=&quot;288&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqDXxt/btsDxSqOr4D/782BcL5XG0oJvP1k9kI3eK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqDXxt/btsDxSqOr4D/782BcL5XG0oJvP1k9kI3eK/img.png&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;257&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; style=&quot;width: 359px;&quot; data-widthpercent=&quot;68.9&quot; height=&quot;180&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqDXxt/btsDxSqOr4D/782BcL5XG0oJvP1k9kI3eK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqDXxt%2FbtsDxSqOr4D%2F782BcL5XG0oJvP1k9kI3eK%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;512&quot; height=&quot;257&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 있는 LoggingResponse는 분명 기본 생성자가 존재하지 않지만, 통신시에 정상적으로 통신이 되는 것을 볼 수 있었다.&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;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bPMvlk/btsDATPK1zv/4u1bwKWY3OWYZevrKK6vYK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bPMvlk/btsDATPK1zv/4u1bwKWY3OWYZevrKK6vYK/img.png&quot; data-origin-width=&quot;644&quot; data-origin-height=&quot;88&quot; data-is-animation=&quot;false&quot; style=&quot;width: 56.5981%; margin-right: 10px;&quot; data-widthpercent=&quot;57.95&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bPMvlk/btsDATPK1zv/4u1bwKWY3OWYZevrKK6vYK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbPMvlk%2FbtsDATPK1zv%2F4u1bwKWY3OWYZevrKK6vYK%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;644&quot; height=&quot;88&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JF7tY/btsDBACqYSq/BvMZ7j1MzL0807kAQH6kdk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JF7tY/btsDBACqYSq/BvMZ7j1MzL0807kAQH6kdk/img.png&quot; data-origin-width=&quot;301&quot; data-origin-height=&quot;134&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;17.79&quot; style=&quot;width: 17.3724%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JF7tY/btsDBACqYSq/BvMZ7j1MzL0807kAQH6kdk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJF7tY%2FbtsDBACqYSq%2FBvMZ7j1MzL0807kAQH6kdk%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;301&quot; height=&quot;134&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xATy5/btsDyAX1lbZ/PiKi3LfvTdI8UcL5mYSfLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xATy5/btsDyAX1lbZ/PiKi3LfvTdI8UcL5mYSfLk/img.png&quot; data-origin-width=&quot;236&quot; data-origin-height=&quot;77&quot; data-is-animation=&quot;false&quot; style=&quot;width: 23.7039%;&quot; data-widthpercent=&quot;24.26&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xATy5/btsDyAX1lbZ/PiKi3LfvTdI8UcL5mYSfLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxATy5%2FbtsDyAX1lbZ%2FPiKi3LfvTdI8UcL5mYSfLk%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;236&quot; height=&quot;77&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린에 내가 모르는 기능이 있을까 싶어 자바로도 간단하게 테스트해보았는데도 결과는 동일했다. @Getter만 존재하는데 어떻게 데이터가 역직렬화되는걸까?&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;해결&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;찾아본 이유를 간단하게 요약하면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 앞서 말했듯 Jackson라이브러리는 ObjectMapper를 사용하는데, ObjectMapper는 java reflection 패키지를 사용하기 때문에(동적 바인딩을 통해서 클래스 정보를 가져올 수 있기 때문에) 기본 생성자가 없어도 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- '기본생성자가 없어도 된다'라는 말은 두 가지 가능성이 있는데, Jackson 라이브러리가 기본 생성자 없이도 다른 생성자로 대체하여 역직렬화를 수행할 수 있게 해주고 있거나(현재 DTO에 @AllArgsConstructor가 존재한다), java reflection을 통해서 동적으로 기본 생성자를 사용하고 있을 것이다라고 예상했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 아래 문서에서, jackson-modules-java8의 parameter-names라는 모듈에서 기본 생성자가 없이도 다른 생성자로 대체하여 역직렬화를 수행할 수 있게 해줌을 알 수 있었다.&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;604&quot; data-origin-height=&quot;173&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cYZrRk/btsDAqmLXFG/QoKfp2LyuyTAMzNHIBCjjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cYZrRk/btsDAqmLXFG/QoKfp2LyuyTAMzNHIBCjjk/img.png&quot; data-alt=&quot;https://elvisciotti.medium.com/jackson-deserialize-immutable-objects-with-lombok-value-annotation-without-jsonproperty-f7ee1babf51e&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cYZrRk/btsDAqmLXFG/QoKfp2LyuyTAMzNHIBCjjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcYZrRk%2FbtsDAqmLXFG%2FQoKfp2LyuyTAMzNHIBCjjk%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;447&quot; height=&quot;128&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;173&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://elvisciotti.medium.com/jackson-deserialize-immutable-objects-with-lombok-value-annotation-without-jsonproperty-f7ee1babf51e&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;123&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AKCMv/btsDyRd6ydL/kgNIqtu3UFu6crQpScJz01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AKCMv/btsDyRd6ydL/kgNIqtu3UFu6crQpScJz01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AKCMv/btsDyRd6ydL/kgNIqtu3UFu6crQpScJz01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAKCMv%2FbtsDyRd6ydL%2FkgNIqtu3UFu6crQpScJz01%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;425&quot; height=&quot;95&quot; data-origin-width=&quot;550&quot; data-origin-height=&quot;123&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고: &lt;a href=&quot;https://elvisciotti.medium.com/jackson-deserialize-immutable-objects-with-lombok-value-annotation-without-jsonproperty-f7ee1babf51e&quot;&gt;https://elvisciotti.medium.com/jackson-deserialize-immutable-objects-with-lombok-value-annotation-without-jsonproperty-f7ee1babf51e&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1705514248393&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;Jackson: deserialize immutable objects with Lombok Value annotation (without JsonProperty)&quot; data-og-description=&quot;The problem&quot; data-og-host=&quot;elvisciotti.medium.com&quot; data-og-source-url=&quot;https://elvisciotti.medium.com/jackson-deserialize-immutable-objects-with-lombok-value-annotation-without-jsonproperty-f7ee1babf51e&quot; data-og-url=&quot;https://elvisciotti.medium.com/jackson-deserialize-immutable-objects-with-lombok-value-annotation-without-jsonproperty-f7ee1babf51e&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/dAuyKg/hyU85cGuxS/RKjPOiF0L25K5YzO2dY2LK/img.png?width=989&amp;amp;height=742&amp;amp;face=0_0_989_742,https://scrap.kakaocdn.net/dn/Xtsxo/hyU8QzPGo2/58CCJc67hxRQlAiO2jQGTk/img.png?width=989&amp;amp;height=742&amp;amp;face=0_0_989_742,https://scrap.kakaocdn.net/dn/X8Nqo/hyU8V8Yye0/XOMdt23rRGkgtutUgGWHgk/img.png?width=989&amp;amp;height=742&amp;amp;face=0_0_989_742&quot;&gt;&lt;a href=&quot;https://elvisciotti.medium.com/jackson-deserialize-immutable-objects-with-lombok-value-annotation-without-jsonproperty-f7ee1babf51e&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://elvisciotti.medium.com/jackson-deserialize-immutable-objects-with-lombok-value-annotation-without-jsonproperty-f7ee1babf51e&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/dAuyKg/hyU85cGuxS/RKjPOiF0L25K5YzO2dY2LK/img.png?width=989&amp;amp;height=742&amp;amp;face=0_0_989_742,https://scrap.kakaocdn.net/dn/Xtsxo/hyU8QzPGo2/58CCJc67hxRQlAiO2jQGTk/img.png?width=989&amp;amp;height=742&amp;amp;face=0_0_989_742,https://scrap.kakaocdn.net/dn/X8Nqo/hyU8V8Yye0/XOMdt23rRGkgtutUgGWHgk/img.png?width=989&amp;amp;height=742&amp;amp;face=0_0_989_742');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Jackson: deserialize immutable objects with Lombok Value annotation (without JsonProperty)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;The problem&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;elvisciotti.medium.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/FasterXML/jackson-modules-java8/tree/2.16/parameter-names&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/FasterXML/jackson-modules-java8/tree/2.16/parameter-names&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://codingwell.tistory.com/182&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://codingwell.tistory.com/182&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 깃헙: &lt;a href=&quot;https://github.com/eckrin/various-tests&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/eckrin/various-tests&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1705514481380&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - eckrin/various-tests: 스프링과 자바 공부를 위한 테스트 프로젝트&quot; data-og-description=&quot;스프링과 자바 공부를 위한 테스트 프로젝트. Contribute to eckrin/various-tests development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/eckrin/various-tests&quot; data-og-url=&quot;https://github.com/eckrin/various-tests&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bmQ8MD/hyU5My41xf/6ormQpttQ5AZVvNCPbt6Nk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/eckrin/various-tests&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/eckrin/various-tests&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bmQ8MD/hyU5My41xf/6ormQpttQ5AZVvNCPbt6Nk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - eckrin/various-tests: 스프링과 자바 공부를 위한 테스트 프로젝트&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;스프링과 자바 공부를 위한 테스트 프로젝트. Contribute to eckrin/various-tests development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>[ Backend ]/Spring</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/181</guid>
      <comments>https://eckrin.tistory.com/181#entry181comment</comments>
      <pubDate>Wed, 17 Jan 2024 00:37:00 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Security] 스프링 시큐리티로 익명 사용자 관리하기</title>
      <link>https://eckrin.tistory.com/180</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GObyK/btsDooiSGaU/sMM0ZMNkBbTjNerAAUkKUk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GObyK/btsDooiSGaU/sMM0ZMNkBbTjNerAAUkKUk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GObyK/btsDooiSGaU/sMM0ZMNkBbTjNerAAUkKUk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGObyK%2FbtsDooiSGaU%2FsMM0ZMNkBbTjNerAAUkKUk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;672&quot; height=&quot;378&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;스프링 시큐리티를 사용하여 프로젝트를 디벨롭하다 보면, 회원가입이나 로그인시에는 시큐리티 필터를 통과할때 인증,인가 프로세스에서 제외하거나, 아예 시큐리티 필터를 타지 않도록 만들어야 한다. 그런데 사실 이런 경우를 제외하고도 로그인이 필요하지 않은 뷰에 필요한 api를 제작할 때 처럼, 시큐리티 필터에서 별도의 인증을 수행하지 않는 요청을 관리해야 하는 경우가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1705167403510&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;.authorizeHttpRequests(authorize -&amp;gt;
    authorize
    .requestMatchers(&quot;/auth/**&quot;).permitAll() // 로그인 필요 X
    .requestMatchers(HttpMethod.GET, &quot;/test/**&quot;).permitAll() // anonymousUser 테스트
    .anyRequest().authenticated()
)&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;지금까지는 위와 같이 Spring Security Configuration에서 특정 요청에 대해서는 모든 인증을 허락하는 식으로 진행했는데, 디버깅을 하다보니 Authentication객체에 의도하지 않은 값이 들어가는 것을 확인할 수 있었고, 관련 서칭을 하다보니 스프링 시큐리티에서 제공하는 '익명 사용자'에 대해 알게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;익명 사용자&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #24292f; text-align: start;&quot;&gt;&amp;nbsp;시큐리티 필터 체인에서 '익명 사용자'란 로그인을 하지 않은 상태의 사용자를 말하는데, 이렇게 로그인하지 않은 상태의 사용자도 별도의 인증정보를 가질 수 있다.&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #24292f; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #24292f; text-align: start;&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 시큐리티 내부 코드를 보면 전반적으로&lt;/p&gt;
&lt;pre id=&quot;code_1705215320174&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if(SecurityContextHolder.getContext().getAuthentication() == null){
    ...
}&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;background-color: #ffffff; color: #24292f; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #24292f; text-align: start;&quot;&gt;과 같은 코드가 여러 군데 나오는데, User객체가 아니라, Authentication객체의 존재 여부로 인증여부를 판단하는 경우가 많다는 것을 알 수 있다.&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;background-color: #ffffff; color: #24292f; text-align: start;&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #24292f; text-align: start;&quot;&gt;Authentication이 없는 경우에 &lt;/span&gt;&lt;b&gt;AnonymousAuthenticationFilter&lt;/b&gt;라는 필터가 &quot;anonymousUser&quot;라는 문자열을 principal로 가지는 Authentication객체를 만들어준다.&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;493&quot; data-origin-height=&quot;175&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWgjhm/btsDrFDm4bw/hBWe0f3pToZBRQo9J56SS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWgjhm/btsDrFDm4bw/hBWe0f3pToZBRQo9J56SS0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWgjhm/btsDrFDm4bw/hBWe0f3pToZBRQo9J56SS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWgjhm%2FbtsDrFDm4bw%2FhBWe0f3pToZBRQo9J56SS0%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;493&quot; height=&quot;175&quot; data-origin-width=&quot;493&quot; data-origin-height=&quot;175&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;익명 사용자로 로그인한 경우의 authentication객체의 상태 예시이다. 내가 만든 커스텀 필터에서는 authentication객체가 null이었는데, 시큐리티 필터 체인을 거친 후 ArgumentResolver에서 SecurityContext의 ContextHolder내의 인증객체에 대해서 디버깅을 찍어보니 &quot;anonymousUser&quot;라는 문자열을 principal로 가지고, &quot;ROLE_ANONYMOUS&quot;라는 role을 authority로 가지는 것을 확인할 수 있었다.&lt;/p&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;다만 authenticated 속성이 true인 것은 이해가 잘 가지 않았는데, 인증되지 않은 객체이므로 authenticated 속성은 false가 되는 것이 맞는 것이 아닐까라는 생각이 들었기 때문이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 별도로 설정을 변경한 부분은 없기 때문에, 스프링 시큐리티가 익명 사용자라도 ROLE_ANONYMOUS라는 authority를 가지는 하나의 인증된 사용자로 보는 것일 것이라는 추측을 할 수 있었다.&lt;/p&gt;</description>
      <category>[ Backend ]/Spring</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/180</guid>
      <comments>https://eckrin.tistory.com/180#entry180comment</comments>
      <pubDate>Sat, 13 Jan 2024 18:31:32 +0900</pubDate>
    </item>
    <item>
      <title>Spring + Kotlin + Elastic Stack으로 로깅시스템 구축해보기</title>
      <link>https://eckrin.tistory.com/179</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개념&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1199&quot; data-origin-height=&quot;425&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/psg3I/btsDfPVoP9a/JKlDGUmVZEK6KfpkN8AAc0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/psg3I/btsDfPVoP9a/JKlDGUmVZEK6KfpkN8AAc0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/psg3I/btsDfPVoP9a/JKlDGUmVZEK6KfpkN8AAc0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fpsg3I%2FbtsDfPVoP9a%2FJKlDGUmVZEK6KfpkN8AAc0%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;361&quot; height=&quot;128&quot; data-origin-width=&quot;1199&quot; data-origin-height=&quot;425&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;ELK란 Elasticsearch, Logstash, Kibana, Beats 등의 스택들로 구성되어 시너지 효과를 내는 기술스택을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;ElasticSearch&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;666&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/EpJLa/btsDhgkV2bx/kcYLo5ocAX8ZKV4P1KGNq0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/EpJLa/btsDhgkV2bx/kcYLo5ocAX8ZKV4P1KGNq0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/EpJLa/btsDhgkV2bx/kcYLo5ocAX8ZKV4P1KGNq0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FEpJLa%2FbtsDhgkV2bx%2FkcYLo5ocAX8ZKV4P1KGNq0%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;423&quot; height=&quot;220&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;666&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;실시간 데이터 검색, 분석을 위한 분산형 데이터 저장/검색 엔진으로, 일종의 데이터베이스 시스템이다. 쉽게 말해서 &lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt;여러 문서(파일) 등에서 특정 문자열을 검색하는 역할을 한다고도 할 수 있다.&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #212529; text-align: left;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;866&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oCmde/btsDitp73vH/clRETtivS8HngqYZa5Xk00/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oCmde/btsDitp73vH/clRETtivS8HngqYZa5Xk00/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oCmde/btsDitp73vH/clRETtivS8HngqYZa5Xk00/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoCmde%2FbtsDitp73vH%2FclRETtivS8HngqYZa5Xk00%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;290&quot; height=&quot;404&quot; data-origin-width=&quot;622&quot; data-origin-height=&quot;866&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;rdb와 유사한 역할을 하는데, 데이터를 색인화하여 빠르게 검색, 분석할 수 있도록 도와주며, 실시간 데이터를 처리해주는 역할을 주로 하며, RDB와 다르게 해쉬테이블로 되어있어 O(1)의 조회성능이 나온다. 위 표는 rdb와 elasticsearch의 용어 대응을 보여주는데, 개인적으로는 꽤 많은 차이를 보이지만 조금이라도 이해를 도울 수 있을 것 같다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1522&quot; data-origin-height=&quot;848&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TYDE4/btsDhj20MGW/dlNnXwrk3YEKdK6lb4Rj20/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TYDE4/btsDhj20MGW/dlNnXwrk3YEKdK6lb4Rj20/img.png&quot; data-alt=&quot;https://www.devkuma.com/docs/elasticsearch/intro/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TYDE4/btsDhj20MGW/dlNnXwrk3YEKdK6lb4Rj20/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTYDE4%2FbtsDhj20MGW%2FdlNnXwrk3YEKdK6lb4Rj20%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;575&quot; height=&quot;320&quot; data-origin-width=&quot;1522&quot; data-origin-height=&quot;848&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.devkuma.com/docs/elasticsearch/intro/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 필드(field, =column)&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;key-value쌍으로 나타나며, 각각의 key는 text, date 등의 유형(type)을 가진다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 도큐먼트(document, =row)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;rdb의 row와는 좀 다른데, key-value쌍인 field가 모여서 구성하는 일종의 파일을 말한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 색인(index, =database)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;도큐먼트들의 집합을 말한다. 도큐먼트가 파일이라고 했으므로, 일종의 폴더로 볼 수 있다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 매핑(mapping, =schema)&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;필드의 key마다 type을 갖는다고 했는데, 이러한 정보들을 정의해놓은 schema라고 볼 수 있으며, index당 하나 존재한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Kibana&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;256&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b2Z3Cr/btsDlyxdPuP/t9QksJlti8aNjgibEU9JFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2Z3Cr/btsDlyxdPuP/t9QksJlti8aNjgibEU9JFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2Z3Cr/btsDlyxdPuP/t9QksJlti8aNjgibEU9JFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2Z3Cr%2FbtsDlyxdPuP%2Ft9QksJlti8aNjgibEU9JFk%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;368&quot; height=&quot;184&quot; data-origin-width=&quot;512&quot; data-origin-height=&quot;256&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;분석 및 시각화 플랫폼으로, ElasticSearch의 데이터를 차트 등으로 시각화하는데 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kibana에서 시각화를 하기 위해서는 Elasticsearch의 &lt;b&gt;Index&lt;/b&gt;에 연결되어 있어야 하며, Index Pattern에 맞추어 데이터를 가져온다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- Index Pattern&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Index 매핑 정보를 Kibana에서 사용하기 적합하게 미리 캐싱해둔 것이다. 여러개의 인덱스에 대한 메타데이터를 병합하여 저장했다가 검색/시각화 생성시 활용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Logstash&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;320&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Lz3zb/btsDivhcXvT/xFHOkBoEpehTfJKoHdr8ek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Lz3zb/btsDivhcXvT/xFHOkBoEpehTfJKoHdr8ek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Lz3zb/btsDivhcXvT/xFHOkBoEpehTfJKoHdr8ek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLz3zb%2FbtsDivhcXvT%2FxFHOkBoEpehTfJKoHdr8ek%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;392&quot; height=&quot;108&quot; data-origin-width=&quot;1160&quot; data-origin-height=&quot;320&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;애플리케이션의 로그를 처리하고 이를 ElasticSearch로 보내는데 사용되는 데이터 처리 파이프라인이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;input plugin(Logstash가 이벤트를 수신하는 방식)과 filter plugin(Logstash가 이벤트를 처리하는 방식)을 지정해줄 수 있다. filter plugin으로 Json을 사용하여 받아서 처리해주면 될 것 같다. 또한 데이터베이스에 무관하게 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Spring Data ElasticSearch&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Spring Data ES는 Elasticsearch 검색엔진을 사용하는 솔루션 개발을 도와주는 모듈이다. Spring Data JPA가 Repository를 컴포넌트로 등록해주고, 인터페이스 명명규칙에 따라서 내부적으로 JPQL을 생성해서 쿼리를 날려주는 것처럼, Spring Data ES도 Repository 인터페이스에 메서드를 정의함으로써 쿼리를 표현할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Spring Data ElasticSearch vs Rest High Level Client&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;b&gt;Spring Data ES&lt;/b&gt;는 ORM을 지원하기 때문에 객체의 형태로 ES와 상호작용할 수 있으며, 별도의 쿼리빌더를 생성하지 않아도 되어 &lt;a href=&quot;https://charlezz.medium.com/%EB%B3%B4%EC%9D%BC%EB%9F%AC%ED%94%8C%EB%A0%88%EC%9D%B4%ED%8A%B8-%EC%BD%94%EB%93%9C%EB%9E%80-boilerplate-code-83009a8d3297&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;보일러 플레이트&lt;/a&gt; 코드를 줄일 수 있다. &lt;b&gt;Rest High Level Client&lt;/b&gt;는 대신 버전에 민감하지 않다는 장점을 갖는다. 참고로 Spring Data ES도 내부적으로 Rest High Level Client를 사용한다고 하니, Spring Data ES가 내부적으로 쿼리를 생성해주는 것으로 추측할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;로깅 시스템 구상&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(1) 클라이언트에서 로그 api를 json형태로 전송하면, &lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;s&gt;서버에서 받아서 로그를 logstash에 저장한다.&lt;/s&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;&lt;s&gt;(2) logstash에서는 로그를 받아서 로그 레코드를 정제하여 elasticsearch에 저장한다.&lt;/s&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(3) elasticsearch에 저정된 로그들은 kibana를 통해서 시각화된 분석이 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(4) elasticsearch에 저장된 로그는 curator를 사용하여 오래된 로그를 자동으로 지워준다.&lt;/p&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;gt; 인스턴스 비용 문제로 logstash를 현재 프로젝트에서 실행하기가 어렵다는 결론이 나와, 스프링부트에서 바로 es를 통해서 kibana로 시각화하는 것으로 결정했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;실습 환경&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- elasticsearch/logstash/kibana: 8.11.3&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- java 17, spring 3.2.1&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;실습&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;A. ElasticSearch 실행&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. elasticsearch 홈페이지(&lt;a href=&quot;https://www.elastic.co/kr/downloads/past-releases#elasticsearch&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.elastic.co/kr/downloads/past-releases#elasticsearch&lt;/a&gt;)에서 원하는 버전 설치후 압축 해제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 터미널에서 폴더내의 ./bin/elasticsearch 파일 실행&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2904&quot; data-origin-height=&quot;80&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FH7s1/btsDhKySPP0/PwAk7z1nckrBVjYtmcO7a1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FH7s1/btsDhKySPP0/PwAk7z1nckrBVjYtmcO7a1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FH7s1/btsDhKySPP0/PwAk7z1nckrBVjYtmcO7a1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFH7s1%2FbtsDhKySPP0%2FPwAk7z1nckrBVjYtmcO7a1%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;932&quot; height=&quot;26&quot; data-origin-width=&quot;2904&quot; data-origin-height=&quot;80&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;만약 위와 같은 오류가 발생한다면 config/elasticsearch.yml에서 xpack 관련 보안옵션 4가지를 false로 설정해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1704879620215&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;xpack.security.enabled: false
xpack.security.enrollment.enabled: false
xpack.security.http.ssl: enabled: false
xpack.security.transport.ssl: enabled: false&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;local이 아니라 실제 운영시에는 ssl을 적용해주어야 하는 것 같음. (&lt;a href=&quot;https://logical-code.tistory.com/215&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://logical-code.tistory.com/215&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. elasticsearch 실행 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;localhost:9200으로 접속하면 elasticsearch 실행을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. elasticsearch에서 제공하는 Rest API를 이용하여 데이터를 관리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고: elastic 가이드북(&lt;a href=&quot;https://esbook.kimjmin.net/04-data&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://esbook.kimjmin.net/04-data&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;B.  Logstash 실행&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;설치파일을 받은 후, Logstash 설치 디렉터리 밑에 config/logstash-sample.conf 파일을 수정하자.&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;1244&quot; data-origin-height=&quot;682&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/np4mW/btsDkwGF1UY/lNHIsvPzB0WsbuDrS8ld81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/np4mW/btsDkwGF1UY/lNHIsvPzB0WsbuDrS8ld81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/np4mW/btsDkwGF1UY/lNHIsvPzB0WsbuDrS8ld81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fnp4mW%2FbtsDkwGF1UY%2FlNHIsvPzB0WsbuDrS8ld81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;592&quot; height=&quot;325&quot; data-origin-width=&quot;1244&quot; data-origin-height=&quot;682&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;input을 수정하여 수집할 데이터소스를 지정하고, output을 통해서 데이터를 elasticsearch로 전송할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후 설치경로 밑의 bin 디렉토리로 이동하여 ./logstash.bat -f logstash-sample.conf 과 같이 -f옵션으로 설정한 configuration 파일을 넘겨주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;C.  Kibana 실행&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;키바나는 받은 압축파일을 풀고, 파일경로 밑의 bin 디렉토리에 있는 kibana파일을 실행시켜주기만 하면 된다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;918&quot; data-origin-height=&quot;46&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHzNcb/btsDloBmZjZ/bFkdn7G0tJmIGZMcyRHoCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHzNcb/btsDloBmZjZ/bFkdn7G0tJmIGZMcyRHoCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHzNcb/btsDloBmZjZ/bFkdn7G0tJmIGZMcyRHoCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHzNcb%2FbtsDloBmZjZ%2FbFkdn7G0tJmIGZMcyRHoCK%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;560&quot; height=&quot;28&quot; data-origin-width=&quot;918&quot; data-origin-height=&quot;46&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;@. brew 설치 시도 -&amp;gt; 사용하지 않음&lt;/b&gt;&lt;/h3&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;2590&quot; data-origin-height=&quot;52&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bIwihc/btsDflmw1Jg/134xZDDmjO20JC8YdkOmhK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bIwihc/btsDflmw1Jg/134xZDDmjO20JC8YdkOmhK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bIwihc/btsDflmw1Jg/134xZDDmjO20JC8YdkOmhK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbIwihc%2FbtsDflmw1Jg%2F134xZDDmjO20JC8YdkOmhK%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;2590&quot; height=&quot;52&quot; data-origin-width=&quot;2590&quot; data-origin-height=&quot;52&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내내 이런 오류가 발생하여 설치하지 못함 (구글링해도 동일 오류가 나오지 않음) -&amp;gt; homebrew관련 문제로 생각하고 일단 포기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;했는데 계속 살펴보다 발견한 글:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/elastic/homebrew-tap/issues/146&quot;&gt;https://github.com/elastic/homebrew-tap/issues/146&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;56&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DZh7e/btsDkNnME3m/ga0CBjdD55LKokwQAdd2c1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DZh7e/btsDkNnME3m/ga0CBjdD55LKokwQAdd2c1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DZh7e/btsDkNnME3m/ga0CBjdD55LKokwQAdd2c1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDZh7e%2FbtsDkNnME3m%2Fga0CBjdD55LKokwQAdd2c1%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;755&quot; height=&quot;70&quot; data-origin-width=&quot;604&quot; data-origin-height=&quot;56&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;gt; elasticache의 경우 해결했으나, logstash와 kibana를 brew install로 설치 시도시 똑같은 오류가 발생하여 포기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가) 공식 가이드에서 다음과 같은 글을 찾았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;758&quot; data-origin-height=&quot;67&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcKwiC/btsDjXdj7RY/dZGnUJobKijaiBuucEIyv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcKwiC/btsDjXdj7RY/dZGnUJobKijaiBuucEIyv0/img.png&quot; data-alt=&quot;https://www.elastic.co/guide/en/logstash/current/brew.html&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcKwiC/btsDjXdj7RY/dZGnUJobKijaiBuucEIyv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcKwiC%2FbtsDjXdj7RY%2FdZGnUJobKijaiBuucEIyv0%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;758&quot; height=&quot;67&quot; data-origin-width=&quot;758&quot; data-origin-height=&quot;67&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.elastic.co/guide/en/logstash/current/brew.html&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Logstash 8.0부터는 homebrew cask를 지원하지 않는것 같다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 직접 설치파일을 다운받아서 실행해주어야 하는것을 권장하고 있다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;참고자료&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.devkuma.com/docs/elasticsearch/intro/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.devkuma.com/docs/elasticsearch/intro/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.slideshare.net/deview/2d1elasticsearch&quot;&gt;https://www.slideshare.net/deview/2d1elasticsearch&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://yeo0.tistory.com/entry/Kibana-%ED%82%A4%EB%B0%94%EB%82%98-%EA%B8%B0%EB%B3%B8%EA%B0%9C%EB%85%90-Index-Pattern-Discover-Visualize-Dashboard&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://yeo0.tistory.com/entry/Kibana-%ED%82%A4%EB%B0%94%EB%82%98-%EA%B8%B0%EB%B3%B8%EA%B0%9C%EB%85%90-Index-Pattern-Discover-Visualize-Dashboard&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;github&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/yourssu/YLS-Backend&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/yourssu/YLS-Backend&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1707202874181&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - yourssu/YLS-Backend: Yourssu Logging System for Backend&quot; data-og-description=&quot;Yourssu Logging System for Backend. Contribute to yourssu/YLS-Backend development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/yourssu/YLS-Backend&quot; data-og-url=&quot;https://github.com/yourssu/YLS-Backend&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/y9fyD/hyVf9HhVmO/rODheDkET2CgpkXQghKiNk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/yourssu/YLS-Backend&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/yourssu/YLS-Backend&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/y9fyD/hyVf9HhVmO/rODheDkET2CgpkXQghKiNk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - yourssu/YLS-Backend: Yourssu Logging System for Backend&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Yourssu Logging System for Backend. Contribute to yourssu/YLS-Backend development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>[ Backend ]/Infra</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/179</guid>
      <comments>https://eckrin.tistory.com/179#entry179comment</comments>
      <pubDate>Mon, 8 Jan 2024 20:23:28 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 동시성 이슈를 고려한 스프링 프로젝트 설계</title>
      <link>https://eckrin.tistory.com/178</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;트래픽이 많아질수록 동시성 이슈를 철저하게 고려해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 스프링에서 발생할 수 있는 동시성 이슈를 체크하고, 다양한 해결방법에 대해서 공부해보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Zwh0x/btsCZhqfJaS/wr8EyqMExKxtgtDvWQ9QLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Zwh0x/btsCZhqfJaS/wr8EyqMExKxtgtDvWQ9QLk/img.png&quot; data-origin-width=&quot;954&quot; data-origin-height=&quot;438&quot; data-is-animation=&quot;false&quot; width=&quot;442&quot; height=&quot;203&quot; data-widthpercent=&quot;44.67&quot; style=&quot;width: 44.1464%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Zwh0x/btsCZhqfJaS/wr8EyqMExKxtgtDvWQ9QLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZwh0x%2FbtsCZhqfJaS%2Fwr8EyqMExKxtgtDvWQ9QLk%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;954&quot; height=&quot;438&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c7KeKm/btsCZ4xrn1Y/K4dB9Acw11Q0R8WFFKBXIk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c7KeKm/btsCZ4xrn1Y/K4dB9Acw11Q0R8WFFKBXIk/img.png&quot; data-origin-width=&quot;966&quot; data-origin-height=&quot;358&quot; data-is-animation=&quot;false&quot; style=&quot;width: 54.6909%;&quot; data-widthpercent=&quot;55.33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c7KeKm/btsCZ4xrn1Y/K4dB9Acw11Q0R8WFFKBXIk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc7KeKm%2FbtsCZ4xrn1Y%2FK4dB9Acw11Q0R8WFFKBXIk%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;966&quot; height=&quot;358&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;동시성 문제는 지역변수와 같이 쓰레드별로 할당되는 공간에서는 발생하지 않으며, &lt;b&gt;싱글톤과 같이 동일한 인스턴스의 필드에 접근하거나, static과 같은 공용 필드를 변경할 때 발생&lt;/b&gt;한다. 위와 같이 간단한 엔티티와 서비스 클래스를 생성해준 후, 재고를 감소시키는 decrease라는 로직이 동시성 이슈 위에서 제대로 동작하는지를 체크해보도록 하자.&lt;/p&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;가장 먼저 들었던 생각은 그냥 &quot;@Transactional 어노테이션 걸어주면 되는거 아니야?&quot;라는 단순한 생각이었다. 트랜잭션을 통해서 데이터의 무결성을 지킬 수 있지 않을까? 라는 생각으로 간단한 테스트코드를 만들었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@BeforeEach
public void init() {
    stockRepository.saveAndFlush(new Stock(1L, 100L));
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;@Test
@DisplayName(&quot;멀티쓰레드 환경에서 Race Condition 발생!!&quot;)
public void 동시요청() throws InterruptedException {
    int threadCount = 100;
    // 쓰레드 32개를 관리하는 쓰레드 풀 객체 생성
    ExecutorService executorService = Executors.newFixedThreadPool(32);
    CountDownLatch latch = new CountDownLatch(threadCount);

    for(int i=0; i&amp;lt;threadCount; i++) {
        executorService.submit(() -&amp;gt; {
            try {
                stockService.decrease(1L, 1L);
            } finally {
                latch.countDown(); // 각 쓰레드의 작업 종료를 명시한다.
            }
        });
    }

    latch.await(); // 메인쓰레드는 latch의 count가 0이 되기를 기다린다.

    Stock stock = stockRepository.findById(1L).orElseThrow();
    Assertions.assertThat(stock.getQuantity()).isEqualTo(0); // race condition으로 인하여 원하는 결과가 나오지 않음
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;*ExecutorService는 간단하게 쓰레드 풀을 관리하는 역할을 하고, CountDownLatch를 통해서 일정 개수의 쓰레드의 실행을 기다렸다가 메인쓰레드가 다음 로직을 진행할 수 있도록 하였다. (자세한 설명은 https://imasoftwareengineer.tistory.com/100 참고)&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;처음 데이터베이스에 재고 100개짜리 상품이 존재하고, 더티체킹을 통해서 100개의 쓰레드에서 각각 재고를 1씩 감소시키는 로직을 실행하였으므로 남은 재고가 0일것이라는 예상을 해볼 수 있었다. 하지만..&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;742&quot; data-origin-height=&quot;140&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rcHcf/btsC4uhA51z/5ZVyseH1veVaX1p4VJEGck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rcHcf/btsC4uhA51z/5ZVyseH1veVaX1p4VJEGck/img.png&quot; data-alt=&quot;11개밖에 감소하지 않았다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rcHcf/btsC4uhA51z/5ZVyseH1veVaX1p4VJEGck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrcHcf%2FbtsC4uhA51z%2F5ZVyseH1veVaX1p4VJEGck%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;493&quot; height=&quot;93&quot; data-origin-width=&quot;742&quot; data-origin-height=&quot;140&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;11개밖에 감소하지 않았다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예상과는 다르게 재고가 남아있는 모습을 확인할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Race Condition&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;3학년때 OS, 데이터베이스 과목에서 들어본 후로 오랜만에 들어보는 것 같은데, Race Condition이란 &quot;&lt;span style=&quot;color: #374151; text-align: start;&quot;&gt;다중 스레드 또는 다중 프로세스 환경에서 &lt;b&gt;공유된 자원에 대한 동시적인 접근으로 인해 예기치 못한 동작이 발생하는 상황&lt;/b&gt;&quot;을 말한다.&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: #374151; text-align: start;&quot;&gt;현재 재고를 감소시키기 위한 decrease() 함수의 동작을 위해서는&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;(1) 데이터베이스에서 product의 재고 수량을 조회 (Read)&lt;br /&gt;(2) read한 수량을 1 감소 (Modify)&lt;br /&gt;(3) 감소시킨 수량을 데이터베이스에 반영 (Update)&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;라는 진행과정이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 쓰레드 A가 데이터를 read하고 update하기까지 걸리는 시간 사이에 다른 쓰레드 B가 update되지 않은 재고 수량을 읽어버린다면, 결국 쓰레드 A, B는 동일한 update 작업을 반복하게 되는 것이 되므로 테스트코드에서 재고가 모두 소진되지 않는 문제가 발생했던 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1.&amp;nbsp; 트랜잭션 격리 수준 변경&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;702&quot; data-origin-height=&quot;284&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bSOSOe/btsC4CUcy4g/0bUJR4PSfFLkyJsQQqBbCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bSOSOe/btsC4CUcy4g/0bUJR4PSfFLkyJsQQqBbCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bSOSOe/btsC4CUcy4g/0bUJR4PSfFLkyJsQQqBbCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbSOSOe%2FbtsC4CUcy4g%2F0bUJR4PSfFLkyJsQQqBbCK%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;383&quot; height=&quot;155&quot; data-origin-width=&quot;702&quot; data-origin-height=&quot;284&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;첫번째로 생각했던 해결책이다. 트랜잭션의 고립성(Isolation)은 &lt;span style=&quot;color: #374151; text-align: left;&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;502&quot; data-origin-height=&quot;159&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bppqig/btsIRencyJ0/NapdXtJVa2kdWVAUZKSsFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bppqig/btsIRencyJ0/NapdXtJVa2kdWVAUZKSsFK/img.png&quot; data-alt=&quot;격리 수준: Serializable&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bppqig/btsIRencyJ0/NapdXtJVa2kdWVAUZKSsFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbppqig%2FbtsIRencyJ0%2FNapdXtJVa2kdWVAUZKSsFK%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;502&quot; height=&quot;159&quot; data-origin-width=&quot;502&quot; data-origin-height=&quot;159&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;격리 수준: Serializable&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;911&quot; data-origin-height=&quot;515&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lsMvp/btsIQviGy86/BhVP3bNQorpYWiNMIOWKN0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lsMvp/btsIQviGy86/BhVP3bNQorpYWiNMIOWKN0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lsMvp/btsIQviGy86/BhVP3bNQorpYWiNMIOWKN0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlsMvp%2FbtsIQviGy86%2FBhVP3bNQorpYWiNMIOWKN0%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;688&quot; height=&quot;389&quot; data-origin-width=&quot;911&quot; data-origin-height=&quot;515&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;398&quot; data-origin-height=&quot;66&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MZ6JS/btsC51tlK5k/cOZFTkaSEHYOKpCk8Ztrkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MZ6JS/btsC51tlK5k/cOZFTkaSEHYOKpCk8Ztrkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MZ6JS/btsC51tlK5k/cOZFTkaSEHYOKpCk8Ztrkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMZ6JS%2FbtsC51tlK5k%2FcOZFTkaSEHYOKpCk8Ztrkk%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;398&quot; height=&quot;66&quot; data-origin-width=&quot;398&quot; data-origin-height=&quot;66&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;s&gt;가장 높은 트랜잭션 격리 수준인 SERIALIZABLE조차 트랜잭션 도중 select 쿼리에 대해서 공유락(LS)만을 걸기 때문에, 다른 쓰레드에서 읽기작업이 진행되는 것을 막을 수는 없기 때문으로 보인다.&lt;/s&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+ 추가)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;격리 수준을 SERIALIZABLE로 지정하면, 모든 트랜잭션이 직렬적으로 실행되기 때문에 동시성 문제가 발생하지는 않는다. 대신 트랜잭션 충돌이 발생하게 되는데, 이렇게 트랜잭션이 실패하면 재시도되지 않고 예외를 던지고 종료된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/moAHy/btsIReU4B8O/pBBm8yYWcaoJpqhAmU5Sbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/moAHy/btsIReU4B8O/pBBm8yYWcaoJpqhAmU5Sbk/img.png&quot; data-origin-width=&quot;460&quot; data-origin-height=&quot;377&quot; data-is-animation=&quot;false&quot; style=&quot;width: 40.1074%; margin-right: 10px;&quot; data-widthpercent=&quot;40.58&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/moAHy/btsIReU4B8O/pBBm8yYWcaoJpqhAmU5Sbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmoAHy%2FbtsIReU4B8O%2FpBBm8yYWcaoJpqhAmU5Sbk%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;460&quot; height=&quot;377&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bu1BFR/btsIRWTXqcv/5qSFY21rxtg9ydU2F39kgk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bu1BFR/btsIRWTXqcv/5qSFY21rxtg9ydU2F39kgk/img.png&quot; data-origin-width=&quot;913&quot; data-origin-height=&quot;511&quot; data-is-animation=&quot;false&quot; style=&quot;width: 58.7298%;&quot; data-widthpercent=&quot;59.42&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bu1BFR/btsIRWTXqcv/5qSFY21rxtg9ydU2F39kgk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbu1BFR%2FbtsIRWTXqcv%2F5qSFY21rxtg9ydU2F39kgk%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;913&quot; height=&quot;511&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;515&quot; data-origin-height=&quot;74&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bc82Bx/btsIQyNgaMZ/6QG7kCnhkhec268Qa3LuL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bc82Bx/btsIQyNgaMZ/6QG7kCnhkhec268Qa3LuL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bc82Bx/btsIQyNgaMZ/6QG7kCnhkhec268Qa3LuL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbc82Bx%2FbtsIQyNgaMZ%2F6QG7kCnhkhec268Qa3LuL0%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;515&quot; height=&quot;74&quot; data-origin-width=&quot;515&quot; data-origin-height=&quot;74&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 위와 같이 트랜잭션 실행 중 예외 발생시 재시도하는 로직을 추가하고, 테스트코드를 돌렸더니 성공하는 것을 확인할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. synchronized 키워드 추가&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;a href=&quot;https://eckrin.tistory.com/173&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;예전에&lt;/a&gt; 정리한적 있듯이, 스레드간 blocking을 이용하여 동기화 블락 전체에 락을 걸어주어 해결할 수 있다. 다만 여기서 유의해야 할 점은, @Transactional을 사용할 때 아래 코드와 같이&amp;nbsp;&lt;b&gt;스프링 트랜잭션은 프록시 방식으로 동작&lt;/b&gt;하기 때문에 기존의 서비스 클래스를 필드로 주입받는 가짜 객체를 사용하여 트랜잭션을 관리하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/As4lL/btsC4fyMiPf/CPLp4tWeNXmeHEB5KAw3v1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/As4lL/btsC4fyMiPf/CPLp4tWeNXmeHEB5KAw3v1/img.png&quot; data-origin-width=&quot;513&quot; data-origin-height=&quot;326&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;60.53&quot; style=&quot;width: 59.8216%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/As4lL/btsC4fyMiPf/CPLp4tWeNXmeHEB5KAw3v1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAs4lL%2FbtsC4fyMiPf%2FCPLp4tWeNXmeHEB5KAw3v1%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;513&quot; height=&quot;326&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bo4RVC/btsC4Jl3WCK/Yc6FlvWdFDVuz2ko6IVbT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bo4RVC/btsC4Jl3WCK/Yc6FlvWdFDVuz2ko6IVbT0/img.png&quot; data-widthpercent=&quot;39.47&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;532&quot; data-origin-width=&quot;546&quot; style=&quot;width: 39.0157%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bo4RVC/btsC4Jl3WCK/Yc6FlvWdFDVuz2ko6IVbT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbo4RVC%2FbtsC4Jl3WCK%2FYc6FlvWdFDVuz2ko6IVbT0%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;546&quot; height=&quot;532&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;왼쪽이 서비스 클래스, 오른쪽이 트랜잭션 프록시 예시&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세히 설명하면, &lt;b&gt;스프링 트랜잭션은 오른쪽과 같이 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;프록시 객체가&amp;nbsp;&lt;/span&gt;실제 서비스 객체를 감싸고 있는 형태로 되어 동작&lt;/b&gt;하는데, 이 과정에서 decrease() 메소드 자체에 대해서는 동시성이 보장되지만, db에 데이터가 업데이트되는 시점은 트랜잭션 종료 시점(endTransaction() 호출 시점)이므로 decrease()와 endTransaction() 사이에 다른 스레드가 db를 조회하게 되면 동시성을 보장할 수 없다.&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;518&quot; data-origin-height=&quot;177&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Mf8HK/btsC6xZN78M/XGR9TkHGr3TSd621tNJE5K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Mf8HK/btsC6xZN78M/XGR9TkHGr3TSd621tNJE5K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Mf8HK/btsC6xZN78M/XGR9TkHGr3TSd621tNJE5K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMf8HK%2FbtsC6xZN78M%2FXGR9TkHGr3TSd621tNJE5K%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;518&quot; height=&quot;177&quot; data-origin-width=&quot;518&quot; data-origin-height=&quot;177&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;341&quot; data-origin-height=&quot;78&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b805DD/btsC77zIyIx/Twx99QOKuKFWoKmX2COCbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b805DD/btsC77zIyIx/Twx99QOKuKFWoKmX2COCbk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b805DD/btsC77zIyIx/Twx99QOKuKFWoKmX2COCbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb805DD%2FbtsC77zIyIx%2FTwx99QOKuKFWoKmX2COCbk%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;341&quot; height=&quot;78&quot; data-origin-width=&quot;341&quot; data-origin-height=&quot;78&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;@Transactional 어노테이션을 제거하여 메소드 단독으로 실행되도록 하거나, 메소드를 호출하는 쪽에 &lt;a href=&quot;https://eckrin.tistory.com/173&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;synchronized&lt;/a&gt; 키워드를 설정해준다면 정상적으로 동작함을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;하지만 전자의 경우 트랜잭션을 적용할 수 없다는 문제가 있고, 두번째 방법도 결국 모든 요청 각각에 lock을 걸어 한번에 하나의 스레드만 실행되게 되므로 성능이 좋지 않을 것임을 예상할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;뿐만 아니라 synchronized 키워드는 '동일 프로세스의 스레드'에 대해서만 동시성을 보장한다. 단일 서버라면 문제가 되지 않겠지만, 여러대의 서버에서 요청이 들어오는 경우는 동시성을 보장할 수 없다.&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. RDB 이용하기&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3-1. Pessimistic Lock (비관적 락)&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;680&quot; data-origin-height=&quot;334&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzENAO/btsC2C8Txi0/qweGMYrj2MVjxaKqZziv9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzENAO/btsC2C8Txi0/qweGMYrj2MVjxaKqZziv9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzENAO/btsC2C8Txi0/qweGMYrj2MVjxaKqZziv9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzENAO%2FbtsC2C8Txi0%2FqweGMYrj2MVjxaKqZziv9k%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;373&quot; height=&quot;183&quot; data-origin-width=&quot;680&quot; data-origin-height=&quot;334&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;데이터베이스에서 데이터에 접근할 때 배타락(EX)를 걸어 다른 프로세스에서 데이터에 접근할 수 없도록 하는 방법이다. 데이터의 정합성이 보장되기는 하지만 성능 감소가 일어날 수 밖에 없다는 단점이 있다. (데이터 충돌이 빈번하게 일어나면 낙관적 락보다는 성능이 좋다.)&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;444&quot; data-origin-height=&quot;67&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dnhYOD/btsC4cINk8B/UqujzkZb0EHWsAJVor7xT0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dnhYOD/btsC4cINk8B/UqujzkZb0EHWsAJVor7xT0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dnhYOD/btsC4cINk8B/UqujzkZb0EHWsAJVor7xT0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdnhYOD%2FbtsC4cINk8B%2FUqujzkZb0EHWsAJVor7xT0%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;444&quot; height=&quot;67&quot; data-origin-width=&quot;444&quot; data-origin-height=&quot;67&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;Spring Data JPA에서 비관적 락을 걸 때는 PESSIMISTIC_WRITE를 주로 사용하는데, 이렇게 비관적 락을 걸어주면 트랜잭션은 락을 획득할 때까지 대기하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3-2. Optimistic Lock (낙관적 락)&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;466&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MdGHb/btsC34RDIl1/aklgsYDcNnAxsXgAHeLKB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MdGHb/btsC34RDIl1/aklgsYDcNnAxsXgAHeLKB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MdGHb/btsC34RDIl1/aklgsYDcNnAxsXgAHeLKB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMdGHb%2FbtsC34RDIl1%2FaklgsYDcNnAxsXgAHeLKB1%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;491&quot; height=&quot;209&quot; data-origin-width=&quot;1094&quot; data-origin-height=&quot;466&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;락을 직접 이용하지는 않고, 별도의 컬럼을 이용하여 정합성을 맞출 수 있다. update를 수행할 때, 읽어온 version값에 해당하는 데이터만 업데이트함으로서 동일한 update 쿼리가 반복 실행되지 않도록 방지한다. 이를 위해서 동일한 update문이 수행되는 것을 방지하는 별도의 애플리케이션 로직이 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dlzcDY/btsCVI94c6J/cejvySo4OUqYCEeJ0dSyxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dlzcDY/btsCVI94c6J/cejvySo4OUqYCEeJ0dSyxk/img.png&quot; data-origin-width=&quot;327&quot; data-origin-height=&quot;42&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;53.26&quot; style=&quot;width: 52.6381%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dlzcDY/btsCVI94c6J/cejvySo4OUqYCEeJ0dSyxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdlzcDY%2FbtsCVI94c6J%2FcejvySo4OUqYCEeJ0dSyxk%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;327&quot; height=&quot;42&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5cp9c/btsC4EZmCa3/fKoAqVf3KgEiIrwwMHfHUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5cp9c/btsC4EZmCa3/fKoAqVf3KgEiIrwwMHfHUk/img.png&quot; data-origin-width=&quot;451&quot; data-origin-height=&quot;66&quot; data-is-animation=&quot;false&quot; style=&quot;width: 46.1992%;&quot; data-widthpercent=&quot;46.74&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5cp9c/btsC4EZmCa3/fKoAqVf3KgEiIrwwMHfHUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5cp9c%2FbtsC4EZmCa3%2FfKoAqVf3KgEiIrwwMHfHUk%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;66&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;393&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4z9cL/btsC4ezQCmC/0WMWWdPZfk05v87zlK4MJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4z9cL/btsC4ezQCmC/0WMWWdPZfk05v87zlK4MJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4z9cL/btsC4ezQCmC/0WMWWdPZfk05v87zlK4MJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4z9cL%2FbtsC4ezQCmC%2F0WMWWdPZfk05v87zlK4MJK%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;614&quot; height=&quot;372&quot; data-origin-width=&quot;649&quot; data-origin-height=&quot;393&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;엔티티에 @Version 어노테이션을 통해서 낙관적 락의 버전을 명시하고, Facade 클래스를 만들어서 version정보가 틀려서 update쿼리가 실패한다면 50ms 이후 재시도하는 별도의 로직으로 감싸주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;충돌이 빈번하게 발생할 것이라고 예상된다면 비관적 락을, 아니라면 낙관적 락을 사용하자.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3-3. Named Lock (네임드 락)&lt;/b&gt;&lt;/h3&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;304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkoFcs/btsC1IhT4NW/8anntBeBA3Y2kohOUapvL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkoFcs/btsC1IhT4NW/8anntBeBA3Y2kohOUapvL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkoFcs/btsC1IhT4NW/8anntBeBA3Y2kohOUapvL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkoFcs%2FbtsC1IhT4NW%2F8anntBeBA3Y2kohOUapvL0%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;626&quot; height=&quot;176&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;304&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;네임드 락은 이름을 가지는 락으로, 이름과 timeout정보를 얻어서 락을 획득하고, 해제하는 방법이다. pessimistic lock은 db상의 데이터에 락을 걸었다면, named lock은 별도의 접근공간에 락을 걸게 된다.&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;514&quot; data-origin-height=&quot;114&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cbDEXx/btsDbfxJRTQ/l7kK8zw1CBbxODggbT3skk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cbDEXx/btsDbfxJRTQ/l7kK8zw1CBbxODggbT3skk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cbDEXx/btsDbfxJRTQ/l7kK8zw1CBbxODggbT3skk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcbDEXx%2FbtsDbfxJRTQ%2Fl7kK8zw1CBbxODggbT3skk%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;514&quot; height=&quot;114&quot; data-origin-width=&quot;514&quot; data-origin-height=&quot;114&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;518&quot; data-origin-height=&quot;202&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oKpHn/btsC4pvdI7d/NCwy7R3GNfaJZdVg79mAMK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oKpHn/btsC4pvdI7d/NCwy7R3GNfaJZdVg79mAMK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oKpHn/btsC4pvdI7d/NCwy7R3GNfaJZdVg79mAMK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoKpHn%2FbtsC4pvdI7d%2FNCwy7R3GNfaJZdVg79mAMK%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;518&quot; height=&quot;202&quot; data-origin-width=&quot;518&quot; data-origin-height=&quot;202&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. NoSQL(Redis) 이용하기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트래픽이 증가한다면 rdb보다는 nosql을 사용하는 것이 성능상의 이득을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4-1. Lettuce (Spring Data Redis)&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #374151; text-align: start;&quot;&gt;&amp;nbsp;Redis의&amp;nbsp;&lt;/span&gt;setnx(set if not exists) 명령어를 활용하여 키가 기존에 존재하지 않을 경우에만 동작하게 할 수 있는데, Spring Data &lt;span style=&quot;color: #374151; text-align: start;&quot;&gt;Redis의&lt;span style=&quot;color: #374151; text-align: start;&quot;&gt;&lt;span&gt; &lt;/span&gt;redis connection pool을 제공하는&lt;/span&gt;&amp;nbsp;클라이언트 라이브러리 중 하나인 Lettuce를 사용하여&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;다음과 같이 활용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(위에 &lt;span style=&quot;color: #374151; text-align: start;&quot;&gt;Lettuce(Spring Data Redis) 라고 써놓은 이유는 기본 redis-cli 라이브러리가 Lettuce이기 때문이다.)&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;656&quot; data-origin-height=&quot;161&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/E5tCJ/btsC4pa2Yv3/DAJwKsI4ipZhDof9a6HoG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/E5tCJ/btsC4pa2Yv3/DAJwKsI4ipZhDof9a6HoG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/E5tCJ/btsC4pa2Yv3/DAJwKsI4ipZhDof9a6HoG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FE5tCJ%2FbtsC4pa2Yv3%2FDAJwKsI4ipZhDof9a6HoG0%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;656&quot; height=&quot;161&quot; data-origin-width=&quot;656&quot; data-origin-height=&quot;161&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;692&quot; data-origin-height=&quot;243&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FXh94/btsC4JUMHjQ/xhcvQCA6zSoOe5SqGZFCpK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FXh94/btsC4JUMHjQ/xhcvQCA6zSoOe5SqGZFCpK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FXh94/btsC4JUMHjQ/xhcvQCA6zSoOe5SqGZFCpK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFXh94%2FbtsC4JUMHjQ%2FxhcvQCA6zSoOe5SqGZFCpK%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;692&quot; height=&quot;243&quot; data-origin-width=&quot;692&quot; data-origin-height=&quot;243&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;while문을 이용하여 spin lock의 형태로 구현하였다. 락을 얻을때까지 sleep(100)을 반복하며 락 획득을 시도하다가, 락을 획득하면 decrease()를 수행한 후 unlock한다. spin lock 방식이므로 많은 스레드가 lock획득을 위해서 대기할수록 redis에 부하가 증가하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Named Lock과 거의 동일한 원리를 가지지만, Redis의 setnx를 사용한다는 점에서 차이가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4-2. Redisson&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;redis pub-sub 기반으로 동작하기 때문에 redis에 가는 부하를 줄여줄 수 있으며, 라이브러리 차원에서 lock을 제공하기 때문에 기존 라이브러리를 그대로 사용해주면 된다.&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;666&quot; data-origin-height=&quot;396&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c9BBqE/btsC6xsQGcJ/0ItASBEQ42VqbKWnLQcVbK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c9BBqE/btsC6xsQGcJ/0ItASBEQ42VqbKWnLQcVbK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c9BBqE/btsC6xsQGcJ/0ItASBEQ42VqbKWnLQcVbK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc9BBqE%2FbtsC6xsQGcJ%2F0ItASBEQ42VqbKWnLQcVbK%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;666&quot; height=&quot;396&quot; data-origin-width=&quot;666&quot; data-origin-height=&quot;396&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;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;github: &lt;a href=&quot;https://github.com/eckrin/sync-example&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/eckrin/sync-example&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고: 자바 ORM 표준 JPA 프로그래밍, 재고시스템으로 알아보는 동시성이슈 해결&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://zzang9ha.tistory.com/443&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://zzang9ha.tistory.com/443&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JPA에서 락 걸기 (&lt;a href=&quot;https://jaehoney.tistory.com/159&quot;&gt;https://jaehoney.tistory.com/159&lt;/a&gt;)&lt;/p&gt;</description>
      <category>[ Backend ]/Spring</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/178</guid>
      <comments>https://eckrin.tistory.com/178#entry178comment</comments>
      <pubDate>Wed, 3 Jan 2024 02:50:01 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Data JPA] @Repository를 생략해도 되는 이유</title>
      <link>https://eckrin.tistory.com/177</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;흔히 Spring Data JPA를 사용하기 위해서 JpaRepository 인터페이스를 사용할 때 아래와 같이 사용하고는 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1702196384099&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface MemberRepository extends JpaRepository&amp;lt;Member, Long&amp;gt; {
}&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;예전에 Spring Data JPA에 관해 공부할 떄 @Repository 어노테이션을 생략해도 된다는 말을 들었는데, 그 이유에 대해서 짧게 정리해보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;@Repository&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;@Repository라는 어노테이션은 왜 사용하는 걸까?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;313&quot; data-origin-height=&quot;117&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhjbY5/btsBDOwKbHL/1w94DTrYWpUOPxpQKknGD0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhjbY5/btsBDOwKbHL/1w94DTrYWpUOPxpQKknGD0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhjbY5/btsBDOwKbHL/1w94DTrYWpUOPxpQKknGD0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhjbY5%2FbtsBDOwKbHL%2F1w94DTrYWpUOPxpQKknGD0%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;313&quot; height=&quot;117&quot; data-origin-width=&quot;313&quot; data-origin-height=&quot;117&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;280&quot; data-origin-height=&quot;110&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/coG8Hw/btsBKiCNUy0/izDvQYqjqv7QLfvFcO7xp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/coG8Hw/btsBKiCNUy0/izDvQYqjqv7QLfvFcO7xp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/coG8Hw/btsBKiCNUy0/izDvQYqjqv7QLfvFcO7xp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcoG8Hw%2FbtsBKiCNUy0%2FizDvQYqjqv7QLfvFcO7xp0%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;308&quot; height=&quot;121&quot; data-origin-width=&quot;280&quot; data-origin-height=&quot;110&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;288&quot; data-origin-height=&quot;111&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/PHtTY/btsBFLzuO0M/ajD8bvVY7xWmnXuL14zj0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/PHtTY/btsBFLzuO0M/ajD8bvVY7xWmnXuL14zj0k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/PHtTY/btsBFLzuO0M/ajD8bvVY7xWmnXuL14zj0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FPHtTY%2FbtsBFLzuO0M%2FajD8bvVY7xWmnXuL14zj0k%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;298&quot; height=&quot;115&quot; data-origin-width=&quot;288&quot; data-origin-height=&quot;111&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;스프링부트 프로젝트에서 많이 사용되는 @Controller, @Service와 @Repository를 살펴보면, @Component 어노테이션이 있는 것을 확인할 수 있다. 스프링은 스프링 Main클래스에 등록되어 있는 @ComponentScan 어노테이션 안의 @ComponentScan 어노테이션을 통해서 @Component 어노테이션이 붙은 클래스들을 빈으로 등록해준다.&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;이렇게 빈으로 등록된 Repository는 @Transactional 어노테이션을 통해서 스프링이 트랜잭션을 관리할 수 있게 되며, JPA 예외를 &lt;span style=&quot;color: #374151; text-align: left;&quot;&gt;DataAccessException 하위 클래스로 변환하여 일괄처리 할 수 있게 돕는다.&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;816&quot; data-origin-height=&quot;203&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bBK4Xi/btsBGmTIs3z/uqHkbAEeO5BshQO8KMZaHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bBK4Xi/btsBGmTIs3z/uqHkbAEeO5BshQO8KMZaHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bBK4Xi/btsBGmTIs3z/uqHkbAEeO5BshQO8KMZaHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbBK4Xi%2FbtsBGmTIs3z%2FuqHkbAEeO5BshQO8KMZaHK%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;816&quot; height=&quot;203&quot; data-origin-width=&quot;816&quot; data-origin-height=&quot;203&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;여기까지는 다들 잘 아는 사실일테고, 나도 당연하다고 생각했는데 그렇다면 왜 Spring Data JPA를 사용할 떄 CRUD를 위한 JpaRepository를 구현하면 @Repository 어노테이션을 쓰지 않아도 되는걸까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;JpaRepository 인터페이스 분석해보기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;887&quot; data-origin-height=&quot;357&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ltcyZ/btsBFFznwp0/kEDAU30BhJWMbA9gfFCyxK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ltcyZ/btsBFFznwp0/kEDAU30BhJWMbA9gfFCyxK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ltcyZ/btsBFFznwp0/kEDAU30BhJWMbA9gfFCyxK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FltcyZ%2FbtsBFFznwp0%2FkEDAU30BhJWMbA9gfFCyxK%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;589&quot; height=&quot;265&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;887&quot; data-origin-height=&quot;357&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;가장 먼저 Spring Data JPA의 핵심이 되는 JpaRepository가 어떤 인터페이스들을 상속받고 있는지 확인해보았다. 위의 클래스 다이어그램은 JpaRepository의 상위 인터페이스들을 나타내는 다이어그램이다. 상위 인터페이스들 중에 @Repository 어노테이션을 가지고 있는 인터페이스가 있을 것이라고 짐작했지만, @Repository 어노테이션은 존재하지 않았다.&lt;/p&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;JpaRepository는 3개의 인터페이스를 상속받고 있다. ListCrudRepository, ListPagingAndSortingRepository, QueryByExampleExecutor라는 3개의 인터페이스가 그것인데, spring-data-jpa 모듈의&amp;nbsp;&lt;i&gt;'org.springframework.data.jpa.repository'&lt;/i&gt;에 위치하는 JpaRepository와는 다르게 상위 인터페이스들은 모두 spring-data-commons 모듈의&amp;nbsp;&lt;i&gt;'org.springframework.data.repository'&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;즉 JpaRepository가 상속받는 인터페이스들은 모두 Spring Data JPA가 아닌, JPA와 상관없이 Spring Data 모듈에서 제공하는 친구들이며, Spring Data 프로젝트에서 공통 모듈들에 대해서 기능(Spring Data JPA, Spring Data Redis, Spring Data JDBC 등)별로 구현체를 만들어 줌을 추측할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wGILn/btsBKeUJVRK/hLyMu94IA7zk14KHcCBiEk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wGILn/btsBKeUJVRK/hLyMu94IA7zk14KHcCBiEk/img.png&quot; data-origin-width=&quot;166&quot; data-origin-height=&quot;219&quot; data-is-animation=&quot;false&quot; data-widthpercent=&quot;40.92&quot; style=&quot;width: 40.4488%; margin-right: 10px;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wGILn/btsBKeUJVRK/hLyMu94IA7zk14KHcCBiEk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwGILn%2FbtsBKeUJVRK%2FhLyMu94IA7zk14KHcCBiEk%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;166&quot; height=&quot;219&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dlaPAU/btsBJadNoYm/iEKbkSGGmujl8Vs3hqWTdK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dlaPAU/btsBJadNoYm/iEKbkSGGmujl8Vs3hqWTdK/img.png&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;223&quot; data-origin-width=&quot;244&quot; style=&quot;width: 58.3884%;&quot; data-widthpercent=&quot;59.08&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dlaPAU/btsBJadNoYm/iEKbkSGGmujl8Vs3hqWTdK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdlaPAU%2FbtsBJadNoYm%2FiEKbkSGGmujl8Vs3hqWTdK%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;244&quot; height=&quot;223&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;왼쪽이 spring-data-jpa모듈 내의 JpaRepository, 오른쪽이 spring-data-common 모듈 내의 상위 인터페이스&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;656&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9gzrP/btsBFqCmsAF/JkS0zHaNZDQtoSw83p508K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9gzrP/btsBFqCmsAF/JkS0zHaNZDQtoSw83p508K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9gzrP/btsBFqCmsAF/JkS0zHaNZDQtoSw83p508K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9gzrP%2FbtsBFqCmsAF%2FJkS0zHaNZDQtoSw83p508K%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;413&quot; height=&quot;212&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;656&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;실제로 Spring Data JPA는 JpaRepository를 상속받은 커스텀 인터페이스에 있는 abstract method를 대상으로 하는 구현 클래스를 자동으로 생성해준다. (SimpleJpaRepository 클래스를 보면 제네릭 기반으로 구현되어 있는 것을 볼 수 있다) 따라서 마찬가지로 Spring Data 프로젝트에서 @Repository의 역할을 하는 기능도 제공해줄 것이라고 추측할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;해결&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;답은 Spring Data JPA 자체에 있었다.&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;887&quot; data-origin-height=&quot;399&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/R5quW/btsBMVtSKb6/g7gBv4hXczsQiiTGQdN4Lk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/R5quW/btsBMVtSKb6/g7gBv4hXczsQiiTGQdN4Lk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/R5quW/btsBMVtSKb6/g7gBv4hXczsQiiTGQdN4Lk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FR5quW%2FbtsBMVtSKb6%2Fg7gBv4hXczsQiiTGQdN4Lk%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;551&quot; height=&quot;248&quot; data-origin-width=&quot;887&quot; data-origin-height=&quot;399&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;오른쪽 위를 보면 JpaRepository는 &lt;i&gt;'org.springframework.data.repository'&lt;/i&gt; 패키지의 &lt;b&gt;Repository 인터페이스&lt;/b&gt;를 상속받는 것을 확인할 수 있었는데, &lt;b&gt;해당 인터페이스를 구현한 클래스는 컴포넌트 스캔의 대상이 된다&lt;/b&gt;고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>[ Backend ]/Spring DB, JPA</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/177</guid>
      <comments>https://eckrin.tistory.com/177#entry177comment</comments>
      <pubDate>Sun, 10 Dec 2023 17:50:30 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 비동기 환경에서 트랜잭션은 어떻게 동작할까?</title>
      <link>https://eckrin.tistory.com/176</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;동기와 비동기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;스프링에서는 @Async 어노테이션을 이용한 비동기 호출을 지원한다. 동기 호출은 일반적인 코드 흐름으로, 코드의 순서에 따라서 순차 처리되는 일반적인 플로우를 말한다. 반면 비동기 호출은 현재 실행중인 코드의 흐름과는 상관없이, 완료 여부를 확인하지 않고 다음 로직으로 넘어가는 것을 말한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(동기 시퀀스 다이어그램)&lt;/b&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;884&quot; data-origin-height=&quot;157&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWHUXw/btsBzL0ACp9/0Z8rkFkFCTFoTF2VxzAgi0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWHUXw/btsBzL0ACp9/0Z8rkFkFCTFoTF2VxzAgi0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWHUXw/btsBzL0ACp9/0Z8rkFkFCTFoTF2VxzAgi0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWHUXw%2FbtsBzL0ACp9%2F0Z8rkFkFCTFoTF2VxzAgi0%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;609&quot; height=&quot;108&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;884&quot; data-origin-height=&quot;157&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;동기 호출의 예시인데,&amp;nbsp; ServiceBus에서 메시지가 반환될때까지 TBJump 정보를 조회하는 플로우는 실행되지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(비동기 시퀀스 다이어그램)&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;428&quot; data-origin-height=&quot;68&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cSTXCZ/btsBC4x4SKx/2DguG4xF6b1gNdtvEViQUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cSTXCZ/btsBC4x4SKx/2DguG4xF6b1gNdtvEViQUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cSTXCZ/btsBC4x4SKx/2DguG4xF6b1gNdtvEViQUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcSTXCZ%2FbtsBC4x4SKx%2F2DguG4xF6b1gNdtvEViQUk%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;334&quot; height=&quot;53&quot; data-origin-width=&quot;428&quot; data-origin-height=&quot;68&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이번에는 비동기 호출의 예시이다. 처음 보낸 요청의 결과를 확인하지 않고, 두번째 로직을 실행하는 모습을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;동기 호출은 코드의 이해와 설계가 쉽지만, 많은 요청을 반복적으로 처리해야 하거나, 요청에 대한 반환이 늦어지는 등의 가능성이 있을 때 비동기를 사용하면 좋다. 대표적으로 데이터베이스나 파일시스템 처리와 같이 disk i/o로 인해서 시간이 많이 걸리는 작업은 비동기로 처리하는 것을 고려해볼만 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;스프링과 비동기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그렇다면 스프링에서 비동기는 어떻게 동작할까?&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;603&quot; data-origin-height=&quot;442&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wzPkB/btsBCtLW9lB/xezrgW4JhZ5GyES8fnnii1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wzPkB/btsBCtLW9lB/xezrgW4JhZ5GyES8fnnii1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wzPkB/btsBCtLW9lB/xezrgW4JhZ5GyES8fnnii1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwzPkB%2FbtsBCtLW9lB%2FxezrgW4JhZ5GyES8fnnii1%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;465&quot; height=&quot;341&quot; data-origin-width=&quot;603&quot; data-origin-height=&quot;442&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;@Async 어노테이션을 사용하여 비동기 호출을 한 후, &lt;b&gt;exception이 터져도 트랜잭션 롤백이 발생하지 않는다&lt;/b&gt;는 사실은 간단한 테스트코드를 통해 알 수 있었다. 또한, &lt;b&gt;비동기 호출 시 별도의 쓰레드에서 프로세스가 실행된다&lt;/b&gt;는 사실도 알 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1017&quot; data-origin-height=&quot;24&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JQYRZ/btsBCuc3LxI/vyEIbhaMTGQl5RzGtPFCTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JQYRZ/btsBCuc3LxI/vyEIbhaMTGQl5RzGtPFCTK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JQYRZ/btsBCuc3LxI/vyEIbhaMTGQl5RzGtPFCTK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJQYRZ%2FbtsBCuc3LxI%2FvyEIbhaMTGQl5RzGtPFCTK%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;1017&quot; height=&quot;24&quot; data-origin-width=&quot;1017&quot; data-origin-height=&quot;24&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;999&quot; data-origin-height=&quot;21&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/6iDjA/btsBBV9Nzjs/OYfQi9MRCtHzevKHQY66FK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/6iDjA/btsBBV9Nzjs/OYfQi9MRCtHzevKHQY66FK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/6iDjA/btsBBV9Nzjs/OYfQi9MRCtHzevKHQY66FK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F6iDjA%2FbtsBBV9Nzjs%2FOYfQi9MRCtHzevKHQY66FK%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;999&quot; height=&quot;21&quot; data-origin-width=&quot;999&quot; data-origin-height=&quot;21&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;gt; 비동기 호출시 다른 쓰레드에서 프로세스가 실행된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;위와 같이 스프링 트랜잭션은 일반적으로 스레드간 전파가 이루어지지 않으며, 따라서 비동기 호출시에는 다른 스레드로 넘어가기에 트랜잭션이 유지되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;스프링 트랜잭션과 스레드&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 스프링은 트랜잭션을 어떻게 스레드별로 관리하고 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;732&quot; data-origin-height=&quot;143&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TRLJ5/btsBFElGgZr/7jaZX93FNRZHMlF3VLCkb1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TRLJ5/btsBFElGgZr/7jaZX93FNRZHMlF3VLCkb1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TRLJ5/btsBFElGgZr/7jaZX93FNRZHMlF3VLCkb1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTRLJ5%2FbtsBFElGgZr%2F7jaZX93FNRZHMlF3VLCkb1%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;732&quot; height=&quot;143&quot; data-origin-width=&quot;732&quot; data-origin-height=&quot;143&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;트랜잭션이 어떻게 관리되는지를 알아보기 위해서, 간단하게 트랜잭션을 얻어오는 코드에서 중단점을 찍고 step into로 디버깅을 해보았다.&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;678&quot; data-origin-height=&quot;100&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cHRpPo/btsBDQ06LRU/6WoGZVnuH36OskGgU0q5ak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cHRpPo/btsBDQ06LRU/6WoGZVnuH36OskGgU0q5ak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cHRpPo/btsBDQ06LRU/6WoGZVnuH36OskGgU0q5ak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcHRpPo%2FbtsBDQ06LRU%2F6WoGZVnuH36OskGgU0q5ak%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;678&quot; height=&quot;100&quot; data-origin-width=&quot;678&quot; data-origin-height=&quot;100&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;674&quot; data-origin-height=&quot;402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c7CxA3/btsBzzGqnHB/EakHuIiKL6fIYTV7uKtum1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c7CxA3/btsBzzGqnHB/EakHuIiKL6fIYTV7uKtum1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c7CxA3/btsBzzGqnHB/EakHuIiKL6fIYTV7uKtum1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc7CxA3%2FbtsBzzGqnHB%2FEakHuIiKL6fIYTV7uKtum1%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;674&quot; height=&quot;402&quot; data-origin-width=&quot;674&quot; data-origin-height=&quot;402&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;코드를 따라들어가다보면 TransactionSynchronizationManager라는 클래스가 나오는데, 찾아보니 이 클래스는 &lt;span style=&quot;color: #374151; text-align: start;&quot;&gt;&lt;a href=&quot;https://eckrin.tistory.com/155&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;트랜잭션 동안 데이터베이스 커넥션, 쓰레드 바인딩, 세션과 같은 트랜잭션에 필요한 리소스를 동기화하고 관리하는 데 사용된다&lt;/a&gt;고 나왔다. 코드를 살펴보면 해당 클래스의 resource 객체에서 다양한 리소스 정보를 가져오는 것을 볼 수 있었는데,&lt;/span&gt;&lt;span style=&quot;color: #374151; text-align: start;&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-filename=&quot;blob&quot; data-origin-width=&quot;699&quot; data-origin-height=&quot;101&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkyIc0/btsBCX0DL8C/TM4VdLpOpytrmLPWP5URe1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkyIc0/btsBCX0DL8C/TM4VdLpOpytrmLPWP5URe1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkyIc0/btsBCX0DL8C/TM4VdLpOpytrmLPWP5URe1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkyIc0%2FbtsBCX0DL8C%2FTM4VdLpOpytrmLPWP5URe1%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;699&quot; height=&quot;101&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;699&quot; data-origin-height=&quot;101&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;resource 객체는 ThreadLocal 타입으로 되어 있었다. 다시 말해&amp;nbsp;&lt;b&gt;트랜잭션 유지에 필요한 리소스들이 쓰레드별로 관리된다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 트랜잭션에 필요한 리소스들은 ThreadLocal을 통해서 쓰레드 단위로 관리되고 있었던 것. 따라서 하나의 스레드에서 여러개의 트랜잭션을 할당해줄 수 있지만, 트랜잭션 자체는 하나의 스레드 내부에서만 유지된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(ThreadLocal에 대해서 모른다면, &lt;a href=&quot;https://eckrin.tistory.com/173&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://eckrin.tistory.com/173&lt;/a&gt;에 정리되어 있다)&lt;/p&gt;</description>
      <category>[ Backend ]/Spring</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/176</guid>
      <comments>https://eckrin.tistory.com/176#entry176comment</comments>
      <pubDate>Fri, 8 Dec 2023 09:16:56 +0900</pubDate>
    </item>
    <item>
      <title>[Java] Thread-safe하게 변수 관리하는 방법들</title>
      <link>https://eckrin.tistory.com/173</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서 일반적으로 사용되는 변수는 여러가지가 있는데, 흔히 유명한 것들이 메소드나 블록 내에서만 유효한 로컬 변수(local variable)나 매개변수(paramter), 객체의 생성과 함께 관리되는 인스턴스 변수(instance variable), static 키워드와 함께 선언되어 클래스 로딩 시점에 초기화되는 스태틱 변수(static variable)와 같은 변수들이 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;하지만 위와 같은 변수들은 멀티쓰레드 환경에서의 safety를 보장하지 않는다. 멀티쓰레드 환경에서는 여러 쓰레드가 동시에 동일 데이터에 접근을 시도하는 경우 race-condition으로 인한 &lt;b&gt;가시성 문제&lt;/b&gt;(한 쓰레드에서 수정한 사항이 다른 쓰레드에서 보이지 않음)나&amp;nbsp;&lt;b&gt;동시 접근 문제&lt;/b&gt;(여러 쓰레드가 동시에 접근하여 값이 유효하게 동작하지 않음) 등이 발생할 수 있다. stack영역의 경우 쓰레드간 공유가 이루어지지 않으나, heap과 static영역은 쓰레드간 공유가 이루어지므로 이러한 공유 자원에 대한 관리를 해주어야 하는데, 이를 위해서 제목에서 소개한 다양한 방법들이 사용된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;No Synchronization&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;일반적으로 동기화를 고려하지 않은 변수의 경우, 메모리 혹은 캐시에서 읽어오고 처리한 이후 다시 메모리나 캐시에 쓰는 작업으로 동작한다. 이러한 방법은 싱글 쓰레드 환경에서는 문제가 발생하지 않지만, multi-core, multi-CPU, multi-level caches 환경에서는 제대로 동작하지 않을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Synchronized&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;Synchronized 키워드를 사용하면 &lt;b&gt;같은 객체에 한하여&lt;/b&gt; 쓰레드간 동기화를 보장해준다(단, static 메서드에 synchronized를 적용할 경우에는 모든 인스턴스에 대해서 동기화를 보장한다). 또한 객체 내부의 모든 synchronized가 사용된 메서드에 락을 걸기 때문에, synchronized가 걸려있는 메서드간에는 상호 충돌이 발생하지 않는다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;(테스트 github: &lt;a href=&quot;https://github.com/eckrin/various-tests&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://github.com/eckrin/various-tests&lt;/a&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: #333333;&quot;&gt;&amp;nbsp;synchronized는 Lock과&amp;nbsp;&lt;b&gt;blocking&lt;/b&gt;을 사용하여 멀티 스레드 환경에서 공유 객체를 동기화하는 키워드이다. 그러나 blocking에는 여러 가지 단점이 존재하는데, 그 중에서 성능이 가장 큰 단점으로 꼽힌다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;특정 스레드가 해당 동기화 블락 전체에 lock을 걸면, 해당 lock에 접근하는 스레드들은 blocking 상태에 들어가기 때문에 아무 작업도 하지 못한 채 자원을 낭비한다. 또한 blocking 상태의 스레드를 준비 혹은 실행 상태로 변경하기 위해 시스템의 자원을 사용해야 하기 때문에, 이로 인해서 성능의 저하가 일어나게 된다.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&amp;nbsp;또한 synchronized 키워드는 하나의 프로세스에서만 동시성을 보장하기 때문에, 서버 여러대가 존재하여 각각의 서버에서 같은 db의 데이터를 공유하게 될 경우 동시성을 보장하지 못하게 된다.&lt;/span&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;이러한 문제들 때문에 non-blocking 방법을 사용하여 원자성을 보장하기 위한 방법으로 사용되는 것이 Atomic 변수이다.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;AtomicInteger&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;AtomicInteger는 int 자료형을 가지고 있는 wrapper 클래스이다. AtomicInteger 외에도 AtomicBoolean, AtomicLong 등의 래퍼 클래스들이 존재한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;254&quot; data-origin-height=&quot;312&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dkgxAC/btsAmHevjMi/BjILZqdnHv4KdQv7OyyJHK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dkgxAC/btsAmHevjMi/BjILZqdnHv4KdQv7OyyJHK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dkgxAC/btsAmHevjMi/BjILZqdnHv4KdQv7OyyJHK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdkgxAC%2FbtsAmHevjMi%2FBjILZqdnHv4KdQv7OyyJHK%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;254&quot; height=&quot;312&quot; data-origin-width=&quot;254&quot; data-origin-height=&quot;312&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;Atomic 변수는 CPU 레벨에서의 CAS(Compare-And-Swap) 방식을 사용하여 동작한다. 이러한 CAS작업은 원자성을 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 쓰레드 환경에서는 메모리를 참조할 때, 각 쓰레드별로 할당된 별도의 캐시를 참조하게 될 수 있다. 따라서 CAS는 &lt;b&gt;현재 쓰레드의 캐시에 저장된 값과, 메모리에 할당한 값을 비교&lt;/b&gt;하여 일치하는 경우 값을 업데이트하고, 일치하지 않는 경우에는 값을 업데이트하지 못하고 재시도하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;347&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/M6xtl/btsAm41AZir/KhkJVqGjGrakPHBwiHG9Ck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/M6xtl/btsAm41AZir/KhkJVqGjGrakPHBwiHG9Ck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/M6xtl/btsAm41AZir/KhkJVqGjGrakPHBwiHG9Ck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FM6xtl%2FbtsAm41AZir%2FKhkJVqGjGrakPHBwiHG9Ck%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;612&quot; height=&quot;347&quot; data-origin-width=&quot;612&quot; data-origin-height=&quot;347&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;특이한 점은 AtomicInteger 내에서 사용하는 value값이 volatile int형으로 선언되어 있다는 점이다. 아래에서 설명하겠지만 volatile은 무조건 캐시가 아닌 메인 메모리를 참조하여 값을 가지고 오는데, 따라서 멀티쓰레드 환경에서 다중 쓰레드 접근이 이루어질 때에도 변수의 가시성과 원자성을 유지할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Volatile&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Volatile이라는 키워드를 사용할 경우, 모든 &lt;b&gt;volatile 변수는 컴퓨터의 메인 메모리로부터 직접 읽어지고, 쓰기 작업시에도 메인 메모리로 직접 쓰여진다(CPU 캐시를 사용하지 않는다)&lt;/b&gt;. 이 방법은 하나의 쓰레드에서 쓰기 작업이 이루어지는 경우, 나머지 모든 쓰레드들에서 해당 변수를 읽는 경우의 가시성을 보장하지만, 동시에 여러 쓰레드에서 쓰기 작업이 이루어지는 경우에는 동시성을 보장하지 않는다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;ThreadLocal&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;ThreadLocal&lt;/span&gt;은&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;쓰레드마다 독립적으로&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;할당된&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;로컬&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;변수를&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;제어할&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;수&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;있도록 자바에서 제공하는 기능이다. 우리는 ThreadLocal의 get()또는&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;set()&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;메서드를&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;통해서&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;쓰레드에&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;독립적으로&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;할당된&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;로컬&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;변수에&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;접근할&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;수&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;있다&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;이&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;변수는&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;쓰레드마다&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;독립적으로&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;할당&lt;/b&gt;되므로&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;다른&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;쓰레드에서&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;접근할&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;수&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&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: #333333; text-align: start;&quot;&gt;&lt;span&gt;ThreadLocal은 set(), get(), remove()라는 3개의 메소드를 사용하여 쉽게 관리할 수 있다.&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_1700033231940&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings(&quot;unchecked&quot;)
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        map.set(this, value);
    } else {
        createMap(t, value);
    }
}

public void remove() {
     ThreadLocalMap m = getMap(Thread.currentThread());
     if (m != null) {
         m.remove(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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;활용 예시)&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1700033162924&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ThreadLocal&amp;lt;CustomClass&amp;gt; tl = new ThreadLocal&amp;lt;&amp;gt;();

// ThreadLoca.set()을 사용하여 값 저장
tl.set(val);

// ThreadLocal.get()을 사용하여 값 가져오기
CustomClass ex = tl.get();

// ThreadLocal.remove()를 사용하여 ThreadLocal 메모리에 할당된 값 모두 제거 (메모리 free)
tl.remove();&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;앞서 이야기했듯이 ThreadLocal을 사용하여 생성된 변수는&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;쓰레드마다&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;독립적으로&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;할당되므로&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;다른&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;쓰레드에서&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;접근할&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;수&lt;span style=&quot;color: #333333; text-align: start;&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: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;다만 ThreadLocal을 사용할 때 반드시 유의해주어야 하는 점이 있는데, WAS와 같이 쓰레드 풀을 활용하는 경우&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;color: #781b33;&quot;&gt;&lt;b&gt;쓰레드가 종료되고 나면&amp;nbsp;쓰레드 풀에&amp;nbsp;할당된 메모리는&amp;nbsp;초기화되지 않고 그대로 반환되기에, 쓰레드 종료 전에 반드시 remove() 메소드를 통해 threadLocal에 할당됐던 메모리 공간을 초기화해주어야 한다.&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;만약 쓰레드 풀에 개인정보와 같은 민감한 정보들이 저장될 경우 사용이 끝난 ThreadLocal을 제거해주지 않으면, 새로운 요청에 대해 쓰레드 풀을 할당해주었을 때 &lt;b&gt;제거되지 않은 타 사용자의 정보가 노출&lt;/b&gt;되는 일도 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;ThreadLocal은 쓰레드 단위 변수 할당을 위한 여러 기능들에서 활용되고 있는데, 대표적으로 Spring security&lt;span&gt;에서&lt;/span&gt; SecurityContextHolder&lt;span&gt;가&lt;/span&gt; SecurityContext&lt;span&gt;를&lt;/span&gt; &lt;span&gt;제공할때&lt;/span&gt; ThreadLocal&lt;span&gt;을&lt;/span&gt; &lt;span&gt;사용하여&lt;/span&gt; &lt;span&gt;다중&lt;/span&gt; &lt;span&gt;쓰레드&lt;/span&gt; &lt;span&gt;환경에서&lt;/span&gt; &lt;span&gt;각각의&lt;/span&gt; &lt;span&gt;쓰레드에&lt;/span&gt; &lt;span&gt;대해서&lt;/span&gt; &lt;span&gt;독립적인&lt;/span&gt; &lt;span&gt;보안&lt;/span&gt; &lt;span&gt;환경을&lt;/span&gt; &lt;span&gt;유지할&lt;/span&gt; &lt;span&gt;수&lt;/span&gt; &lt;span&gt;있고&lt;/span&gt;, &lt;span&gt;트랜잭션이&lt;/span&gt; Datasource&lt;span&gt;가&lt;/span&gt; database connection&lt;span&gt;을&lt;/span&gt; &lt;span&gt;획득하는&lt;/span&gt; &lt;span&gt;과정에서도&lt;/span&gt; 커넥션이 저장된 TransactionContext 객체를 ThreadLocal&lt;span&gt;을&lt;/span&gt; &lt;span&gt;사용하여&lt;/span&gt; 관리함으로서&amp;nbsp;&lt;span&gt;멀티&lt;/span&gt;&lt;span&gt;쓰레드&lt;/span&gt; &lt;span&gt;환경에서&lt;/span&gt; &lt;span&gt;쓰레드간의&lt;/span&gt; &lt;span&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;&amp;nbsp;(&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/lang/ThreadLocal.html&quot;&gt;https://docs.oracle.com/javase/8/docs/api/java/lang/ThreadLocal.html&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;br /&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- ThreadLocal을 제외하고, synchronized, atomic-, volatile 중에서는 synchronized가 가장 낮은 성능을 보인다. 이는 synchronized 키워드가 blocking방식을 채택하고 있으며, lock을 사용함으로써 다른 방법보다 strict하고 inflexible한 방법을 채택하고 있기 때문으로 보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- volatile 키워드는 cpu cache를 사용하는 대신, memory에 직접 i/o를 진행한다. 따라서 멀티쓰레드 환경에서 thread-safe할 수는 있으나, cache를 사용하는 방법보다 성능 하락이 있을 수 있음을 짐작할 수 있다.&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;700&quot; data-origin-height=&quot;633&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/z8mlq/btsAus8Q8py/oiXNa64DXPBLKMilESjcbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/z8mlq/btsAus8Q8py/oiXNa64DXPBLKMilESjcbk/img.png&quot; data-alt=&quot;http://tutorials.jenkov.com/images/java-concurrency/cpu-cache-coherence-and-java-concurrency-1.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/z8mlq/btsAus8Q8py/oiXNa64DXPBLKMilESjcbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fz8mlq%2FbtsAus8Q8py%2FoiXNa64DXPBLKMilESjcbk%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;509&quot; height=&quot;460&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;633&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;http://tutorials.jenkov.com/images/java-concurrency/cpu-cache-coherence-and-java-concurrency-1.png&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 멀티쓰레드 환경에서, 모든 쓰레드는 cpu cache를 공유할 수도, 공유하지 않을 수도 있다. 일반적으로 L1~L3까지로 구성된 cpu cache의 경우, L1과 같은 상위 캐시는 seperated한 상태로 사용할 수 있으나, 메모리와 가까운 L3와 같은 하위 캐시는 shared한 상태로 사용할 수 있다. 다만 이것은 고정된 방법은 아니고, 아키텍처와 configuration에 의존적이다.&lt;/p&gt;</description>
      <category>[ Languages ]/Java</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/173</guid>
      <comments>https://eckrin.tistory.com/173#entry173comment</comments>
      <pubDate>Fri, 3 Nov 2023 09:26:42 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Security] Security 인증, 인가 설정</title>
      <link>https://eckrin.tistory.com/172</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;788&quot; data-origin-height=&quot;343&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/MZxIt/btszF2b1HOF/WKKKCXTmC41QKTMyZCDfVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MZxIt/btszF2b1HOF/WKKKCXTmC41QKTMyZCDfVk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MZxIt/btszF2b1HOF/WKKKCXTmC41QKTMyZCDfVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMZxIt%2FbtszF2b1HOF%2FWKKKCXTmC41QKTMyZCDfVk%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;675&quot; height=&quot;294&quot; data-origin-width=&quot;788&quot; data-origin-height=&quot;343&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;작년에 스프링 시큐리티를 처음 공부했을 때(&lt;a href=&quot;https://eckrin.tistory.com/103&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://eckrin.tistory.com/103&lt;/a&gt;) 시큐리티 구조와 초기 설계에 대해서 글을 썼던 적이 있는데, 실제로 사용하면서 궁금했던 점들을 정리해보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SecurityFilterChain과 WebSecurityCustomizer&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;먼저 시큐리티 설정을 위해서 Configuration 파일을 만들 때 httpbasic, csrf, cors 등 다양한 설정을 했는데, 그 중 antMatchers(스프링 3.x버전에서는 requestMatchers로 대체)를 사용하는 SecurityFilterChain과 WebSecurityCustomizer에 대한 설명을 해보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SecurityFilterChain에서는 특정 경로로 들어오는 요청 패턴에 대한 인증, 인가에 대한 제어를 할 수 있다. 아래 두 가지 예시를 보자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1697382709583&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
public class SecurityConfig {

    @Bean
    public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception {
        http
            .authorizeRequests(authorizeRequests -&amp;gt;
                authorizeRequests
                    .antMatchers(&quot;/auth/**&quot;).permitAll()
                    .antMatchers(&quot;/admin/**&quot;).hasRole(&quot;ADMIN&quot;)
                    .anyRequest().authenticated()
            )
            .httpBasic(withDefaults()); // Optional, for HTTP Basic Authentication
        return http.build();
    }
    
    ...
}&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;- /auth/**&lt;span style=&quot;background-color: #f7f7f8; color: #374151; text-align: left;&quot;&gt; 패턴의 요청은 &lt;/span&gt;.antMatchers(&quot;/auth/**&quot;).permitAll()&lt;span style=&quot;background-color: #f7f7f8; color: #374151; text-align: left;&quot;&gt; 설정에 의해 &quot;인증&quot;을 필요로 하지 않으며 모든 사용자에게 허용된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- /admin/**&lt;span style=&quot;background-color: #f7f7f8; color: #374151; text-align: left;&quot;&gt; 패턴의 요청은 &lt;/span&gt;.antMatchers(&quot;/admin/**&quot;).hasRole(&quot;ADMIN&quot;)&lt;span style=&quot;background-color: #f7f7f8; color: #374151; text-align: left;&quot;&gt; 설정에 의해 &quot;인증&quot;과 &quot;ROLE_ADMIN&quot; 권한을 필요로 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f7f7f8; color: #374151; text-align: left;&quot;&gt;- 그 외의 모든 요청은 &lt;/span&gt;.anyRequest().authenticated()&lt;span style=&quot;background-color: #f7f7f8; color: #374151; text-align: left;&quot;&gt; 설정에 의해 &quot;인증&quot;과 &quot;인가&quot;를 필요로 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 모든 요청은 시큐리티 필터를 통과한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;즉 SecurityFilterChain에서 authorizeHttpRequest() 설정을 통해서 인증을 제외한다고 해서 필터를 통과하지 않는 것은 아니다. 스프링 시큐리티를 Jwt와 함께 사용하게 되는 경우가 있는데, 시큐리티 필터 체인에 Jwt 헤더 검증 로직을 넣게 되면 이렇게 설정할 경우 인증을 원하지 않는 요청에서도 exception이 던져지는 것을 볼 수 있다. 무슨 말이나면,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1697434316968&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
@EnableWebSecurity
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class SecurityConfig {

    private final JwtTokenProvider tokenProvider;
    private final ExceptionHandleFilter exceptionFilter;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http
                ....
                
                .authorizeHttpRequests(authorize -&amp;gt;
                        authorize
                                .requestMatchers(
                                        &quot;/auth/**&quot;
                                ).permitAll() //로그인, 회원가입,
                                .requestMatchers(HttpMethod.OPTIONS, &quot;/**&quot;).permitAll() //cors
                                .anyRequest().authenticated()
                )
                .addFilterBefore(new JwtAuthenticationFilter(tokenProvider), UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(exceptionFilter, JwtAuthenticationFilter.class);

        return http.build();
        
        ...
}&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;위와 같이 설정한 후, /auth/** 요청의 인가처리를 배제했으므로 문제가 생기지 않을 것이라고 예상했었는데, 커스텀하여 추가한 JwtAuthenticationFilter를 통과하다 던져진 exception으로 인해서 문제가 발생하였다.&lt;/p&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;필터를 아예 통과하지 않게 하기 위해서는 WebSecurityCustomizer를 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1697433074844&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Bean
public WebSecurityCustomizer webSecurityCustomizer() {
    return web -&amp;gt; web.ignoring().requestMatchers(
            &quot;/auth/**&quot;
            );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>[ Backend ]/Spring</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/172</guid>
      <comments>https://eckrin.tistory.com/172#entry172comment</comments>
      <pubDate>Mon, 16 Oct 2023 00:11:55 +0900</pubDate>
    </item>
    <item>
      <title>OOP와 다형성, 의존 역전</title>
      <link>https://eckrin.tistory.com/171</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;다형성&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;객체지향에서 말하는 다형성이란 무엇인가? 위키피디아에서는 &quot;&lt;span style=&quot;background-color: #ffffff; color: #303a3e; text-align: start;&quot;&gt;프로그램 언어 각 요소들(상수, 변수, 식, 객체, 메소드 등)이 다양한 자료형(type)에 속하는 것이 허가되는 성질&quot;이라고 말하고 있으며, 오버라이딩으로 대표되는 런타임 다형성과 오버로딩으로 대표되는 컴파일 다형성을 자연스럽게 떠올리게 될 것이다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #303a3e; text-align: start;&quot;&gt;의존 역전&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;하지만 사실 다형성의 진가는 DIP와 플러그인 아키텍쳐의 기반이 된다는 데에 있다. '클린 아키텍쳐'라는 책에서는 '&lt;b&gt;객체지향의 다형성이 제어의 흐름을 간접적으로 전환하는 규칙을 부과한다&lt;/b&gt;'라고 말하고 있다. 다형성이라는 개념이 사용되기 전 소프트웨어는 어떤 모습이었을까? 모든 프로그램들은 main함수가 고수준 함수를 호출하고, 고수준 함수가 저수준 함수를 호출하는 형태로 되어 있었을 것이라고 예상할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1561&quot; data-origin-height=&quot;732&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beaiIB/btsv9MpU5Uh/gWMd3YtePws8vyEXQRk4dK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beaiIB/btsv9MpU5Uh/gWMd3YtePws8vyEXQRk4dK/img.jpg&quot; data-alt=&quot;제어흐름과 의존성의 방향이 일치한다 - 출처: clean architecture&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beaiIB/btsv9MpU5Uh/gWMd3YtePws8vyEXQRk4dK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeaiIB%2Fbtsv9MpU5Uh%2FgWMd3YtePws8vyEXQRk4dK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;668&quot; height=&quot;313&quot; data-origin-width=&quot;1561&quot; data-origin-height=&quot;732&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;제어흐름과 의존성의 방향이 일치한다 - 출처: clean architecture&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 호출함수는 피호출 함수의 이름을 명시해야 하고, 결국 프로그램의 제어 흐름은 시스템의 동작에 따라 결정되며, &lt;b&gt;소스코드의 의존성은 제어흐름에 따라서 결정&lt;/b&gt;되는 일방적인 흐름이 만들어졌을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;하지만 다형성을 적용하면 이러한 일방적인 흐름에 변화가 생긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-29 오후 11.24.08.png&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;394&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b5dM7W/btsv7ucthfM/LbP0k4KVTpOqQoe72vC0l0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b5dM7W/btsv7ucthfM/LbP0k4KVTpOqQoe72vC0l0/img.png&quot; data-alt=&quot;제어흐름과 의존성의 방향이 일치하지 않는다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b5dM7W/btsv7ucthfM/LbP0k4KVTpOqQoe72vC0l0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb5dM7W%2Fbtsv7ucthfM%2FLbP0k4KVTpOqQoe72vC0l0%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;483&quot; height=&quot;238&quot; data-filename=&quot;스크린샷 2023-09-29 오후 11.24.08.png&quot; data-origin-width=&quot;800&quot; data-origin-height=&quot;394&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;제어흐름과 의존성의 방향이 일치하지 않는다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;위와 같이 간단한 예를 보자. 고수준 모듈인 M1은 저수준 모듈 M2의 f1() 메소드를 호출한다. 다시말해 고수준 모듈인 M1이 저수준 모듈인 M2를 호출하는 제어 흐름을 가진다. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;하지만 소스코드상으로 M1은 M2에 대해 알지 못하며, 단순히 인터페이스 I1의 추상 메소드 f1()을 호출했을 뿐이다. &lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;여기서 모듈 M2가 인터페이스 I1에 의존성(상속)을 가졌지만, 제어 흐름은 인터페이스 I1를 통해서 모듈 M2로 들어가는 것을 확인할 수 있다. 이는 &lt;b&gt;제어의 흐름이 의존성과 정반대&lt;/b&gt;라는 것을 의미한다.&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&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&gt; &lt;/span&gt;흐름은&lt;span&gt; &lt;/span&gt;호출하는&lt;span&gt; &lt;/span&gt;모듈에서&lt;span&gt; &lt;/span&gt;구현체가&lt;span&gt; &lt;/span&gt;존재하는&lt;span&gt; &lt;/span&gt;모듈로&lt;span&gt; &lt;/span&gt;연결되지만&lt;span&gt;, &lt;/span&gt;호출하는&lt;span&gt; &lt;/span&gt;모듈의&lt;span&gt; &lt;/span&gt;의존성은&lt;span&gt; &lt;/span&gt;인터페이스가&lt;span&gt; &lt;/span&gt;존재하는&lt;span&gt; &lt;/span&gt;모듈로&lt;span&gt;, &lt;/span&gt;구현체가&lt;span&gt; &lt;/span&gt;존재하는&lt;span&gt; &lt;/span&gt;모듈의&lt;span&gt; &lt;/span&gt;의존성은&lt;span&gt; &lt;/span&gt;인터페이스가&lt;span&gt; &lt;/span&gt;존재하는&lt;span&gt; &lt;/span&gt;모듈로&lt;span&gt; &lt;/span&gt;연결된다&lt;span&gt;. &lt;/span&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;결국 OOP의 다형성은 의존성을 역전시킬 수 있게 만들며, DIP의 기반이 된다&lt;/span&gt;&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;b&gt;배포 독립성&lt;/b&gt;(Independent Deployability)을 만들어 낸다. 이것이 결국 팀 단위로 서로를 신경쓰지 않고 모듈을 독립적으로 개발하는 &lt;b&gt;개발 독립성&lt;/b&gt;(Independent Developability)의 기반이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시&lt;span&gt; &lt;/span&gt;돌아가서&lt;span&gt;, 그렇다면&amp;nbsp;&lt;/span&gt;객체지향과&lt;span&gt; &lt;/span&gt;객체지향&lt;span&gt; &lt;/span&gt;프로그래밍의 핵심은&lt;span&gt;&amp;nbsp;&lt;/span&gt;무엇인가&lt;span&gt;? &lt;/span&gt;객체지향&lt;span&gt; &lt;/span&gt;프로그래밍이란&lt;span&gt; &amp;ldquo;&lt;/span&gt;다형성을&lt;span&gt; &lt;/span&gt;이용하여&lt;span&gt; &lt;/span&gt;전체&lt;span&gt; &lt;/span&gt;시스템의&lt;span&gt; &lt;/span&gt;모든&lt;span&gt; &lt;/span&gt;소스코드&lt;span&gt; &lt;/span&gt;의존성에&lt;span&gt; &lt;/span&gt;대한&lt;span&gt; &lt;/span&gt;절대적인&lt;span&gt; &lt;/span&gt;제어&lt;span&gt; &lt;/span&gt;권한을&lt;span&gt; &lt;/span&gt;획득하는&lt;span&gt; &lt;/span&gt;능력&lt;span&gt;&amp;rdquo;&lt;/span&gt;이라고도 할 수 있다&lt;span&gt;. &lt;/span&gt;객체지향을 통해 플러그인&lt;span&gt; &lt;/span&gt;아키텍처를&lt;span&gt; &lt;/span&gt;구성하고&lt;span&gt;, &lt;/span&gt;모듈간의&lt;span&gt; &lt;/span&gt;독립성을&lt;span&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;&amp;nbsp;&lt;/p&gt;</description>
      <category>[ 아키텍쳐, 방법론, 디자인패턴 등 ]</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/171</guid>
      <comments>https://eckrin.tistory.com/171#entry171comment</comments>
      <pubDate>Fri, 29 Sep 2023 23:02:31 +0900</pubDate>
    </item>
    <item>
      <title>[Spring Data JPA] Page, Slice</title>
      <link>https://eckrin.tistory.com/170</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;831&quot; data-origin-height=&quot;550&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FQ1LK/btsvksfoI8p/kAd5qLIjTY3dDnWWoPMO50/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FQ1LK/btsvksfoI8p/kAd5qLIjTY3dDnWWoPMO50/img.webp&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FQ1LK/btsvksfoI8p/kAd5qLIjTY3dDnWWoPMO50/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFQ1LK%2FbtsvksfoI8p%2FkAd5qLIjTY3dDnWWoPMO50%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;461&quot; height=&quot;305&quot; data-origin-width=&quot;831&quot; data-origin-height=&quot;550&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-24 오후 4.49.46.png&quot; data-origin-width=&quot;1156&quot; data-origin-height=&quot;704&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dqVh9g/btsvx3dSyWG/KnRtLysNRBKk6aGQ7gwGL1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dqVh9g/btsvx3dSyWG/KnRtLysNRBKk6aGQ7gwGL1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dqVh9g/btsvx3dSyWG/KnRtLysNRBKk6aGQ7gwGL1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdqVh9g%2Fbtsvx3dSyWG%2FKnRtLysNRBKk6aGQ7gwGL1%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;428&quot; height=&quot;261&quot; data-filename=&quot;스크린샷 2023-09-24 오후 4.49.46.png&quot; data-origin-width=&quot;1156&quot; data-origin-height=&quot;704&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Spring Data JPA가 제공하는 JPARepository 인터페이스는 PagingAndSortingRepository와 CrudRepository, Repository 인터페이스를 차례로 상속받는데, 그 중에서 페이지네이션을 지원하기 위한 repository가 &lt;b&gt;PagingAndSortingRepository&lt;/b&gt;이다. PagingAndSortingRepository를 보면 Pageable 타입을 인자로 받고, Page타입을 반환하는 findAll() 추상메소드가 존재하는 것을 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-24 오후 4.35.20.png&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;692&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nD6bb/btsvj9fJbdz/NyWkTUMQaGp3ZOpjhMtmb0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nD6bb/btsvj9fJbdz/NyWkTUMQaGp3ZOpjhMtmb0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nD6bb/btsvj9fJbdz/NyWkTUMQaGp3ZOpjhMtmb0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnD6bb%2Fbtsvj9fJbdz%2FNyWkTUMQaGp3ZOpjhMtmb0%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;740&quot; height=&quot;383&quot; data-filename=&quot;스크린샷 2023-09-24 오후 4.35.20.png&quot; data-origin-width=&quot;1338&quot; data-origin-height=&quot;692&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1306&quot; data-origin-height=&quot;60&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XHxfz/btsvdpKfkcp/3iURlBzW93lijbAJaAW1C1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XHxfz/btsvdpKfkcp/3iURlBzW93lijbAJaAW1C1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XHxfz/btsvdpKfkcp/3iURlBzW93lijbAJaAW1C1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXHxfz%2FbtsvdpKfkcp%2F3iURlBzW93lijbAJaAW1C1%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;753&quot; height=&quot;35&quot; data-origin-width=&quot;1306&quot; data-origin-height=&quot;60&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;여기서 인자로 넘어오는 Pageable 인터페이스는 페이지 번호, 페이지 크기, 정렬순서와 같은 정보들을 갖고 있으며, PageRequest의 of()라는 &lt;a href=&quot;https://inpa.tistory.com/entry/GOF-%F0%9F%92%A0-%EC%A0%95%EC%A0%81-%ED%8C%A9%ED%86%A0%EB%A6%AC-%EB%A9%94%EC%84%9C%EB%93%9C-%EC%83%9D%EC%84%B1%EC%9E%90-%EB%8C%80%EC%8B%A0-%EC%82%AC%EC%9A%A9%ED%95%98%EC%9E%90&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;정적 팩토리 메소드&lt;/a&gt;를 통해서 Pageable 인터페이스를 구현한 PageRequest 타입의 객체를 생성할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-24 오후 4.48.06.png&quot; data-origin-width=&quot;582&quot; data-origin-height=&quot;402&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvNWzb/btsvj88Zj5X/MkX5ajcekWC2eWlfad68ik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvNWzb/btsvj88Zj5X/MkX5ajcekWC2eWlfad68ik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvNWzb/btsvj88Zj5X/MkX5ajcekWC2eWlfad68ik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvNWzb%2Fbtsvj88Zj5X%2FMkX5ajcekWC2eWlfad68ik%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;270&quot; height=&quot;186&quot; data-filename=&quot;스크린샷 2023-09-24 오후 4.48.06.png&quot; data-origin-width=&quot;582&quot; data-origin-height=&quot;402&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;위에서 설명한 Pageable타입의 객체를 인자로 하여 넘겨주면 Spring Data JPA의 PagingAndSortingRepository를 이용해서 페이지네이션을 사용할 수 있다. JPA에서 지원하는 페이지네이션 방법은 크게 두 가지로 분류해볼 수 있는데, Page와 Slice이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Page 간단 소개&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_스크린샷 2023-09-24 오후 6.14.54.png&quot; data-origin-width=&quot;2908&quot; data-origin-height=&quot;1138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AG7Vq/btsvlARsS9q/j2hCdgH5fZgCOAtCpZZN21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AG7Vq/btsvlARsS9q/j2hCdgH5fZgCOAtCpZZN21/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AG7Vq/btsvlARsS9q/j2hCdgH5fZgCOAtCpZZN21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAG7Vq%2FbtsvlARsS9q%2Fj2hCdgH5fZgCOAtCpZZN21%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;768&quot; height=&quot;301&quot; data-filename=&quot;edited_스크린샷 2023-09-24 오후 6.14.54.png&quot; data-origin-width=&quot;2908&quot; data-origin-height=&quot;1138&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;b&gt;offset을 이용한 페이징&lt;/b&gt;을 위해서 사용되는 객체이다. (단, Page라고 무조건 offset방식의 페이징을 의미하는 것은 아닌듯 하다.) 이때 offset방식 페이징이란 쿼리문에서 조회를 시작할 기준점을 설정하는 것을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Page는 객체 안에 현재 페이지의 데이터와 전체 페이지 수와 같은 정보를 포함하고 있기 때문에 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;반환 리스트의 element들을 일정 개수(size)만큼 노출시키고, 페이지 번호(page)&lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;를 통해서 원하는 결과가 포함된 리스트로 바로 이동할 수 있다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-24 오후 5.15.01.png&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;710&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yr3Y3/btsvkTDHJnh/IpVSm7QVQbw9SqgKMGlWZ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yr3Y3/btsvkTDHJnh/IpVSm7QVQbw9SqgKMGlWZ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yr3Y3/btsvkTDHJnh/IpVSm7QVQbw9SqgKMGlWZ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fyr3Y3%2FbtsvkTDHJnh%2FIpVSm7QVQbw9SqgKMGlWZ1%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;297&quot; height=&quot;287&quot; data-filename=&quot;스크린샷 2023-09-24 오후 5.15.01.png&quot; data-origin-width=&quot;734&quot; data-origin-height=&quot;710&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: #333333; text-align: start;&quot;&gt;&lt;span&gt;참고로 Page는 후술할 Slice 인터페이스를 상속받고 있기 때문에 Slice에서 사용되는 모든 메서드는 Page가 동일하게 사용할 수 있다.&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;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;Slice 간단 소개&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;432&quot; data-origin-height=&quot;960&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zjeOf/btsviUQJ2yG/NRcF1gUupbKWrfqFgNpMF1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zjeOf/btsviUQJ2yG/NRcF1gUupbKWrfqFgNpMF1/img.jpg&quot; data-alt=&quot;https://www.dailypop.kr/news/articleView.html?idxno=46704&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zjeOf/btsviUQJ2yG/NRcF1gUupbKWrfqFgNpMF1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzjeOf%2FbtsviUQJ2yG%2FNRcF1gUupbKWrfqFgNpMF1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;136&quot; height=&quot;302&quot; data-origin-width=&quot;432&quot; data-origin-height=&quot;960&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.dailypop.kr/news/articleView.html?idxno=46704&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;Slice는 페이징된 데이터의 &lt;b&gt;마지막 id값을 기준으로 다음 데이터를 조회&lt;/b&gt;하는 &lt;b&gt;커서 기반 페이지네이션&lt;/b&gt; 방식이며, 페이지마다 현재 페이지의 정보와 다음 페이지에 대한 정보(다음 페이지가 존재하는지 여부)가 담겨 있다. 가장 대표적인 예시가 앱에서 무한 스크롤을 구현할때 많이 쓰인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Page와 Slice - Tradeoff&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그렇다면 한쪽 방향으로만 조회가 가능한 Slice보다는 방향성에 있어서 제약을 덜 갖는 Page가 낫지 않을까? 라는 생각이 들수도 있다.&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;하지만 Page는 Slice와 다르게 전체 데이터의 개수를 파악하는 과정이 필요하기에 &lt;b&gt;count 쿼리&lt;/b&gt;가 한번 추가로 나가는데, 데이터베이스 엔진이 캐시를 이용하는 등의 방법으로 최적화되어있는 특별한 경우가 아니라면 모든 데이터에 대한 조회가 이루어지게 되어 O(N)의 시간복잡도를 가지게 된다. 따라서 Slice를 사용하는 것보다 Page를 사용하는 것이 성능적으로는 손해일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한, offset 방식의 페이징은&lt;b&gt;&amp;nbsp;offset&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;을 계산하여 데이터베이스에 쿼리를 날리는 방식을 사용하며, 페이지 번호(page), Page 객체에는 한 페이지에 불러올 데이터 건수(size, limit), 정렬 조건(sort)과 같은 정보들이 들어갈 수 있다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;이렇게 offset을 사용하는 방식은 offset까지의 데이터를 모두 읽게 만들기에 O(N)의 조회 비용을 가져 성능 저하가 발생할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 PagingAndSortingRepository의 메소드를 테스트해보았다. BaseTest에서 @SetUp으로 5명의 User를 insert해준 뒤, 다음과 같은 테스트를 돌려보았다.&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class PageSliceTest extends BaseTest{

    private final int PAGE_SIZE = 2;
    private final String sortStd = &quot;id&quot;;

    @Test
    @DisplayName(&quot;Page, Slice 쿼리 테스트&quot;)
    public void pageSliceTest() {
        // Pageable pageable = PageRequest.of(1, PAGE_SIZE);
        Pageable pageable = PageRequest.of(1, PAGE_SIZE, Sort.by(sortStd));

        Page&amp;lt;User&amp;gt; pageBy = userRepository.findPageBy(pageable);
        Slice&amp;lt;User&amp;gt; sliceBy = userRepository.findSliceBy(pageable);
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-25 오전 9.37.34.png&quot; data-origin-width=&quot;462&quot; data-origin-height=&quot;356&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/beWm7T/btsvkPIgUZ3/ee1QFJzZ1YHsm5hqzWvIKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/beWm7T/btsvkPIgUZ3/ee1QFJzZ1YHsm5hqzWvIKk/img.png&quot; data-alt=&quot;findPageBy()&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/beWm7T/btsvkPIgUZ3/ee1QFJzZ1YHsm5hqzWvIKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbeWm7T%2FbtsvkPIgUZ3%2Fee1QFJzZ1YHsm5hqzWvIKk%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;462&quot; height=&quot;356&quot; data-filename=&quot;스크린샷 2023-09-25 오전 9.37.34.png&quot; data-origin-width=&quot;462&quot; data-origin-height=&quot;356&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;findPageBy()&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-25 오전 9.37.51.png&quot; data-origin-width=&quot;443&quot; data-origin-height=&quot;243&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c6Nx2g/btsvmxN1Vss/zhf4nkPqh8tFCKXajU2YKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c6Nx2g/btsvmxN1Vss/zhf4nkPqh8tFCKXajU2YKK/img.png&quot; data-alt=&quot;findSliceBy()&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c6Nx2g/btsvmxN1Vss/zhf4nkPqh8tFCKXajU2YKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc6Nx2g%2FbtsvmxN1Vss%2Fzhf4nkPqh8tFCKXajU2YKK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;443&quot; height=&quot;243&quot; data-filename=&quot;스크린샷 2023-09-25 오전 9.37.51.png&quot; data-origin-width=&quot;443&quot; data-origin-height=&quot;243&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;findSliceBy()&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Page로 조회해올 때 Slice보다 count쿼리 하나가 더 나가는 것을 확인할 수 있었다. db 전체 데이터를 한번 스캔해야 하니 시간이 더 걸릴 수 밖에 없다. 심지어 count(*)가 아니라 count(user.id)로 조회하는 것을 보아 쿼리 최적화가 되지 않은 모습도 확인할 수 있다.&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;추가로 해볼 수 있는 고민들&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;1. Slice와 no-offset&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Slice와 no-offset을 구분하자.&lt;/p&gt;
&lt;pre class=&quot;sql&quot; style=&quot;background-color: #f6f8fa; color: #24292e; text-align: start;&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;SELECT *
FROM 테이블
WHERE 조건문
ORDER BY id DESC
OFFSET 페이지번호
LIMIT 페이지사이즈&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 방식인 &lt;b&gt;offset&lt;/b&gt;과 &lt;b&gt;limit&lt;/b&gt;을 사용한 페이징 쿼리의 예시이다. 그런데 이와 같은 형태의 쿼리로 페이징을 진행하면 앞에서 읽었던 행들을 다시 읽어야 하기 때문에 offset이 증가할수록 탐색속도가 느려진다는 문제가 발생한다. 따라서 대신 사용하는 방법이&lt;b&gt; 클러스터 인덱스인 PK를 조건문에 붙여서 인덱스 단위로 빠르게 조회&lt;/b&gt;하는 &lt;b&gt;no-offset 페이징&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;sql&quot; style=&quot;background-color: #f6f8fa; color: #24292e; text-align: start;&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;SELECT *
FROM 테이블
WHERE 조건문 AND id &amp;lt; lastId
ORDER BY id DESC
LIMIT 페이지사이즈&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;처음과 다르게 offset쿼리가 없어지고, 조건문에 pk제약을 붙은 모습을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 성능상의 이유로 무한스크롤을 구현할 때 일반적으로 no-offset방식의 페이징을 사용하고, Slice가 no-offset 페이지의 대표적인 예시라고 알고 있었는데, 위에서 로그로 찍힌 Slice 쿼리를 보니 여전히 offset쿼리가 포함되어 나가는 것을 확인할 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 Slice는 일반적으로 no-offset방식으로 페이징하지만, &lt;b&gt;Spring Data JPA 메소드에 Slice를 전달하는 것 자체로는 no-offset을 지원하지 않는다.&lt;/b&gt; no-offset 방식의 페이징을 위해서는 Querydsl 등의 방법을 통해 아래와 같이 offset쿼리를 직접 제거해주어야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-26 오후 11.57.31.png&quot; data-origin-width=&quot;2396&quot; data-origin-height=&quot;582&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bM77Xb/btsvMTqgBOG/B4Vc2EQRGZ0MYc4sYU8651/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bM77Xb/btsvMTqgBOG/B4Vc2EQRGZ0MYc4sYU8651/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bM77Xb/btsvMTqgBOG/B4Vc2EQRGZ0MYc4sYU8651/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbM77Xb%2FbtsvMTqgBOG%2FB4Vc2EQRGZ0MYc4sYU8651%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;2396&quot; height=&quot;582&quot; data-filename=&quot;스크린샷 2023-09-26 오후 11.57.31.png&quot; data-origin-width=&quot;2396&quot; data-origin-height=&quot;582&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;2. 데이터 중복이나 손실&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 offset방식을 사용한 방식은 조회 중에 새로운 데이터가 추가되면 &lt;b&gt;데이터가 중복&lt;/b&gt;되어 표시되거나, &lt;b&gt;누락&lt;/b&gt;되는 현상이 발생할 수도 있다. 간단하게 데이터베이스에 3개의 데이터가 존재한다고 가정하자.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-24 오후 8.43.28.png&quot; data-origin-width=&quot;662&quot; data-origin-height=&quot;324&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mNw3k/btsvkRy9L1G/T8LrvG2x1MqEM1HoljrA01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mNw3k/btsvkRy9L1G/T8LrvG2x1MqEM1HoljrA01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mNw3k/btsvkRy9L1G/T8LrvG2x1MqEM1HoljrA01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmNw3k%2FbtsvkRy9L1G%2FT8LrvG2x1MqEM1HoljrA01%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;411&quot; height=&quot;201&quot; data-filename=&quot;스크린샷 2023-09-24 오후 8.43.28.png&quot; data-origin-width=&quot;662&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;testdata 테이블에 다음과 같은 3개의 데이터가 들어있고, 최근순 정렬로 2개씩 페이지네이션을 진행한다고 가정하자. 첫 페이지에는 (3, c++)과 (2, java)가 노출될 것이다. 이 상태에서 무한 스크롤을 통해서 한 페이지를 더 조회하면 (1, kotlin)이 노출되기를 기대하고 있을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-24 오후 8.47.43.png&quot; data-origin-width=&quot;662&quot; data-origin-height=&quot;352&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LvF17/btsvkPVEb9l/EAlYeuBvLqYq5h2btymTXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LvF17/btsvkPVEb9l/EAlYeuBvLqYq5h2btymTXk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LvF17/btsvkPVEb9l/EAlYeuBvLqYq5h2btymTXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLvF17%2FbtsvkPVEb9l%2FEAlYeuBvLqYq5h2btymTXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;397&quot; height=&quot;211&quot; data-filename=&quot;스크린샷 2023-09-24 오후 8.47.43.png&quot; data-origin-width=&quot;662&quot; data-origin-height=&quot;352&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 첫 페이지를 조회한 직후 (4, ada)라는 데이터가 추가되었다고 가정하면, 두번째 페이지를 출력하기 위해서 요청을 보냈을 때는 (2, java)와 (1, kotlin)이 나오게 된다. 추가한 (4, ada) 데이터가 최근순 정렬을 하고 나니 첫 번째 페이지에 들어가버리고, &lt;b&gt;첫번째 페이지에 나온 (2, java)가 두번째 페이지에도 나와버리는 문제가 발생&lt;/b&gt;한 것이다. 따라서 새로운 데이터가 추가될 경우 이를 처리하거나, 페이지 번호와 관련된 정보를 유지하면서 조회를 진행해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이를 해결하기 위해서는 커서를 사용한 no-offset방식의 페이징을 진행해서 해결할 수 있다. 정렬 기준이 시간 내림차순이라면 단순히 커서를 id나 timestamp로 잡아주면 되기 때문이다.&lt;/p&gt;
&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: #333333;&quot;&gt;&amp;nbsp;(나도 infinity scroll을 위한 Slice 사용중 위와 같은 문제가 발생한 적이 있는데, 내가 사용했던 방법은 첫 조회시점 이후에 추가된 데이터는 리스트에서 제외하는 방법이었다. 무조건 최신순으로 정렬한다는 비즈니스적 합의 이후에 lastId라는 기준을 두어 '어디부터 출력하면 되는지'를 알 수 있게 하였다. 일종의 책갈피 역할을 한다고 생각하면 될 것 같다.)&amp;nbsp;&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-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-25 오후 4.10.47.png&quot; data-origin-width=&quot;921&quot; data-origin-height=&quot;398&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qdFyO/btsvDiv5HGC/h9KrBuk5D0KTRndEFRpijk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qdFyO/btsvDiv5HGC/h9KrBuk5D0KTRndEFRpijk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qdFyO/btsvDiv5HGC/h9KrBuk5D0KTRndEFRpijk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqdFyO%2FbtsvDiv5HGC%2Fh9KrBuk5D0KTRndEFRpijk%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;756&quot; height=&quot;327&quot; data-filename=&quot;스크린샷 2023-09-25 오후 4.10.47.png&quot; data-origin-width=&quot;921&quot; data-origin-height=&quot;398&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-25 오후 4.12.45.png&quot; data-origin-width=&quot;1194&quot; data-origin-height=&quot;291&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l2HlG/btsvqzx8sRW/iKtLlld2cE24JKu4RsfSJ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l2HlG/btsvqzx8sRW/iKtLlld2cE24JKu4RsfSJ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l2HlG/btsvqzx8sRW/iKtLlld2cE24JKu4RsfSJ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl2HlG%2Fbtsvqzx8sRW%2FiKtLlld2cE24JKu4RsfSJ0%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;1194&quot; height=&quot;291&quot; data-filename=&quot;스크린샷 2023-09-25 오후 4.12.45.png&quot; data-origin-width=&quot;1194&quot; data-origin-height=&quot;291&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2023-09-25 오후 4.14.52.png&quot; data-origin-width=&quot;460&quot; data-origin-height=&quot;177&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfikiY/btsvkPvfUk4/tQn4O2oWWsIaKO8cpQ3C6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfikiY/btsvkPvfUk4/tQn4O2oWWsIaKO8cpQ3C6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfikiY/btsvkPvfUk4/tQn4O2oWWsIaKO8cpQ3C6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfikiY%2FbtsvkPvfUk4%2FtQn4O2oWWsIaKO8cpQ3C6k%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;460&quot; height=&quot;177&quot; data-filename=&quot;스크린샷 2023-09-25 오후 4.14.52.png&quot; data-origin-width=&quot;460&quot; data-origin-height=&quot;177&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;위 예시가 pk를 사용한 no-offset방식의 페이징이다. lastId라는 쿼리파라미터를 사용하였는데, 처음 요청시에는 lastId를 보내지 않는다. 그러면 ltId() 메소드에서는 BooleanExpression의 리턴값이 null이 되어 조건이 없이 모든 product에 대해서 조회하게 된다. 그 다음에 클라이언트는 반환받은 값중 가장 id가 작은 product의 id를 lastId에 넣어 보낸다. 그러면 그 id보다 1 작은 product부터만 조회하여 페이지네이션을 진행하게 되고, 데이터가 밀리거나 중복되는 현상은 일어나지 않는다.&lt;/p&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;추가로 infinity scroll방식이 아닌, Page방식의 페이지네이션의 경우는 이전 페이지의 element가 더 이상 표시되지 않기에 같은 데이터가 다른 페이지에 각각 한번씩 표시되더라도 비즈니스적으로 문제가 되지 않을 여지가 높아서 고려하지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;3. 정렬조건이 추가로 붙으면..&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;하지만 최신순 데이터가 아니라, 정렬을 요구하는 데이터라면 또 이야기가 달라진다. 예를 들어, 위에서 최신순이 아니라 좋아요가 많은 순서대로 출력하기를 요구받았다고 하자. 이제는 id가 아니라 product의 좋아요 수를 기준으로 출력해주어야 하기 때문에, lastId 대신 lastLikeCount와 같은 다른 조건의 기준점을 사용해야 한다. 대신 이제는 lastId를 사용함으로서 예방했던 신규데이터 추가를 방지할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #333333; text-align: left;&quot;&gt;첫 번째 아이디어&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #333333; text-align: left;&quot;&gt;&amp;nbsp;신규데이터 추가를 아예 방지하면 된다. 첫 요청시 서버로 lastId가 null로 들어오면 첫 요청에 lastId를 세팅해서 보내주고, &lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #333333; text-align: left;&quot;&gt;그 이후로는 lastId를 계속 주고받으면서 where조건으로 처음 조회 시점 이후의 데이터가 조회되지 않게 설정할 수 있다. 문제는 &lt;b&gt;1. 쿼리 where절에 고정 조건이 추가되어 성능 저하&lt;/b&gt;가 있을 수 있고, &lt;b&gt;2. 검색조건을 초기화(lastId=null)하기 전에는 새로운 데이터는 표시되지 않는다&lt;/b&gt;는 문제가 존재한다. 또한 &lt;/span&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #333333; text-align: left;&quot;&gt;클러스터 인덱스를 활용할 수 없으므로 pk로 no-offset 페이징을 하는 것보다는 속도가 훨씬 느리다.&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;background-color: #fcfcfc; color: #333333; text-align: left;&quot;&gt;두 번째 아이디어&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #333333; text-align: left;&quot;&gt;&amp;nbsp;첫 번째 아이디어와 동일하게 lastId를 사용하되, 정렬조건을 unique한 컬럼으로 한정하고 해당 컬럼을 대상으로 인덱스를 생성해준다. 그러면 꼭 pk가 아니더라도 &lt;b&gt;논클러스터링 인덱스를 사용&lt;/b&gt;하여 효율적인 no-offset방식의 페이징이 가능해진다. 물론 unique한 컬럼으로 정렬조건이 한정된다는 단점이 있지만, pk를 제외한 다른 컬럼으로도 인덱스를 활용하여 데이터베이스의 읽기 성능 저하를 막을 수 있다.&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;4. (쓸데없는 고민) 그런데 no-offset이 무조건 더 빠를까?&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;대규모 데이터셋의 경우 당연히 인덱스를 활용한 no-offset 페이징이 빠르겠지만, 데이터가 적어 인덱싱을 활용한 조회가 성능에 크게 영향을 끼치지 않는다면 no-offset이 무조건 빠르다고는 장담할 수 없다. 데이터가 적은 경우 성능차이가 미미하고, 오히려 과한 인덱스 생성으로 인해 너무 많은 컬럼에 인덱스가 생성되는 상황이라면 인덱스 부하(추가적인 저장공간이 필요하고, 데이터의 변경에 따라 인덱스도 업데이트되어야 하니)로 인한 overhead도 존재하기 때문이다. 다만 개발의 방향성은 항상 worst-case로 잡는 것이 맞으므로 no-offset 페이징을 꺼릴 필요는 없겠다.&lt;/p&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;참고자료: count(*)와 count(user.id)의 성능차이&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://yjh5369.tistory.com/entry/MySQL-COUNT%EC%9D%98-%EC%9E%98%EB%AA%BB%EB%90%9C-%EC%9D%B8%EC%8B%9D%EA%B3%BC-%EC%86%8D%EB%8F%84-%EC%B0%A8%EC%9D%B4&quot;&gt;https://yjh5369.tistory.com/entry/MySQL-COUNT%EC%9D%98-%EC%9E%98%EB%AA%BB%EB%90%9C-%EC%9D%B8%EC%8B%9D%EA%B3%BC-%EC%86%8D%EB%8F%84-%EC%B0%A8%EC%9D%B4&lt;/a&gt;&lt;/p&gt;</description>
      <category>[ Backend ]/Spring DB, JPA</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/170</guid>
      <comments>https://eckrin.tistory.com/170#entry170comment</comments>
      <pubDate>Sun, 24 Sep 2023 17:07:08 +0900</pubDate>
    </item>
    <item>
      <title>application.yml 설정값 노출 관리하기</title>
      <link>https://eckrin.tistory.com/169</link>
      <description>&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;스프링 프로젝트에서 데이터베이스 연결정보, 서버 포트, 로깅수준 같은 어플리케이션에 관련된 다양한 설정값들을 저장하기 위해서 application.yml을 사용한다. 그런데 이러한 정보들 중에는 외부로 노출되어서는 안되는 값들이 많이 존재하기 때문에, 아무 생각없이 Github과 같은 public 저장소에 올렸다가는 정보가 다 털릴 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;832&quot; data-origin-height=&quot;408&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/l8Ll1/btsCTxnkPFd/kBBPf96OFrZg5l3bqcX2Wk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/l8Ll1/btsCTxnkPFd/kBBPf96OFrZg5l3bqcX2Wk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/l8Ll1/btsCTxnkPFd/kBBPf96OFrZg5l3bqcX2Wk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fl8Ll1%2FbtsCTxnkPFd%2FkBBPf96OFrZg5l3bqcX2Wk%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;359&quot; height=&quot;176&quot; data-origin-width=&quot;832&quot; data-origin-height=&quot;408&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bw3Ilj/btsC2xsjjaP/LuyTIPQWzaXkWVPY8HoQNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bw3Ilj/btsC2xsjjaP/LuyTIPQWzaXkWVPY8HoQNk/img.png&quot; style=&quot;width: 25.4466%; margin-right: 10px;&quot; width=&quot;284&quot; height=&quot;574&quot; data-widthpercent=&quot;26.05&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;1476&quot; data-origin-width=&quot;730&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bw3Ilj/btsC2xsjjaP/LuyTIPQWzaXkWVPY8HoQNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbw3Ilj%2FbtsC2xsjjaP%2FLuyTIPQWzaXkWVPY8HoQNk%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;730&quot; height=&quot;1476&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ca5FyI/btsC2AvQPy8/B8sKS4yB8MB3SRTuS4wBU1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ca5FyI/btsC2AvQPy8/B8sKS4yB8MB3SRTuS4wBU1/img.png&quot; style=&quot;width: 26.8664%; margin-right: 10px;&quot; width=&quot;265&quot; height=&quot;507&quot; data-widthpercent=&quot;27.51&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;1398&quot; data-origin-width=&quot;730&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ca5FyI/btsC2AvQPy8/B8sKS4yB8MB3SRTuS4wBU1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fca5FyI%2FbtsC2AvQPy8%2FB8sKS4yB8MB3SRTuS4wBU1%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;730&quot; height=&quot;1398&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c3UzuL/btsCTCu9W0r/24yTWxMDAK4OUrgHBsjoH0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c3UzuL/btsCTCu9W0r/24yTWxMDAK4OUrgHBsjoH0/img.png&quot; style=&quot;width: 45.3614%;&quot; width=&quot;362&quot; height=&quot;411&quot; data-widthpercent=&quot;46.44&quot; data-is-animation=&quot;false&quot; data-origin-height=&quot;828&quot; data-origin-width=&quot;730&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c3UzuL/btsCTCu9W0r/24yTWxMDAK4OUrgHBsjoH0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc3UzuL%2FbtsCTCu9W0r%2F24yTWxMDAK4OUrgHBsjoH0%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;730&quot; height=&quot;828&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2752&quot; data-origin-height=&quot;222&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bzGMww/btsC4GhXNAB/x12ACbtU8BFoHUgfjMRKv1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bzGMww/btsC4GhXNAB/x12ACbtU8BFoHUgfjMRKv1/img.png&quot; data-alt=&quot;이미 몇번 당한 적이 있다..ㅋㅋ&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bzGMww/btsC4GhXNAB/x12ACbtU8BFoHUgfjMRKv1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbzGMww%2FbtsC4GhXNAB%2Fx12ACbtU8BFoHUgfjMRKv1%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;2752&quot; height=&quot;222&quot; data-origin-width=&quot;2752&quot; data-origin-height=&quot;222&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이미 몇번 당한 적이 있다..ㅋㅋ&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;(백엔드에서 잘 처리해놨다고 생각해도 어떤식으로든(노션, 발표자료, organization 등등..) 노출될 수 있으니 항상 조심하자)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;어쨌든 모든 git repository를 private로 닫아둘수는 없으므로, public으로 열어두려면 아예 configuration파일 자체의 노출을 막거나, 암호화하는 등의 처리를 해주어야 하는데, 그 방법들에 대해서 다루어보도록 하겠다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1. .gitigore 사용하기&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;제일 간단한 방법으로, .gitignore파일에 yml파일을 ignore설정해서 깃헙 레포에 올라가지 않도록만 해주면 된다. 하지만 빌드에 필요한 설정파일이 원격 저장소로 아예 올라가지 않기 때문에, 원격 레포에서도 설정파일을 필요로 하는 경우(CI를 위해 build를 거쳐야 하는 경우 등)는 사용할 수 없는 방법이라는 문제가 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2. Override Configuration Properties&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;93&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bHlD11/btsC1InpnqQ/JaSUNk6PFrV8evXmceceKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bHlD11/btsC1InpnqQ/JaSUNk6PFrV8evXmceceKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHlD11/btsC1InpnqQ/JaSUNk6PFrV8evXmceceKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbHlD11%2FbtsC1InpnqQ%2FJaSUNk6PFrV8evXmceceKk%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;708&quot; height=&quot;93&quot; data-origin-width=&quot;708&quot; data-origin-height=&quot;93&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;432&quot; data-origin-height=&quot;29&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9eHjl/btsC4ERY89q/0kf1fa01zdO2wdmcUr2op1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9eHjl/btsC4ERY89q/0kf1fa01zdO2wdmcUr2op1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9eHjl/btsC4ERY89q/0kf1fa01zdO2wdmcUr2op1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9eHjl%2FbtsC4ERY89q%2F0kf1fa01zdO2wdmcUr2op1%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;521&quot; height=&quot;35&quot; data-origin-width=&quot;432&quot; data-origin-height=&quot;29&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;인텔리제이에서 configuration property값들을 name-value형태로 저장해둘 수 있다. 저장해둔 값들은 yml에서 위와 같이 활용할 수 있다. 비슷하게 깃헙 원격 레포지토리에서는 Repository Secrets라는 이름으로, 그리고 ec2같은 원격 서버에서는 환경변수로 값들을 key-value형태로 관리할 수 있기 때문에 해당 방법으로 CI-CD 파이프라인까지 구축할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;710&quot; data-origin-height=&quot;492&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c0i9h4/btsC4o9wL9i/esfy59xctOnYCRU3GzEFF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c0i9h4/btsC4o9wL9i/esfy59xctOnYCRU3GzEFF0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c0i9h4/btsC4o9wL9i/esfy59xctOnYCRU3GzEFF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc0i9h4%2FbtsC4o9wL9i%2Fesfy59xctOnYCRU3GzEFF0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;414&quot; height=&quot;287&quot; data-origin-width=&quot;710&quot; data-origin-height=&quot;492&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;기본적으로 test모듈의 설정파일의 경우 main의 설정파일을 그대로 받고, 차이가 있는 부분은 test모듈의 설정파일로 반영되기 때문에 테스트 환경에서만 필요한 구성정보를 test 모듈 내의 yml에 정의해놓고 사용하면 된다고 알고 있었다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그런데&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;사용하다보니 내 문제인지는 몰라도, 테스트코드에서 property값이 정상적으로 주입되지 않는 문제가 발생했다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;34&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wSRvD/btsCTvpw4YY/hV8Kyzfyjkarl9h8Qb67u0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wSRvD/btsCTvpw4YY/hV8Kyzfyjkarl9h8Qb67u0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wSRvD/btsCTvpw4YY/hV8Kyzfyjkarl9h8Qb67u0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwSRvD%2FbtsCTvpw4YY%2FhV8Kyzfyjkarl9h8Qb67u0%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;928&quot; height=&quot;34&quot; data-origin-width=&quot;928&quot; data-origin-height=&quot;34&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;기존에는 테스트환경에서 항상 h2 database로 인메모리 db를 별도로 사용해서 몰랐는데, 테스트 프로젝트에서 우연히 main과 동일한 db를 사용하려다 보니 위와 같이 database access 오류가 떴고, main에 있는 application.yml을 configuration property를 사용하지 않고 하드코딩해주니 동작하는 것을 발견했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;나는 인텔리제이에서 제공하는&lt;span style=&quot;background-color: #ffffff; color: #0d0d0d; text-align: start;&quot;&gt; &quot;Override Configuration Properties&quot; 설정을 이용했는데, 테스트 코드에서는 해당 설정을 이용할 수 없었다.&lt;/span&gt; test모듈에서는 아예 시스템 환경변수를 이용하거나, 하드코딩하거나, 보안이 걱정된다면 원격 저장소에 올라가지 않도록 ignore하는 방법을 이용해야 할 듯 싶다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. jasypt를 사용한 암호화&lt;/b&gt;&lt;/h2&gt;</description>
      <category>[ Backend ]/기타</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/169</guid>
      <comments>https://eckrin.tistory.com/169#entry169comment</comments>
      <pubDate>Fri, 15 Sep 2023 14:28:35 +0900</pubDate>
    </item>
    <item>
      <title>Git 브랜치 전략 - Git Flow, Github Flow</title>
      <link>https://eckrin.tistory.com/167</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Git은 협업을 위한 툴이다. 그래서 나도 지금까지 프로젝트를 할 때마다 나름의 커밋 컨벤션도 세우고, 작업마다 브랜치를 파서 작업한 후 pr을 올리고 merge conflict를 체크하고, 간단한 코드리뷰를 통해서 approve받은 후에야 develop으로 머지하는 프로세스들을 익히고 지켰다. 하지만 확실한 규칙을 설정하고 진행한 것이 아니기 때문에 reset --hard를 한다거나, 같은 branch에서 conflict가 난다거나 한다는 문제들이 발생했다. 그래서 브랜치 전략이라는 패턴에 대해서 간단히 정리해보고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;브랜치 전략이란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;효율적인 협업을 위해서 Git을 사용하는데, 협업하는 팀원들간에 스타일의 차이가 있기 때문에 사용방식의 통일을 주기 위해서 사용되는 전략을 말한다. 크게 Git Flow와 Github Flow 두가지 전략이 존재하는데, 각각에 대해 정리해보겠다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Git Flow&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Git Flow 전략은 상용 서비스를 진행중인 경우에 많이 사용된다. master와 develop이라는 항상 존재하는 &lt;b&gt;메인 브랜치&lt;/b&gt;가 존재하고, hotfix, release, feature와 같이 메인 브랜치의 필요에 따라서 생성, 삭제되는&lt;b&gt; 보조 브랜치&lt;/b&gt;가 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;473&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bq33Pp/btstw0kojI8/AUJuzMdQcuiKL6K6FDOwJK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bq33Pp/btstw0kojI8/AUJuzMdQcuiKL6K6FDOwJK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bq33Pp/btstw0kojI8/AUJuzMdQcuiKL6K6FDOwJK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbq33Pp%2Fbtstw0kojI8%2FAUJuzMdQcuiKL6K6FDOwJK%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;856&quot; height=&quot;473&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;473&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;b&gt;master &lt;/b&gt;브랜치는 라이브 서비스되고 있거나 당장 배포 가능한 상태의 브랜치를 말하고, &lt;b&gt;develop &lt;/b&gt;브랜치에서는 다음 배포 기능을 개발하는 브랜치이다. 일반적으로 develop 브랜치를 default 브랜치로 지정하여 이것을 중심으로 작업하고, develop에서 문제가 발생하지 않음을 확인했다면 master브랜치를 업데이트한다.&lt;/p&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;b&gt;feature&lt;/b&gt; 브랜치는 기능을 추가를 목적으로 하는 브랜치로, 작업이 끝나면 develop 브랜치로 머지한다.&lt;/p&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;b&gt;release&lt;/b&gt; 브랜치는 master에서 서비스 중이고, 기능 추가(feature)가 적당히 완료된 경우 새로운 버전 출시를 준비하기 위한 브랜치이다. release 브랜치가 분기된 경우 버그 수정과 같은 릴리즈 관련 작업들이 수행되며, 배포 가능한 상태가 된다면 master 브랜치에 merge한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;만약 release단계에서 버그가 발견되어 수정한 경우에는 develop브랜치쪽에도 merge를 진행하여 오류를 수정한다.&lt;/span&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;+ 버전별로 릴리즈를 관리할 경우 'release-*'나 'release/*'와 같이 이름짓는다. (ex. release-4.0.0, release/v4.0.0-feat-0907)&lt;/p&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;b&gt;hotfix&lt;/b&gt; 브랜치는 release 브랜치까지 잡아내지 못해 라이브서버에 버그가 존재할경우 버그를 수정하기 위한 브랜치이다. release 브랜치와 비슷하게 'hotfix-*'과 같이 버전별로 관리할 수 있으며, 버그를 해결하면 제거한다.&lt;/p&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;보조 브랜치는 메인 브랜치의 필요에 의해서 생성된다. 메인 브랜치는 기존에 잘 작동하는 개발코드가 담기며, 보조 브랜치는 기능의 추가, 버그 수정 등으로 새로 변경되는 코드를 분리하고 보존하는 역할을 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Git Flow 전략 작업 예시&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;lt;커밋&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 작업 시작전에 작업용 브랜치를 파고 작업을 시작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; git branch origin release/v4.0.0-fix-0905&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; git checkout origin release/v4.0.0-fix-0905&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 작업이 끝나면 git fetch를 통해서 동일한 브랜치명을 다른 사람이 사용하는 방식으로 브랜치를 만들 경우, 원격 저장소의 변경 사항이 생겼는지 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;참고로 작업이 끝난 후 로컬서버가 돌고있다면 중단해주는 것이 좋다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. git pull을 통해서 원격 저장소의 최신 데이터를 로컬로 가져온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. commit convention대로 git commit한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; git commit -am &quot;feat: 기능 ~추가, Related to: #AOP-1234&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. git push하면 작업내용이 원격 저장소로 push된다.&lt;/p&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;lt;배포&amp;gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;6. 팀 슬랙이나 카톡방에 배포사실을 알린다. (ex. dev서버 release/v4.0.0-fix-0905 브랜치 배포예정)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;7. clean-build&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. 터미널에서 (mvn사용시) mvn package jib:build 통해서 이미지를 생성해준다. 생성된 이미지 여기서 문제가 발생했다면 clean-build를 다시 해주고 재실행한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;9. 이미지 생성을 확인한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10. argocd에서 서버를 restart해주면 올라간 이미지를 바탕으로 서버가 재실행된다.&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;GitHub Flow&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;259&quot; data-origin-height=&quot;194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/VvwPa/btstwG7BIzm/DxpR4xwZMofa7VCChYx2DK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/VvwPa/btstwG7BIzm/DxpR4xwZMofa7VCChYx2DK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/VvwPa/btstwG7BIzm/DxpR4xwZMofa7VCChYx2DK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FVvwPa%2FbtstwG7BIzm%2FDxpR4xwZMofa7VCChYx2DK%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;308&quot; height=&quot;231&quot; data-origin-width=&quot;259&quot; data-origin-height=&quot;194&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;GitHub Flow 전략은 간단하게 상용 서비스를 위한 master 브랜치와, master 브랜치에서 만들어지는 브랜치, 이렇게 2개의 브랜치만이 존재한다. 따라서 master 외 새로 생성하는 브랜치는 목적을 알 수 있도록 명명 규칙을 잘 세워야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;먼저 개발이 시작되면 master로부터 새로운 브랜치를 만들고 작업한다. 그리고 기능 개발이 완료되는 등 merge 준비가 완료되는 경우 pull request를 생성한다. 이것을 이용해서 코드리뷰를 진행하고, 문제가 없다고 생각하면 배포한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;GitHub Flow 전략 작업 예시&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 작업 시작 전에 브랜치를 파고 작업한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; git branch origin feat/drawer-yb/0905&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; git checkout origin feat/drawer-yb/0905&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 작업이 끝나면 git fetch와 git update를 통해서 변경내역을 확인한 후, 원격 저장소에 add commit push한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 해당 단위 스프린트가 끝나면 pr을 날린다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. pr 내용가지고 코드리뷰를 진행하고, merge conflict 여부를 확인하고 수정할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 문제가 없다면 master 브랜치로 합친다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 긴 호흡단위로 개발하며, 주기적으로 릴리즈, QA, 테스트, 핫픽스 등을 수행할 수 있는 기업단위 프로젝트라면 git-flow가 적합하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 수시로 릴리즈 되어야 하는 서비스를 지속적으로 테스트하고 배포하는 스타트업이나, 프로젝트 시작단계라면 github-flow를 통해서 생산성을 향상시킬 수 있다.&lt;/p&gt;</description>
      <category>[ 아키텍쳐, 방법론, 디자인패턴 등 ]</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/167</guid>
      <comments>https://eckrin.tistory.com/167#entry167comment</comments>
      <pubDate>Fri, 8 Sep 2023 11:24:45 +0900</pubDate>
    </item>
    <item>
      <title>Java 17(Spring 3.x)에서 Swagger가 동작하지 않을때..</title>
      <link>https://eckrin.tistory.com/166</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;345&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d561ec/btsCGawUceR/nD6KNLxw1Qjah129V4KjGk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d561ec/btsCGawUceR/nD6KNLxw1Qjah129V4KjGk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d561ec/btsCGawUceR/nD6KNLxw1Qjah129V4KjGk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd561ec%2FbtsCGawUceR%2FnD6KNLxw1Qjah129V4KjGk%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;656&quot; height=&quot;177&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;345&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;swagger에는 크게 springfox, springdoc 2가지 라이브러리가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;springfox가 먼저 나왔고 springdoc이 나중에 나왔다고 하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 Springboot 3.x버전의 경우 현재는 springfox를 이용할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(정확히 말하면 springboot 3.x버전이 &lt;b&gt;java17&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;springfox를 이용하기 위해서는 springboot 2.x버전으로 다운그레이드하거나 업데이트를 기다려야 하는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;본인이 스프링 3.x버전(java 17)을 사용한다면 springdoc을 이용하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;springdoc에서 configuration파일은 아래와 같이 구성하면 되고, @EnableSwagger2 어노테이션도 사용할 필요는 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;접속시 도메인은 http://{localhost}:{8080}/swagger-ui/index.html 을 사용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1693838229702&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configuration
public class SwaggerConfig {

    private final String TITLE = &quot;제목&quot;;
    private final String DESCRIPTION = &quot;설명&quot;;
    private final String VERSION = &quot;버전&quot;;

    @Bean
    public OpenAPI api() {
        return new OpenAPI()
                .info(new Info()
                        .title(TITLE)
                        .description(DESCRIPTION)
                        .version(VERSION)
                );
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>[ Backend ]/기타</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/166</guid>
      <comments>https://eckrin.tistory.com/166#entry166comment</comments>
      <pubDate>Mon, 4 Sep 2023 23:38:04 +0900</pubDate>
    </item>
    <item>
      <title>[Kotlin] 자바 대신 코틀린 사용하기</title>
      <link>https://eckrin.tistory.com/165</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;440&quot; data-origin-height=&quot;200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/X1nvx/btsFcBMtS2l/yI9bbc4slvaURYL9PY4lK0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/X1nvx/btsFcBMtS2l/yI9bbc4slvaURYL9PY4lK0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/X1nvx/btsFcBMtS2l/yI9bbc4slvaURYL9PY4lK0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FX1nvx%2FbtsFcBMtS2l%2FyI9bbc4slvaURYL9PY4lK0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;317&quot; height=&quot;144&quot; data-origin-width=&quot;440&quot; data-origin-height=&quot;200&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;들어가며&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;최근 Toss 같이 자바+스프링 대신 코틀린+스프링을 사용하는 기업들이 증가하고 있다. 지금 속한 동아리의 백엔드 팀에서도 자바가 아닌 코틀린을 사용하고 있다. 동아리에서 일년 가까운 기간동안 일하며 코틀린을 사용했지만, 지금까지 코틀린으로 작성한 코드를 살펴보니 단순히 자바 코드를 코틀린으로 번역한 수준에서 크게 벗어나지 않다는 것을 느꼈다. 그래서 향후 코틀린을 의미있게 사용하기 위해서, 자바 대신 코틀린을 사용했을 때 가질 수 있는 이점들을 정리하고자 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;컴파일 프로세스와 롬복&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;자바의 컴파일 과정에 대해서는 &lt;a href=&quot;https://eckrin.tistory.com/95&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이전&lt;/a&gt;에 설명한 적이 있다. 그렇다면 코틀린은 어떻게 컴파일되고 실행될까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코틀린은 문법에서 차이가 있을 뿐, 자바와 완벽하게 상호 운영 가능하다는 특징을 가진다. 심지어 둘을 같이 사용할 수도 있다. 코틀린 또한 JVM 위에서 동작하며, 아래와 같은 과정을 거친다.&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;1. 코틀린 소스코드(*.kt)는 코틀린 컴파일러(kotlinc)에 의해서 바이트 코드(*.class)로 컴파일된다. 이때 코틀린 소스코드가 참조하는 Java 소스코드도 함께 로딩되어 사용된다.&lt;br /&gt;2. 자바 소스코드(*.java)는 자바 컴파일러(JDK의 javac)에 의해서 바이트 코드(*.class)로 컴파일된다.&amp;nbsp;&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;코틀린 파일도 결국 바이트코드(*.class)로 변환되기 때문에, JVM에서 인터프리터나 JIT 컴파일러를 활용하여 이진코드의 형태로 실행될 수 있다.&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Null Safety&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;코틀린을 처음 사용했을때 가장 먼저 느꼈고, 지금까지도 제일 크게 체감하고 있는 부분이다. 자바로 비즈니스 로직 코드를 작성하다 보면 다음과 같은 상황들에 흔히 마주치게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1709010731657&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if(article.getImage().equals(SPECIFIC_IMAGE)) {...}&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;게시글의 이미지가 특정 이미지일때 분기처리를 할 필요가 있다고 하자. 로직을 그대로 코드로 옮기면 위와 같고 컴파일러는 아무런 문제도 찾아내지 못하지만, 사실 해당 코드는 런타임에 의도치 않게 NPE을 발생시킬 위험이 있다. article.getImage()가 null일수도, article 자체가 null일수도 있기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'가장 좋은 에러는 컴파일 에러다'라는 말을 들어본 적이 있을 것이다. 프로그램이 실행 중 발생하는 예기치 못한 Throwable(런타임 예외나 에러 등)은 잡아내기도 힘들고, 복구에 많은 리소스가 들어갈 가능성이 있다. 자바는 모든 변수를 nullable하게 정의하고 있기 때문에, 컴파일 시점의 null safety를 위해서는 아래와 같이 분기시 추가로 검사하거나, try-catch로 예외를 잡아내는 코드를 추가해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1709011355472&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;if(article!=null &amp;amp;&amp;amp; article.getImage()!=null &amp;amp;&amp;amp; article.getImage().equals(SPECIFIC_IMAGE)) {...}&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;이와 같은 자바의 특징은 로직과 관련없는 추가적인 코드를 작성하게 만들어 가독성을 해치고, 유지보수에까지 악영향을 끼칠 수 있다. (자바에서도 이러한 문제를 인지하여 Java 8부터 Optional이라는 타입을 도입하였으나, 기본적으로 모든 변수가 nullable하기 때문에 완벽한 null-safety를 보장하기 어렵다는 문제가 있고, 문법이 더 복잡하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 코틀린은 모든 객체변수를 not-nullable하게 정의하기에 변수가 null값을 가지기 위해서는 코드상으로 명시되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1709012117200&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Class Article(
    var id: Long?
    var title: String
    var image: String?
)&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1709012185785&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;if(article.image == SPECIFIC_IMAGE) {...}&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;어떠한 사유로 image에는 null값이 들어갈 수 있었고, 따라서 article의 image는 nullable value로 선언했다. 이제 저 위에서 자바로 작성했던 코드를 코틀린으로 그대로 옮겨보았다. 자바로 작성했을 때는 아무런 오류가 발생하지 않았지만, 코틀린은 컴파일러가 오류를 발생시킨다.&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;442&quot; data-origin-height=&quot;26&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqrLlc/btsFioAU0ba/EkGTWGuAIuwklrEfgVlJk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqrLlc/btsFioAU0ba/EkGTWGuAIuwklrEfgVlJk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqrLlc/btsFioAU0ba/EkGTWGuAIuwklrEfgVlJk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqrLlc%2FbtsFioAU0ba%2FEkGTWGuAIuwklrEfgVlJk1%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;442&quot; height=&quot;26&quot; data-origin-width=&quot;442&quot; data-origin-height=&quot;26&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;기본값인 not-nullable value와, '?'키워드를 통해서 선언한 nullable value를 구분하고 있음을 알 수 있다. 이러한 코틀린의 특성을 이용하여 자바에서는  런타임에 발생할 수 있는 예외를 컴파일 시점에 잡아낼 수 있다.&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Lombok과 Getter / Setter&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;자바는 getter와 setter의 작성을 개발자에게 위임하고 있다. 따라서 DTO, VO와 같이 getter나 setter가 필요한 모든 클래스에서 명시적으로 getter/setter를 선언해주어야 했고, 이러한 불편과 보일러 플레이트 코드들을 해소하기 위해서 Lombok이라는 라이브러리를 많이 사용한다.&lt;/p&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;하지만 코틀린은 lombok 라이브러리를 아예 지원하지 않는다. 지원하지 못한다고 표현하는 것이 맞는 것 같은데, Lombok 라이브러리는 &lt;b&gt;자바 코드(.java)가 자바 바이트 코드(.class)로 컴파일될때 Lombok 어노테이션을 해당하는 부분의 자바 바이트코드로 자동으로 대체&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;506&quot; data-origin-height=&quot;204&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qrVsq/btsFjJrhJmq/av3yRJiglZUSsIvb3LocqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qrVsq/btsFjJrhJmq/av3yRJiglZUSsIvb3LocqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qrVsq/btsFjJrhJmq/av3yRJiglZUSsIvb3LocqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqrVsq%2FbtsFjJrhJmq%2Fav3yRJiglZUSsIvb3LocqK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;506&quot; height=&quot;204&quot; data-origin-width=&quot;506&quot; data-origin-height=&quot;204&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;*.java파일과 *.kt파일이 각각의 컴파일러에 의해서 바이트 코드로 변환되는데, 코틀린이 먼저 컴파일된 후 자바 컴파일이 진행된다. 그런데 앞서 말했듯이 Lombok은 javac의 Annotation Processing 단계에서 어노테이션을 실제 바이트코드로 변환하기 때문에, &lt;b&gt;코틀린 컴파일 시점에는 롬복이 어노테이션 자체로만 존재&lt;/b&gt;하기에 동작하지 않는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 코틀린은 언어 자체로 lombok의 기능들을 일부 지원해준다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1709016248689&quot; class=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;Class Article(
    val id: Long
    var title: String
    var image: String?
)&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;위와 같은 클래스가 있다고 하자. 코틀린은 객체 프로퍼티에 한하여 val이 선언된 경우 getter를, var이 선언된 경우 getter와 setter를 자동으로 생성해준다. 자바와 같이 함수로 접근하기보다는 '.'을 이용하여 객체 프로퍼티에 접근하는 방식으로 표현된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1709016477572&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;article.id = 1L // val에 값을 할당하려고 하므로 컴파일에러 발생
article.title = &quot;ex&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;206&quot; data-origin-height=&quot;26&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/rHUjr/btsFiM2M7dS/Si5hIB00aDcKkekonKjuqk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/rHUjr/btsFiM2M7dS/Si5hIB00aDcKkekonKjuqk/img.png&quot; data-alt=&quot;val에 값을 할당하려고 했을 때 에러&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/rHUjr/btsFiM2M7dS/Si5hIB00aDcKkekonKjuqk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FrHUjr%2FbtsFiM2M7dS%2FSi5hIB00aDcKkekonKjuqk%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;206&quot; height=&quot;26&quot; data-origin-width=&quot;206&quot; data-origin-height=&quot;26&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;val에 값을 할당하려고 했을 때 에러&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Static과 Object, Companion Object&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;자바에는 static 이라는 키워드가 존재한다. 기본적으로 자바의 static 키워드는 다음과 같은 의미와 특징을 가진다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;1. static 변수의 경우 객체 생성 시점에 생성되는 인스턴스 변수나 멤버변수와 다르게, 클래스 로딩 시점에 클래스 로더에 의해서 로딩된다.&lt;br /&gt;2. 클래스 단위에서 논리적인 의미를 가지며, 인스턴스 생성 없이 접근 가능하다. 따라서 유틸리티 클래스에 static 메서드를 만들어 활용하는 경우가 많다.&lt;br /&gt;3. Heap이 아닌 Static영역에 할당되며, GC에 의해 자동으로 메모리 할당이 해제되지 않는다.&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;그런데 코틀린에는 static 키워드가 존재하지 않고, 대신 &lt;b&gt;Object&lt;/b&gt;와 &lt;b&gt;Companion Object&lt;/b&gt;를 활용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;첫 방법은 코틀린의 싱글톤을 지원하는 &lt;b&gt;object&lt;/b&gt;를 이용하는 방법이다. 자바에서 싱글톤 패턴의 구현을 위해서는 스프링과 같은 프레임워크를 사용하거나, &lt;a href=&quot;https://eckrin.tistory.com/91&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;직접 구현&lt;/a&gt;해주어야 했다. private 생성자를 통해서 객체 생성을 방지하고, static변수를 활용하여 하나의 객체만 사용되도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 코틀린은 object 키워드를 사용하여 언어차원에서 싱글톤 선언을 지원한다.&lt;/p&gt;
&lt;pre id=&quot;code_1709474676203&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;object Event {
    val coupons = ListOf()

    fun registerCoupon(name: String): Car {
        coupon.add(name)
    }
}&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;위 코드에서 event는 싱글톤으로 동작하며, Event객체를 여러개 생성하더라도 내부의 coupons라는 리스트는 새로 생성되지 않는다. 이를 이용하면 object키워드를 이용하여 싱글톤 클래스를 생성하고, 내부에 있는 변수들은 자동으로 static 멤버와 동일하게 사용할 수 있다.&lt;/p&gt;
&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;companion object&lt;/b&gt;를 사용할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1709475106800&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Event {
    companion object {
        val coupons = listOf()
        
        fun registerCoupon(name: String): Car {
            coupon.add(name)
        }
    }
}&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;자바의 static과 거의 동일하지만, 단순히 클래스 레벨의 메서드나 변수를 선언하는 역할만을 하는 java의 static과 다르게, 클래스 내에 실제 'companion object'라는 객체의 형태로 존재한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;궁금해요&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 왜 코틀린은 static 키워드를 사용하지 않고 object와 companion object라는 다른 형태로 제공할까? 내 결론은 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 자바의 static은 논리적인 문제를 가지고 있다. 바로 static 변수나 static 메서드를 클래스명이 아닌 인스턴스에서 접근할 수 있다는 것.&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;1414&quot; data-origin-height=&quot;106&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/esyqz0/btsFt3ctP5W/x64TWmmYrGYrVaBnwxmyAk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/esyqz0/btsFt3ctP5W/x64TWmmYrGYrVaBnwxmyAk/img.png&quot; data-alt=&quot;IDE가 경고해주기는 하지만, 인스턴스로 스태틱 필드에 접근하는 것을 막지 못한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/esyqz0/btsFt3ctP5W/x64TWmmYrGYrVaBnwxmyAk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fesyqz0%2FbtsFt3ctP5W%2Fx64TWmmYrGYrVaBnwxmyAk%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;1414&quot; height=&quot;106&quot; data-origin-width=&quot;1414&quot; data-origin-height=&quot;106&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;IDE가 경고해주기는 하지만, 인스턴스로 스태틱 필드에 접근하는 것을 막지 못한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바는 클래스명이 아닌 인스턴스를 통해&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;static 멤버에&lt;/span&gt; 접근하는 것을 허용하고 있다. 나는 자바에서 static키워드는 논리적으로 대체하기 어려운 중요한 문법이라고 생각한다. 하지만 이러한 자바의 너그러움(?)으로 인해, 개발자는 인스턴스의 멤버에 접근할 때 이것이 static인지, non-static인지 알 수 있는 방법이 없다. IDE가 띄워주는 경고가 아니었으면 일일히 클래스에 들어가서 멤버에 static이 붙었는지 확인해야 할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 처음으로 돌아가서, static은 '인스턴스에 의존적이지 않음'을 선언하는데 목적이 있다. 인스턴스가 아닌 클래스 단위로 의미를 가져야하는 사용처를 생각해보면 크게 두가지가 떠오른다. - 유틸리티 클래스와 싱글톤이 그것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 싱글톤은 object로, 유틸리티 클래스는 companion object라는 키워드를 통해 사용처를 분리하여 지원하는 것이라고 나는 이해했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 코틀린에서는 인스턴스에서 static멤버를 호출하는 문제도 발생하지 않는다. object의 경우 인스턴스 생성 자체가 제한되니 당연하고, companion object 또한 클래스 내부에 존재하지만 인스턴스와의 독립성을 부여하여 인스턴스 단위의 호출을 막았기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고자료&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NPE의 한계: &lt;a href=&quot;https://medium.com/@PurpleGreenLemon/how-null-references-became-the-billion-dollar-mistake-bcf0c0cc72ef&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://medium.com/@PurpleGreenLemon/how-null-references-became-the-billion-dollar-mistake-bcf0c0cc72ef&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Kotlin 컴파일 프로세스: &lt;a href=&quot;https://d2.naver.com/helloworld/6685007&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://d2.naver.com/helloworld/6685007&lt;/a&gt;&lt;/p&gt;</description>
      <category>[ Languages ]/Kotlin</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/165</guid>
      <comments>https://eckrin.tistory.com/165#entry165comment</comments>
      <pubDate>Fri, 1 Sep 2023 14:50:15 +0900</pubDate>
    </item>
    <item>
      <title>다양한 디자인 패턴 - (1) 구조 패턴</title>
      <link>https://eckrin.tistory.com/164</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1259&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLVGW1/btsDKa4kz9E/7aOr3mCc5DZQLZjuDa5au0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLVGW1/btsDKa4kz9E/7aOr3mCc5DZQLZjuDa5au0/img.jpg&quot; data-alt=&quot;귀엽다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLVGW1/btsDKa4kz9E/7aOr3mCc5DZQLZjuDa5au0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLVGW1%2FbtsDKa4kz9E%2F7aOr3mCc5DZQLZjuDa5au0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;134&quot; height=&quot;132&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;1259&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;귀엽다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;구조 패턴?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;구조 패턴은 클래스나 객체 구조의 조합을 사용하여 더 큰 구조를 형성하는 방법을 다루는 패턴이다. 시스템이 복잡한 경우, 클래스나 객체의 조합을 상요하여 구조를 단순화하고 &lt;b&gt;모듈성을 증가&lt;/b&gt;시키는 데 도움을 주고, 유연한 구조의 시스템을 만들어 &lt;b&gt;코드의 재사용성&lt;/b&gt;을 높일 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;퍼사드(Facade) 패턴&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;퍼사드 패턴은 복잡한 시스템 내의&lt;b&gt; 일련의 서브시스템에 대한 간단한 인터페이스를 제공&lt;/b&gt;하여 시스템의 복잡성을 감소시키는 패턴이다. 시스템이 각 서브시스템에 직접 접근하는 대신, 퍼사드를 통해 통합된 인터페이스의 형태로 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;processing&quot;&gt;&lt;code&gt;public static void treatFile() {
    String readContent = null;
    try (BufferedWriter writer = new BufferedWriter(new FileWriter(FILE_NAME))) {
        writer.write(FILE_CONTENT);
    } catch (IOException e) {
        e.printStackTrace();
    }
    try (BufferedReader reader = new BufferedReader(new FileReader(FILE_NAME))) {
        StringBuilder stringBuilder = new StringBuilder();
        String line;
        while ((line = reader.readLine()) != null) {
            stringBuilder.append(line);
            stringBuilder.append(System.lineSeparator());
        }
        readContent = stringBuilder.toString();
    } catch (IOException e) {
        e.printStackTrace();
    }
    System.out.println(readContent);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 treatFile() 메소드는 BufferedReader, BufferedWriter, StringBuilder와 같은 다양한 라이브러리를 사용하여 파일을 쓰고, 읽는 작업을 하고 있다. 이러한 다양한 라이브러리를 활용하기 위해서 객체들을 초기화하고, 올바른 순서로 메소드를 실행하는 등의 작업을 비즈니스 로직 내에서 수행해야 한다.&amp;nbsp;&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public class FileFacade {
    public String readFile(String fileName) {
        try (BufferedReader reader = new BufferedReader(new FileReader(fileName))) {
            StringBuilder stringBuilder = new StringBuilder();
            String line;
            while ((line = reader.readLine()) != null) {
                stringBuilder.append(line);
                stringBuilder.append(System.lineSeparator());
            }
            return stringBuilder.toString();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    public void writeFile(String fileName, String content) {
        try (BufferedWriter writer = new BufferedWriter(new FileWriter(fileName))) {
            writer.write(content);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;public static void treatFile() {
    FileFacade fileFacade = new FileFacade();
    fileFacade.writeFile(FILE_NAME, FILE_CONTENT);
    String readContent = fileFacade.readFile(FILE_NAME);
    System.out.println(readContent);
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대신 FileFacade라는 클래스를 만들고, 퍼사드 클래스의 메소드를 통해서 기능을 제공하고 있다. 이렇게 퍼사드 패턴은 비슷한 역할이나 연관된 기능을 하는 여러개의 클래스나 메소드들을 하나의 단일 인터페이스로 묶어서 제공하는 디자인 패턴이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;퍼사드 패턴은 다음과 같은 장점들을 제공한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 퍼사드를 통해서 비즈니스 코드는 서브시스템과 직접적으로 상호작용하지 않는다. 위 예시에서는 자바 라이브러리만을 사용했지만, 복잡한 외부 라이브러리를 다루어야 하는 경우에 비즈니스 코드는 &lt;b&gt;외부 라이브러리와 직접 상호작용하지 않으므로 의존성이 최소화&lt;/b&gt;된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;서브시스템의 변경이 발생해도 퍼사드만 수정하면 되므로 유지보수가 용이&lt;/b&gt;하다. 퍼사드를 사용하지 않았다면 인터페이스를 사용한 모든 클래스를 수정해야 할 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 퍼사드를 통해서 단일 인터페이스를 사용하므로, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;비즈니스 코드의&lt;/span&gt;&amp;nbsp;&lt;b&gt;복잡성이 감소하고 가독성이 증가&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;프록시 패턴, &lt;/b&gt;&lt;b&gt;데코레이터 패턴&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적인 클라이언트-서버 구조에서 클라이언트는 서버쪽으로 요청을 보내고, 서버는 해당 요청을 받아 처리한다. 그런데 이러한 과정 중간에 클라이언트의 요청을 받아 특정한 일을 하는 프로세스가 추가될 수 있는데, 이렇게 특별한 프로세스를 처리하는 단계를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;프록시(Proxy)&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;1408&quot; data-origin-height=&quot;194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buCkem/btsEFjsKLVr/RuY6QH9LU2gQ9KXXAvSzFK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buCkem/btsEFjsKLVr/RuY6QH9LU2gQ9KXXAvSzFK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buCkem/btsEFjsKLVr/RuY6QH9LU2gQ9KXXAvSzFK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuCkem%2FbtsEFjsKLVr%2FRuY6QH9LU2gQ9KXXAvSzFK%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;445&quot; height=&quot;61&quot; data-origin-width=&quot;1408&quot; data-origin-height=&quot;194&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중요한 점은 클라이언트는 프록시의 존재는 모른다는 것이다. 중개 서버가 추가되는 것과의 차이라고 할 수 있는데, 클라이언트는 여전히 기존 서버로 요청을 보내고, 프록시의 존재 유무와 상관없이 동일한 결과가 반환되기를 기대한다. 다시 말하면 서버에서 처리하는 서비스 코드가 변경될 때, 프록시에 변경이 전이되면 안된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;664&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cWudMB/btsEEvU4ZpT/r8ouK9TGrcp5ZYhn5zbsKK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cWudMB/btsEEvU4ZpT/r8ouK9TGrcp5ZYhn5zbsKK/img.png&quot; data-alt=&quot;https://refactoring.guru/ko/design-patterns/proxy&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cWudMB/btsEEvU4ZpT/r8ouK9TGrcp5ZYhn5zbsKK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcWudMB%2FbtsEEvU4ZpT%2Fr8ouK9TGrcp5ZYhn5zbsKK%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;367&quot; height=&quot;375&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;650&quot; data-origin-height=&quot;664&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://refactoring.guru/ko/design-patterns/proxy&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 클라이언트는 인터페이스에 의존하여 자신이 프록시에 의존하고 있는지, 서비스 로직에 의존하고 있는지를 모르게 하고, 서비스 코드와 프록시를 위한 코드는 모두 동일한 인터페이스에 의존하여 핵심 로직을 구현하도록 한다. 또한 프록시는 실제 객체(위 그림에서 Service)에 의존한다. 일반적으로 프록시 클래스가 로직 클래스를 감싸서 로직을 추가하는 형태로 구현한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 디자인 패턴을 프록시 패턴이라고 하는데, 데코레이터 패턴도 동일한 구조를 갖는다. 프록시를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;접근 제어&lt;/u&gt;(캐싱, 인가)를 목적으로 사용할 때는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;프록시 패턴&lt;/b&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;u&gt;기능 추가&lt;/u&gt;(로깅)를 목적으로 할 때는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;데코레이터 패턴&lt;/b&gt;이라고 부른다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;어댑터 패턴&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;아주 유용한 라이브러리가 존재한다고 하자. 그런데 이 라이브러리는 특정 패턴의 데이터만을 받아들일 수 있다. 이 때 다양한 데이터들이 해당 라이브러리를 이용하기 위해서는 라이브러리가 지원하는 데이터로 변환하는 과정이 필요할 것이다. 이렇게 호환되지 않는 인터페이스들을 연결하는 디자인 패턴을 '어댑터 패턴'이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;296&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c7jRGf/btsFHs9aZ6P/QvwqIgwK0bt4YZcNjkoRw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c7jRGf/btsFHs9aZ6P/QvwqIgwK0bt4YZcNjkoRw0/img.png&quot; data-alt=&quot;https://refactoring.guru/ko/design-patterns/adapter&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c7jRGf/btsFHs9aZ6P/QvwqIgwK0bt4YZcNjkoRw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc7jRGf%2FbtsFHs9aZ6P%2FQvwqIgwK0bt4YZcNjkoRw0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;538&quot; height=&quot;296&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;296&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://refactoring.guru/ko/design-patterns/adapter&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바에서 어댑터 패턴은 인터페이스 구현과 객체 합성을 이용하면 된다. 위 다이어그램에서 Adapter 클래스의 목표는 공통 프로토콜인 Client Interface를 통해서 Service클래스의 메서드를 호출하는 것이다. 따라서 중간의 Adapter 클래스는 Service클래스를 참조하고 있으며 동시에 Client Interface를 구현하고 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>[ 아키텍쳐, 방법론, 디자인패턴 등 ]</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/164</guid>
      <comments>https://eckrin.tistory.com/164#entry164comment</comments>
      <pubDate>Wed, 16 Aug 2023 10:46:00 +0900</pubDate>
    </item>
    <item>
      <title>쿠버네티스(Kubernetes) 알아보기</title>
      <link>https://eckrin.tistory.com/162</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;321&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/D184e/btsp64jAC2H/shQtzdRebwBjD9fO0KpSNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/D184e/btsp64jAC2H/shQtzdRebwBjD9fO0KpSNk/img.png&quot; data-alt=&quot;https://www.samsungsds.com/kr/insights/220222_kubernetes1.html&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/D184e/btsp64jAC2H/shQtzdRebwBjD9fO0KpSNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FD184e%2Fbtsp64jAC2H%2FshQtzdRebwBjD9fO0KpSNk%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;752&quot; height=&quot;284&quot; data-origin-width=&quot;850&quot; data-origin-height=&quot;321&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.samsungsds.com/kr/insights/220222_kubernetes1.html&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;쿠버네티스란?&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;역사&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;오늘날의&lt;span&gt; &lt;/span&gt;어플리케이션은&lt;span&gt; &lt;/span&gt;하나의&lt;span&gt; &lt;/span&gt;통일체의&lt;span&gt; &lt;/span&gt;형태로&lt;span&gt; &lt;/span&gt;동작하는&lt;span&gt; &lt;/span&gt;것이&lt;span&gt; &lt;/span&gt;아니라&lt;span&gt;, &lt;/span&gt;하나의&lt;span&gt; &lt;/span&gt;어플리케이션의&lt;span&gt; &lt;/span&gt;동작을&lt;span&gt; &lt;/span&gt;위해서&lt;span&gt; &lt;/span&gt;필요한&lt;span&gt; &lt;/span&gt;수많은&lt;span&gt; &lt;/span&gt;컨테이너들이&lt;span&gt; &lt;/span&gt;느슨하게&lt;span&gt; &lt;/span&gt;결합되어&lt;span&gt; &lt;/span&gt;함께&lt;span&gt; &lt;/span&gt;작동하는&lt;span&gt; &lt;/span&gt;형태로&lt;span&gt; &lt;/span&gt;구성되어 있다&lt;span&gt;. MSA&lt;/span&gt;환경을&lt;span&gt; &lt;/span&gt;기반으로&lt;span&gt; &lt;/span&gt;만들어진&lt;span&gt; &lt;/span&gt;어플리케이션이&lt;span&gt; &lt;/span&gt;여러&lt;span&gt; &lt;/span&gt;개의&lt;span&gt; &lt;/span&gt;서버로&lt;span&gt; &lt;/span&gt;구성되어&lt;span&gt; &lt;/span&gt;있고&lt;span&gt;, &lt;/span&gt;각&lt;span&gt; &lt;/span&gt;서비스가&lt;span&gt; &lt;/span&gt;독립적으로&lt;span&gt; &lt;/span&gt;개발되고&lt;span&gt; &lt;/span&gt;배포되는&lt;span&gt; &lt;/span&gt;구조로&lt;span&gt; &lt;/span&gt;이루어져&lt;span&gt; &lt;/span&gt;있다는&lt;span&gt; &lt;/span&gt;것을&lt;span&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&gt; &lt;/span&gt;그림은&lt;span&gt; &lt;/span&gt;전통적인&lt;span&gt; &lt;/span&gt;배포&lt;span&gt; &lt;/span&gt;환경과&lt;span&gt;, &lt;/span&gt;가상&lt;span&gt; &lt;/span&gt;환경을&lt;span&gt; &lt;/span&gt;이용한&lt;span&gt; &lt;/span&gt;배포&lt;span&gt;, &lt;/span&gt;그리고&lt;span&gt; &lt;/span&gt;컨테이너를&lt;span&gt; &lt;/span&gt;사용한&lt;span&gt; &lt;/span&gt;배포의&lt;span&gt; &lt;/span&gt;차이를&lt;span&gt; &lt;/span&gt;보여준다&lt;span&gt;. &lt;/span&gt;전동적인&lt;span&gt; &lt;/span&gt;배포는&lt;span&gt; &lt;/span&gt;물리적인&lt;span&gt; &lt;/span&gt;컴퓨터&lt;span&gt; &lt;/span&gt;하나와&lt;span&gt; &lt;/span&gt;하나의&lt;span&gt; &lt;/span&gt;운영체제&lt;span&gt; &lt;/span&gt;위에&lt;span&gt; &lt;/span&gt;여러개의&lt;span&gt; &lt;/span&gt;프로그램을&lt;span&gt; &lt;/span&gt;설치하는&lt;span&gt; &lt;/span&gt;방식이었다&lt;span&gt;. &lt;/span&gt;한대의&lt;span&gt; &lt;/span&gt;컴퓨터에서&lt;span&gt; &lt;/span&gt;여러&lt;span&gt; &lt;/span&gt;프로그램을&lt;span&gt; &lt;/span&gt;실행하다&lt;span&gt; &lt;/span&gt;보니&lt;span&gt; &lt;/span&gt;애플리케이션의&lt;span&gt; &lt;/span&gt;개수가&lt;span&gt; &lt;/span&gt;늘어날수록&lt;span&gt; &lt;/span&gt;퍼포먼스&lt;span&gt; &lt;/span&gt;저하와&lt;span&gt; &lt;/span&gt;프로그램&lt;span&gt; &lt;/span&gt;충돌이&lt;span&gt; &lt;/span&gt;일어나는&lt;span&gt;&amp;nbsp;등의 문제가 나타났다&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&gt; &lt;/span&gt;문제를&lt;span&gt; &lt;/span&gt;해결하기&lt;span&gt; &lt;/span&gt;위해서&lt;span&gt; &lt;/span&gt;여러개의&lt;span&gt; &lt;/span&gt;물리적&lt;span&gt; &lt;/span&gt;컴퓨터를&lt;span&gt; &lt;/span&gt;이용하는&lt;span&gt; &lt;/span&gt;원시적인&lt;span&gt; &lt;/span&gt;방법도&lt;span&gt; &lt;/span&gt;있지만&lt;span&gt;, &lt;/span&gt;하나의&lt;span&gt; &lt;/span&gt;시스템&lt;span&gt; &lt;/span&gt;위에&lt;span&gt; &lt;/span&gt;여러개의&lt;span&gt; &lt;/span&gt;가상&lt;span&gt; &lt;/span&gt;컴퓨터(VM)를&lt;span&gt; &lt;/span&gt;올려&lt;span&gt; &lt;/span&gt;구동하는&lt;span&gt; &lt;/span&gt;방법이 있다&lt;span&gt;. &lt;/span&gt;각각의&lt;span&gt; VM&lt;/span&gt;은&lt;span&gt; &lt;/span&gt;논리적&lt;span&gt; &lt;/span&gt;컴퓨터이므로&lt;span&gt;, CPU&lt;/span&gt;와&lt;span&gt; &lt;/span&gt;메모리를&lt;span&gt; &lt;/span&gt;각각&lt;span&gt; &lt;/span&gt;할당할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;이렇게&lt;span&gt; &lt;/span&gt;하면&lt;span&gt; &lt;/span&gt;가상머신&lt;span&gt; &lt;/span&gt;각각의&lt;span&gt; &lt;/span&gt;성능을&lt;span&gt; &lt;/span&gt;조절할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있으며&lt;span&gt;&amp;nbsp;&lt;/span&gt;프로그램&lt;span&gt; &lt;/span&gt;충돌을&lt;span&gt; &lt;/span&gt;방지할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있지만&lt;span&gt;, &lt;/span&gt;무거운&lt;span&gt; VM &lt;/span&gt;여러개를&lt;span&gt; &lt;/span&gt;결국&lt;span&gt; &lt;/span&gt;하나의&lt;span&gt; &lt;/span&gt;하드웨어가&lt;span&gt; &lt;/span&gt;감당해야&lt;span&gt; &lt;/span&gt;하기에&lt;span&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&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;그래서&lt;span&gt; &lt;/span&gt;나온&lt;span&gt; &lt;/span&gt;방법이&lt;span&gt; &lt;/span&gt;여러개의&lt;span&gt; VM&lt;/span&gt;을&lt;span&gt; &lt;/span&gt;올리는&lt;span&gt; &lt;/span&gt;대신&lt;span&gt; '&lt;/span&gt;&lt;b&gt;컨테이너&lt;span&gt; &lt;/span&gt;런타임&lt;/b&gt;&lt;span&gt;'&lt;/span&gt;이라는&lt;span&gt; &lt;/span&gt;것을&lt;span&gt; &lt;/span&gt;사용하여&lt;span&gt;, &lt;/span&gt;모든&lt;span&gt; &lt;/span&gt;애플리케이션이&lt;span&gt; &lt;/span&gt;하나의&lt;span&gt; OS&lt;/span&gt;를&lt;span&gt; &lt;/span&gt;공유하지만&lt;span&gt; &lt;/span&gt;각각의&lt;span&gt; &lt;/span&gt;컨테이너&lt;span&gt; &lt;/span&gt;사이에는&lt;span&gt; &lt;/span&gt;간섭을&lt;span&gt; &lt;/span&gt;일으키지&lt;span&gt; &lt;/span&gt;않도록&lt;span&gt; &lt;/span&gt;독립적인&lt;span&gt; &lt;/span&gt;자원을&lt;span&gt; &lt;/span&gt;할당하고&lt;span&gt; &lt;/span&gt;관리해주는&lt;span&gt; &lt;/span&gt;방법이며&lt;span&gt;, &lt;/span&gt;이러한&lt;span&gt; &lt;/span&gt;컨테이너&lt;span&gt; &lt;/span&gt;런타임을&lt;span&gt; &lt;/span&gt;관리하기&lt;span&gt; &lt;/span&gt;위한&lt;span&gt; &lt;/span&gt;툴이&lt;span&gt; &lt;/span&gt;쿠버네티스이다&lt;span&gt;. &lt;/span&gt;참고로&lt;span&gt; &lt;/span&gt;도커가&lt;span&gt; &lt;/span&gt;대표적인&lt;span&gt; &lt;/span&gt;컨테이너&lt;span&gt; &lt;/span&gt;런타임이며&lt;span&gt;, &lt;/span&gt;그래서&lt;span&gt; &lt;/span&gt;도커랑&lt;span&gt; &lt;/span&gt;엮여서&lt;span&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;&amp;nbsp;정리하면&lt;span&gt; &lt;/span&gt;앱이&lt;span&gt; &lt;/span&gt;구동되는&lt;span&gt; &lt;/span&gt;환경&lt;span&gt; &lt;/span&gt;전반을&lt;span&gt; &lt;/span&gt;감싸서&lt;span&gt; &lt;/span&gt;실행될&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있도록&lt;span&gt; &lt;/span&gt;하는&lt;span&gt; &lt;/span&gt;격리기술을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;컨테이너&lt;/b&gt;라고&lt;span&gt; &lt;/span&gt;하는데&lt;span&gt;, &lt;/span&gt;그러한&lt;span&gt; &lt;/span&gt;컨테이너를&lt;span&gt; &lt;/span&gt;다루기&lt;span&gt; &lt;/span&gt;위한&lt;span&gt; &lt;/span&gt;도구를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;컨테이너&lt;/b&gt;&lt;span&gt;&lt;b&gt; &lt;/b&gt;&lt;/span&gt;&lt;b&gt;런타임&lt;/b&gt;이라고&lt;span&gt; 한다(가장 유명한 컨테이너 런타임이 도커(Docker)이다 - k8s는 컨테이너 런타임 인터페이스(&lt;b&gt;CLI&lt;/b&gt;)라는 프로토콜을 사용하여 컨테이너와 통신을 진행한다). 그리고&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;이러한&lt;span&gt; &lt;/span&gt;수많은&lt;span&gt; &lt;/span&gt;컨테이너들을&lt;span&gt; &lt;/span&gt;모두&lt;span&gt; &lt;/span&gt;독립적으로&lt;span&gt; &lt;/span&gt;관리하며&lt;span&gt; &lt;/span&gt;배포하는&lt;span&gt; &lt;/span&gt;일은&lt;span&gt; &lt;/span&gt;쉽지&lt;span&gt; &lt;/span&gt;않기에&lt;span&gt; &lt;/span&gt;컨테이너들을&lt;span&gt; &lt;/span&gt;전반적으로&lt;span&gt; &lt;/span&gt;관리하는&lt;span&gt;&amp;nbsp;&lt;b&gt;컨테이너&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;b&gt;오케스트레이션&lt;/b&gt;이라는&lt;span&gt; &lt;/span&gt;기술을&lt;span&gt; &lt;/span&gt;사용하게&lt;span&gt; 되는데,&amp;nbsp;&lt;/span&gt;쿠버네티스는&lt;span&gt; &lt;/span&gt;이러한&lt;span&gt; &lt;/span&gt;컨테이너&lt;span&gt; &lt;/span&gt;오케스트레이션을&lt;span&gt; &lt;/span&gt;통해서&lt;span&gt; &lt;/span&gt;컨테이너를&lt;span&gt; &lt;/span&gt;다루기&lt;span&gt; &lt;/span&gt;위한&lt;span&gt; &lt;/span&gt;툴이다&lt;span&gt;&amp;nbsp;&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;b&gt;&lt;span style=&quot;color: #333333;&quot;&gt;k8s(컨테이너 오케스트레이션)을 이용했을 때의 장점&lt;br /&gt;&lt;/span&gt;&lt;/b&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;- 서비스 디스커버리와 로드 밸런싱&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;- 스토리지 오케스트레이션&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;- 롤아웃, 롤백 자동화&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;- 자동화된 빈 패킹&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;- 자동화된 복구&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;- 시크릿, 구성 관리&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;kubectl&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;k8s를 사용하다 보면 kubectl이라는 명령어를 자주 이용하게 된다. 이것은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Kubernetes API 서버와 유저의 통신을 중개&lt;/b&gt;하는 역할을 한다. 사용자의 명령어가 입력되면 그것을 바탕으로 API서버에 요청을 전송하고, 응답을 다시 사용자에게 전송한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 'kubectl exec -it [파드명] -- redis-cli' 와 같이 파드 내의 redis를 실행했다고 하자. 그럼 대략적으로 다음과 같은 과정을 거치게 된다.&lt;/p&gt;
&lt;blockquote style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot; data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;1. K8s API서버로 파드 내의 redis-cli를 실행하라는 요청을 보낸다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;2. k8s API서버는 이를 받아 처리하고, 파드가 실행중인 노드로 요청을 전달한다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;3. 각 노드에 있는 에이전트인 Kubelet은 요청을 받아서 명령어를 실행한다.&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;4. 실행 결과는 다시 API서버와 kubectl을 차례대로 거쳐 사용자에게 출력된다.&amp;nbsp;&lt;/span&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;쿠버네티스 오브젝트&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;쿠버네티스 오브젝트란 서비스, 파드와 같이 영속성을 갖는, '의도성을 가지는 레코드'라고 할 수 있다. 오브젝트를 생성하면 쿠버네티스 시스템은 해당 오브젝트에 의도한 상태를 유지하기 위해 작동하게 되는데, 이 '의도한 상태'를 yml문서에 담아 나타낼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 오브젝트를 조작하기 위한 모든 요청은 쿠버네티스 API 호출을 통해서 이루어지며, 일반적으로 이러한 정보들은 .yml파일의 형태로 저장된다. kubectl은 yml 형태의 파일을 json으로 변환하여 쿠버네티스 API로 요청하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;vs Docker-Compose&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그러면 쿠버네티스와 docker-compose는 무슨 차이를 가질까? 도커 컴포즈도 docker-compose.yml파일에 미리 여러 컨테이너를 정의하고 한번에 순차 실행시키고, 둘 다 comfiguration yaml 파일을 통해서 관리한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비슷해 보이지만 docker-compose는 단순히 도커상에서 여러개의 컨테이너를 한번에 관리하기 위한 &lt;b&gt;편의성을 위한 방법&lt;/b&gt;이고, 쿠버네티스는 도커보다 많은 컨테이너를 관리하는 컨테이너 오케스트레이션을 위해서 필요한 기능이다. &lt;span style=&quot;font-family: -apple-system, BlinkMacSystemFont, 'Helvetica Neue', 'Apple SD Gothic Neo', Arial, sans-serif; letter-spacing: 0px;&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개념 정리하기&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Pod&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;쿠버네티스는&lt;span&gt; &lt;/span&gt;컨테이너를&lt;span&gt; &lt;/span&gt;직접&lt;span&gt; &lt;/span&gt;실행하지&lt;span&gt; &lt;/span&gt;않고&lt;span&gt;, &lt;/span&gt;컨테이너(이미지)를&lt;span&gt; &lt;/span&gt;&lt;b&gt;파드(Pod)&lt;/b&gt;라는&lt;span&gt; &lt;/span&gt;단위에&lt;span&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;파드는&lt;span&gt; &lt;/span&gt;&lt;b&gt;컨테이너가&lt;span&gt; 1&lt;/span&gt;개&lt;span&gt; &lt;/span&gt;이상&lt;span&gt; &lt;/span&gt;캡슐화되어 저장공간과&lt;span&gt;&amp;nbsp;네트워크&lt;/span&gt;를&lt;span&gt; &lt;/span&gt;공유하는&lt;span&gt; &lt;/span&gt;객체&lt;/b&gt;를&lt;span&gt; &lt;/span&gt;말한다&lt;span&gt;. &lt;/span&gt;파드는&lt;span&gt; &lt;/span&gt;클러스터에서&lt;span&gt; &lt;/span&gt;관리하는&lt;span&gt; &lt;/span&gt;배포의&lt;span&gt; &lt;/span&gt;최소&lt;span&gt; &lt;/span&gt;단위이기 때문에&lt;span&gt;, 파드에 속한 컨테이너를 어떻게 구동할지에 대한 명세를 갖는다. &lt;/span&gt;컨테이너와&lt;span&gt; &lt;/span&gt;다르게&lt;span&gt;, &lt;/span&gt;파드는&lt;span&gt; &lt;/span&gt;구동&lt;span&gt; &lt;/span&gt;상태를&lt;span&gt; &lt;/span&gt;유지하기&lt;span&gt; &lt;/span&gt;위해서&lt;span&gt; &lt;/span&gt;사용되는&lt;span&gt; &lt;/span&gt;일회성&lt;span&gt; &lt;/span&gt;자원이며&lt;span&gt;, &lt;/span&gt;필요에&lt;span&gt; &lt;/span&gt;따라서&lt;span&gt; &lt;/span&gt;언제든&lt;span&gt; &lt;/span&gt;삭제되고&lt;span&gt; &lt;/span&gt;생성될&lt;span&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;&amp;nbsp;하나의&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;파드는&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;하나의&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;컨테이너를&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;포함(Wrapper)하는&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;것이&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;일반적이지만&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;같은&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;생명주기를&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;공유하고&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;함께&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;붙어있어야&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;하는&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;컨테이너들이&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;존재한다면 여러개의 컨테이너를&amp;nbsp;하나의&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;파드로&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;묶어&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;관리할&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;수&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;있다&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;따라서&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;위에서&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;1&lt;/span&gt;개의&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;스프링&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;부트&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;프로젝트와&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;3&lt;/span&gt;개의&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;database&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;컨테이너&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;각각을&amp;nbsp;별도의&amp;nbsp;파드로&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;관리할&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;수도&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 파드는 후술할 &lt;b&gt;워커 노드&lt;/b&gt;에서 실행된다. 즉, 워커 노드는 파드를 실행하는 일종의 가상 머신이 된다. 하지만 &lt;b&gt;동일한 종류의 파드라고 해서 동일한 워커 노드 안에서 실행되지는 않는다&lt;/b&gt;. &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;동일한 Deployment에서 생성된&lt;span&gt; 동일한 종류의 파드들이 서로 다른 워커노드에서 실행될 수 있다. 이렇게 흩어진 노드들은 Control-plane노드의 컨트롤러 매니저나 스케줄러에 의해서 삭제, 생성될 수 있다.&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;1200&quot; data-origin-height=&quot;116&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bA3prw/btsLmJM9Cl0/Vmz7zroDT7oIr3hvicvEV0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bA3prw/btsLmJM9Cl0/Vmz7zroDT7oIr3hvicvEV0/img.png&quot; data-alt=&quot;이렇게 직접 생성하는 경우는 드물다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bA3prw/btsLmJM9Cl0/Vmz7zroDT7oIr3hvicvEV0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbA3prw%2FbtsLmJM9Cl0%2FVmz7zroDT7oIr3hvicvEV0%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;1200&quot; height=&quot;116&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;116&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;이렇게 직접 생성하는 경우는 드물다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;파드는 사용자에 의해 직접 생성되지는 않고, Deployment, StatefulSet, Daemonset과 같은 상위 수준의 워크로드 리소스로 파드를 관리한다. 이는 &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&gt;파드는 크게 다음과 같은 라이프사이클(상태)을 가진다.&lt;/span&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 26.7442%;&quot;&gt;값&lt;/td&gt;
&lt;td style=&quot;width: 73.2558%;&quot;&gt;의미&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 26.7442%; text-align: left;&quot;&gt;Pending&lt;/td&gt;
&lt;td style=&quot;width: 73.2558%;&quot;&gt;클러스터에서 승인되었지만, 컨테이너가 설정되지 않음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 26.7442%; text-align: left;&quot;&gt;Running&lt;/td&gt;
&lt;td style=&quot;width: 73.2558%;&quot;&gt;파드가 노드에 바인딩되었고, 컨테이너가 모두 생성됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 26.7442%; text-align: left;&quot;&gt;Succeeded&lt;/td&gt;
&lt;td style=&quot;width: 73.2558%;&quot;&gt;파드의 모든 컨테이너가 성공적으로 종료됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 26.7442%; text-align: left;&quot;&gt;Failed&lt;/td&gt;
&lt;td style=&quot;width: 73.2558%;&quot;&gt;파드의 하나 이상의 컨테이너가 실패로 종료됨&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 26.7442%; text-align: left;&quot;&gt;Unknown&lt;/td&gt;
&lt;td style=&quot;width: 73.2558%;&quot;&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;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Cluster&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;클러스터는&lt;span&gt; &lt;/span&gt;애플리케이션&lt;span&gt; &lt;/span&gt;컨테이너를&lt;span&gt; &lt;/span&gt;실행하기&lt;span&gt; &lt;/span&gt;위한&lt;span&gt; &lt;/span&gt;&lt;b&gt;일련의&lt;span&gt; &lt;/span&gt;노드들로 이루어진&lt;span&gt; &lt;/span&gt;머신들의 집합&lt;/b&gt;으로&lt;span&gt;, &lt;/span&gt;여러개의&lt;span&gt; &lt;/span&gt;머신&lt;span&gt; &lt;/span&gt;또는&lt;span&gt; &lt;/span&gt;노드가&lt;span&gt; &lt;/span&gt;함께&lt;span&gt; &lt;/span&gt;작동하여&lt;span&gt; &lt;/span&gt;컨테이너화된&lt;span&gt; &lt;/span&gt;애플리케이션을&lt;span&gt; &lt;/span&gt;배포하고&lt;span&gt; &lt;/span&gt;관리한다.&lt;span&gt; 클러스터 안에 존재하는 모든 &lt;/span&gt;파드들은&lt;span&gt; k8s의 마스터 노드에 의해서 워커&amp;nbsp;&lt;/span&gt;노드에&lt;span&gt; &lt;/span&gt;적절하게&lt;span&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;Node&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;1076&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bjBHq3/btsLz9qDIYj/6O9KMNnrnXkKIa3R3dvllk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bjBHq3/btsLz9qDIYj/6O9KMNnrnXkKIa3R3dvllk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bjBHq3/btsLz9qDIYj/6O9KMNnrnXkKIa3R3dvllk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbjBHq3%2FbtsLz9qDIYj%2F6O9KMNnrnXkKIa3R3dvllk%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;717&quot; height=&quot;482&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;1076&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;span&gt; &lt;/span&gt;클러스터&lt;span&gt; &lt;/span&gt;내에서&lt;span&gt; &lt;/span&gt;애플리케이션을&lt;span&gt; &lt;/span&gt;실행하고&lt;span&gt;, &lt;/span&gt;쿠버네티스&lt;span&gt; &lt;/span&gt;오브젝트를&lt;span&gt; &lt;/span&gt;관리하는&lt;span&gt; &lt;/span&gt;&lt;b&gt;머신&lt;/b&gt;을&lt;span&gt; &lt;/span&gt;말한다&lt;span&gt;. 이러한 노드는 가상머신(VM)일수도, 물리적 머신(온프레미스)일수도 있다.&amp;nbsp;&lt;/span&gt;&lt;b&gt;하나의&lt;span&gt; &lt;/span&gt;클러스터&lt;span&gt; &lt;/span&gt;안에는&lt;span&gt; &lt;/span&gt;여러개의&lt;span&gt; &lt;/span&gt;노드가&lt;span&gt; &lt;/span&gt;존재&lt;/b&gt;하며, &lt;b&gt;각&lt;span&gt; &lt;/span&gt;노드는&lt;span&gt; &lt;/span&gt;클러스터&lt;span&gt; &lt;/span&gt;내의&lt;span&gt; &lt;/span&gt;파드를&lt;span&gt; &lt;/span&gt;실행하고&lt;span&gt; &lt;/span&gt;관리&lt;/b&gt;한다&lt;span&gt;. &lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;후술하겠지만 &lt;/span&gt;쿠버네티스&lt;span&gt; &lt;/span&gt;환경&lt;span&gt; &lt;/span&gt;전반에&lt;span&gt; &lt;/span&gt;대한&lt;span&gt; &lt;/span&gt;컨트롤을&lt;span&gt; &lt;/span&gt;하기&lt;span&gt; &lt;/span&gt;위한&lt;span&gt; scheduler&lt;/span&gt;나&lt;span&gt; controller manager등의&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;마스터&lt;span&gt; &lt;/span&gt;노드와&lt;span&gt;, &lt;/span&gt;파드에&lt;span&gt; &lt;/span&gt;대한&lt;span&gt; &lt;/span&gt;컨트롤을&lt;span&gt; &lt;/span&gt;하기&lt;span&gt; &lt;/span&gt;위한&lt;span&gt; kubelet&lt;/span&gt;과&lt;span&gt; kube proxy&lt;/span&gt;와&lt;span&gt; &lt;/span&gt;같은&lt;span&gt; &lt;/span&gt;워커&lt;span&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&gt; &lt;/span&gt;&lt;b&gt;클러스터는&lt;span&gt; &lt;/span&gt;노드들의&lt;span&gt; &lt;/span&gt;집합이고&lt;span&gt;, &lt;/span&gt;노드&lt;span&gt; &lt;/span&gt;안에서는 파드가&lt;span&gt;&amp;nbsp;&lt;/span&gt;실행되며&lt;span&gt;, &lt;/span&gt;파드&lt;span&gt; &lt;/span&gt;안에는 한 개 이상의 컨테이너가&lt;span&gt;&amp;nbsp;&lt;/span&gt;실행되고&lt;span&gt;, 해당&amp;nbsp;&lt;/span&gt;컨테이너&lt;span&gt; &lt;/span&gt;안에서 애플리케이션이&lt;span&gt;&amp;nbsp;&lt;/span&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;Context&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;클러스터와&lt;span&gt; &lt;/span&gt;노드가&lt;span&gt; &lt;/span&gt;물리적인&lt;span&gt; &lt;/span&gt;구성&lt;span&gt; &lt;/span&gt;단위라면&lt;span&gt;, &lt;/span&gt;컨텍스트와&lt;span&gt; &lt;/span&gt;네임스페이스는&lt;span&gt; &lt;/span&gt;논리적인&lt;span&gt; &lt;/span&gt;구성&lt;span&gt; &lt;/span&gt;단위라고&lt;span&gt; &lt;/span&gt;할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨텍스트는&lt;span&gt; &lt;/span&gt;연결&lt;span&gt; &lt;/span&gt;대상이&lt;span&gt; &lt;/span&gt;되는&lt;span&gt; &lt;/span&gt;클러스터&lt;span&gt;, &lt;/span&gt;네임스페이스&lt;span&gt;, &lt;/span&gt;그리고&lt;span&gt; &lt;/span&gt;유저를&lt;span&gt; &lt;/span&gt;묶어놓은&lt;span&gt; &lt;/span&gt;구성&lt;span&gt; &lt;/span&gt;단위를&lt;span&gt; &lt;/span&gt;말한다&lt;span&gt;.&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;사용하는&lt;span&gt; &lt;/span&gt;컨텍스트에&lt;span&gt; &lt;/span&gt;따라서&lt;span&gt; &lt;/span&gt;대상이&lt;span&gt; &lt;/span&gt;되는&lt;span&gt; &lt;/span&gt;클러스터와&lt;span&gt; &lt;/span&gt;유저&lt;span&gt;, &lt;/span&gt;네임스페이스를&lt;span&gt; &lt;/span&gt;다르게&lt;span&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;NameSpace&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;네임스페이스는&lt;span&gt; &lt;/span&gt;쿠버네티스&lt;span&gt; &lt;/span&gt;오브젝트들을&lt;span&gt; &lt;/span&gt;&lt;b&gt;논리적&lt;/b&gt;으로&lt;span&gt;&amp;nbsp;묶은&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;구성&lt;span&gt; &lt;/span&gt;단위이다&lt;span&gt;. &lt;/span&gt;인그레스&lt;span&gt;, &lt;/span&gt;서비스&lt;span&gt;, &lt;/span&gt;파드와&lt;span&gt; &lt;/span&gt;같은&lt;span&gt; &lt;/span&gt;오브젝트들을&lt;span&gt; &lt;/span&gt;유사한&lt;span&gt; &lt;/span&gt;목적을&lt;span&gt; &lt;/span&gt;가진&lt;span&gt; &lt;/span&gt;묶음으로&lt;span&gt; grouping&lt;/span&gt;해서&lt;span&gt; &lt;/span&gt;네임스페이스라는&lt;span&gt; &lt;/span&gt;단위로&lt;span&gt; &lt;/span&gt;관리할&lt;span&gt; &lt;/span&gt;수&lt;span&gt;&amp;nbsp;있다&lt;/span&gt;&lt;span&gt;.&lt;span&gt;&amp;nbsp;&lt;/span&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;1502&quot; data-origin-height=&quot;792&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kDgWW/btsLAv7gJ0S/K3sNjZzH7wcAaK6imJ3w9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kDgWW/btsLAv7gJ0S/K3sNjZzH7wcAaK6imJ3w9k/img.png&quot; data-alt=&quot;https://artist-developer.tistory.com/33&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kDgWW/btsLAv7gJ0S/K3sNjZzH7wcAaK6imJ3w9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkDgWW%2FbtsLAv7gJ0S%2FK3sNjZzH7wcAaK6imJ3w9k%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;533&quot; height=&quot;281&quot; data-origin-width=&quot;1502&quot; data-origin-height=&quot;792&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://artist-developer.tistory.com/33&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;네임스페이스는 왜 사용할까? 대표적인 예시는 위처럼 배포 환경을 기준으로 namespace를 분리한다면, 실사용자가 많은 production 환경에는 많은 리소스를 할당하고, 테스트를 위한 staging이나 develop 환경에는 적은 리소스를 할당하여 리소스를 효과적으로 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;&lt;span&gt;Controller&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;컨트롤러는 클러스터의 상태를 관찰한 다음, 파드를 관리하여 클러스터의 상태를 의도에 근접하게 이동시키는 역할을 한다. 사용자가 관리하는 k8s 객체를 &lt;b&gt;워크로드 리소스&lt;/b&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&gt;&lt;span&gt;대표적으로는 다음과 같은 워크로드 리소스가 있다. 각 워크로드 리소스마다 관리의 범위가 다른 것을 확인할 수 있다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;- 상태를 유지하지 않아도 되는 파드를 관리하는 경우&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;-&amp;gt; Replication Controller&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;-&amp;gt; ReplicaSet&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;-&amp;gt; Deployment&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;- 클러스터 전체에 배포가 필요한 파드를 관리하는 경우&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;-&amp;gt; Daemon Set&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;- 상태관리가 필요한 파드를 관리하는 경우&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;-&amp;gt; Stateful Set&lt;/span&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;- 배치성 작업을 진행하는 파드를 관리하는 경우&lt;/span&gt;&lt;br /&gt;&lt;span style=&quot;color: #333333;&quot;&gt;-&amp;gt; Cronjob&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Deployment, ReplicaSet&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;애플리케이션의 배포와 업데이트를 관리하는 리소스로, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;Pod 생성을 비롯한 라이프사이클을 관리&lt;/span&gt;한다. 기존 ReplicaSet과 RepliactionController의 기능을 포함하면서 추가적인 기능을 제공한다. Deployment는 롤링 업데이트, 롤백, 스케일링과 같은 애플리케이션의 라이프사이클 관리를 지원하며, 이를 통해서 안정적으로 업데이트를 수행하거나 롤백할 수 있다.&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;대표적인 옵션으로&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;ReplicaSet이&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;있는데&lt;span&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;이것은&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;파드의&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;복제본(Replica)들을&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;관리하는 컨트롤러의&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;역할을&lt;span&gt;&amp;nbsp;한다&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;후술하겠지만 이러한 &lt;/span&gt;ReplicaSet은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;파드의&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;인스턴스&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;개수를&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;일정&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;개수&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;이상으로&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;유지&lt;/b&gt;하도록&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;하여&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;파드의&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;안정성을&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;보장한다&lt;span&gt;. (이중화를 통해 한 서버에 장애가 발생했을 때 failover가 가능하게끔 한다)&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;&lt;span&gt;Deployment 예시&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;내부에 1개의 컨테이너를 가진 파드 하나를 생성하는 Deployment의 예시이다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1735623595394&quot; class=&quot;yaml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: apps/v1
kind: Deployment # 생성하려는 k8s 리소스 유형
metadata:
  name: my-deployment # 사용할 리소스명
spec: # deployment 구성에 대한 세부 내용
  replicas: 1 # 시작 시 생성할 실행 파드의 개수
  selector: # 라벨을 통해 제어할 파드 선택
    matchLabels:
      tier: one # tier가 one인 모든 파드 제어
  template: # deployment에서 생성되어야 할 파드 정보
    kind: Pod
    metadata:
      labels: # 생성할 라벨. selector.matchLabel을 통해 연결 가능
        tier: one
    spec: # deployment 내부 개별 pod의 사양
      containters: 
        - name: first-node
          image: eckrin/kube-first-app:2 # 추가하고 싶은 컨테이너 이미지&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1735623775836&quot; class=&quot;yaml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl apply -f==deployment.yml&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;yml 파일을 바탕으로 kubectl을 사용하여 deployment를 적용할 수 있다.&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Service&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;파드&lt;span&gt; &lt;/span&gt;내의&lt;span&gt; &lt;/span&gt;컨테이너끼리는&lt;span&gt; localhost&lt;/span&gt;로&lt;span&gt; &lt;/span&gt;접근이&lt;span&gt; &lt;/span&gt;가능하지만&lt;span&gt;, &lt;/span&gt;외부에서&lt;span&gt; &lt;/span&gt;파드에&lt;span&gt; &lt;/span&gt;접근하기&lt;span&gt; &lt;/span&gt;위해서는&lt;span&gt; &lt;/span&gt;서비스를&lt;span&gt; &lt;/span&gt;정의해주어야&lt;span&gt;&amp;nbsp;한&lt;/span&gt;다&lt;span&gt;. 서&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스란 '파드 그룹을 네트워크 서비스로 노출하는 추상화 방법'을 말하는데,&amp;nbsp; &lt;b&gt;Pod를 논리적으로 구분하여 특정 IP를 통해 접근하도록 하는 것&lt;/b&gt;이다. 쿠버네티스는 ClusterIp, External Name, NodePort, LoadBalancer 등의 서비스를 제공한다.&lt;/p&gt;
&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: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;또한&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;파드와&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;동일하게&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;yml&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;파일을&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;사용하여&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;세팅해줄&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;수&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;있다&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1522&quot; data-origin-height=&quot;1300&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bLwBTd/btsBUBKKEOL/7tOWgy44ESIT0TJVDaRr4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bLwBTd/btsBUBKKEOL/7tOWgy44ESIT0TJVDaRr4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bLwBTd/btsBUBKKEOL/7tOWgy44ESIT0TJVDaRr4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbLwBTd%2FbtsBUBKKEOL%2F7tOWgy44ESIT0TJVDaRr4k%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;501&quot; height=&quot;428&quot; data-origin-width=&quot;1522&quot; data-origin-height=&quot;1300&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;span&gt; yml&lt;/span&gt;은&lt;span&gt; 밑에서&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;쿠버네티스&lt;span&gt; &lt;/span&gt;실습을&lt;span&gt; 할 때&lt;/span&gt;&lt;span&gt;&amp;nbsp;&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&gt;&amp;nbsp;kind&lt;/span&gt;필드를&lt;span&gt; Service&lt;/span&gt;로&lt;span&gt; &lt;/span&gt;지정해주어&lt;span&gt; &lt;/span&gt;서비스임을&lt;span&gt; &lt;/span&gt;명시하고&lt;span&gt;, metadata&lt;/span&gt;의&lt;span&gt; name&lt;/span&gt;에&lt;span&gt; &lt;/span&gt;서비스&lt;span&gt; &lt;/span&gt;이름을&lt;span&gt; &lt;/span&gt;지정해준다&lt;span&gt;. &lt;/span&gt;그&lt;span&gt; &lt;/span&gt;다음&lt;span&gt; spec &lt;/span&gt;필드&lt;span&gt; &lt;/span&gt;맡에&lt;span&gt; &lt;/span&gt;연결한&lt;span&gt; &lt;/span&gt;파드의&lt;span&gt; &lt;/span&gt;네트워크와&lt;span&gt; &lt;/span&gt;관련된&lt;span&gt; &lt;/span&gt;설정들을&lt;span&gt; &lt;/span&gt;하면&lt;span&gt; &lt;/span&gt;되는데&lt;span&gt;, &lt;/span&gt;위와&lt;span&gt; &lt;/span&gt;같이&lt;span&gt; &lt;/span&gt;설정하면&lt;span&gt; spring-kubernetes&lt;/span&gt;라는&lt;span&gt; &lt;/span&gt;라벨을&lt;span&gt; &lt;/span&gt;가진&lt;span&gt; &lt;/span&gt;파드를&lt;span&gt; &lt;/span&gt;대상으로&lt;span&gt;, &lt;/span&gt;외부에서&lt;span&gt; TCP &lt;/span&gt;연결을&lt;span&gt; &lt;/span&gt;통해&lt;span&gt; 30001&lt;/span&gt;번&lt;span&gt; &lt;/span&gt;포트로&lt;span&gt; &lt;/span&gt;오는&lt;span&gt; &lt;/span&gt;요청들을&lt;span&gt; &lt;/span&gt;받아서&lt;span&gt; &lt;/span&gt;내부&lt;span&gt; 8080&lt;/span&gt;번&lt;span&gt; &lt;/span&gt;포트번호를&lt;span&gt; &lt;/span&gt;갖는&lt;span&gt; &lt;/span&gt;파드로&lt;span&gt; &lt;/span&gt;요청을&lt;span&gt; &lt;/span&gt;전달해준다&lt;span&gt;. (port &lt;/span&gt;필드의&lt;span&gt; &lt;/span&gt;경우&lt;span&gt; &lt;/span&gt;내부에서&lt;span&gt; &lt;/span&gt;서비스를&lt;span&gt; &lt;/span&gt;사용하기&lt;span&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;&amp;nbsp; 위 yml에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;타입이&lt;span&gt; 'nodePort'&lt;/span&gt;로&lt;span&gt;&amp;nbsp;&lt;/span&gt;되어있다는&lt;span&gt;&amp;nbsp;특징이&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;있는데&lt;b&gt;&lt;span&gt; nodePort&lt;/span&gt;&lt;/b&gt;의&lt;span&gt; &lt;/span&gt;경우&lt;span&gt; &lt;/span&gt;클러스터&lt;span&gt; &lt;/span&gt;내의&lt;span&gt; &lt;/span&gt;노드에&lt;span&gt; &lt;/span&gt;외부로&lt;span&gt; &lt;/span&gt;노출되는&lt;span&gt; &lt;/span&gt;포트를&lt;span&gt; &lt;/span&gt;할당하여&lt;span&gt; &lt;/span&gt;외부에서&lt;span&gt; IP&lt;/span&gt;와&lt;span&gt; &lt;/span&gt;포트를&lt;span&gt; &lt;/span&gt;통해서&lt;span&gt; &lt;/span&gt;서비스에&lt;span&gt; &lt;/span&gt;직접&lt;span&gt; &lt;/span&gt;접근할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있지만&lt;span&gt;,&lt;/span&gt;&lt;span&gt;&lt;b&gt; ClusterIP&lt;/b&gt;와 같은 &lt;/span&gt;경우는&lt;span&gt; &lt;/span&gt;클러스터&lt;span&gt; &lt;/span&gt;내부에서만&lt;span&gt; &lt;/span&gt;접근이&lt;span&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;&amp;nbsp;사실&lt;span&gt; &lt;/span&gt;쿠버네티스에서&lt;span&gt; &lt;/span&gt;서비스는&lt;span&gt; &lt;/span&gt;개발&lt;span&gt; &lt;/span&gt;및&lt;span&gt; &lt;/span&gt;테스트&lt;span&gt; &lt;/span&gt;환경에서만&lt;span&gt; &lt;/span&gt;외부로&lt;span&gt; IP&lt;/span&gt;를&lt;span&gt; &lt;/span&gt;개방하고&lt;span&gt;, &lt;/span&gt;&lt;b&gt;실제&lt;span&gt; &lt;/span&gt;서비스에서는 클라이언트에서는&lt;span&gt; &lt;/span&gt;접근이&lt;span&gt; &lt;/span&gt;불가능하도록&lt;span&gt; &lt;/span&gt;생성&lt;/b&gt;한다&lt;span&gt;. &lt;/span&gt;따라서&lt;span&gt; &lt;/span&gt;클라이언트에서&lt;span&gt; &lt;/span&gt;접근할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있는&lt;span&gt; &lt;/span&gt;외부&lt;span&gt; ip&lt;/span&gt;와&lt;span&gt; port&lt;/span&gt;를&lt;span&gt; &lt;/span&gt;제공하고&lt;span&gt;, &lt;/span&gt;외부&lt;span&gt; &lt;/span&gt;클라이언트와&lt;span&gt; &lt;/span&gt;쿠버네티스&lt;span&gt; &lt;/span&gt;서비스를&lt;span&gt; &lt;/span&gt;연결해&lt;span&gt; &lt;/span&gt;줄&lt;span&gt; &lt;/span&gt;무언가가&lt;span&gt; &lt;/span&gt;필요한데&lt;span&gt;, &lt;/span&gt;그것이&lt;span&gt; &lt;/span&gt;서비스에&lt;span&gt; &lt;/span&gt;대한&lt;span&gt; &lt;/span&gt;로드&lt;span&gt; &lt;/span&gt;밸런서인&lt;span&gt; ingress이다.&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span&gt;Ingress&lt;/span&gt;&lt;/b&gt;&lt;/h3&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;1922&quot; data-origin-height=&quot;556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oEtbx/btsBY81P0vZ/4KUhzUBicEmKK7ZGpgzz4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oEtbx/btsBY81P0vZ/4KUhzUBicEmKK7ZGpgzz4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oEtbx/btsBY81P0vZ/4KUhzUBicEmKK7ZGpgzz4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoEtbx%2FbtsBY81P0vZ%2F4KUhzUBicEmKK7ZGpgzz4k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;522&quot; height=&quot;151&quot; data-origin-width=&quot;1922&quot; data-origin-height=&quot;556&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;span&gt; &lt;/span&gt;말했듯&lt;span&gt;&amp;nbsp;상용 서비스&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;환경에서는&lt;span&gt; &lt;/span&gt;서비스를&lt;span&gt; &lt;/span&gt;통해&lt;span&gt; &lt;/span&gt;외부로&lt;span&gt; &lt;/span&gt;직접&lt;span&gt; &lt;/span&gt;개방하는&lt;span&gt; &lt;/span&gt;경우는&lt;span&gt; &lt;/span&gt;많지&lt;span&gt; &lt;/span&gt;않으며&lt;span&gt;, &lt;/span&gt;클라이언트에서&lt;span&gt; &lt;/span&gt;접근가능한&lt;span&gt; &lt;/span&gt;외부&lt;span&gt; &lt;/span&gt;주소와&lt;span&gt; &lt;/span&gt;쿠버네티스&lt;span&gt; &lt;/span&gt;서비스를&lt;span&gt; &lt;/span&gt;연결해주는&lt;span&gt; &lt;/span&gt;로드&lt;span&gt; &lt;/span&gt;밸런서를&lt;span&gt; &lt;/span&gt;두는&lt;span&gt; &lt;/span&gt;경우가&lt;span&gt; &lt;/span&gt;일반적이다&lt;span&gt;. &lt;/span&gt;이는&lt;span&gt; &lt;/span&gt;서비스를&lt;span&gt; &lt;/span&gt;외부에서&lt;span&gt; &lt;/span&gt;노출시킬&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있는&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;자체도&lt;span&gt; &lt;/span&gt;제한이&lt;span&gt; &lt;/span&gt;있을&lt;span&gt; &lt;/span&gt;뿐&lt;span&gt; &lt;/span&gt;아니라&lt;span&gt;, &lt;/span&gt;다수의&lt;span&gt; &lt;/span&gt;백엔드&lt;span&gt; &lt;/span&gt;서비스가&lt;span&gt; &lt;/span&gt;존재할&lt;span&gt; &lt;/span&gt;경우&lt;span&gt; &lt;/span&gt;트래픽을&lt;span&gt; &lt;/span&gt;분산시키거나&lt;span&gt;, &lt;/span&gt;복잡한&lt;span&gt; &lt;/span&gt;네트워크&lt;span&gt; &lt;/span&gt;정책을&lt;span&gt; &lt;/span&gt;관리하거나&lt;span&gt; &lt;/span&gt;라우팅할&lt;span&gt; &lt;/span&gt;필요가&lt;span&gt; &lt;/span&gt;있는&lt;span&gt; &lt;/span&gt;등&lt;span&gt; &lt;/span&gt;여러가지&lt;span&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;span&gt; &lt;/span&gt;인그레스&lt;span&gt; &lt;/span&gt;설정을&lt;span&gt; &lt;/span&gt;위해서는&lt;span&gt; &lt;/span&gt;인그레스&lt;span&gt; &lt;/span&gt;컨트롤러가&lt;span&gt; &lt;/span&gt;필요한데&lt;span&gt;, &lt;/span&gt;사용하려는&lt;span&gt; &lt;/span&gt;인그레스&lt;span&gt; &lt;/span&gt;컨트롤러의&lt;span&gt; &lt;/span&gt;옵션에&lt;span&gt; &lt;/span&gt;따라서&lt;span&gt; yml&lt;/span&gt;파일을&lt;span&gt; &lt;/span&gt;설정해주고,&lt;span&gt;&amp;nbsp;&lt;/span&gt;인그레스&lt;span&gt; &lt;/span&gt;리소스를&lt;span&gt; &lt;/span&gt;정의해준&lt;span&gt; &lt;/span&gt;후&lt;span&gt; &lt;/span&gt;클러스터에&lt;span&gt; &lt;/span&gt;적용하면&lt;span&gt;&amp;nbsp;된다&lt;/span&gt;&lt;span&gt;. 대표적인 인그레스 컨트롤러는 nginx, traefik, HAProxy와 같은 것들이 있는데, 일반적으로 인그레스 컨트롤러는 제공하는 yml의 private ip정도만 커스텀하고, 인그레스 리소스 파일을 수정해주면 된다.&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;pre id=&quot;code_1702539585160&quot; class=&quot;yaml&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: my-ingress
  namespace: my-namespace
  annotations:
    nginx.ingress.kubernetes.io/rewrite-target: /
spec:
  rules:
    - host: my-domain.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: app1-service
                port:
                  number: 80
  - host: my-domain2.com
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: app2-service
                port:
                  number: 80&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;my-namespace라는 namespace 내에서만 이 인그레스에 접근할 수 있으며, spec.rules 밑에 정의된 2개의 호스트에 대해서 라우팅을 진행하는 간단한&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;인그레스 yml파일의 예시이다.&lt;span&gt;&amp;nbsp;이 인그레스는 my-domain.com으로 들어오는 요청은 app1-service로, my-domain2.com으로 들어오는 요청은 app2-service로 라우팅해준다.&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Volume&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;컨테이너와는 독립적이지만, Pod에 종속된 저장소를 말한다. 파드에 종속된 만큼 설정값에 따라서 파드의 생명주기에 따라서 저장소의 데이터가 손실되는 등의 특징이 있다. 이러한 종속성을 없애기 위해서는 PV와 PVC를 사용할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;PV, PVC&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2560&quot; data-origin-height=&quot;1490&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cvDgI3/btsBYcKeDDr/tXKZvUjtVyl1gIYMDvOOUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cvDgI3/btsBYcKeDDr/tXKZvUjtVyl1gIYMDvOOUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cvDgI3/btsBYcKeDDr/tXKZvUjtVyl1gIYMDvOOUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcvDgI3%2FbtsBYcKeDDr%2FtXKZvUjtVyl1gIYMDvOOUk%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;572&quot; height=&quot;333&quot; data-origin-width=&quot;2560&quot; data-origin-height=&quot;1490&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&gt;&amp;nbsp;&lt;/span&gt;파드&lt;span&gt; &lt;/span&gt;자체는&lt;span&gt; &lt;/span&gt;영속성을&lt;span&gt; &lt;/span&gt;보장하지&lt;span&gt; &lt;/span&gt;않기&lt;span&gt; &lt;/span&gt;때문에 파드도 파드 자신의&lt;span&gt; &lt;/span&gt;생명주기와&lt;span&gt; &lt;/span&gt;무관하게&lt;span&gt; &lt;/span&gt;저장되는&lt;span&gt; &lt;/span&gt;데이터&lt;span&gt; &lt;/span&gt;저장소가&lt;span&gt; &lt;/span&gt;필요한데&lt;span&gt;, &lt;/span&gt;이를&lt;span&gt; &lt;/span&gt;위한&lt;span&gt; &lt;/span&gt;것이&lt;span&gt; PV&lt;/span&gt;와&lt;span&gt; PVC이&lt;/span&gt;다&lt;span&gt;.&lt;b&gt;&lt;span&gt;&amp;nbsp;PV&lt;/span&gt;&lt;/b&gt;는&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;데이터&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;저장소&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;자체를&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;의미하고&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;클러스터에서&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;사용&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;가능한&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;스토리지 자체를 나타내며,&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;PVC&lt;/b&gt;&lt;/span&gt;는&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;사용자가&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;PV&lt;/span&gt;에&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;하는&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;요청에 대한 인터페이스를&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;말한다.&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&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;&amp;nbsp;이것들은&lt;span&gt; &lt;/span&gt;말&lt;span&gt; &lt;/span&gt;그대로&lt;span&gt; &lt;/span&gt;데이터의&lt;span&gt; &lt;/span&gt;영속성을&lt;span&gt; &lt;/span&gt;보장해줄&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있는&lt;span&gt; &lt;/span&gt;저장장치&lt;span&gt;, &lt;/span&gt;즉&lt;span&gt; &lt;/span&gt;볼륨을&lt;span&gt; &lt;/span&gt;의미한다&lt;span&gt;. &lt;/span&gt;단&lt;span&gt;, &lt;/span&gt;이때&lt;span&gt; &lt;/span&gt;쿠버네티스&lt;span&gt; &lt;/span&gt;볼륨을&lt;span&gt; &lt;/span&gt;파드에&lt;span&gt; &lt;/span&gt;직접&lt;span&gt; &lt;/span&gt;할당하는&lt;span&gt; &lt;/span&gt;것이&lt;span&gt; &lt;/span&gt;아니라&lt;span&gt;, &lt;/span&gt;중간에&lt;span&gt;&lt;b&gt; PVC&lt;/b&gt;(Claim)&lt;/span&gt;를&lt;span&gt; &lt;/span&gt;두고&lt;span&gt; PV&lt;/span&gt;와&lt;span&gt; &lt;/span&gt;파드&lt;span&gt; &lt;/span&gt;사이에&lt;span&gt; PVC&lt;/span&gt;를&lt;span&gt; &lt;/span&gt;바인딩해서&lt;span&gt; &lt;/span&gt;파드가&lt;span&gt; &lt;/span&gt;사용할&lt;span&gt; &lt;/span&gt;저장공간과&lt;span&gt; &lt;/span&gt;파드를&lt;span&gt; &lt;/span&gt;분리했다&lt;span&gt;. &lt;/span&gt;이러한&lt;span&gt; &lt;/span&gt;구조는&lt;span&gt; &lt;/span&gt;파드&lt;span&gt; &lt;/span&gt;각각의&lt;span&gt; &lt;/span&gt;상태와&lt;span&gt; &lt;/span&gt;상황에&lt;span&gt; &lt;/span&gt;맞게&lt;span&gt; &lt;/span&gt;다양한&lt;span&gt; &lt;/span&gt;스토리지를&lt;span&gt; &lt;/span&gt;활용할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있게&lt;span&gt;&amp;nbsp;한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;PV&lt;/span&gt;를&lt;span&gt; &lt;/span&gt;만드는&lt;span&gt; &lt;/span&gt;단계를&lt;span&gt; &lt;/span&gt;프로비저닝이라고&lt;span&gt; &lt;/span&gt;하는데&lt;span&gt;, &lt;/span&gt;이미&lt;span&gt; &lt;/span&gt;정의된&lt;span&gt; PV&lt;/span&gt;를&lt;span&gt; &lt;/span&gt;사용하는&lt;span&gt; &lt;/span&gt;정적&lt;span&gt; &lt;/span&gt;프로비저닝과&lt;span&gt;, &lt;/span&gt;요청시마다&lt;span&gt; PV&lt;/span&gt;를&lt;span&gt; &lt;/span&gt;생성하는&lt;span&gt; &lt;/span&gt;동적&lt;span&gt; &lt;/span&gt;프로비저닝이&lt;span&gt; &lt;/span&gt;존재한다&lt;span&gt;. &lt;/span&gt;정적&lt;span&gt; &lt;/span&gt;프로비저닝은&lt;span&gt; &lt;/span&gt;사용&lt;span&gt; &lt;/span&gt;가능한&lt;span&gt; &lt;/span&gt;스토리지&lt;span&gt; &lt;/span&gt;용량에&lt;span&gt; &lt;/span&gt;제한이&lt;span&gt; &lt;/span&gt;있는&lt;span&gt; &lt;/span&gt;경우&lt;span&gt; &lt;/span&gt;유용하고&lt;span&gt;, &lt;/span&gt;동적&lt;span&gt; &lt;/span&gt;프로비저닝은&lt;span&gt; &lt;/span&gt;사용자가&lt;span&gt; &lt;/span&gt;원하는&lt;span&gt; &lt;/span&gt;용량만큼을&lt;span&gt; &lt;/span&gt;생성해서&lt;span&gt; &lt;/span&gt;사용하는&lt;span&gt; &lt;/span&gt;방법으로&lt;span&gt; PVC&lt;/span&gt;를&lt;span&gt; &lt;/span&gt;거쳐서&lt;span&gt; PV&lt;/span&gt;를&lt;span&gt; &lt;/span&gt;요청했을&lt;span&gt; &lt;/span&gt;때&lt;span&gt; &lt;/span&gt;생성된다&lt;span&gt;.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;k8s Architecture&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;1012&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bqGcIM/btsBZE0yVvH/5JcyvAcwLGtnnTGBp0sUE1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bqGcIM/btsBZE0yVvH/5JcyvAcwLGtnnTGBp0sUE1/img.png&quot; data-alt=&quot;https://www.opsramp.com/guides/why-kubernetes/kubernetes-architecture/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bqGcIM/btsBZE0yVvH/5JcyvAcwLGtnnTGBp0sUE1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbqGcIM%2FbtsBZE0yVvH%2F5JcyvAcwLGtnnTGBp0sUE1%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;688&quot; height=&quot;435&quot; data-origin-width=&quot;1600&quot; data-origin-height=&quot;1012&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.opsramp.com/guides/why-kubernetes/kubernetes-architecture/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;lt;Control-Plane Nodes&amp;gt;&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;가장&lt;span&gt; &lt;/span&gt;먼저&lt;span&gt; &lt;/span&gt;쿠버네티스&lt;span&gt; &lt;/span&gt;클러스터로&lt;span&gt; &lt;/span&gt;들어오는&lt;span&gt; &lt;/span&gt;모든&lt;span&gt; REST &lt;/span&gt;요청들은&lt;span&gt;&lt;b&gt; API &lt;/b&gt;&lt;/span&gt;&lt;b&gt;서버&lt;/b&gt;에서&lt;span&gt; &lt;/span&gt;받아서&lt;span&gt; &lt;/span&gt;유효성을&lt;span&gt; &lt;/span&gt;검증한&lt;span&gt; &lt;/span&gt;다음&lt;span&gt;, &lt;/span&gt;해당&lt;span&gt; &lt;/span&gt;요청을&lt;span&gt; &lt;/span&gt;처리할&lt;span&gt; &lt;/span&gt;적절한&lt;span&gt; &lt;/span&gt;컴포넌트에게&lt;span&gt; &lt;/span&gt;전달된다&lt;span&gt;. &lt;/span&gt;사용자의&lt;span&gt; &lt;/span&gt;요청을&lt;span&gt; &lt;/span&gt;받으면&lt;span&gt;&amp;nbsp;일반적으로 API &lt;/span&gt;서버는&lt;span&gt; Authentication&lt;/span&gt;을&lt;span&gt; &lt;/span&gt;통해서&lt;span&gt; &lt;/span&gt;요청에&lt;span&gt; &lt;/span&gt;대한&lt;span&gt; &lt;/span&gt;인증&lt;span&gt; &lt;/span&gt;정보를&lt;span&gt; &lt;/span&gt;파악하고&lt;span&gt;, RBAC Authorization&lt;/span&gt;을&lt;span&gt; &lt;/span&gt;통해서&lt;span&gt; &lt;/span&gt;접근&lt;span&gt; &lt;/span&gt;허용&lt;span&gt; &lt;/span&gt;여부를&lt;span&gt; &lt;/span&gt;결정하고&lt;span&gt;, Admission Control&lt;/span&gt;을&lt;span&gt; &lt;/span&gt;통해서&lt;span&gt; &lt;/span&gt;인증과&lt;span&gt; &lt;/span&gt;인가에&lt;span&gt; &lt;/span&gt;대한&lt;span&gt; &lt;/span&gt;필요한&lt;span&gt; &lt;/span&gt;변경을&lt;span&gt; &lt;/span&gt;진행한다&lt;span&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;두번째로는&lt;span&gt; &lt;/span&gt;&lt;b&gt;컨트롤러&lt;/b&gt;&lt;span&gt;&lt;b&gt; &lt;/b&gt;&lt;/span&gt;&lt;b&gt;매니저가&lt;/b&gt;&lt;span&gt; &lt;/span&gt;있는데&lt;span&gt;, &lt;/span&gt;클러스터를&lt;span&gt; &lt;/span&gt;구성하는&lt;span&gt; 4&lt;/span&gt;가지&lt;span&gt; &lt;/span&gt;리소스&lt;span&gt; &lt;/span&gt;객체를&lt;span&gt; &lt;/span&gt;통제하는&lt;span&gt; &lt;/span&gt;컴포넌트이다&lt;span&gt;. &lt;/span&gt;노드가&lt;span&gt; &lt;/span&gt;다운되었을때&lt;span&gt; &lt;/span&gt;조치하거나&lt;span&gt;, &lt;/span&gt;네임스페이스의&lt;span&gt; &lt;/span&gt;계정과&lt;span&gt; &lt;/span&gt;인증 토큰을&lt;span&gt; &lt;/span&gt;생성하고&lt;span&gt;, &lt;/span&gt;서비스&lt;span&gt; &lt;/span&gt;생성시&lt;span&gt; &lt;/span&gt;적절한&lt;span&gt; &lt;/span&gt;리소스를&lt;span&gt; &lt;/span&gt;생성하고&lt;span&gt;, &lt;/span&gt;파드의&lt;span&gt; &lt;/span&gt;개수를&lt;span&gt; &lt;/span&gt;통제하는&lt;span&gt; &lt;/span&gt;등 &lt;b&gt;노드를 전체적으로 제어&lt;/b&gt;하는&lt;span&gt;&amp;nbsp;&lt;/span&gt;역할을&lt;span&gt;&amp;nbsp;한다&lt;/span&gt;&lt;span&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;다음으로는&lt;span&gt; &lt;/span&gt;파드를&lt;span&gt; &lt;/span&gt;어떤&lt;span&gt; &lt;/span&gt;노드에&lt;span&gt; &lt;/span&gt;생성할지를&lt;span&gt; &lt;/span&gt;결정하는&lt;span&gt; &lt;/span&gt;&lt;b&gt;스케줄러가&lt;/b&gt;&lt;span&gt; &lt;/span&gt;있다&lt;span&gt;. &lt;/span&gt;노드의&lt;span&gt; &lt;/span&gt;리소스&lt;span&gt; &lt;/span&gt;현황이나&lt;span&gt; yml &lt;/span&gt;설정과&lt;span&gt; &lt;/span&gt;같은&lt;span&gt; &lt;/span&gt;요소들을&lt;span&gt; &lt;/span&gt;고려하여&lt;span&gt;&amp;nbsp;어떤 노드에 생성할지를&amp;nbsp;&lt;/span&gt;결정하게&lt;span&gt;&amp;nbsp;된&lt;/span&gt;다&lt;span&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;마지막으로&lt;span&gt; &lt;/span&gt;쿠버네티스&lt;span&gt; &lt;/span&gt;오브젝트의&lt;span&gt; &lt;/span&gt;상태&lt;span&gt; &lt;/span&gt;정보를&lt;span&gt; &lt;/span&gt;저장하는&lt;span&gt; &lt;b&gt;etcd&lt;/b&gt;&lt;/span&gt;가&lt;span&gt; &lt;/span&gt;있다&lt;span&gt;. &lt;/span&gt;이&lt;span&gt; etcd&lt;/span&gt;에&lt;span&gt; &lt;/span&gt;쿠버네티스&lt;span&gt; &lt;/span&gt;오브젝트의&lt;span&gt; &lt;/span&gt;모든&lt;span&gt; &lt;/span&gt;정보가&lt;span&gt; &lt;/span&gt;저장되어&lt;span&gt; &lt;/span&gt;있기&lt;span&gt; &lt;/span&gt;때문에&lt;span&gt; &lt;/span&gt;쿠버네티스&lt;span&gt; &lt;/span&gt;클러스터&lt;span&gt; &lt;/span&gt;백업시&lt;span&gt; &lt;/span&gt;이&lt;span&gt; etcd&lt;/span&gt;만&lt;span&gt; &lt;/span&gt;백업해주면&lt;span&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;&lt;b&gt;&lt;span&gt;&amp;lt;Worker Nodes&amp;gt;&lt;/span&gt;&lt;/b&gt;&lt;span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;워커 노드는 CPU와 메모리를 가지는 일종의 컴퓨터라고 할 수 있다.&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;파드는 쿠버네티스에 의해서 사용 가능한 워커 노드로 배포되어, 그&lt;/span&gt;&amp;nbsp;안에서 파드가 실행되며, 외부 통신을 통해서 파드를 관리할 수 있다.&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&gt;kubelet&lt;/span&gt;&lt;/b&gt;은&lt;span&gt; &lt;/span&gt;워커노드에&lt;span&gt; &lt;/span&gt;실행된&lt;span&gt; &lt;/span&gt;컨테이너가&lt;span&gt; &lt;/span&gt;잘&lt;span&gt; &lt;/span&gt;동작하도록&lt;span&gt; &lt;/span&gt;관리하는&lt;span&gt; &lt;/span&gt;컴포넌트로&lt;span&gt;, &lt;/span&gt;자신의&lt;span&gt; &lt;/span&gt;노드에&lt;span&gt; &lt;/span&gt;실행된&lt;span&gt; &lt;/span&gt;컨테이너가&lt;span&gt; &lt;/span&gt;정상적으로&lt;span&gt; &lt;/span&gt;동작하도록&lt;span&gt; &lt;/span&gt;모니터링하고&lt;span&gt; &lt;/span&gt;관리하는&lt;span&gt; &lt;/span&gt;역할을&lt;span&gt;&amp;nbsp;한&lt;/span&gt;다&lt;span&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;다음으로는&lt;b&gt;&lt;span&gt; kube-proxy&lt;/span&gt;&lt;/b&gt;가&lt;span&gt; &lt;/span&gt;있다&lt;span&gt;. &lt;/span&gt;이것은&lt;span&gt; &lt;/span&gt;네트워크&lt;span&gt; &lt;/span&gt;프록시&lt;span&gt; &lt;/span&gt;컴포넌트라고&lt;span&gt; &lt;/span&gt;부르는데&lt;span&gt;, &lt;/span&gt;클라이언트와&lt;span&gt; &lt;/span&gt;서버간의&lt;span&gt; &lt;/span&gt;통신을&lt;span&gt; &lt;/span&gt;중개하거나&lt;span&gt; &lt;/span&gt;제어하여&lt;span&gt; &lt;/span&gt;여러가지&lt;span&gt; &lt;/span&gt;기능을&lt;span&gt; &lt;/span&gt;하는데&lt;span&gt; &lt;/span&gt;사용된다&lt;span&gt;. &lt;/span&gt;쉽게&lt;span&gt; &lt;/span&gt;말하면&lt;span&gt; &lt;/span&gt;외부의&lt;span&gt; &lt;/span&gt;서비스&lt;span&gt; &lt;/span&gt;오브젝트와&lt;span&gt; &lt;/span&gt;파드를&lt;span&gt; &lt;/span&gt;연결하는&lt;span&gt; &lt;/span&gt;역할을&lt;span&gt; &lt;/span&gt;하고&lt;span&gt;, &lt;/span&gt;파드&lt;span&gt; &lt;/span&gt;간의&lt;span&gt; &lt;/span&gt;네트워크&lt;span&gt; &lt;/span&gt;트래픽을&lt;span&gt; &lt;/span&gt;관리한다&lt;span&gt;.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;마지막으로&lt;span&gt; &lt;/span&gt;&lt;b&gt;컨테이너&lt;span&gt; &lt;/span&gt;런타임(Docker)&lt;/b&gt;이다&lt;span&gt;. &lt;/span&gt;처음에&lt;span&gt; &lt;/span&gt;쿠버네티스에&lt;span&gt; 역사에 대해 설명할 때,&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;모든&lt;span&gt; &lt;/span&gt;애플리케이션이&lt;span&gt; &lt;/span&gt;하나의&lt;span&gt; OS&lt;/span&gt;를&lt;span&gt; &lt;/span&gt;공유하지만&lt;span&gt; &lt;/span&gt;각각의&lt;span&gt; &lt;/span&gt;컨테이너&lt;span&gt; &lt;/span&gt;사이에는&lt;span&gt; &lt;/span&gt;간섭을&lt;span&gt; &lt;/span&gt;일으키지&lt;span&gt; &lt;/span&gt;않도록&lt;span&gt; &lt;/span&gt;독립적인&lt;span&gt; &lt;/span&gt;자원을&lt;span&gt; &lt;/span&gt;할당하고&lt;span&gt; &lt;/span&gt;관리하기&lt;span&gt; &lt;/span&gt;위해&lt;span&gt; &lt;/span&gt;사용하는&lt;span&gt; &lt;/span&gt;것이&lt;span&gt; &lt;/span&gt;컨테이너&lt;span&gt; &lt;/span&gt;런타임이라고 말했었는데, 쿠버네티스는&lt;span&gt; 각 워커노드마다 존재하는 컨테이너 런타임을 사용하여 kubelet과 상호작용하여 컨테이너를 관리한다.&lt;/span&gt;&lt;span&gt;&lt;br /&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;특징&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Owner-Dependents&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;k8s의 모든 오브젝트는 &lt;b&gt;Owner&lt;/b&gt; 아니면 &lt;b&gt;Dependent&lt;/b&gt; 관계로 연결되어 있다(예를 들면, Replicaset은 파드 집합들의 owner라고 할 수 있다). 따라서 k8은 이를 이용하여 여러 가지 기능을 지원한다. 오브젝트를 삭제할 때, 종속 오브젝트들을 자동으로 삭제하도록 cascading deletion을 지원하기도 하고, kubelet이 사용되지 않는 이미지나 컨테이너에 대한 GC를 진행하기도 한다(이미지는 5min, 컨테이너는 1min).&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;쿠버네티스 실습해보기&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;k8s configuration file 세팅하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;쿠버네티스에서&lt;/span&gt; &lt;span&gt;지원하는&lt;/span&gt; &lt;span&gt;여러가지&lt;/span&gt; &lt;span&gt;기능들을&lt;/span&gt; &lt;span&gt;컨트롤하기&lt;/span&gt; &lt;span&gt;위해서는&lt;/span&gt; yml &lt;span&gt;또는&lt;/span&gt; json&lt;span&gt;으로&lt;/span&gt; &lt;span&gt;구성된&lt;/span&gt; configuration file&lt;span&gt;을&lt;/span&gt; &lt;span&gt;정의해야&lt;/span&gt;&amp;nbsp;한&lt;span&gt;다&lt;/span&gt;. &lt;span&gt;홈&lt;/span&gt; &lt;span&gt;디렉토리&lt;/span&gt; &lt;span&gt;아래&lt;/span&gt; .kube &lt;span&gt;디렉토리에&lt;/span&gt; configuration &lt;span&gt;파일을&lt;/span&gt; &lt;span&gt;만들어주면&lt;/span&gt;&amp;nbsp;된&lt;span&gt;다&lt;/span&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1702537189441&quot; class=&quot;yaml&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: v1
clusters:
- cluster:
    certificate-authority-data: ~~
    server: https://aks-d-aop-dns-pue1y1ro.hcp.koreacentral.azmk8s.io:443
  name: AKS-D-AOP
contexts:
- context:
    cluster: AKS-D-AOP
    user: clusterUser_RG-AOP-DEV_AKS-D-AOP
  name: AKS-D-AOP
current-context: AKS-D-AOP
kind: Config
preferences: {}
users:
- name: clusterUser_RG-AOP-DEV_AKS-D-AOP
  user:
    client-certificate-data: ~~
    client-key-data ~~
    token: ~~&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;크게 cluster, user, context의 세 파트로 나누어서 보면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 AKS-D-AOP&lt;span&gt;라는&lt;/span&gt; &lt;span&gt;이름을&lt;/span&gt; &lt;span&gt;갖는&lt;/span&gt; &lt;b&gt;&lt;span&gt;클러스터(cluster)&lt;/span&gt;&lt;/b&gt;&amp;nbsp;&lt;span&gt;하나를&lt;/span&gt; &lt;span&gt;정의할 때&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;certificate-authority-data&lt;span&gt;는&lt;/span&gt; &lt;span&gt;클러스터에&lt;/span&gt; &lt;span&gt;대한&lt;/span&gt; CA &lt;span&gt;인증서를&lt;/span&gt; &lt;span&gt;나타내는데&lt;/span&gt;, &lt;span&gt;클러스터와&lt;/span&gt; &lt;span&gt;사용자&lt;/span&gt; &lt;span&gt;간의&lt;/span&gt; &lt;span&gt;안전한&lt;/span&gt; &lt;span&gt;통신을&lt;/span&gt; &lt;span&gt;위한&lt;/span&gt; &lt;span&gt;인증서와&lt;/span&gt; &lt;span&gt;관련된&lt;/span&gt; &lt;span&gt;정보를&lt;/span&gt; &lt;span&gt;포함하고&lt;/span&gt; &lt;span&gt;있다&lt;/span&gt;. server&lt;span&gt;는&lt;/span&gt; &lt;span&gt;접근하려는&lt;/span&gt; &lt;span&gt;애저&lt;/span&gt; &lt;span&gt;쿠버네티스&lt;/span&gt; &lt;span&gt;서버의&lt;/span&gt; url&lt;span&gt;을&lt;/span&gt; &lt;span&gt;나타낸다&lt;/span&gt;.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&amp;nbsp;밑에&lt;/span&gt; &lt;span&gt;있는&lt;/span&gt; &lt;b&gt;users&lt;/b&gt; &lt;span&gt;필드를&lt;/span&gt; &lt;span&gt;통해서는&lt;/span&gt;&amp;nbsp;clusterUser_RG-AOP-DEV_AKS-D-AOP&lt;span&gt;라는&lt;/span&gt; &lt;span&gt;유저를&lt;/span&gt; &lt;span&gt;정의하고&lt;/span&gt;, &lt;span&gt;사용자의&lt;/span&gt; &lt;span&gt;클라이언트&lt;/span&gt; &lt;span&gt;인증서와&lt;/span&gt; &lt;span&gt;키를&lt;/span&gt; Base64&lt;span&gt;로&lt;/span&gt; &lt;span&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;b&gt;contexts&lt;/b&gt; &lt;span&gt;필드는&lt;/span&gt; &lt;span&gt;클러스터와&lt;/span&gt; &lt;span&gt;사용자를&lt;/span&gt; &lt;span&gt;결합하여&lt;/span&gt; &lt;span&gt;특정&lt;/span&gt; &lt;span&gt;작업을&lt;/span&gt; &lt;span&gt;수행하는데&lt;/span&gt; &lt;span&gt;사용되는&lt;/span&gt; &lt;span&gt;환경을&lt;/span&gt; &lt;span&gt;정의하는데,&lt;/span&gt;&amp;nbsp;&lt;span&gt;먼저&lt;/span&gt; &lt;span&gt;정의한&lt;/span&gt; AKS-D-AOP&lt;span&gt;라는&lt;/span&gt; &lt;span&gt;클러스터와&lt;/span&gt;, &lt;span&gt;밑에서&lt;/span&gt; &lt;span&gt;정의한&lt;/span&gt; clusterUser&lt;span&gt;를&lt;/span&gt; &lt;span&gt;결합하여&lt;/span&gt; AKS-D-AOP&lt;span&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;실습 1. pod 사용해서 컨테이너 실행시키기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;간단하게 spring 프로젝트와 mysql, redis, mongodb 컨트롤러를 만들어주고, 쿠버네티스를 사용하여 연결해보자. docker-compose 사용시와 동일하게 쿠버네티스 사용시에도 configuration 설정을 위해 yaml파일을 작성해주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1691313350188&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata: # Pod명과 라벨(오브젝트 분류를 위함) 지정
  name: web
  labels:
    app: spring-kubernetes
spec: # Pod에서 실행될 컨테이너의 스펙(이미지, CPU, 메모리 등)을 지정
  containers:
    - name: mysql-db
      image: mysql
      ports:
        - containerPort: 3306
      env:
        - name: MYSQL_DATABASE
          value: &quot;mysql-database-name&quot;
        - name: MYSQL_ROOT_HOST
          value: '%'
        - name: MYSQL_ROOT_PASSWORD
          value: &quot;mysql-database-root-password&quot;

    - name: redis-db
      image: redis
      ports:
        - containerPort: 6379

    - name: mongo-db
      image: mongo
      ports:
        - containerPort: 27017
      env:
        - name: MONGO_INITDB_ROOT_USERNAME
          value: &quot;mongo-root-username&quot;
        - name: MONGO_INITDB_ROOT_PASSWORD
          value: &quot;mongo-root-password&quot;
        - name: MONGO_INITDB_DATABASE
          value: &quot;mongo-root-database&quot;

    - name: spring-docker
      image: spring-docker-spring-boot
      ports:
        - containerPort: 8080
      env:
        - name: MYSQL_URL
          value: &quot;jdbc:mysql://host.docker.internal:3306/mysql-database-name?serverTimezone=Asia/Seoul&quot;
        - name: MYSQL_USERNAME
          value: &quot;mysql-database-username&quot;
        - name: MYSQL_PASSWORD
          value: &quot;mysql-database-password&quot;
        - name: MONGO_HOST
          value: &quot;host.docker.internal&quot;
        - name: MONGO_PORT
          value: &quot;27017&quot;
        - name: MONGO_URI
          value: &quot;mongodb:/mongo-username:mongo-password@host.docker.internal:27017/docker_test?authSource=admin&quot;
        - name: REDIS_HOST
          value: &quot;host.docker.internal&quot;
        - name: REDIS_PORT
          value: &quot;6379&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1691313520968&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ kubectl apply -f &amp;lt;yml 파일경로&amp;gt;
pod/web created

$ kubectl get pods
$ kubectl get all

$ kubectl describe pod &amp;lt;pod-name&amp;gt;

$ kubectl delete pod &amp;lt;pod-name&amp;gt;&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;그런데 spring 컨테이너는 실행되지 않고&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;Failed to pull image &quot;docker.io/library/spring-docker-spring-boot&quot;: rpc error: code = Unknown desc = Error response from daemon: pull access denied for spring-docker-spring-boot, repository does not exist or may require 'docker login': denied: requested access to the resource is denied&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 오류가 발생하였다.&lt;/p&gt;
&lt;pre id=&quot;code_1691345871803&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;kubectl create secret docker-registry docker-hub-secret --docker-server=https://index.docker.io/v1/ --docker-username=YOUR_DOCKERHUB_USERNAME --docker-password=YOUR_DOCKERHUB_PASSWORD --docker-email=YOUR_DOCKERHUB_EMAIL&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1691345890879&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Pod
metadata:
  name: web
  labels:
    app: spring-kubernetes
spec:
  containers:
    - name: spring-docker
      image: docker.io/library/spring-docker-spring-boot
      ports:
        - containerPort: 8080
      env:
        # 환경 변수 설정
      imagePullSecrets:  # Docker Hub Secret 지정
        - name: docker-hub-secret&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;- docker pull&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. secret 추가하고 imagePullSecrets를 추가해주기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. docker login해보기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; docker login panic assignment to entry in nil map.. 오류가 떠서 로그아웃후 로그인 해주었더니 해결&lt;/p&gt;
&lt;pre id=&quot;code_1691381883725&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ docker logout
$ docker login&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 그래도 해결되지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. docker hub에 푸시하고 yml파일에 이미지 경로를 docker.io/&amp;lt;사용자명&amp;gt;/&amp;lt;image명&amp;gt;과 같이 지정해주어 허브에서 갖고오게 한다. push하기 전에 이미지명을 바꾸어주어야 한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;673&quot; data-origin-height=&quot;87&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dhEkcy/btsp7Bvzycs/T1XTnXDKU88Km71gpvfX71/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dhEkcy/btsp7Bvzycs/T1XTnXDKU88Km71gpvfX71/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dhEkcy/btsp7Bvzycs/T1XTnXDKU88Km71gpvfX71/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdhEkcy%2Fbtsp7Bvzycs%2FT1XTnXDKU88Km71gpvfX71%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;673&quot; height=&quot;87&quot; data-origin-width=&quot;673&quot; data-origin-height=&quot;87&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;pre id=&quot;code_1691382049776&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ docker image tag &amp;lt;image명&amp;gt; &amp;lt;docker유저명&amp;gt;/&amp;lt;image명&amp;gt;
$ docker push &amp;lt;docker유저명&amp;gt;/&amp;lt;image명&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1425&quot; data-origin-height=&quot;397&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RgLdC/btsp91z4UPi/pEwEwiB2QokkhSAODndMr1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RgLdC/btsp91z4UPi/pEwEwiB2QokkhSAODndMr1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RgLdC/btsp91z4UPi/pEwEwiB2QokkhSAODndMr1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRgLdC%2Fbtsp91z4UPi%2FpEwEwiB2QokkhSAODndMr1%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;1425&quot; height=&quot;397&quot; data-origin-width=&quot;1425&quot; data-origin-height=&quot;397&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1666&quot; data-origin-height=&quot;121&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMRYS1/btsp7Ke1FL3/iyMgYXiveSNZpTJshLLk4k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMRYS1/btsp7Ke1FL3/iyMgYXiveSNZpTJshLLk4k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMRYS1/btsp7Ke1FL3/iyMgYXiveSNZpTJshLLk4k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMRYS1%2Fbtsp7Ke1FL3%2FiyMgYXiveSNZpTJshLLk4k%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;1666&quot; height=&quot;121&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1666&quot; data-origin-height=&quot;121&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;실습 2. service 사용해서 만들어진 pod에 접근하기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;하지만 이렇게 생성된 Pod는 외부에서 직접 접근할 수 없으며, 외부에서 접근하기 위해서는 서비스를 사용해야 한다. 쿠버네티스에서 각 Pod들은 고유한 IP주소를 가지는데, 이 IP는 클러스터 내부에서만 유효하다. 보안적인 문제도 있고, Pod는 수시로 생성/삭제되기기에 동적으로 IP가 할당되기에 외부에서 직접 접근하기 쉽지 않다. 따라서 Service 리소스를 생성하여 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;생성한 Pod와 연결한 뒤, 스프링부트 컨테이너에 접근해보도록 하겠다.&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: #333333; text-align: start;&quot;&gt;&amp;nbsp;먼저 Service 리소스를 생성하여 Pod와 연결해보도록 하자. 이렇게 하면 서비스는 Pod의 ip주소와 port를 고정시켜 외부에서 그것을 사용하여 접근할 수 있게 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;yaml&quot; style=&quot;background-color: #2b2b2b; color: #a9b7c6;&quot;&gt;&lt;code&gt;apiVersion: v1
kind: Service
metadata:
  name: web-service
  labels:
    app: spring-kubernetes-service
spec:
  selector:
    app: spring-kubernetes # Pod의 라벨과 일치
  type: NodePort
  ports:
    - port: 80 # 서비스 사용 포트
      targetPort: 8080 # 내부 pod로 연결할 포트
      nodePort: 30001 # 외부에 노출할 포트
      protocol: TCP
      name: http
&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;위와 같이 service.yml을 만들고, Pod를 만들때와 동일하게 apply 명령어를 사용하여 실행시켜준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1691385744201&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ kubectl apply -f &amp;lt;service명&amp;gt;

$ kubectl get services &amp;lt;service명&amp;gt;

$ kubectl get all&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;138&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bc9G8Q/btsqaPmtY3q/eEx6Ck9U5I23F9Y6Ykihbk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bc9G8Q/btsqaPmtY3q/eEx6Ck9U5I23F9Y6Ykihbk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bc9G8Q/btsqaPmtY3q/eEx6Ck9U5I23F9Y6Ykihbk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbc9G8Q%2FbtsqaPmtY3q%2FeEx6Ck9U5I23F9Y6Ykihbk%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;760&quot; height=&quot;138&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;760&quot; data-origin-height=&quot;138&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;kubectl get all 명령어를 통해서&amp;nbsp; 조회해보면 서비스가 생성된 것을 확인할 수 있고, 생성된 포트를 통해 접속하면 스프링부트에 접근 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;891&quot; data-origin-height=&quot;556&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPDwMv/btsqrDZTpFX/K4jWkGeWZfZ0k4w69sd6FK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPDwMv/btsqrDZTpFX/K4jWkGeWZfZ0k4w69sd6FK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPDwMv/btsqrDZTpFX/K4jWkGeWZfZ0k4w69sd6FK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPDwMv%2FbtsqrDZTpFX%2FK4jWkGeWZfZ0k4w69sd6FK%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;674&quot; height=&quot;421&quot; data-origin-width=&quot;891&quot; data-origin-height=&quot;556&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;실습 3. secret 사용해서 민감한 정보 숨기기&lt;/b&gt;&lt;/h3&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;601&quot; data-origin-height=&quot;391&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3Op0m/btsq5Z1nnG6/CcbjqqTs4Xr5kS3ihMlUvK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3Op0m/btsq5Z1nnG6/CcbjqqTs4Xr5kS3ihMlUvK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3Op0m/btsq5Z1nnG6/CcbjqqTs4Xr5kS3ihMlUvK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3Op0m%2Fbtsq5Z1nnG6%2FCcbjqqTs4Xr5kS3ihMlUvK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;481&quot; height=&quot;313&quot; data-origin-width=&quot;601&quot; data-origin-height=&quot;391&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;쿠버네티스에서는 환경변수를 사용하여 Pod에 secret 정보를 주입할 수 있다. 비슷한 기능으로 ConfigMap이 있지만 이는 민감한 정보를 다루는데 사용하는 것을 권장하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;secret을 사용하기 위해서는 아래와 같이 kubectl을 사용하여 직접 secret을 만들 수도 있지만&lt;/p&gt;
&lt;pre id=&quot;code_1691862025386&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ kubectl create secret ...&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;secret이 복잡해질 경우를 대비해 yml을 사용하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 docker-hub-secret이라는 secret을 생성했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1691862100818&quot; class=&quot;yaml&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# secret.yml
apiVersion: v1
kind: Secret
metadata:
  name: docker-hub-secret
type: Opaque
data:
  MONGO_INITDB_ROOT_USERNAME: ENCODED_MONGO_INITDB_ROOT_USERNAME
  MONGO_INITDB_ROOT_PASSWORD: ENCODED_MONGO_INITDB_ROOT_PASSWORD
  MONGO_INITDB_DATABASE: ENCODED_MONGO_INITDB_DATABASE
  ...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1691862226733&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ kubectl apply -f secret.yml
$ kubectl get secrets # check working&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그다음 secret을 등록해주고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1691862340711&quot; class=&quot;yaml&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# pod.yml
apiVersion: v1
kind: Pod
metadata:
  name: web
  labels:
    app: spring-kubernetes
spec:
  containers:
    - name: mongo-db
      image: mongo
      ports:
        - containerPort: 27017
      envFrom:
        - secretRef:
            name: docker-hub-secret

  imagePullSecrets: # Docker Hub Secret 지정
    - name: docker-hub-secret&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같이 Pod를 수정해주면 된다. 기존에는 pod.yml의 env: 아래에 줄줄이 평문으로 환경변수들을 노출시켰으나, 이제는 등록한 secret을 명시해주기만 하면 끝&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;실습 4. 볼륨 적용해보기&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;기타 궁금한 점&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Pod 생성시 registry.k8s.io/pause:3.9라는 의도하지 않는 정체불명의 컨테이너가 생성됨.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1626&quot; data-origin-height=&quot;65&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nGY3g/btsp90Igl4I/N99AkpU2rYLs4i299puxB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nGY3g/btsp90Igl4I/N99AkpU2rYLs4i299puxB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nGY3g/btsp90Igl4I/N99AkpU2rYLs4i299puxB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnGY3g%2Fbtsp90Igl4I%2FN99AkpU2rYLs4i299puxB0%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;1626&quot; height=&quot;65&quot; data-origin-width=&quot;1626&quot; data-origin-height=&quot;65&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;671&quot; data-origin-height=&quot;495&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dD0IfK/btsqm70McZy/K3hwrDPade359roSw0xw4K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dD0IfK/btsqm70McZy/K3hwrDPade359roSw0xw4K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dD0IfK/btsqm70McZy/K3hwrDPade359roSw0xw4K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdD0IfK%2Fbtsqm70McZy%2FK3hwrDPade359roSw0xw4K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;671&quot; height=&quot;495&quot; data-origin-width=&quot;671&quot; data-origin-height=&quot;495&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자동 생성되는 컨테이너라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. kubectl get all로 조회해보면 service/kubernetes라는 서비스가 기본으로 생성되어 있는 것을 볼 수 있는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;764&quot; data-origin-height=&quot;43&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dPgCy0/btsqodUmvXh/MiKzdA2BKmifmk3vlck3M0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dPgCy0/btsqodUmvXh/MiKzdA2BKmifmk3vlck3M0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dPgCy0/btsqodUmvXh/MiKzdA2BKmifmk3vlck3M0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdPgCy0%2FbtsqodUmvXh%2FMiKzdA2BKmifmk3vlck3M0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;764&quot; height=&quot;43&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;764&quot; data-origin-height=&quot;43&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;121&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cytZ0e/btsqh08Ju6b/NONVayZkKnKzfTsy6MBWEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cytZ0e/btsqh08Ju6b/NONVayZkKnKzfTsy6MBWEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cytZ0e/btsqh08Ju6b/NONVayZkKnKzfTsy6MBWEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcytZ0e%2Fbtsqh08Ju6b%2FNONVayZkKnKzfTsy6MBWEK%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;668&quot; height=&quot;121&quot; data-origin-width=&quot;668&quot; data-origin-height=&quot;121&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역시 1번과 같이 자동 생성되며, 위와 같은 역할을 하는 서비스이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. kubectl apply -f &amp;lt;pod명&amp;gt;으로 pod를 실행시키면, pod 안에 있는 스프링부트 컨테이너가 1번 죽었다가 다시 새로 실행해서 동작함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1638&quot; data-origin-height=&quot;112&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYYJqC/btsqm6AObW2/eFzq5ViN2Mkqs6NARgNomk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYYJqC/btsqm6AObW2/eFzq5ViN2Mkqs6NARgNomk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYYJqC/btsqm6AObW2/eFzq5ViN2Mkqs6NARgNomk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYYJqC%2Fbtsqm6AObW2%2FeFzq5ViN2Mkqs6NARgNomk%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;1638&quot; height=&quot;112&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1638&quot; data-origin-height=&quot;112&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;이것도 docker-compose 사용했을때처럼 실행시점 문제 같아서 initcontainer 관련 설정 찾아보자&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;4. NodePort를 사용했을때 ip주소가 localhost임. (LoadBalancer Ingress 부분)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1691414068663&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ kubectl describe service &amp;lt;service명&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;805&quot; data-origin-height=&quot;443&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4A8oe/btsqs701F3K/p55Umki3ALXAjVmRMtlX8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4A8oe/btsqs701F3K/p55Umki3ALXAjVmRMtlX8k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4A8oe/btsqs701F3K/p55Umki3ALXAjVmRMtlX8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4A8oe%2Fbtsqs701F3K%2Fp55Umki3ALXAjVmRMtlX8k%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;805&quot; height=&quot;443&quot; data-origin-width=&quot;805&quot; data-origin-height=&quot;443&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;찾아보니 로컬 개발환경에서는 localhost로 지정할 수 있고, 아니면 서비스 configuration yml에 externalIp를 설정해주면 변경 가능한 것 같다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>[ Backend ]/Infra</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/162</guid>
      <comments>https://eckrin.tistory.com/162#entry162comment</comments>
      <pubDate>Sun, 6 Aug 2023 02:41:28 +0900</pubDate>
    </item>
    <item>
      <title>코테 문제유형 정리 + 팁</title>
      <link>https://eckrin.tistory.com/159</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1080&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfqkje/btsD0GKdyr9/K8CE64SbQABGA33pTh6Aw0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfqkje/btsD0GKdyr9/K8CE64SbQABGA33pTh6Aw0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfqkje/btsD0GKdyr9/K8CE64SbQABGA33pTh6Aw0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbfqkje%2FbtsD0GKdyr9%2FK8CE64SbQABGA33pTh6Aw0%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;257&quot; height=&quot;257&quot; data-origin-width=&quot;1080&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;전역&lt;/b&gt;&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;문제가 안풀린다면?&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. 유형조차 모르겠다 (뭘 써야할지 모르겠다)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. 방법을 아는데 틀린다 (어딘가 실수했을 부분이 있을거다)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;2-1. 범위를 초과했다 (int범위를 넘어 long으로 처리 등)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;2-2. 문제의 조건을 까먹었다 (문제에서 제시한 예외, 숨은 예외)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3. 어디가 틀렸는지 모르겠다 (실수할만한 부분을 다 체크했는데 왜 틀렸는지를 모르겠다&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;3-1. 유형을 아예 잘못생각했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;공통 팁&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. 자료구조를 활용할 때, 값과 인덱스를 둘 다 활용할 수 있다면 인덱스를 사용하는 것이 활용범위가 더 넓다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;2. 범위 체크 잘하기 (int범위를 넘는 경우 조심)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;3. 최적화 문제의 경우 brute force가 아니라면 대부분 그리디나 DP로 풀린다. 먼저 그리디로 접근해보고 안된다면 DP로 접근하기&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;4. map이나 set같은 자료구조를 쓰는 것보다 index에 대한 value를 활용하여 배열로 접근하는 것이 훨씬 간단하다 (&lt;a href=&quot;http://boj.kr/16472&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BOJ16472&lt;/a&gt;)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;배열&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;1. 2차원 배열 인덱스를 탐색할 때, %연산과 /연산을 표현해서 연속된 인덱스로 접근 가능하다.&lt;/p&gt;
&lt;pre id=&quot;code_1727969218128&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int[][] arr = new int[N][M];
for(int i=0; i&amp;lt;N*M; i++) {
    System.out.println(arr[i/M][i%M]);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;코테에 많이 나오는 유형&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 구현, 그리디&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 그래프 탐색(BFS, DFS)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 이분 탐색, 파라미터 탐색&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- DP (LIS, LCS, Knapsack)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 자료구조(스택, 큐, 우선순위 큐, 덱)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 백트래킹&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 트리, 그래프(MST, 이진트리, 위상정렬, union-find, floyd, dijkstra)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;투 포인터&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 투 포인터 템플릿&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1728232217259&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int l = 0, r = n-1;
while(l&amp;lt;r) { 이진탐색과 상이하게 종료조건에 등호가 들어가지 않는다.
    if(len[l]+len[r]==x) {
        // 찾음
        break;
    } else if(len[l]+len[r]&amp;gt;x) {
        r--;
    } else {
        l++;
    }
}&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;a href=&quot;https://www.acmicpc.net/problem/2531&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BOJ 2531(회전 초밥)&lt;/a&gt; &amp;gt;&amp;gt; &lt;b&gt;투 포인터&lt;/b&gt; 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 투포인터 사용시 모든 증가로직과 감소로직은 묶어서 실행해야 함 (&lt;a href=&quot;https://www.acmicpc.net/source/64968030&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.acmicpc.net/source/64968030&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;http://boj.kr/2470&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BOJ 2470(두 용액)&lt;/a&gt;, &lt;a href=&quot;http://boj.kr/2467&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BOJ 2467(용액)&lt;/a&gt; &amp;gt;&amp;gt; 투 포인터 사용&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: (인덱스 비교시) 투포인터 사용시 whlie문 등호 있으면 안됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a style=&quot;color: #0070d1; text-align: start;&quot; href=&quot;https://www.acmicpc.net/problem/3649&quot;&gt;BOJ3649(로봇 프로젝트))&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 투포인터로 등호조건을 찾을때는 &lt;b&gt;l과 r의 증감순서가 중요하지 않다&lt;/b&gt;는 것을 기억하자. (답을 지나칠 일은 없다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Stack&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://www.acmicpc.net/problem/9935&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BOJ 9935(문자열 폭발)&lt;/a&gt;, &lt;a href=&quot;https://www.acmicpc.net/problem/16120&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;16120(PPAP)&lt;/a&gt; &amp;gt;&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;문자열 + Stack&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;: 문자열을 char배열로 변환(str.toCharArray())하고 부분문자열이 일치하는지 검사(stack.get()이용)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://www.acmicpc.net/problem/2493&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BOJ 2493(탑)&lt;/a&gt;, &lt;a href=&quot;https://www.acmicpc.net/problem/17298&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;17298(오큰수)&lt;/a&gt;, &lt;a href=&quot;https://www.acmicpc.net/problem/17299&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;17299(오등큰수)&lt;/a&gt; &amp;gt;&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;&quot;오른쪽에 있는 것 중에서 가장 왼쪽에 있는 것&quot;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;: 스택에 넣었다가 조건에 만족하는 것이 나오면 연속으로 빼주기. 끝까지 남은것은 조건을 만족하는 것이 없음&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;Tip) 스택에 넣을때 인덱스를 넣는게 활용범위가 커지므로 좋음.

&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;스택에 인덱스를 넣으면 인덱스를 활용해서 바로 arr값까지 구할 수 있다. 하지만 arr값을 직접 넣으면 인덱스를 알 수 없다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/source/64008052&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.acmicpc.net/source/64008052&lt;/a&gt;를 &lt;a href=&quot;https://www.acmicpc.net/source/64008563&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.acmicpc.net/source/64008563&lt;/a&gt;로 수정한 것을 보면 장점을 확실히 알 수 있음. 왼쪽코드는 테스트케이스가 worst case로 나오면 시간초과 가능성이 충분해보임&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;a href=&quot;https://eckrin.tistory.com/9&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;Binary Search&lt;/b&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 이진탐색 템플릿&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1690273317112&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int 정답;
while(left&amp;lt;=right) {
  mid = (left+right)/2;
  int 정답후보 = function(mid);
  if(정답후보&amp;gt;=최소정답) {
    left = mid+1;
    정답 = Math.min(정답, mid);
  } else {
    right = mid-1;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://www.acmicpc.net/problem/1654&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BOJ 1654(랜선 자르기)&lt;/a&gt;, &lt;a href=&quot;https://www.acmicpc.net/problem/2805&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2805(나무 자르기)&lt;/a&gt; &amp;gt;&amp;gt; 대표적인 이분탐색 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 답을 범위를 정해놓고 이분탐색해서 찾아내는 문제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://www.acmicpc.net/problem/1365&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BOJ 1365(꼬인 전깃줄)&lt;/a&gt; &amp;gt;&amp;gt; &lt;a href=&quot;https://eckrin.tistory.com/30&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;이분탐색을 사용한 LIS&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;: Well-known 문제. &lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;간단히 순서대로 선을 이어줄 때,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;직전에 들어왔던 입력값보다 작은 값이 입력되면 꼬이게&lt;span style=&quot;color: #555555; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;된다&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;http://boj.kr/2343&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BOJ 2343(기타 레슨)&lt;/a&gt; &amp;gt;&amp;gt; 매개변수 탐색&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;: 템플릿 (while(l&amp;lt;=r), l = mid+1, r = mid-1) 확인하기, 조건 확인하기 (&lt;a href=&quot;https://www.acmicpc.net/source/65075124&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.acmicpc.net/source/65075124&lt;/a&gt;)&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;a href=&quot;https://eckrin.tistory.com/10&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;DP&lt;/b&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://www.acmicpc.net/problem/2293&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BOJ 2293(동전 1)&lt;/a&gt; &amp;gt;&amp;gt; &lt;b&gt;DP 기준의 변화 (for문 안팎 전환)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: &quot;동전의 순서가 달라도 같은 것으로 취급&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 금액 기준으로 사용가능한 동전의 경우의 수를 구하는것이 아니라, 사용가능한 동전을 기준으로 금액을 구함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;a href=&quot;https://eckrin.tistory.com/59&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;DFS&lt;/b&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://softeer.ai/practice/6248&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Softeer 출퇴근길&lt;/a&gt; &amp;gt;&amp;gt; 중복 방문이 가능할 경우 dfs 역추적은 불가능한 것으로 보임&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 방향성 그래프에서 중복방문이 가능할경우 한번 방문했다고 재방문을 막을경우 탐색하지 못하는 경로가 생길 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 방향성 그래프에서 역방향 그래프를 만들면 해당 노드로 올 수 있는 점들을 한번의 탐색으로 구할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;a href=&quot;https://eckrin.tistory.com/59&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;BFS&lt;/b&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;오답시 체크할 위치 : 인덱스 위치 잘못기재([i][j],[j][i]), 큐 or visited 초기화 로직, 큐 삽입조건문&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/2636&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;- BOJ 2636(치즈)&lt;/a&gt; &amp;gt;&amp;gt; 고려해야 될 것이 여러개인 BFS&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 문제를 풀기전에 모든 조건을 완벽히 시뮬레이팅 해놓고 들어가야 헛수고를 덜함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;★ 추가로 BFS는 큐에 넣은 직후 갱신해주어야 시간초과가 나지 않음. ★&lt;/b&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;예시&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/source/64076941&quot;&gt;https://www.acmicpc.net/source/64076941&lt;/a&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;과&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;a href=&quot;https://www.acmicpc.net/source/64077141&quot;&gt;https://www.acmicpc.net/source/64077141&lt;/a&gt;에서 bfs코드 부분을 비교해보면 알 수 있다.&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.acmicpc.net/problem/13549&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;- BOJ 13549(숨바꼭질 3)&lt;/a&gt; &amp;gt;&amp;gt; 큐의 앞쪽에 있는것이 무조건 낮은 depth를 가져야 함.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: &lt;b&gt;BFS시 같은 루프단계에서도 우선순위가 존재한다면, 순서를 지켜주어야함&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://www.acmicpc.net/problem/4179&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BOJ 4179(불)&lt;/a&gt; &amp;gt;&amp;gt; BFS는 &lt;b&gt;방문여부 체크 배열(visited) 필수&lt;/b&gt;라고 할 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://www.acmicpc.net/problem/11048&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BOJ 11048(이동하기)&lt;/a&gt; &amp;gt;&amp;gt; BFS는 visited 배열 필수! 값으로만 체크하다보면 값이 같고 방문하지 않은 곳을 탐색하지 않을 수 있다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(&lt;a href=&quot;https://www.acmicpc.net/source/81696419&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/source/81696419&lt;/a&gt;, &lt;a href=&quot;https://www.acmicpc.net/source/81696800&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/source/81696800&lt;/a&gt;&amp;nbsp;차이 비교)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;a href=&quot;https://eckrin.tistory.com/20&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;Backtracking&lt;/b&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;http://boj.kr/1987&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BOJ 1987&lt;/a&gt;(알파벳) &amp;gt;&amp;gt; dfs+백트래킹인데 많이 헷갈린 문제&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1691661163366&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.*;
import java.util.*;

//시간복잡도가 어떻게되지?
//bfs는 백트래킹이 안되므로 불가
public class Main
{
    static int R, C;
    static char[][] board;
    static boolean[] visit = new boolean[26];
    static int[] dx = {1,0,-1,0};
    static int[] dy = {0,-1,0,1};
    static Stack&amp;lt;Node&amp;gt; stack = new Stack&amp;lt;&amp;gt;();
    static int max = Integer.MAX_VALUE;
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String[] line_1 = br.readLine().split(&quot; &quot;);
		R = Integer.parseInt(line_1[0]);
		C = Integer.parseInt(line_1[1]);
		board = new char[R+1][C+1];
		
		for(int i=1; i&amp;lt;=R; i++) {
		    char[] line_R = br.readLine().toCharArray();
		    for(int j=1; j&amp;lt;=C; j++) {
		        board[i][j] = line_R[j-1];
		    }
		}
		
		stack.add(new Node(1, 1, 1));
		visit[board[1][1]-'A']=true;
		
		while(!stack.isEmpty()) {
		    Node n = stack.pop();
		    max = Math.max(max, n.depth);
		    
		    for(int i=0; i&amp;lt;4; i++) {
		        int nx = n.x+dx[i];
		        int ny = n.y+dy[i];
		        
		        if(1&amp;lt;=nx &amp;amp;&amp;amp; nx&amp;lt;=C &amp;amp;&amp;amp; 1&amp;lt;=ny &amp;amp;&amp;amp; ny&amp;lt;=R) {
		            if(!visit[board[ny][nx]-'A']) {
		                visit[board[ny][nx]-'A'] = true;
		                stack.add(new Node(nx, ny, n.depth+1));
		                flag = true;
		            }
		        }
		    }
		    if(!flag) { //더이상 깊이탐색이 이루어지지 않음
		        stack..?
		    }
		}
	}
}

class Node {
    int x;
    int y;
    int depth;
    
    public Node(int x, int y, int depth) {
        this.x = x;
        this.y = y;
        this.depth = depth;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;트리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;그래프&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;http://boj.kr/20040&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BOJ 20040&lt;/a&gt;(사이클 게임) &amp;gt;&amp;gt; &lt;a href=&quot;https://eckrin.tistory.com/151&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;Union-find&lt;/a&gt;를 통한 사이클 판별&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: union과 find 템플릿 익히기 (find를 while문이 아닌 재귀로 구현해야 하는 이유: &lt;a href=&quot;https://www.acmicpc.net/problem/20040&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://www.acmicpc.net/problem/20040&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;http://boj.kr/1446&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BOJ 1446&lt;/a&gt;(지름길) &amp;gt;&amp;gt; Dijkstra&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: map정보를 초기화해주고, 초기화한 map을 이용해서 dist를 초기화해야 함 (처음에 시작점과 연결된 점들의 dist 정보는 초기화되어있어야 함)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;http://boj.kr/10282&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BOJ 10282&lt;/a&gt;(해킹) &amp;gt;&amp;gt; Dijkstra&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: &lt;a href=&quot;https://eckrin.tistory.com/125&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;다익스트라는 BFS와 다르게 방문 직후에 방문설정을 하면 안된다.&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://www.acmicpc.net/problem/2252&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BOJ 2262&lt;/a&gt;(줄 세우기) &amp;gt;&amp;gt; 위상 정렬&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 진입 차수 배열을 만들고, 차수가 0인것이 다음 정렬 순서가 된다. 이를 반복해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;a href=&quot;https://eckrin.tistory.com/34&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;&lt;b&gt;그리디&lt;/b&gt;&lt;/a&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;a href=&quot;https://www.acmicpc.net/problem/5585&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BOJ 5585 (거스름돈)&lt;/a&gt;, &lt;a href=&quot;https://www.acmicpc.net/problem/1931&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BOJ 1931(회의실 배정)&lt;/a&gt; &amp;gt;&amp;gt; 그리디 well-known 문제들&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리디의 경우 코테에서 이정도 난이도로 출제된다. (가능성 낮음)&lt;/p&gt;</description>
      <category>[ 코딩테스트 ]</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/159</guid>
      <comments>https://eckrin.tistory.com/159#entry159comment</comments>
      <pubDate>Sun, 23 Jul 2023 04:49:09 +0900</pubDate>
    </item>
    <item>
      <title>Docker와 포트포워딩</title>
      <link>https://eckrin.tistory.com/158</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;도커 컨테이너를 실행하면 NAT와 비슷한 환경에서 사용하게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;790&quot; data-origin-height=&quot;659&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/vdUTM/btsnY0jixqy/pqUxJ77iovI6vCE3KYx461/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/vdUTM/btsnY0jixqy/pqUxJ77iovI6vCE3KYx461/img.png&quot; data-alt=&quot;NAT이란: https://eckrin.tistory.com/71&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/vdUTM/btsnY0jixqy/pqUxJ77iovI6vCE3KYx461/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FvdUTM%2FbtsnY0jixqy%2FpqUxJ77iovI6vCE3KYx461%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;666&quot; height=&quot;556&quot; data-origin-width=&quot;790&quot; data-origin-height=&quot;659&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;NAT이란: https://eckrin.tistory.com/71&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너가 실행되는 호스트 시스템에는 이러한 기능이 사용되기 때문에, 외부에서 컨테이너 안에서 실행하는 프로그램에 대한 접근을 위해 포트포워딩이 필요할 수 있다. 한마디로 호스트의 해당 포트를 컨테이너의 대상 포트로 연결해서 컨테이너 외부에서도 접근할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1689610446544&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run -p 8080:80 --name redis_container redis_image&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;따라서 도커에서는 위와 같이 -p옵션을 통해서 포트포워딩을 설정할 수 있는데, 위와 같이 컨테이너를 실행하면 이 redis 컨테이너의 8080번 포트로 들어오는 모든 호스트 요청은 도커 컨테이너의 80번 포트로 전달된다. 만약 호스트의 8080포트가 이미 사용중인 경우, 8079로 접속하면 도커로 연결되도록 포워딩 해줄수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;얼마 전 redis 실행시 이러한 포트포워딩 설정을 해주지 않아 Connection 에러가 생긴적이 있다. (&lt;a href=&quot;https://eckrin.tistory.com/157&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://eckrin.tistory.com/157&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>[ Backend ]/Infra</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/158</guid>
      <comments>https://eckrin.tistory.com/158#entry158comment</comments>
      <pubDate>Mon, 17 Jul 2023 17:43:06 +0900</pubDate>
    </item>
    <item>
      <title>도커(Docker) 알아보기</title>
      <link>https://eckrin.tistory.com/157</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;간단한 역사&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;849&quot; data-origin-height=&quot;341&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dsxdIN/btsqh3Df5BN/GOEF6bxD01TrmZiB4Hq441/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dsxdIN/btsqh3Df5BN/GOEF6bxD01TrmZiB4Hq441/img.png&quot; data-alt=&quot;도커에 대한 검색을 하면 흔히 나오는 사진이다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dsxdIN/btsqh3Df5BN/GOEF6bxD01TrmZiB4Hq441/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdsxdIN%2Fbtsqh3Df5BN%2FGOEF6bxD01TrmZiB4Hq441%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;822&quot; height=&quot;330&quot; data-origin-width=&quot;849&quot; data-origin-height=&quot;341&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;도커에 대한 검색을 하면 흔히 나오는 사진이다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. &lt;b&gt;Tradition Deployment&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;물리적인 컴퓨터 한 대(Hardware)에 하나의 OS를 깔고 그 위에 여러가지 프로그램을 설치하는 방식이다. 한 대의 컴퓨터에서 여러 프로그램을 수행하다 보니 퍼포먼스 저하, 또는 프로그램 충돌이 일어날 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. &lt;b&gt;Virtualized Deployment&lt;/b&gt;:&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;VMware, VirtualBox 등.. 리눅스쓸때 지겹게 썼던 그것이다. 1번 방법으로 문제를 해결하기 위해 여러개의 물리적 컴퓨터를 이용하는 방법 대신, Hypervisor를 사용하여 하나의 시스템위에 여러개의 가상 컴퓨터를 올려 구동한다. 각각의 가상머신은 논리적 컴퓨터이므로, CPU, 메모리, 저장장치를 각각 할당할 수 있다 (물론 Hardware의 리소스를 잡아먹는다). 이 방법을 사용하면 가상머신 각각의 성능을 임의로 조절할 수 있고, 여러개의 프로그램을 각각의 가상화된 컴퓨터 위에서 프로그램을 실행시키는 방법으로 프로그램을 분리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;1번 방법의 한계를 해결할 수 있으나, 무거운 가상 컴퓨터를 여러개 사용하기 때문에 여러개의 OS와 CPU, 메모리 등을 할당해주어야 하기 때문에 무거운 편이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3.&lt;b&gt; Container Deployment&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;2번 방법에서 Hypervisor를 통해서 여러개의 가상 컴퓨터를 올려서 구동하는 대신, &lt;b&gt;Container Runtime&lt;/b&gt;를 사용한다. 컨테이너와 이전 가상머신의 가장 큰 차이점은 프로그램 구동을 위해서 별도의 OS를 설치할 필요가 없으며, 모든 컨테이너가 같은 OS를 공유한다는 것이다. 하지만 전통적 배포 방법과는 다르게 프로그램 사이에 간섭을 일으키지 못하도록 제한하며 독립적인 자원을 사용할 수 있도록 할당해주고 관리한다는 차이를 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이러한 것을 가능하게 하는 &lt;b&gt;Container Runtime&lt;/b&gt; 중 가장 유명한 것이 &lt;b&gt;도커(Docker)&lt;/b&gt;이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;도커란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;리눅스 컨테이너를 다루는 도구(컨테이너 런타임) 중 하나이다. VM과 비슷한 역할을 하지만 OS단위로 가상화하지 않으며, App단위로 쪼갠다는 특징이 있다. (virutualbox나 vmware workstation같은 기존의 VM은 하나의 호스트 아래에서 여러개의 운영체제를 생성하여 가동하는 형식이었다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존 VM위에 올라간 운영체제 각각은 base OS나 다른 VM과는 별도로 동작하며, 완전히 독립된 공간에서 사용되기 때문에 각각이 완전히 분리된 환경에서 실행되며, 가상머신 각각의 이미지 사이즈가 매우 크다. 반면 도커는 앱 단위로 컨테이너라는 작은 규모로 가상화하기 때문에 기존의 VM보다 작은 크기를 가진다.&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;도커 이미지란?&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;도커 이미지란 &lt;b&gt;서비스 운영에 필요한 다양한 서버 프로그램, 소스코드 및 라이브러리, 실행파일을 묶어놓은 읽기 전용 스냅샷&lt;/b&gt;을 말하며, 실행 중인 이미지를 컨테이너라고 한다. 간단히 말해 특정 프로세스를 실행하기 위한 모든 라이브러리와 설정값, 파일들을 모아놓은 것을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;1178&quot; data-origin-height=&quot;356&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bAvLsu/btsnLu6x8bP/OTV8iysrGGgtoomwo2hS9k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bAvLsu/btsnLu6x8bP/OTV8iysrGGgtoomwo2hS9k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bAvLsu/btsnLu6x8bP/OTV8iysrGGgtoomwo2hS9k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbAvLsu%2FbtsnLu6x8bP%2FOTV8iysrGGgtoomwo2hS9k%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;1178&quot; height=&quot;356&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;1178&quot; data-origin-height=&quot;356&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 이미지들은 단순히 템플릿일 뿐이므로 상태값을 갖지 않고, 하나의 이미지로부터 여러 개의 컨테이너가 생성될 수 있으며, 컨테이너가 삭제되더라도 이미지는 변하지 않고 남아있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;도커 컨테이너란?&lt;/b&gt;&lt;/h2&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;일반적으로 &lt;b&gt;앱이 구동되는 환경까지 감싸 실행할 수 있도록 하는 격리 기술&lt;/b&gt;을 컨테이너라고 하는데, 도커에서 실행 중인 이미지를 컨테이너라고 한다. 도커 엔진 위의 논리적인 공간을 이야기하며, 이러한 논리적인 공간(=컨테이너)을 만들어 다양한 어플리케이션을 설치하여 하나의 서버처럼 사용할 수 있게 된다. 하나의 컨테이너 안에 다양한 어플리케이션을 담고, 컨테이너에 접근하여 그 안의 어플리케이션을 사용할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;하드웨어 가상화를 제공하는 VM과 다르게 도커는 다른 커널을 공유하는 여러 컨테이너를 가지는데, 개발환경 하나를 구축하기 위해서 들어가는 많은 수고들을 최소화하기 위해서 컨테이너라는 환경에 세팅을 마치고 그 컨테이너 환경을 배포함으로서 번거로운 부분을 최소화할 수 있다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;1189&quot; data-origin-height=&quot;336&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bm3x4P/btsn0Fx5T89/ocU1EnhkX1WlkzYZzK2w5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bm3x4P/btsn0Fx5T89/ocU1EnhkX1WlkzYZzK2w5k/img.png&quot; data-alt=&quot;docker-desktop에서 컨테이너를 들어가보면 컨테이너마다 이미지 정보가 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bm3x4P/btsn0Fx5T89/ocU1EnhkX1WlkzYZzK2w5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbm3x4P%2Fbtsn0Fx5T89%2FocU1EnhkX1WlkzYZzK2w5k%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;1189&quot; height=&quot;336&quot; data-filename=&quot;edited_blob&quot; data-origin-width=&quot;1189&quot; data-origin-height=&quot;336&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;docker-desktop에서 컨테이너를 들어가보면 컨테이너마다 이미지 정보가 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Dockerfile&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이번엔 도커를 이용하여 스프링부트 프로젝트 이미지를 만들고, 해당 이미지를 바탕으로 컨테이너를 만들어 보도록 하겠다. 대상이 되는 스프링부트 프로젝트는 mysql, redis, mongodb와 연결되어있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 먼저 할 일은 Dockerfile을 작성하는 일이다. Dockerfile은 도커에 존재하는 이미지를 기반으로 새로운 이미지와 컨테이너를 생성할 수 있게 하는 파일이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1689494049874&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM openjdk:17
ARG JAR_FILE=./build/libs/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT [&quot;java&quot;,&quot;-jar&quot;,&quot;/app.jar&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(참고: &lt;a href=&quot;https://docs.docker.com/engine/reference/builder/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://docs.docker.com/engine/reference/builder/&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;988&quot; data-origin-height=&quot;356&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/QaH61/btsnFbesvUL/kspQabT0yFTwAX5XkDU6c0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/QaH61/btsnFbesvUL/kspQabT0yFTwAX5XkDU6c0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/QaH61/btsnFbesvUL/kspQabT0yFTwAX5XkDU6c0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQaH61%2FbtsnFbesvUL%2FkspQabT0yFTwAX5XkDU6c0%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;752&quot; height=&quot;271&quot; data-origin-width=&quot;988&quot; data-origin-height=&quot;356&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Dockerfile은 반드시 이 From절로 시작해야 한다. 이것은 build시에 기초가 될 Base Image를 지정하는 역할을 한다. 위에서 openjdk:17이라는 것은 JDK 17을 사용한다는 것을 말한다. From절에 Base Image와 태그를 지정하면 도커가 해당 이미지를 가져온다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;995&quot; data-origin-height=&quot;120&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FlMRt/btsnOsZ2z2T/7J6TZldiplFEN9IDnCIoqK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FlMRt/btsnOsZ2z2T/7J6TZldiplFEN9IDnCIoqK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FlMRt/btsnOsZ2z2T/7J6TZldiplFEN9IDnCIoqK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFlMRt%2FbtsnOsZ2z2T%2F7J6TZldiplFEN9IDnCIoqK%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;778&quot; height=&quot;94&quot; data-origin-width=&quot;995&quot; data-origin-height=&quot;120&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;FROM다음에 나오는 ARG명령어는 docker build시 넘길 인자를 정의하기 위해서 사용된다. 예를들어 위의 예시에서는 'ARG JAR_FILE = ./build/libs/*.jar'로 표현되었는데, 이렇게 설정하면 일종의 변수를 설정해두는것같이 ${JAR_FILE}과 같이 사용할 수 있다. 위 예시에서는 (COPY ${JAR_FILE} app.jar과 같이 사용하고 있다.&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;981&quot; data-origin-height=&quot;178&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bj35c3/btsnFehY83V/KwBDyXt2nYRpIq7MKTSqQ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bj35c3/btsnFehY83V/KwBDyXt2nYRpIq7MKTSqQ0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bj35c3/btsnFehY83V/KwBDyXt2nYRpIq7MKTSqQ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbj35c3%2FbtsnFehY83V%2FKwBDyXt2nYRpIq7MKTSqQ0%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;728&quot; height=&quot;132&quot; data-origin-width=&quot;981&quot; data-origin-height=&quot;178&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; COPY명령은 호스트 컴퓨터의 디렉터리나 파일을 도커이미지로 복사하기 위해서 사용된다. 위 명령에서는 'COPY ./build/libs/*.jar app.jar'와 같이 사용되어 해당 경로에 있는 jar파일을 도커 이미지에 복사하기 위한 목적으로 사용되었다.&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;978&quot; data-origin-height=&quot;310&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRVoFZ/btsnLa6oW6k/Na1YRxRXpa8YTq7r4yXj9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRVoFZ/btsnLa6oW6k/Na1YRxRXpa8YTq7r4yXj9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRVoFZ/btsnLa6oW6k/Na1YRxRXpa8YTq7r4yXj9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRVoFZ%2FbtsnLa6oW6k%2FNa1YRxRXpa8YTq7r4yXj9K%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;734&quot; height=&quot;233&quot; data-origin-width=&quot;978&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;ENTRYPOINT명령은 이미지를 컨테이너로 띄우고 나서 실행되어야 하는 커맨드를 지정하는데, 위에서와 같이 ENTRYPOINT [&quot;java&quot;,&quot;-jar&quot;,&quot;/app.jar&quot;]와 같이 사용되면 컨테이너로 올라간 직후 java -jar /app.jar명령을 실행하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;876&quot; data-origin-height=&quot;191&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVbJNx/btsnFL7DwPN/mJr4WVDppYjTiHVXt4l5q1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVbJNx/btsnFL7DwPN/mJr4WVDppYjTiHVXt4l5q1/img.png&quot; data-alt=&quot;마지막 줄처럼 여러 개의 CMD가 있을 경우, 마지막 CMD를 제외한 나머지는 무시된다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVbJNx/btsnFL7DwPN/mJr4WVDppYjTiHVXt4l5q1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVbJNx%2FbtsnFL7DwPN%2FmJr4WVDppYjTiHVXt4l5q1%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;736&quot; height=&quot;160&quot; data-origin-width=&quot;876&quot; data-origin-height=&quot;191&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;마지막 줄처럼 여러 개의 CMD가 있을 경우, 마지막 CMD를 제외한 나머지는 무시된다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CMD도 동일하게 컨테이너 시작 시 실행될 커맨드를 지정하는 명령어인데, 항상 실행되어야 하는 커맨드를 지정하기 위한 목적으로 사용하는 ENTRYPOINT와 다르게 선택적으로 사용할 수 있다.&lt;/p&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;pre id=&quot;code_1689496738446&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ENTRYPOINT [&quot;python&quot;]
CMD [&quot;helloworld.py]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 Dockerfile을 작성했다고 하자.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1689496806909&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run python_image
&amp;gt;&amp;gt; python helloworld.py&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 별도의 인자 없이 실행하게 되면 뒤에 지정한 python helloworld.py와 같이 명령이 실행된다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 파라미터에 별도의 인자를 지정하게 되면 Dockerfile에 CMD를 이용해서 정의한 hellworld.py가 아닌 입력한 파라미터를 덧붙여 실행하게 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1689497044573&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run python_image testworld.py
&amp;gt;&amp;gt; python testworld.py&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;pre id=&quot;code_1689582658431&quot; class=&quot;dockerfile&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FROM openjdk:17
ARG JAR_FILE=./build/libs/*.jar
COPY ${JAR_FILE} app.jar
ENTRYPOINT [&quot;java&quot;,&quot;-jar&quot;,&quot;/app.jar&quot;]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 정리하자면 처음에 나왔던 이 도커파일은 jdk17을 사용하고, ./build/libs/에 있는 jar파일을 app.jar라는 이름으로 컨테이너로 복제한 이후 java -jar /app.jar명령을 통해 실행하라는 것을 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Docker-Compose&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1689497293153&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;version: '3'
services:
  database-mysql:
    container_name: mysql_container
    image: mysql/mysql-server
    ports:
      - &quot;3306:3306&quot;
    volumes:
      - C:\Users\Home\docker\mysql:/var/lib/mysql
    environment:
      MYSQL_DATABASE: docker_test
      MYSQL_ROOT_HOST: '%'
      MYSQL_ROOT_PASSWORD: 1234
      TZ: 'Asia/Seoul'

  database-redis:
    container_name: redis_container
    image: redis:latest
    ports:
      - &quot;6379:6379&quot;
    volumes:
      - C:\Users\Home\docker\redis:/data

  database-mongo:
    container_name: mongo_container
    image: mongo
    ports:
      - &quot;27017:27017&quot;
    volumes:
      - C:\Users\Home\docker\mongo:/data/db
    environment:
      - MONGO_INITDB_ROOT_USERNAME=root
      - MONGO_INITDB_ROOT_PASSWORD=1234
      - MONGO_INITDB_DATABASE=docker_test

  spring-boot:
    container_name: spring-docker-test
    build:
      context: .
      dockerfile: Dockerfile
    environment:
      - TZ=Asia/Seoul
      - MYSQL_URL=jdbc:mysql://host.docker.internal:3306/docker_test?serverTimezone=Asia/Seoul
      - MYSQL_USERNAME=root
      - MYSQL_PASSWORD=1234
      - MONGO_HOST=host.docker.internal
      - MONGO_PORT=27017
      - MONGO_URI=mongodb://root:1234@host.docker.internal:27017/docker_test?authSource=admin
      - REDIS_HOST=host.docker.internal
      - REDIS_PORT=6379
    ports:
      - &quot;8080:8080&quot;
    depends_on:
      - database-mongo
      - database-mysql
      - database-redis&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;environment를 통해서 환경변수를 일괄적으로 넘겨줄 수 있다.&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;위 예시는 docker-compose를 사용하여 한번에 여러개의 컨테이너를 생성하는 예시이다. 이렇게&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&amp;nbsp;파일로부터 설정을 읽어들임으로서&lt;/span&gt; 하나 이상의 컨테이너로부터 이루어진 서비스를 실행하는 절차를 자동화 할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;+ 해결법 찾아보기&lt;/b&gt;&lt;/span&gt; - ec2에 actions를 사용해서 .jar를 올릴때는 그냥 ec2 리눅스서버의 환경변수를 설정해줌으로서 노출되어서는 안되는 secretKey를 암호화할 수 있었다. 그런데 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;ec2서버에 도커를 올려서 실행하게 되면 이 경우 ec2에서 docker-compose를 통해서 실행할때 environment로 환경변수를 넘겨줘야 한다. 문제는 docker-compose파일에 적힌 secretKey 자체가 노출된다는 것이다. gitignore해주는 방법이 있기는 한데.. 다른방법이 있을듯?&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;/** 일단 깃헙에서 노출되는 문제는 docker-compose의 environment들을 변수로 관리한다음, 로컬에서는 .env파일을 활용해서 값을 보관한 다음 gitignore하고, 깃헙에서는 actions secrets으로 넘기면 해결할 수 있다. **/&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;DB 연결&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(이미지 생성한 이후)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- springboot, dockerfile&lt;/p&gt;
&lt;pre id=&quot;code_1689431323344&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker build -t docker-spring-test .
docker run springbootapp //컨테이너 직접 생성
docker-compose up //docker-compose 이용&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://ttl-blog.tistory.com/761&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://ttl-blog.tistory.com/761&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://da2uns2.tistory.com/entry/Docker-%EB%8F%84%EC%BB%A4%EC%97%90-Spring-Boot-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://da2uns2.tistory.com/entry/Docker-%EB%8F%84%EC%BB%A4%EC%97%90-Spring-Boot-%EA%B5%AC%EC%B6%95%ED%95%98%EA%B8%B0&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- redis&lt;/p&gt;
&lt;pre id=&quot;code_1689431279865&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run --name redis-container -v C:\Users\Home\docker\redis:/data/db -p 6379:6379 redis
docker exec -it &amp;lt;컨테이너 ID&amp;gt; redis-cli //redis client

keys *&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://hello-i-t.tistory.com/137&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://hello-i-t.tistory.com/137&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- mysql&lt;/p&gt;
&lt;pre id=&quot;code_1689431332364&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run --name mysql-container -p 3306:3306 -v C:\Users\Home\docker\mysql:/var/lib/mysql -e MYSQL_ROOT_PASSWORD=1234 mysql
docker start mysql-container
docker exec -it &amp;lt;컨테이너 ID&amp;gt; bash

mysql -u username -p password

use users;
select * from users;&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://poiemaweb.com/docker-mysql&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://poiemaweb.com/docker-mysql&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://shawn-dev.oopy.io/accessing-local-db-from-docker-container&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://shawn-dev.oopy.io/accessing-local-db-from-docker-container&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- mongodb&lt;/p&gt;
&lt;pre id=&quot;code_1689431357217&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run --name mongodb-container -v C:\Users\Home\docker\mongo:/data/db -d -p 27017:27017 mongo
docker start mongodb-container
docker exec -it &amp;lt;컨테이너 ID&amp;gt; bash

mongosh -u username -p password

//계정이 없다면
mongosh
use admin
db.createUser(
  {
    user: &quot;username&quot;,
    pwd:  &quot;password&quot;,
    roles: [
    	{ &quot;role&quot; : &quot;readWriteAnyDatabase&quot;, &quot;db&quot; : &quot;admin&quot; },
    	{ &quot;role&quot; : &quot;userAdminAnyDatabase&quot;, &quot;db&quot; : &quot;admin&quot; },
        { &quot;role&quot; : &quot;dbAdminAnyDatabase&quot;, &quot;db&quot; : &quot;admin&quot; },
        { &quot;role&quot; : &quot;clusterAdmin&quot;, &quot;db&quot; : &quot;admin&quot; },
        { &quot;role&quot; : &quot;restore&quot;, &quot;db&quot; : &quot;admin&quot; },
        { &quot;role&quot; : &quot;backup&quot;, &quot;db&quot; : &quot;admin&quot; }
	]
  }
)

//확인
use admin
show collections
db.system.users.find()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;156&quot; data-origin-height=&quot;66&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/nUVaM/btsnGlgmsnV/cYCncKVvfnubUBW6JPGn81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/nUVaM/btsnGlgmsnV/cYCncKVvfnubUBW6JPGn81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/nUVaM/btsnGlgmsnV/cYCncKVvfnubUBW6JPGn81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FnUVaM%2FbtsnGlgmsnV%2FcYCncKVvfnubUBW6JPGn81%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;156&quot; height=&quot;66&quot; data-origin-width=&quot;156&quot; data-origin-height=&quot;66&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 응답이 오면 성공적으로 계정이 생성된 것.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://poiemaweb.com/docker-mongodb&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://poiemaweb.com/docker-mongodb&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.bearpooh.com/127&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.bearpooh.com/127&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.bearpooh.com/168&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.bearpooh.com/168&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;볼륨&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://velog.io/@ette9844/Windows10-%EC%97%90%EC%84%9C-varlibdocker-%EC%B0%BE%EA%B8%B0&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@ette9844/Windows10-%EC%97%90%EC%84%9C-varlibdocker-%EC%B0%BE%EA%B8%B0&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;=======================================================================================&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;발생한 다양한 에러들&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1. mysql, mongodb 연결은 성공했는데 redis 동작이 안된다. 처음에는 yml에 오타냈겠지 정도로 생각했는데 실행된 컨테이너 목록을 보니 redis만 Port가 지정되지 않은 것처럼 보였다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1065&quot; data-origin-height=&quot;194&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/7iVhH/btsnXiqsjoJ/Bvs7PIAqCdXx9GRIMdnKY0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/7iVhH/btsnXiqsjoJ/Bvs7PIAqCdXx9GRIMdnKY0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/7iVhH/btsnXiqsjoJ/Bvs7PIAqCdXx9GRIMdnKY0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F7iVhH%2FbtsnXiqsjoJ%2FBvs7PIAqCdXx9GRIMdnKY0%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;1065&quot; height=&quot;194&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1065&quot; data-origin-height=&quot;194&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금은 mysql, redis, mongodb 모두 port가 존재하는데 redis만 포트가 없기에 구글링을 통해 아래와 같이 수정했다 (더럽게 안나옴)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;935&quot; data-origin-height=&quot;741&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OKFeF/btsnF6qoz1l/2REivl8SB4Btzf7EAuiXx1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OKFeF/btsnF6qoz1l/2REivl8SB4Btzf7EAuiXx1/img.png&quot; data-alt=&quot;https://junior-datalist.tistory.com/351&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OKFeF/btsnF6qoz1l/2REivl8SB4Btzf7EAuiXx1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOKFeF%2FbtsnF6qoz1l%2F2REivl8SB4Btzf7EAuiXx1%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;935&quot; height=&quot;741&quot; data-origin-width=&quot;935&quot; data-origin-height=&quot;741&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://junior-datalist.tistory.com/351&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제 찾는데 엄청 오래걸렸다. &lt;span style=&quot;color: #ee2323;&quot;&gt;그런데 mysql은 왜 port 설정 안해줬는데도 동작한걸까? 기본으로 설정되는거같긴 한데&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;size18&quot;&gt;2. mongodb는 초기화 안되고 users테이블 유지되는데 mysql 컨테이너 종료시 데이터가 날아가버린다. 삭제되는 시점이 docker-compose로 재실행하는 시점에 db에서 데이터가 날아가는것이 ddl_auto가 create일때 모습같은데 ddl_auto는 validate였다.&lt;/p&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;gt; 컴퓨터 껏다가 켜고나니 해결 (이미지 삭제-실행, 컨테이너 삭제-실행 등으로는 해결되지 않은 문제. 아무래도 컴퓨터를 껐다 키는 과정에서 mysql 컨테이너도 껏다 켜졌는데, 그때 문제가 해결된게 아닌가 싶다.&lt;span style=&quot;color: #333333;&quot;&gt; 왜 발생했는지 원인은 찾지 못함)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 아무래도 docker-compose 바뀐 코드를 clean build안해줘서 그런듯. &lt;span style=&quot;color: #ee2323;&quot;&gt;build는 수동&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;size18&quot;&gt;3. 로컬에서 돌리면 run은 되는데 run창은 안떴다. 컴퓨터 재부팅, 캐시삭제 등 소용없고 다른 프로젝트에서는 제대로 동작하는 모습을 보아 인텔리제이의 문제도 아닌것 같음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;-&amp;gt; 파일하나 코드한줄씩 지워가면서 뭐가문젠지 테스트하다가 run창을 띄우는 단축키를 찾아서 alt+4 눌러보니 run창이 안떴을뿐 안에서 동작하고 있었음. 버그인것같다.. 수정한 파일을 하나씩 재작업해가면서 어디서 문제가 발생했는지 찾아봤는데 Dockerfile을 생성하고 난 이후부터 run창이 뜨지 않았다. 우연일까?&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1901&quot; data-origin-height=&quot;203&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/qcH50/btsnHRfbdN0/Kk4X98D19l1STXRNw135Zk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/qcH50/btsnHRfbdN0/Kk4X98D19l1STXRNw135Zk/img.png&quot; data-alt=&quot;alt+4 누르니 제대로 나오더라&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/qcH50/btsnHRfbdN0/Kk4X98D19l1STXRNw135Zk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FqcH50%2FbtsnHRfbdN0%2FKk4X98D19l1STXRNw135Zk%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;1901&quot; height=&quot;203&quot; data-origin-width=&quot;1901&quot; data-origin-height=&quot;203&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;alt+4 누르니 제대로 나오더라&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;github: &lt;a href=&quot;https://github.com/eckrin/docker-kubernetes&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/eckrin/docker-kubernetes&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1691757613007&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - eckrin/docker-kubernetes: docker+kubernetes practice&quot; data-og-description=&quot;docker+kubernetes practice. Contribute to eckrin/docker-kubernetes development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/eckrin/docker-kubernetes&quot; data-og-url=&quot;https://github.com/eckrin/docker-kubernetes&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bWxTXJ/hyTBCL3jxf/Z8ndekWfuFNHT74Cejt5Bk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/eckrin/docker-kubernetes&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/eckrin/docker-kubernetes&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bWxTXJ/hyTBCL3jxf/Z8ndekWfuFNHT74Cejt5Bk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - eckrin/docker-kubernetes: docker+kubernetes practice&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;docker+kubernetes practice. Contribute to eckrin/docker-kubernetes development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고:&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://docs.docker.com/get-started/&quot;&gt;https://docs.docker.com/get-started/&lt;/a&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #737373; text-align: center;&quot;&gt;&lt;a href=&quot;https://kubernetes.io/ko&quot;&gt;https://kubernetes.io/ko&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>[ Backend ]/Infra</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/157</guid>
      <comments>https://eckrin.tistory.com/157#entry157comment</comments>
      <pubDate>Fri, 14 Jul 2023 18:59:29 +0900</pubDate>
    </item>
    <item>
      <title>[JPA] N+1 문제</title>
      <link>https://eckrin.tistory.com/156</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;N+1 Problem&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;N+1 문제란, 연관관계가 설정된 엔티티를 조회할 때 연관된 엔티티의 개수(N)만큼 추가적인 쿼리가 발생하는 문제를 말한다.&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;Member와 Order, Delivery엔티티가 있다고 하자. Order는 주문정보를 담은 엔티티로, Member(회원)와 다대일 연관관계를 가지며, 배송정보를 담은 엔티티인 Delivery와는 일대일 연관관계를 갖는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot_20230711_234225_Noteshelf.jpg&quot; data-origin-width=&quot;1207&quot; data-origin-height=&quot;952&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/JnNhp/btsnfGfb5vJ/5UU0DssnBxl7jCGgKtQJH1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/JnNhp/btsnfGfb5vJ/5UU0DssnBxl7jCGgKtQJH1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/JnNhp/btsnfGfb5vJ/5UU0DssnBxl7jCGgKtQJH1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJnNhp%2FbtsnfGfb5vJ%2F5UU0DssnBxl7jCGgKtQJH1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;533&quot; height=&quot;420&quot; data-filename=&quot;Screenshot_20230711_234225_Noteshelf.jpg&quot; data-origin-width=&quot;1207&quot; data-origin-height=&quot;952&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;pre id=&quot;code_1689086126471&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
@Table(name=&quot;orders&quot;)
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Order {

    @Id @GeneratedValue
    @Column(name=&quot;order_id&quot;)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;member_id&quot;)
    private Member member;

    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = &quot;delivery_id&quot;)
    private Delivery delivery;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1689086162515&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
@Getter @Setter
public class Member {

    @Id @GeneratedValue
    @Column(name=&quot;member_id&quot;)
    private Long id;

    private String name;

    @OneToMany(mappedBy = &quot;member&quot;)
    private List&amp;lt;Order&amp;gt; orders = new ArrayList&amp;lt;&amp;gt;();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1689086180408&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
@Getter @Setter
public class Delivery {

    @Id @GeneratedValue
    @Column(name=&quot;delivery_id&quot;)
    private Long id;

    @OneToOne(mappedBy = &quot;delivery&quot;, fetch = FetchType.LAZY)
    private Order order;
}&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;pre id=&quot;code_1689087288418&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@GetMapping(&quot;/**&quot;)
public List&amp;lt;GetOrderListResponseDto&amp;gt; getOrderList() {
    List&amp;lt;Order&amp;gt; orders = orderRepository.findAll(new OrderSearch());
    ...
}&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;이러한 상황에서 Order리스트를 조회하는 쿼리를 날린다고 하자. 처음에는 select * from order와 같이 &lt;b&gt;order를 위한 1개의 select문을&lt;/b&gt; 날린다. 그런데 지연 로딩을 설정해놓았기 때문에 Order와 연관관계를 가지는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;Member와 Delivery 객체가 영속성 콘텍스트 안에 존재하지 않는다&lt;/b&gt;&lt;/span&gt;. 따라서 select문을 통해서 가져온 리스트의 &lt;b&gt;N개의 원소 각각에 대해서 Member와 Delivery정보를 조회하는 쿼리가 N개씩 추가로&lt;/b&gt;&amp;nbsp;나가게 되는 것이다. &lt;b&gt;(1+N+N)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이러한 현상은 &lt;b&gt;각 Order에 연관관계를 갖는 Member가 1차 캐시(영속성 콘텍스트)에 존재하지 않기 때문&lt;/b&gt;에 나타난다. 따라서 만약 N개의 Order들이 모두 동일한 Member로부터 생성되었다면, 첫 로딩을 제외하면 이미 영속성 콘텍스트 내에 Member객체가 존재하므로 Member에 관련해서는 1번의 쿼리만 나가게 된다(1+1+N)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1235&quot; data-origin-height=&quot;698&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/q0kpj/btsngwwnNWJ/RybbYfMAi4RUkykpmU1Mu0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/q0kpj/btsngwwnNWJ/RybbYfMAi4RUkykpmU1Mu0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/q0kpj/btsngwwnNWJ/RybbYfMAi4RUkykpmU1Mu0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fq0kpj%2FbtsngwwnNWJ%2FRybbYfMAi4RUkykpmU1Mu0%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;644&quot; height=&quot;364&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1235&quot; data-origin-height=&quot;698&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그렇다면 fetchType을 EAGER로 설정하면 해결되지 않을까? 라는 생각이 자연스럽게 든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 EAGER로 설정해놓았다 &lt;span style=&quot;letter-spacing: 0px;&quot;&gt;하더라도 Order리스트를 받아오기 전에 연관관계를 가지는 엔티티 정보를 가져오는 것이 아니라, 일단 Order리스트를 받아온 후에 각각의 리스트 원소에 대해서 추가 쿼리가 나가는 방식으로 동작한다. &lt;/span&gt;&lt;b&gt;결국 fetchType이 EAGER와 LAZY라는 사실은 추가쿼리(N)가 나가는 시점에만 영향을 끼치고&lt;/b&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;, 리스트를 받아온 이후에 추가 쿼리가 나가게 되어 N+1문제가 발생하게 된다.&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;520&quot; data-origin-height=&quot;225&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eljXia/btsrhGgo0Zi/SiCxjc6Oo3StBdUmd4v4YK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eljXia/btsrhGgo0Zi/SiCxjc6Oo3StBdUmd4v4YK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eljXia/btsrhGgo0Zi/SiCxjc6Oo3StBdUmd4v4YK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeljXia%2FbtsrhGgo0Zi%2FSiCxjc6Oo3StBdUmd4v4YK%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;481&quot; height=&quot;208&quot; data-origin-width=&quot;520&quot; data-origin-height=&quot;225&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;593&quot; data-origin-height=&quot;577&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwgFfM/btsq2jOme9k/PogrQgm2bQs5vgWiOwRj6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwgFfM/btsq2jOme9k/PogrQgm2bQs5vgWiOwRj6k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwgFfM/btsq2jOme9k/PogrQgm2bQs5vgWiOwRj6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwgFfM%2Fbtsq2jOme9k%2FPogrQgm2bQs5vgWiOwRj6k%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;467&quot; height=&quot;454&quot; data-origin-width=&quot;593&quot; data-origin-height=&quot;577&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;jpql이 나가는것을 보면 Order리스트를 조회하는 시점에 delivery를 조회하는 쿼리 하나와, order, delivery, member 엔티티를 3중조인한 inner join문 하나가 날아간다. 이 쿼리 자체는 EAGER로 해놨으므로 날아가는 것으로 보인다,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;그런데 EAGER의 경우 Order를 위한 1개의 select쿼리 뒤에 delivery를 위한 1개의 쿼리와 3중 조인문 2개의 쿼리가 나가고 있는데 (위 사진)&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal; background-color: #ffffff; color: #24292f; text-align: start;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;join문이 EAGER처리를 위한 쿼리라면 왜 order 조회쿼리 뒤에 바로 나가지 않고 delivery 조회쿼리가 한번 나간 뒤에 나가는건지&lt;/li&gt;
&lt;li&gt;왜 delivery에 대한 추가 쿼리는 나가는데 member는 조회쿼리가 나가지 않고 join문이 나가는지&lt;/li&gt;
&lt;li&gt;EAGER의 경우에도 모든 연관객체가 영속성 콘텍스트 안에 존재하지 않는다면 정확히 1+N개의 쿼리가 나가는지 (=LAZY와 똑같이 1+N개의 쿼리가 나가는지)&lt;/li&gt;
&lt;/ol&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;이 3개가 이해되지 않는다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;EAGER일때 쿼리를 이해해보려고 노력했는데 &quot;LAZY와는 추가쿼리(N)가 나가는 시점의 차이가 있다&quot; 정도 말고는 이해가 되지 않는다.&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;background-color: #ffffff; color: #24292f; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;span style=&quot;text-align: start;&quot;&gt;-&amp;gt; 이거 다른 요인때문에 이렇게 조인문 날아갔을수도 있다. (참고:&amp;nbsp;&lt;/span&gt;&lt;a style=&quot;color: #ee2323;&quot; href=&quot;https://velog.io/@jinyoungchoi95/JPA-%EB%AA%A8%EB%93%A0-N1-%EB%B0%9C%EC%83%9D-%EC%BC%80%EC%9D%B4%EC%8A%A4%EA%B3%BC-%ED%95%B4%EA%B2%B0%EC%B1%85)&quot;&gt;https://velog.io/@jinyoungchoi95/JPA-%EB%AA%A8%EB%93%A0-N1-%EB%B0%9C%EC%83%9D-%EC%BC%80%EC%9D%B4%EC%8A%A4%EA%B3%BC-%ED%95%B4%EA%B2%B0%EC%B1%85)&lt;/a&gt;&lt;/b&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;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;이러한 문제들은 fetch join을 이용하여 해결할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Fetch Join&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;JPA 내부적으로 jpql을 사용하여 join을 사용하게 되면 join된 모든 테이블 정보를 가져오는 것이 아니라, 오직 jpql에서 주체가 되는 엔티티(from절에 해당하는 엔티티)만 조회하여 컨텍스트에 저장한다.&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;439&quot; data-origin-height=&quot;22&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bZ0OBM/btsniteKC5c/AxVmOomIL4JrTLY77eiWak/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bZ0OBM/btsniteKC5c/AxVmOomIL4JrTLY77eiWak/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bZ0OBM/btsniteKC5c/AxVmOomIL4JrTLY77eiWak/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbZ0OBM%2FbtsniteKC5c%2FAxVmOomIL4JrTLY77eiWak%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;439&quot; height=&quot;22&quot; data-origin-width=&quot;439&quot; data-origin-height=&quot;22&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;fetch join을 사용하지 않는 경우에, 위와 같은 jqpl문을 돌리면&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;246&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cLnQsh/btsnnfGDF1p/hJKB8FwMeRcn5bqW1hTFq1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cLnQsh/btsnnfGDF1p/hJKB8FwMeRcn5bqW1hTFq1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cLnQsh/btsnnfGDF1p/hJKB8FwMeRcn5bqW1hTFq1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcLnQsh%2FbtsnnfGDF1p%2FhJKB8FwMeRcn5bqW1hTFq1%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;566&quot; height=&quot;246&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;246&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;N+1에서 '1'에 해당하는 쿼리가 위와 같이 나가는 것을 볼 수 있는데, 자세히 보면 Order의 단순 필드들에 대한 조회만 이루어져 영속화된다는 것을 알 수 있다. 즉 Order와 연관관계를 맺은 테이블의 필드는 조회되지 않으며, 따라서 그에 대한 접근을 필요로 할 경우 추가로 쿼리가 나가야 한다.&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;369&quot; data-origin-height=&quot;20&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WiX2u/btsnhdwJo7I/nlrlSJZGbb8nWKypLmA451/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WiX2u/btsnhdwJo7I/nlrlSJZGbb8nWKypLmA451/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WiX2u/btsnhdwJo7I/nlrlSJZGbb8nWKypLmA451/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWiX2u%2FbtsnhdwJo7I%2FnlrlSJZGbb8nWKypLmA451%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;369&quot; height=&quot;20&quot; data-origin-width=&quot;369&quot; data-origin-height=&quot;20&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번에는 fetch join을 사용해보자.&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;427&quot; data-origin-height=&quot;358&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bcltwW/btsnnTQ2Qpn/VogERqTq0YBwsQVSBD50Xk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bcltwW/btsnnTQ2Qpn/VogERqTq0YBwsQVSBD50Xk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bcltwW/btsnnTQ2Qpn/VogERqTq0YBwsQVSBD50Xk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbcltwW%2FbtsnnTQ2Qpn%2FVogERqTq0YBwsQVSBD50Xk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;427&quot; height=&quot;358&quot; data-origin-width=&quot;427&quot; data-origin-height=&quot;358&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 join을 사용했을 때와는 다르게 Order 테이블과 연관관계를 갖는 Member 테이블의 필드들까지 한번에 조회해서 영속화하는 것을 볼 수 있다. 따라서 order 리스트의 element들에 대해서 추가적인 쿼리를 날릴 필요가 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;그런데..&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그렇다면 fetch join만 사용하면 문제가 다 해결된 걸까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 나타난 사례들은 주체가 되는 엔티티인 Order를 중심으로 보았을 때 @ManyToOne 또는 @OneToOne 관계를 갖는 엔티티들에 대한 예시이다. 따라서 단순하게 fetch join해서 한꺼번에 가져오더라도 결함이 발생하지 않았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690358369100&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
@Table(name=&quot;orders&quot;)
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Order {

    @Id @GeneratedValue
    @Column(name=&quot;order_id&quot;)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;member_id&quot;)
    private Member member;

    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = &quot;delivery_id&quot;)
    private Delivery delivery;
    
    @OneToMany(mappedBy = &quot;order&quot;, cascade = CascadeType.ALL) //영속성 전이
    private List&amp;lt;OrderItem&amp;gt; orderItems = new ArrayList&amp;lt;&amp;gt;();
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1690358453545&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
@Getter @Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class OrderItem {

    @Id @GeneratedValue
    @Column(name = &quot;order_item_id&quot;)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name=&quot;item_id&quot;)
    private Item item;

    @JsonIgnore
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name=&quot;order_id&quot;)
    private Order order;
}&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;이번에는 Order가 @OneToMany관계를 가지고 있는 OrderItem이라는 엔티티를 추가했다. 말 그대로 OneToMany이므로 1개의 Order에 여러개의 OrderItem이 연관관계를 가질 수 있으므로 fetchjoin을 하면 문제가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690358703467&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public List&amp;lt;Order&amp;gt; findAllWithItem() {
    return em.createQuery(
            &quot;select o from Order o&quot; +
                    &quot; join fetch o.member m&quot; +
                    &quot; join fetch o.delivery d&quot; +
                    &quot; join fetch o.orderItems oi&quot; +
                    &quot; join fetch oi.item i&quot;, Order.class
            ).getResultList();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1084&quot; data-origin-height=&quot;205&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KifBQ/btso0sMTOLs/oCwKrNu0Sme2eB47phKgTk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KifBQ/btso0sMTOLs/oCwKrNu0Sme2eB47phKgTk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KifBQ/btso0sMTOLs/oCwKrNu0Sme2eB47phKgTk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKifBQ%2Fbtso0sMTOLs%2FoCwKrNu0Sme2eB47phKgTk%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;1084&quot; height=&quot;205&quot; data-origin-width=&quot;1084&quot; data-origin-height=&quot;205&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;위와 같이 rdbms상에서 join을 진행하면 order 하나에 여러개의 orderitems가 매핑되므로, 같은 order_id를 갖는 여러개의 행이 나오는 것을 볼 수 있다. db상에서는 컬렉션이 존재할 수 없으니까 당연한 결과이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 결과를 그대로 받아서 뿌리면, 각각의 Order가 매핑된 OrderItem 개수만큼 중복해서 나타나는 현상이 발생하게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제는 distinct 키워드를 사용하여 해결할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1690360205622&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public List&amp;lt;Order&amp;gt; findAllWithItem() {
    return em.createQuery(
            &quot;select distinct o from Order o&quot; +
                    &quot; join fetch o.member m&quot; +
                    &quot; join fetch o.delivery d&quot; +
                    &quot; join fetch o.orderItems oi&quot; +
                    &quot; join fetch oi.item i&quot;, Order.class
            ).getResultList();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;db에서 distinct는 완전히 동일한 row에 대해서만 적용되지만, &lt;b&gt;jpql에 distinct를 작성하면 hibernate가 루트 엔티티 id값이 같은 행들은 하나만 나오게 필터링&lt;/b&gt;해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;극한의 최적화&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이제 distinct 키워드를 이용한 fetch join을 이용하면 모든 문제가 해결된 것 같아 보인다. 하지만 이렇게 컬렉션을 무작정 fetch join할 경우 페이징이 불가능하다는 문제가 있다. db에서 쿼리를 실행한 이후 나온 결과값을 JPA를 사용하여 필터링을 해주다 보니, db에서는 limit, offset과 같은 페이징을 어떻게 처리해야 할지 알 수 없다.&amp;nbsp;&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;1. fetchType은 Lazy로 설정한다.&lt;br /&gt;2. *ToOne 관계를 가지는 엔티티들은 기존에 했듯이 fetch join을 사용한다.&lt;br /&gt;3. *ToMany관계를 가지는 엔티티가 존재한다면 &lt;b&gt;hibernate.default_batch_fetch_size&lt;/b&gt;를 적용해준다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;핵심은 hibernate.default_batch_fetch_size를 설정해주는건데, 이 옵션을 설정해주면 쿼리가 나갈때 연관관계를 맺는 엔티티들에 대해서 in 쿼리문을 사용하여 한번에 조회해오게 된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;1+N 문제는 루트 엔티티가 컬렉션으로 반환될 때, 연관관계를 가지는 엔티티들에 대해서 매번 추가쿼리가 나감으로서 발생했다. 하지만 default_batch_fetch_size를 설정해주면 루트 엔티티 조회시 연관관계를 갖는 엔티티들에 대해 한번에 size만큼의 조회쿼리를 날림으로서 한번에 많은 데이터를 조회해오게 해서 N개의 추가쿼리가 나가던 현상을 N/size개의 추가쿼리가 나가도록 해준다.&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;729&quot; data-origin-height=&quot;112&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ccqvdg/btso11Pqe5q/wcsZpwdteDsYmaCQI5qWa0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ccqvdg/btso11Pqe5q/wcsZpwdteDsYmaCQI5qWa0/img.png&quot; data-alt=&quot;in 쿼리를 사용하여 한꺼번에 조회해오게 한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ccqvdg/btso11Pqe5q/wcsZpwdteDsYmaCQI5qWa0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fccqvdg%2Fbtso11Pqe5q%2FwcsZpwdteDsYmaCQI5qWa0%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;729&quot; height=&quot;112&quot; data-origin-width=&quot;729&quot; data-origin-height=&quot;112&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;in 쿼리를 사용하여 한꺼번에 조회해오게 한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 방법은 distinct를 사용한 fetch join과 다르게 db로 날리는 쿼리를 조작하는 방식이므로 페이징 또한 가능하다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;정리&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;JPA를 사용했을 때 발생하는 성능 문제의 반 이상이 N+1문제라고 한다. fetch join을 적극적으로 사용하는 습관을 기르자. 물론 그것보다 중요한 것은 JPARepository 인터페이스를 이용하거나, Querydsl을 사용할 때 내부적으로 어떤 형태의 쿼리가 나갈지를 예측할 수 있어야겠지&lt;/p&gt;</description>
      <category>[ Backend ]/Spring DB, JPA</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/156</guid>
      <comments>https://eckrin.tistory.com/156#entry156comment</comments>
      <pubDate>Tue, 11 Jul 2023 15:48:23 +0900</pubDate>
    </item>
    <item>
      <title>[Spring DB] 스프링과 트랜잭션, 트랜잭션 전파</title>
      <link>https://eckrin.tistory.com/155</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;스프링의 트랜잭션&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링을 사용하면 @Transactional 어노테이션을 사용하여 선언적 트랜잭션 관리를 하게 된다. 이렇게 선언적 트랜잭션 관리 방식을 사용하게 되면 &lt;a href=&quot;https://eckrin.tistory.com/184&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;프록시 방식의 AOP&lt;/a&gt;가 동작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;그 전에 앞서 공부했던 스프링의 db 연동과정부터 차례로 살펴보면 다음과 같은 흐름을 가진다.&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-filename=&quot;blob&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;1050&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Hd2GG/btsIxI4BKbg/wr0Qi3Qu81kG4mbMzaeX80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Hd2GG/btsIxI4BKbg/wr0Qi3Qu81kG4mbMzaeX80/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Hd2GG/btsIxI4BKbg/wr0Qi3Qu81kG4mbMzaeX80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FHd2GG%2FbtsIxI4BKbg%2Fwr0Qi3Qu81kG4mbMzaeX80%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;529&quot; height=&quot;397&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;1400&quot; data-origin-height=&quot;1050&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;blockquote data-ke-style=&quot;style3&quot;&gt;0. 트랜잭션을 시작하기 위해서는 db 커넥션이 필요하다.&lt;br /&gt;1. 스프링 컨테이너에 등록된 &lt;b&gt;트랜잭션 매니저&lt;/b&gt;는 &lt;b&gt;datasource&lt;/b&gt;라는 객체를 통해서 스프링이 관리하는 커넥션 풀에서 커넥션을 획득한다.&lt;br /&gt;2. &lt;b&gt;트랜잭션 매니저&lt;/b&gt;는 &lt;b&gt;트랜잭션 컨텍스트&lt;/b&gt;를 통해 생성된 커넥션을 &lt;b&gt;트랜잭션 동기화 매니저&lt;/b&gt;에 보관한다. 트랜잭션 컨텍스트는 트랜잭션 매니저와 트랜잭션 동기화 매니저 사이에서 트랜잭션의 작업을 관리한다.&lt;br /&gt;3. 트랜잭션 동기화 매니저는 &lt;a href=&quot;https://eckrin.tistory.com/173&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ThreadLocal&lt;/a&gt;을 통해서 스레드마다 각각의 커넥션을 사용하도록 한다.&lt;br /&gt;4. Repository에서 DB 커넥션이 필요하면 데이터베이스 동기화 매니저에서 가져다가 사용한다.&lt;br /&gt;5. 비즈니스 로직이 끝나고, 트랜잭션이 종료되면 획득한 커넥션을 통해 DB에 커밋/롤백한다.&lt;br /&gt;6. 트랜잭션 동기화 매니저의 커넥션을 정리한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 &lt;b&gt;트랜잭션 매니저&lt;/b&gt;가 커넥션 획득과 반납을 담당한다면, 트랜잭션의 시작과 종료을 관리하는 객체는 '&lt;b&gt;트랜잭션 프록시&lt;/b&gt;'이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;트랜잭션 프록시&lt;/b&gt;는 트랜잭션을 적용할 클래스에 직접 접근하지 않고, 해당 클래스의 프록시를 만들어서 스프링 컨테이너에 등록한다. 이렇게 하는 이유는 일반적인 프록시 사용 이유와 비슷한데, 접근 권한과 함께 부가 기능을 활용할 수 있기 때문이다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&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;603&quot; data-origin-height=&quot;257&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cxzp1e/btsl0WX0DBx/K71BIZypEkXEPz708Uy3UK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cxzp1e/btsl0WX0DBx/K71BIZypEkXEPz708Uy3UK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cxzp1e/btsl0WX0DBx/K71BIZypEkXEPz708Uy3UK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcxzp1e%2Fbtsl0WX0DBx%2FK71BIZypEkXEPz708Uy3UK%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;472&quot; height=&quot;201&quot; data-origin-width=&quot;603&quot; data-origin-height=&quot;257&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;트랜잭션 프록시를 도입하기 이전에는 스프링 컨테이너에 등록된 Transaction Manager를 획득하고, 획득한 Transaction Manager에서 직접 트랜잭션을 얻고 commit 또는 rollback하는 코드를 삽입했다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;337&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bWISJn/btsl1znTSHf/76bJ0g5fKDIJDdkdu7SYR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bWISJn/btsl1znTSHf/76bJ0g5fKDIJDdkdu7SYR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bWISJn/btsl1znTSHf/76bJ0g5fKDIJDdkdu7SYR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbWISJn%2Fbtsl1znTSHf%2F76bJ0g5fKDIJDdkdu7SYR0%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;820&quot; height=&quot;459&quot; data-origin-width=&quot;602&quot; data-origin-height=&quot;337&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;트랜잭션 프록시를 도입한 이후에는 트랜잭션을 처리하는 객체와 비즈니스를 위한 서비스를 분리할 수 있다.&lt;span&gt;&amp;nbsp;&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;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;다양한 트랜잭션 매니저&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1748&quot; data-origin-height=&quot;678&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/J4ndw/btsGSiZCEJt/7Kt2KtHgbbj43YyIUPyP3K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/J4ndw/btsGSiZCEJt/7Kt2KtHgbbj43YyIUPyP3K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/J4ndw/btsGSiZCEJt/7Kt2KtHgbbj43YyIUPyP3K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FJ4ndw%2FbtsGSiZCEJt%2F7Kt2KtHgbbj43YyIUPyP3K%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;1748&quot; height=&quot;678&quot; data-origin-width=&quot;1748&quot; data-origin-height=&quot;678&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;PlatformTransactionManager&lt;/b&gt;: 모든 트랜잭션 매니저의 상위 인터페이스이다. 트랜잭션 관리를 위한 기본 인터페이스를 제공한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;ChainedTransactionManager&lt;/b&gt;: 여러개의 다른 트랜잭션 매니저를 연결해서 사용하기 위하여 사용하는 클래스이다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;-&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Abstract&lt;/b&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;&lt;b&gt;PlatformTransactionManager&lt;/b&gt;: 트랜잭션 관리자를 만들기 위한 추상 클래스이다. 이를&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;JpaTransactionManager,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;DatasourceTransactionManager,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #fafafa; color: #333333; text-align: start;&quot;&gt;JtaTransactionManager 등으로 구현하여 JPA, JDBC, JTA와 같은 다양한 데이터 액세스 기술을 지원한다.&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;예외 발생과 트랜잭션&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이전에 정리(&lt;a href=&quot;https://eckrin.tistory.com/144&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://eckrin.tistory.com/144&lt;/a&gt;)한 적 있듯이 예외는 체크 예외와 언체크 예외로 구분된다. 그런데 트랜잭션 진행 중 예외가 발생했다면 그것의 종류에 따라 처리 방법(커밋/롤백)이 달라지게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;365&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/DWvhZ/btsl0T7ZO7T/IktWFY9LDyjRHihkqbKAck/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/DWvhZ/btsl0T7ZO7T/IktWFY9LDyjRHihkqbKAck/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/DWvhZ/btsl0T7ZO7T/IktWFY9LDyjRHihkqbKAck/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FDWvhZ%2Fbtsl0T7ZO7T%2FIktWFY9LDyjRHihkqbKAck%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;608&quot; height=&quot;365&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;365&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;- 체크 예외&lt;/b&gt;: RuntimeException을 제외한 Exception 하위 단계의 예외들을 말하며, 비즈니스적 의미가 있을 경우 사용된다. 체크 예외 발생시 진행중인 트랜잭션은 커밋된다. 체크 예외 발생시에도 롤백하고 싶다면 @Transactional 어노테이션에 'rollbackFor = ' 옵션을 설정해주면 된다.&lt;br /&gt;&lt;b&gt;- 언체크 예외&lt;/b&gt;: RuntimeException의 하위 Exception들을 말하며, 시스템 오류나 네트워크 오류 등 복구 불가능한 예외들을 말한다. 언체크 예외 발생시 진행중인 트랜잭션은 롤백된다.&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;나는 일반적으로 서비스 로직 개발 시에 발생할 수 있는 오류(유저가 존재하지 않는다거나, 입력 양식에 오류가 있다던가)들에 대해서 언체크 예외를 사용했는데(RuntimeException을 상속받아 커스텀 예외를 만들어 사용), 비즈니스적인 오류에 대해서는 체크 예외를 사용해야 한다길래 고민을 조금 해보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;내 결론은&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;거래 서비스를 가정하자. 만약 유저가 존재하지 않아서 예외를 터트리고 비즈니스 로직을 종료해야 하는 상황이라면 기존에 해왔던 것처럼 런타임에러를 발생시키면 될 것이다. 하지만 만약 해당 예외가 발생했을 경우 진행중인 로직을 유지하고 예외를 처리하겠다고 마음먹으면 체크 예외를 발생시키고 잡아서 처리해주면 된다. 간단한 예시를 들자면 다음과 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1688305706124&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Transactional
fun service1() {
  //service logic
  ... ?: throw CustomUncheckedException(&quot;유저가 존재하지 않습니다&quot;)
}  

@Transactional
fun service2() {
  //service logic
  if(문제 발생시) {
    order.payStatus = PayEnumType.STANDBY
    throw CustomCheckedException(&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;첫 번째 케이스는 유저가 존재하지 않으면 에러를 터트리고 서비스 로직을 종료시킨다. 따라서 클라이언트 쪽으로는 JSON등을 이용하여 커스텀된 오류 메세지가 전달될 것이고, 트랜잭션은 rollback되는 것이 맞다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 케이스는 유저가 존재하지 않으면 에러를 터트리되, 거래 서비스를 종료하는 것이 아니라 대기상태로 전환하는 것을 목표로 한다. 이 경우 생성된 order정보가 db에 남으며, &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;고객은 해당 서비스를 이어서 작성하는 등 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;color: #333333; text-align: start;&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;&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;트랜잭션 관리 (HikariPool)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;스프링은 DB와의 연결을 관리하기 위하여 히카리(Hikari)라는 커넥션 풀을 사용한다. DB와 연결하는 작업은 적지 않은 자원을 사용하므로 일정량의 jdbc 커넥션을 connection들을 미리 생성해두고, 어떤 공간(pool)에 담아두고 꺼내 사용하게 된다. db요청이 들어오면 쓰레드는 hikari에 커넥션을 요청하고, hikari는 커넥션 풀에서 만들어진 커넥션을 꺼내 프록시의 형태로 제공하는 식이다.&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;684&quot; data-origin-height=&quot;40&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dcKtJw/btsmkJvA3XF/3WVMLBUp1DfKg9BUVVCr21/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dcKtJw/btsmkJvA3XF/3WVMLBUp1DfKg9BUVVCr21/img.png&quot; data-alt=&quot;set log visible&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dcKtJw/btsmkJvA3XF/3WVMLBUp1DfKg9BUVVCr21/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdcKtJw%2FbtsmkJvA3XF%2F3WVMLBUp1DfKg9BUVVCr21%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;684&quot; height=&quot;40&quot; data-origin-width=&quot;684&quot; data-origin-height=&quot;40&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;set log visible&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1001&quot; data-origin-height=&quot;66&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cJltan/btsmlm1bORA/cEomlHtL384QlEC7KYbSL0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cJltan/btsmlm1bORA/cEomlHtL384QlEC7KYbSL0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cJltan/btsmlm1bORA/cEomlHtL384QlEC7KYbSL0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcJltan%2Fbtsmlm1bORA%2FcEomlHtL384QlEC7KYbSL0%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;1001&quot; height=&quot;66&quot; data-origin-width=&quot;1001&quot; data-origin-height=&quot;66&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;트랜잭션을 요청해보면 이렇게 Hikari가 conn0이라는 커넥션을 제공하는데, 실제 객체 그대로 제공하는 것이 아니라 HikariProxyConnection@1626288305와 같이 프록시 형태로 실제 커넥션을 감싸서 제공하는 것을 로그로 확인할 수 있다.&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;953&quot; data-origin-height=&quot;42&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bVuRMk/btsmiD3Wfrw/vmHMRiaAKvIEqdY2DJdPY1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bVuRMk/btsmiD3Wfrw/vmHMRiaAKvIEqdY2DJdPY1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bVuRMk/btsmiD3Wfrw/vmHMRiaAKvIEqdY2DJdPY1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbVuRMk%2FbtsmiD3Wfrw%2FvmHMRiaAKvIEqdY2DJdPY1%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;953&quot; height=&quot;42&quot; data-origin-width=&quot;953&quot; data-origin-height=&quot;42&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 히카리 프록시 커넥션과 동일한 물리 커넥션을 사용하지만, 프록시 객체의 주소가 다르므로 둘은 다른 커넥션이며, 생성된 트랜잭션도 다르다고 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;트랜잭션 전파 - REQUIRED&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그렇다면 하나의 트랜잭션 실행 중 다른 트랜잭션이 실행하면 어떻게 동작할까? 트랜잭션 전파 옵션에 따라 다르지만, 기본 옵션인 REQUIRED 기준으로 설명하면, 이미 진행중인 트랜잭션(외부 트랜잭션)이 존재하는 상황에 트랜잭션이 추가로 동시 수행될 경우(내부 트랜잭션) 모두 하나의 물리 트랜잭션으로 묶어서 관리한다. 이를 진행중인 트랜잭션에 참여(participate)한다라고 표현하기도 하며, 새로운 커넥션을 생성하지 않고 기존의 트랜잭션을 사용한다는 것을 의미한다. 즉 맨 처음 시작된 논리 트랜잭션 커밋 이전의 논리 트랜잭션 커밋도 모두 물리적으로는 무시된다.&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;661&quot; data-origin-height=&quot;31&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bwOPyt/btsmiHyB24H/n7FAwRLqLGuWAT3IVGSY6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bwOPyt/btsmiHyB24H/n7FAwRLqLGuWAT3IVGSY6k/img.png&quot; data-alt=&quot;진행중인 트랜잭션에 참여 (별다른 물리적 동작은 X)&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bwOPyt/btsmiHyB24H/n7FAwRLqLGuWAT3IVGSY6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbwOPyt%2FbtsmiHyB24H%2Fn7FAwRLqLGuWAT3IVGSY6k%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;661&quot; height=&quot;31&quot; data-origin-width=&quot;661&quot; data-origin-height=&quot;31&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;진행중인 트랜잭션에 참여 (별다른 물리적 동작은 X)&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;336&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ct5BYr/btsml3tJjH4/iUFBe3EVr2bktPNKHJfW80/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ct5BYr/btsml3tJjH4/iUFBe3EVr2bktPNKHJfW80/img.png&quot; data-alt=&quot;두번째 트랜잭션이 시작할때도 트랜잭션 매니저를 통해서 새로운 트랜잭션 시작을 요청하지만, 트랜잭션 매니저는 이미 동작하는 트랜잭션이 존재함을 확인한 뒤 신규 트랜잭션을 반환하지 않는다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ct5BYr/btsml3tJjH4/iUFBe3EVr2bktPNKHJfW80/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fct5BYr%2Fbtsml3tJjH4%2FiUFBe3EVr2bktPNKHJfW80%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;722&quot; height=&quot;400&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;336&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;두번째 트랜잭션이 시작할때도 트랜잭션 매니저를 통해서 새로운 트랜잭션 시작을 요청하지만, 트랜잭션 매니저는 이미 동작하는 트랜잭션이 존재함을 확인한 뒤 신규 트랜잭션을 반환하지 않는다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 처음 시작한 논리 트랜잭션(=외부 트랜잭션)과 이후 시작한 논리 트랜잭션(내부 트랜잭션)은 모두 물리적으로 하나의 트랜잭션으로 묶여있기에, 두 트랜잭션 진행 중 커넥션을 요청할 경우 트랜잭션 동기화 매니저에서 동일한 커넥션을 획득하게 된다. 한마디로&lt;b&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;1002&quot; data-origin-height=&quot;31&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bgMIL5/btsmj9hNTRT/uoF1bC6QmmqS7RFR6sEjuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bgMIL5/btsmj9hNTRT/uoF1bC6QmmqS7RFR6sEjuK/img.png&quot; data-alt=&quot;내부 트랜잭션 롤백시 트랜잭션 동기화 매니저에 rollbackOnly=true 옵션을 설정(마킹)한다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bgMIL5/btsmj9hNTRT/uoF1bC6QmmqS7RFR6sEjuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbgMIL5%2Fbtsmj9hNTRT%2FuoF1bC6QmmqS7RFR6sEjuK%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;1002&quot; height=&quot;31&quot; data-origin-width=&quot;1002&quot; data-origin-height=&quot;31&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;내부 트랜잭션 롤백시 트랜잭션 동기화 매니저에 rollbackOnly=true 옵션을 설정(마킹)한다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&amp;nbsp;물리 트랜잭션&lt;/b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;내에 존재하는 여러개의 트랜잭션들은&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;b&gt;논리 트랜잭션&lt;/b&gt;이라고 하며, 물리 트랜잭션이 커밋되기 위해서는 내부의 모든 논리 트랜잭션이 커밋되어야 한다. 하나의 논리 트랜잭션이라도 롤백되면 물리 트랜잭션도 롤백된다. 내부 트랜잭션이 커밋되고 외부 트랜잭션이 롤백되면 문제가 발생하지 않지만, 내부 트랜잭션이 롤백되면 트랜잭션 동기화 매니저에 rollbackOnly = true옵션이 설정되는데 이 상태에서 외부 트랜잭션 커밋을 시도하면 UnexpectedRollbackException이 발생하며 외부 트랜잭션도 롤백되었음을 알려준다.&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;608&quot; data-origin-height=&quot;361&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/buDXTS/btsml5rSacX/xSZwErc3hx93FGiwmfc8eK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/buDXTS/btsml5rSacX/xSZwErc3hx93FGiwmfc8eK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/buDXTS/btsml5rSacX/xSZwErc3hx93FGiwmfc8eK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbuDXTS%2Fbtsml5rSacX%2FxSZwErc3hx93FGiwmfc8eK%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;728&quot; height=&quot;432&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;361&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;트랜잭션 전파 - REQUIRES_NEW&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;앞서 설명한 동작은 트랜잭션이 REQUIRED방식으로 동작할 경우에 수행된다. 하지만 내부 트랜잭션 수행시 REQUIRES_NEW 옵션을 주어 실행하게 되면 내부 트랜잭션 실행시 기존 트랜잭션을 무시하고 새로운 물리 트랜잭션을 시작하게 되어, 외부와 내부 트랜잭션이 별도의 물리 트랜잭션을 사용한다.(= 다른 데이터베이스 커넥션을 사용한다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1688386603720&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;DefaultTransactionAttribute definition = new DefaultTransactionAttribute();
definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); //REQUIRES_NEW -&amp;gt; 기존 트랜잭션을 무시하고 새 트랜잭션 생성
TransactionStatus inner = txManager.getTransaction(definition);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;733&quot; data-origin-height=&quot;37&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwucuz/btsml3Vamcz/nFmpJ6AlhlCtU4NH31FVaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwucuz/btsml3Vamcz/nFmpJ6AlhlCtU4NH31FVaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwucuz/btsml3Vamcz/nFmpJ6AlhlCtU4NH31FVaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcwucuz%2Fbtsml3Vamcz%2FnFmpJ6AlhlCtU4NH31FVaK%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;733&quot; height=&quot;37&quot; data-origin-width=&quot;733&quot; data-origin-height=&quot;37&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;759&quot; data-origin-height=&quot;37&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/lWiSD/btsmlh0pt2L/kdMKoPUC2RP7LmcaaiAE9K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/lWiSD/btsmlh0pt2L/kdMKoPUC2RP7LmcaaiAE9K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/lWiSD/btsmlh0pt2L/kdMKoPUC2RP7LmcaaiAE9K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FlWiSD%2Fbtsmlh0pt2L%2FkdMKoPUC2RP7LmcaaiAE9K%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;759&quot; height=&quot;37&quot; data-origin-width=&quot;759&quot; data-origin-height=&quot;37&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;위 첫 번째 로그는 외부 트랜잭션 실행시 나오는 로그이고, 밑의 로그는 내부 트랜잭션 실행시 나오는 로그이다. REQUIRED옵션과 다르게 두 트랜잭션이 실행된 db커넥션 이름도 다르다.(conn0, conn1). 즉 내부 트랜잭션 실행시 별도의 물리적 커넥션을 얻어와 별도의 트랜잭션을 만들었음을 알 수 있다.&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;606&quot; data-origin-height=&quot;384&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/WL4kc/btsl9LIRpQE/UzZnYVr3RkaigMYkkvbNyk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/WL4kc/btsl9LIRpQE/UzZnYVr3RkaigMYkkvbNyk/img.png&quot; data-alt=&quot;내부/외부 트랜잭션이 별도의 커넥션을 가지는 것을 볼 수 있다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/WL4kc/btsl9LIRpQE/UzZnYVr3RkaigMYkkvbNyk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWL4kc%2Fbtsl9LIRpQE%2FUzZnYVr3RkaigMYkkvbNyk%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;745&quot; height=&quot;472&quot; data-origin-width=&quot;606&quot; data-origin-height=&quot;384&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;내부/외부 트랜잭션이 별도의 커넥션을 가지는 것을 볼 수 있다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;REQUIRES_NEW와 같은 방법은 각각의 트랜잭션을 물리적으로 별도로 처리할 수 있다는 장점을 갖지만, 하나의 요청 로직으로 인해 여러개의 db커넥션이 사용될 수 있기 때문에 물리적 트랜잭션이 빠르게 반환되지 않는다면 트랜잭션 동기화 매니저에 존재하는 커넥션들이 빠르게 고갈될 수 있다는 문제가 있다. 따라서 일반적으로 REQUIRED옵션을 기본 옵션으로 많이 사용한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;트랜잭션 전파 심화&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;다음과 같은 상황을 생각하자. 회원가입 로직을 로그로 남기기 위하여, db에 로그 테이블을 만들고 저장하는 과정을 서비스에서 한꺼번에 진행한다. 이때 당연하게도 로그를 작성하는 동안 문제가 발생했다고 비즈니스 로직이 롤백되어서는 안된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1688842571298&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//AuthService.java
@Transactional
public void signIn(String username) {
    Member member = new Member(username);
    Log logMessage = new Log(username);

    memberRepository.save(member);
    logRepository.save(logMessage);
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1688842640198&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//LogRepository.java
@Transactional
public void save(Log logMessage) {
    em.persist(logMessage);
}&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1688842661101&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//MemberRepository.java
@Transactional
public void save(Member member) {
    em.persist(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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 말해 로그를 작성하는 로직에서 예외가 터지면, 그냥 아래 코드처럼 그걸 받아서 정상 흐름으로 변환시켜주고, 회원가입 프로세스에서 예외가 터지면 정상적으로 트랜잭션이 롤백되어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 하면 문제를 해결할 수 있을까?&lt;/p&gt;
&lt;pre id=&quot;code_1688842866976&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Transactional
public void signIn(String username) {
    Member member = new Member(username);
    Log logMessage = new Log(username);

    memberRepository.save(member);
    try {
        logRepository.save(logMessage);
    } catch(RuntimeException e) {
        //에러를 받아 정상로직으로 변환
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;897&quot; data-origin-height=&quot;484&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cuin1l/btsmPAml6m1/jpgotxIZZ5nQg9mkhEr3uK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cuin1l/btsmPAml6m1/jpgotxIZZ5nQg9mkhEr3uK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cuin1l/btsmPAml6m1/jpgotxIZZ5nQg9mkhEr3uK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcuin1l%2FbtsmPAml6m1%2FjpgotxIZZ5nQg9mkhEr3uK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;764&quot; height=&quot;412&quot; data-origin-width=&quot;897&quot; data-origin-height=&quot;484&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;앞서 얘기했듯, Service와 Repository 각각의 함수에 @Transactional을 걸어두고, Service 안의 메소드에서 Repository의 메소드를 호출하는 식으로 동작하면, Repository의 로직에서 에러가 발생하여 롤백되는 순간 트랜잭션 동기화 매니저의 해당 커넥션에는 rollbackOnly=true옵션이 설정되어 버린다.&lt;/p&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;즉, Service 코드에서 예외가 터지지 않는다고 해도 Repository에서 발생한 예외로 인해서 이미 물리 트랜잭션의 롤백은 확정되어 버린 것이다. 결국 에러를 처리했다고 생각하고 물리 트랜잭션을 커밋하는 순간 아래 그림처럼 UnexpectedRollbackException이 터지며 물리 트랜잭션이 롤백된다.&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;897&quot; data-origin-height=&quot;475&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/pY4Ab/btsmO5TT9Gj/f5misqv7R7eMsFEjVABYLk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/pY4Ab/btsmO5TT9Gj/f5misqv7R7eMsFEjVABYLk/img.png&quot; data-alt=&quot;논리 트랜잭션 실패로 인한 rollbackOnly = true 옵션 설정 -&amp;amp;gt; 물리 트랜잭션 롤백&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/pY4Ab/btsmO5TT9Gj/f5misqv7R7eMsFEjVABYLk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FpY4Ab%2FbtsmO5TT9Gj%2Ff5misqv7R7eMsFEjVABYLk%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;784&quot; height=&quot;415&quot; data-origin-width=&quot;897&quot; data-origin-height=&quot;475&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;논리 트랜잭션 실패로 인한 rollbackOnly = true 옵션 설정 -&amp;gt; 물리 트랜잭션 롤백&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;LogRepository라도 상황이 다르지 않을 것이다. 로그를 남기는 과정에서 예외가 터지면 MemberService의 트랜잭션까지 롤백되게 되는데, 이럴때 REQUIRES_NEW 옵션을 이용할 수 있다.&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;background-color: #fcfcfc; color: #333333; text-align: left;&quot;&gt;&amp;nbsp;@Transaction(propagation = REQUIRES_NEW)옵션을 적용한다면 각각의 내부 트랜잭션이 각각 새로운 커넥션을 할당받아 별도의 물리 트랜잭션으로 동작할 것이므로 예외가 발생한 LogRepository의 트랜잭션을 제외한 나머지 트랜잭션은 롤백되지 않을 것이다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>[ Backend ]/Spring DB, JPA</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/155</guid>
      <comments>https://eckrin.tistory.com/155#entry155comment</comments>
      <pubDate>Sun, 2 Jul 2023 22:25:13 +0900</pubDate>
    </item>
    <item>
      <title>[JPA] 쓰기 지연으로 인한 서비스 오류 경험</title>
      <link>https://eckrin.tistory.com/154</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;구체적인 상황은 다음과 같다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Admin 페이지에서 POST api 요청이 올때마다 db에서 &lt;b&gt;기존 데이터들을 모두 제거한 후에&amp;nbsp;request body로 들어온 데이터들을 저장&lt;/b&gt;해주는 로직을 기대하고 다음과 같은 코드를 작성하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1687316849419&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;featureRepository.deleteAll()
sectionRepository.deleteAll()

//req로 들어온 데이터 저장
...&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;하지만 클라이언트 qa중 500 server error가 발생한다는 말을 듣고 찾아보니 다음과 같은 로그가 남겨져 있는 것을 볼 수 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;38&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dp7hT7/btskUL81Htk/YUNkqnmxBSlex4b2bszOS0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dp7hT7/btskUL81Htk/YUNkqnmxBSlex4b2bszOS0/img.png&quot; data-alt=&quot;Duplicate entry ~ for key ~&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dp7hT7/btskUL81Htk/YUNkqnmxBSlex4b2bszOS0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdp7hT7%2FbtskUL81Htk%2FYUNkqnmxBSlex4b2bszOS0%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;640&quot; height=&quot;38&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;38&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Duplicate entry ~ for key ~&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;엔티티에서 unique값으로 설정해놓은 필드가 중복된다는 뜻이었다. 그런데 삽입된 데이터는 중복이 아예 없는데, 이상하다고 생각하면서 코드를 다시 보던 중 JPA 쓰기 지연과 관련된 문제일 수 있겠다는 생각이 들어 찾아보니 문제가 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;867&quot; data-origin-height=&quot;257&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Qs68Z/btskS2wBJMp/KQP5LhUVjgpqHO9jGvCeZ0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Qs68Z/btskS2wBJMp/KQP5LhUVjgpqHO9jGvCeZ0/img.png&quot; data-alt=&quot;알아도 까먹었으면 넌 모르는거다&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Qs68Z/btskS2wBJMp/KQP5LhUVjgpqHO9jGvCeZ0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FQs68Z%2FbtskS2wBJMp%2FKQP5LhUVjgpqHO9jGvCeZ0%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;867&quot; height=&quot;257&quot; data-origin-width=&quot;867&quot; data-origin-height=&quot;257&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;알아도 까먹었으면 넌 모르는거다&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;기존 코드는 repository에서 delete쿼리를 날리고 뒤에 insert쿼리가 날아가는 것을 의도하고 만들었는데, delete와 insert 사이에 트랜잭션이 종료(커밋)될 일이 발생하지 않으면 SQL이 바로 날아가지 않는다.&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt; jpa는 entitymanager(영속성 컨텍스트)에 저장된(영속화된) 엔티티를 기준으로 쿼리를 쓰기 버퍼에 모아놓고, 트랜잭션 커밋시 모아놓은 쿼리를 날린다. 그런데 이때 날아가는 쿼리는 순서대로 날아가는 것이 아니라, hibernate가 지정한 순서대로 날아간다. 그걸 설명한게 위의 사진이다.&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: #333333; text-align: start;&quot;&gt;(참고: &lt;/span&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;a href=&quot;https://eckrin.tistory.com/18&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://eckrin.tistory.com/18)&lt;/a&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1687356194636&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[JPA] JPA 구조, 특성&quot; data-og-description=&quot;- DB와 객체를 어떻게 설계하여 매핑(Mapping)할 것인가? - 내부에서 JPA는 어떻게 동작할까? 어플리케이션의 개발시 하나의 EntityManagerFactory를 통해서 고객의 요청(트랜잭션)이 올때마다 EntityManager가&quot; data-og-host=&quot;eckrin.tistory.com&quot; data-og-source-url=&quot;https://eckrin.tistory.com/18&quot; data-og-url=&quot;https://eckrin.tistory.com/18&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/CeZpX/hyS5xPOK5J/0l2PKr9aHSxR2veQUFfWc0/img.png?width=701&amp;amp;height=416&amp;amp;face=0_0_701_416,https://scrap.kakaocdn.net/dn/bboneR/hyS5suc4T9/BU7akPA9ZKf56ala8K7Lj1/img.png?width=701&amp;amp;height=416&amp;amp;face=0_0_701_416,https://scrap.kakaocdn.net/dn/bXayW8/hyS5qb5yGl/36PzjcAYpkHBNu66LHMEk1/img.png?width=701&amp;amp;height=416&amp;amp;face=0_0_701_416&quot;&gt;&lt;a href=&quot;https://eckrin.tistory.com/18&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://eckrin.tistory.com/18&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/CeZpX/hyS5xPOK5J/0l2PKr9aHSxR2veQUFfWc0/img.png?width=701&amp;amp;height=416&amp;amp;face=0_0_701_416,https://scrap.kakaocdn.net/dn/bboneR/hyS5suc4T9/BU7akPA9ZKf56ala8K7Lj1/img.png?width=701&amp;amp;height=416&amp;amp;face=0_0_701_416,https://scrap.kakaocdn.net/dn/bXayW8/hyS5qb5yGl/36PzjcAYpkHBNu66LHMEk1/img.png?width=701&amp;amp;height=416&amp;amp;face=0_0_701_416');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[JPA] JPA 구조, 특성&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;- DB와 객체를 어떻게 설계하여 매핑(Mapping)할 것인가? - 내부에서 JPA는 어떻게 동작할까? 어플리케이션의 개발시 하나의 EntityManagerFactory를 통해서 고객의 요청(트랜잭션)이 올때마다 EntityManager가&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;eckrin.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;결국 처음에 실행한 delete문 대신, 뒤에서 나온 insert문이 먼저 나와서 'unique 컬럼에 대해서 이미 존재하는 필드값이 포함된 데이터를 삽입했다'라면서 에러를 낸 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1687354285434&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;featureRepository.deleteAllInBatch()
sectionRepository.deleteAllInBatch()

featureRepository.flush()
sectionRepository.flush()

println(&quot;feature:${featureRepository.findAll().size}&quot;)
println(&quot;section:${sectionRepository.findAll().size}&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;delete문 직후 flush를 호출하여 db와 싱크를 맞춰주면, 내가 의도한 순서대로 동작하게 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 deleteAll() 같은경우 iter문을 통해서 리스트의 element마다 삭제 쿼리가 나가는데, 대신 &lt;b&gt;deleteAllInBatch&lt;/b&gt;() 메소드를 사용하게 되면 delete * from table; 과 같이 한번에 모두 삭제하는 쿼리 하나가 나가므로 네트워크 통신상의 이득도 볼 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>[ Backend ]/Spring DB, JPA</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/154</guid>
      <comments>https://eckrin.tistory.com/154#entry154comment</comments>
      <pubDate>Wed, 21 Jun 2023 12:00:47 +0900</pubDate>
    </item>
    <item>
      <title>[DB] Redis</title>
      <link>https://eckrin.tistory.com/153</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;mysql에 이어 두 번째 데이터베이스 익히기&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://redis.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://redis.io/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1691922096849&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;Redis&quot; data-og-description=&quot;Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache, and message broker&quot; data-og-host=&quot;redis.io&quot; data-og-source-url=&quot;https://redis.io/&quot; data-og-url=&quot;https://redis.io/&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://redis.io/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://redis.io/&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;Redis&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache, and message broker&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;redis.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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;NoSQL&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;'데이터베이스에는 관계형 데이터베이스(RDBMS) 이외에도 여러가지 형태의 데이터베이스가 존재한다' 라고 데이터베이스 전공수업때 배운 적이 있다. 지금까지 내가 써온 Mysql은 &lt;b&gt;관계형 데이터베이스(RDBMS)&lt;/b&gt;의 대표적인 예시로, SQL이라고 불리는 쿼리에 의해 저장되며, 모든 데이터는 정해진(정적) 스키마에 맞추어 테이블 형태로 저장되며, 테이블은 열과 행으로 구성되어 관계를 표현한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;b&gt;NoSQL&lt;/b&gt;은 &quot;Not Only&quot; SQL이라는 뜻이다. 데이터베이스의 활용도가 늘어남에 따라 자연스럽게 데이터량과 트래픽은 증가하게 되는데, 이를 감당하기 위한 &lt;span style=&quot;color: #953b34;&quot;&gt;Scale-up&lt;/span&gt;은 리소스가 많이 들어간다는 단점을 가진다. 예를 들어 MySQL만을 사용하던 시스템이 DB가 감당할 수 있는 양 이상의 트래픽을 받아 timeout이 발생하는 등의 오류가 발생한다고 가정하자. 이와 같은 상황에서 선택할 수 있는 해결책은 2가지정도가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 방법 1. 서비스를 중단하고, 서버에 더 많은 메모리와 CPU를 증설한 뒤 서비스를 재시작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp; 방법 2. 서비스를 중단하고, 더 나은 성능을 가진 새로운 서버를 가져와서 데이터를 옮긴다음 서비스를 재시작한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;두 가지 방법 모두 서버 성능의 향상을 위해서는 서비스의 중단이 뒤따른다는 문제가 있다. 또한 하나의 장비는 반드시 물리적인 성능 한계를 가질 수 밖에 없고, 그때마다 서비스를 중단하고 교체해주어야 된다는 해결할 수 없는 근본적인 문제가 존재한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 일반적으로 Scale-up 대신 &lt;span style=&quot;color: #953b34;&quot;&gt;Scale-out&lt;/span&gt;하는 방식으로 업그레이드 하기 위한 목적으로 NoSQL이 많이 사용되고 있다. 유명한 NoSQL로는 MongoDB, Redis 등이 있는데, 이러한 NoSQL Database들은 다양한 형태의 저장 기술을 지원하여 RDBMS의 특징인 '정적인 스키마'에서 벗어나 수평적 확장을 가능하게 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #953b34;&quot;&gt;*&lt;/span&gt; scale up은 서버 교체 등을 통해서 처리 능력을 향상시키는 것이고, scale out은 서버의 대수를 늘리는 등 수평적인 확장을 통해서 처리능력을 향상시키는 방식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;CAP theory&lt;/span&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;CAP이라는 이론이 있다. 간단하게 정리하면 &quot;&lt;b&gt;분산 컴퓨터 시스템&lt;/b&gt;에서 일관성(Consistency), 가용성(Availability), 분산 허용(Partitioning Tolerance)의 3가지 특징중 최대 2가지만 지원할 수 있다&quot; 라는 내용이다.&lt;span style=&quot;color: #333333;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;분산 컴퓨터 시스템&lt;/span&gt;이란 다중 시스템 환경에서 소프트웨어가 작동하는 것을 말한다.&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 일관성(Consistency): 여러 하드웨어에서 같은 시간에 조회하는 데이터는 동일한 데이터임을 보증해야 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 가용성(Availability): 클러스터링된 노드 일부가 실패하더라도 항상 응답이 가능해야 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;- 분산 허용(Partitioning Tolerance): 클러스터링 노드 간 네트워크에 에러가 발생하더라도 정상적으로 서비스를 수행해야 한다.&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p style=&quot;color: #333333; text-align: start;&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;대부분의 NoSQL은 분산 환경에서 동작하도록 설계되어 있다. 따라서 동일한 데이터가 물리적으로 다른 여러 하드웨어에 저장되고 조회된다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Redis&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;위에서 설명한 NoSQL 중에서 가장 유명한 dbms중 하나인 redis는 &lt;span style=&quot;background-color: #fcfcfc; color: #666666; text-align: left;&quot;&gt;&lt;b&gt;key-value구조의 비정형 데이터&lt;/b&gt;를 저장하고 관리하기 위한 i&lt;b&gt;n-memory 데이터 구조&lt;/b&gt;를 가진 dbms이다. rdbms의 과부하를 방지하기 위한 캐시 서버로 유용하게 사용된다. 메모리에서 데이터를 처리하기에 MySQL보다 월등히 빠른 속도를 가지며, 단순한 key-value의 형태가 아닌, value가 String, Lists, Sorted Sets, Hashes와 같은 다양한 자료구조를 지원한다. 또한 싱글 스레드환경에서 동작하기에 동기화를 신경쓰지 않아도 되지만, keys *와 같이 O(N)의 시간복잡도를 갖는 명령어 사용을 주의해야 한다.&amp;nbsp;&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;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;String&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;redis의 가장 큰 장점중에 하나는 여러가지 자료구조를 지원한다는 사실이다. 가장 먼저 알아볼 자료구조는 String이다. 이름이 String이더라도 Integer, Long과 같은 숫자나 이진 데이터도 저장할 수 있다. (정수, 실수형같은 타입 구분이 없다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1691926268960&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ set &amp;lt;key&amp;gt; &amp;lt;value&amp;gt;

$ get &amp;lt;key&amp;gt;
$ &amp;lt;value&amp;gt;

$ del &amp;lt;key&amp;gt;&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;key값은 unique하며, 동일한 key값을 가지는 쌍을 set하면 value값이 업데이트된다. java 라이브러리의 HashMap과 동일해보인다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Sorted Set&lt;/b&gt;&lt;/h3&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;785&quot; data-origin-height=&quot;616&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/w7IxT/btsq69wjAh1/KkF1fqLda0N7YJKk3Am2X0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/w7IxT/btsq69wjAh1/KkF1fqLda0N7YJKk3Am2X0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/w7IxT/btsq69wjAh1/KkF1fqLda0N7YJKk3Am2X0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fw7IxT%2Fbtsq69wjAh1%2FKkF1fqLda0N7YJKk3Am2X0%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;551&quot; height=&quot;432&quot; data-origin-width=&quot;785&quot; data-origin-height=&quot;616&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;redis의 Sorted Set(zSet)은 정렬된 집합 데이터 구조로, key, member, score라는 3가지 용어가 나온다. zSet또한 key-value형태의 자료구조와 유사한데, member는 key, score는 value를 의미하며 반드시 숫자로 입력해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉 Redis의 zSet에서의 key는 zSet 자체를 의미하며, member와 score가 해당 zSet의 key와 value역할을 하는 것이다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하나의 zSet에서 member는 unique하며, member값을 통해 O(logN)의 시간복잡도로 score에 접근할 수 있다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;311&quot; data-origin-height=&quot;81&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/T1Vk5/btsqZvudLQH/PUHQgWmskpfDEjOVseqk81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/T1Vk5/btsqZvudLQH/PUHQgWmskpfDEjOVseqk81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/T1Vk5/btsqZvudLQH/PUHQgWmskpfDEjOVseqk81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FT1Vk5%2FbtsqZvudLQH%2FPUHQgWmskpfDEjOVseqk81%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;311&quot; height=&quot;81&quot; data-origin-width=&quot;311&quot; data-origin-height=&quot;81&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Redis Hash&lt;/b&gt;&lt;/h3&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;419&quot; data-origin-height=&quot;203&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bMhJy5/btszeKKzLTB/T3iLY8etk2buWltq3mi4w1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bMhJy5/btszeKKzLTB/T3iLY8etk2buWltq3mi4w1/img.png&quot; data-alt=&quot;https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&amp;amp;amp;blogId=wideeyed&amp;amp;amp;logNo=221428664697&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bMhJy5/btszeKKzLTB/T3iLY8etk2buWltq3mi4w1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbMhJy5%2FbtszeKKzLTB%2FT3iLY8etk2buWltq3mi4w1%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;419&quot; height=&quot;203&quot; data-origin-width=&quot;419&quot; data-origin-height=&quot;203&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&amp;amp;blogId=wideeyed&amp;amp;logNo=221428664697&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Redis의 Hash는 위와 같이 하나의 key 아래에 여러개의 field-value쌍이 존재하고 있다. 자바의 HashMap과 유사한데, HashMap에서는 key-value라고 용어를 쓰는데 redis에는 카테고리를 key라고 부르고, 쌍을 field-value라고 하니 혼동하지 말자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Hash에서 주로 사용하는 명령어는 다음과 같이 HSET, HGET, HGETALL이 있는데, HSET은 hash의 field-value쌍을 설정하는 명령어이고, HGET은 하나의 key 밑에 존재하는 field에 대한 value값을 조회 할 수 있는 명령어이며 HGETALL은 하나의 key밑에 존재하는 모든 field-value쌍을 가져올 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 예시를 보면 이해가 어렵지 않을 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;Screenshot 2023-10-27 at 4.10.33 PM.png&quot; data-origin-width=&quot;1220&quot; data-origin-height=&quot;692&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bvLeha/btszkTrFWjT/CVVhLKAF79RTyExX9ZVbm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bvLeha/btszkTrFWjT/CVVhLKAF79RTyExX9ZVbm0/img.png&quot; data-alt=&quot;https://redis.io/docs/data-types/hashes/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bvLeha/btszkTrFWjT/CVVhLKAF79RTyExX9ZVbm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbvLeha%2FbtszkTrFWjT%2FCVVhLKAF79RTyExX9ZVbm0%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;628&quot; height=&quot;356&quot; data-filename=&quot;Screenshot 2023-10-27 at 4.10.33 PM.png&quot; data-origin-width=&quot;1220&quot; data-origin-height=&quot;692&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://redis.io/docs/data-types/hashes/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;b&gt;HSET bike:1 model Deimos brand Ergonom type 'Enduro bikes' price 4972&lt;/b&gt;&lt;br /&gt;&amp;gt; bike:1이라는 key 밑에&amp;nbsp;&lt;br /&gt;&amp;lt;model : Deimos&amp;gt;, &amp;lt;brand : Ergonom&amp;gt;, &amp;lt;type, 'Enduro bikes'&amp;gt;, &amp;lt;price : 4972&amp;gt; 라는 4개의 field-value쌍이 저장되어 있다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;HGET bike:1 model&lt;/b&gt;&lt;br /&gt;&amp;gt; bike:1이라는 key 밑에 있는 model이라는 field에 대응하는 value값을 조회하므로, 'Deimos'가 조회된다.&lt;br /&gt;&lt;br /&gt;&lt;b&gt;HGETALL bike:1&lt;/b&gt;&lt;br /&gt;&amp;gt; bike:1이라는 key 밑에 저장되어 있는 모든 field-value쌍을 조회한다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;참고&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://redis.io/docs/data-types/hashes/&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://redis.io/docs/data-types/hashes/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&amp;amp;blogId=wideeyed&amp;amp;logNo=221428664697&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://m.blog.naver.com/PostView.naver?isHttpsRedirect=true&amp;amp;blogId=wideeyed&amp;amp;logNo=221428664697&lt;/a&gt;&lt;/p&gt;</description>
      <category>[ CS기초 ]/데이터베이스</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/153</guid>
      <comments>https://eckrin.tistory.com/153#entry153comment</comments>
      <pubDate>Thu, 11 May 2023 23:32:59 +0900</pubDate>
    </item>
    <item>
      <title>[JPA] 연관관계 설정 고민</title>
      <link>https://eckrin.tistory.com/152</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/r73nz/btsJJ8twXph/ydxx6pAkwLOR2AdxUPLck0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/r73nz/btsJJ8twXph/ydxx6pAkwLOR2AdxUPLck0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/r73nz/btsJJ8twXph/ydxx6pAkwLOR2AdxUPLck0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fr73nz%2FbtsJJ8twXph%2Fydxx6pAkwLOR2AdxUPLck0%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;768&quot; height=&quot;500&quot; data-origin-width=&quot;768&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 일대일 매핑에서 어느쪽을 연관관계의 주인으로 하는것이 맞는가?&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;반드시 다(N)쪽이 fk를 가져 연관관계의 주인이 되는 다대일 관계와 다르게, 일대일 매핑의 경우 어느쪽이 fk를 갖게 하여 주인으로 설정하든 기능적으로 문제가 없다. 어느쪽을 연관관계의 주인으로 설정하는 것이 좋을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일대일 매핑에서도 주 테이블(자주 접근하는 테이블)과 대상 테이블이 존재하는데, 일반적으로 주 테이블에 외래키가 있는 것이 좋다고 느꼈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;내가 진행했던 프로젝트 중에, feature별로 고유 이름을 가지게 하고, 사용자들이 그 고유 이름을 기준으로 즐겨찾기를 등록하여 feature가 변경되거나 삭제되더라도 고유 이름에 대한 즐겨찾기는 유지되게 하는 요구사항이 있었다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1685183951003&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
@Table(name = &quot;features&quot;)
class Feature(
    ...

    @field:OneToOne(fetch = FetchType.LAZY)
    @field:JoinColumn(name = &quot;feature_name_id&quot;)
    val featureName: FeatureName,

    ...
    
) : BaseTimeEntity()&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1685183971573&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Entity
@Table(name = &quot;feature_names&quot;)
class FeatureName(
   ...

    @field:OneToOne(mappedBy = &quot;featureName&quot;, orphanRemoval = false)
    var feature: Feature?
    
    ...

) : BaseTimeEntity()&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;고유 이름을 저장하기 위해서, feature_name이라는 엔티티를 만들어 db에 고유 이름을 저장할 수 있게끔 했는데, 이때 잠시 fk를 어디에 저장할 것인가에 대한 고민을 했었다. 결론은 자주 사용하는 주 테이블인 feature에 fk를 저장하고, orphanRemoval = false속성을 설정하여 feature가 삭제되더라도 반대 feature_names 테이블의 데이터는 유지되게끔 하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;위와 같이 논리적인 이유도 있지만, 가장 큰 이유는 데이터의 삽입이 연관관계의 주인쪽에서만 가능하기 때문이다. 즉, 외래키(featurename_id)를 수정하는 동작은 feature에서 이루어져야 하며, feature에서 고유 이름을 저장하는 것은 가능하지만 고유 이름 테이블쪽에서 feature를 생성하는 것은 불가능하기 때문이다. 양방향 연관관계가 설정되어 있다고 해서 featureName 객체에 feature를 삽입해봤자 JPA는 의도대로 동작하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;다시 말하지만 양방향 연관관계를 설정하는 것은 양쪽에서 제어가 가능하게 하는 것이 아니다. 데이터베이스상에서는 연관관계의 주인쪽에서만 관계를 맺은 쪽의 데이터에 접근할 수 있으며, 반대쪽에서는 탐색의 역할만 가능하다.&lt;/p&gt;</description>
      <category>[ Backend ]/Spring DB, JPA</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/152</guid>
      <comments>https://eckrin.tistory.com/152#entry152comment</comments>
      <pubDate>Sun, 7 May 2023 19:07:46 +0900</pubDate>
    </item>
    <item>
      <title>[알고리즘] union-find 알고리즘</title>
      <link>https://eckrin.tistory.com/151</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;유니온 파인드 알고리즘&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;유니온 파인드 알고리즘은 그래프상에서 두 노드가 같은 그래프에 속하는지 판별하는 알고리즘이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tnSeu/btr7XdE5Suk/5hTFXEwMVtP2EcfYxtKhnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tnSeu/btr7XdE5Suk/5hTFXEwMVtP2EcfYxtKhnk/img.png&quot; data-alt=&quot;https://ip99202.github.io/posts/%EC%9C%A0%EB%8B%88%EC%98%A8-%ED%8C%8C%EC%9D%B8%EB%93%9C(Union-Find)/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tnSeu/btr7XdE5Suk/5hTFXEwMVtP2EcfYxtKhnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtnSeu%2Fbtr7XdE5Suk%2F5hTFXEwMVtP2EcfYxtKhnk%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;647&quot; height=&quot;364&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;720&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://ip99202.github.io/posts/%EC%9C%A0%EB%8B%88%EC%98%A8-%ED%8C%8C%EC%9D%B8%EB%93%9C(Union-Find)/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같이 노드들이 존재할 경우, 유니온 파인드 알고리즘을 위해서는 &lt;b&gt;노드를 나타내는 일차원 정수배열&lt;/b&gt;을 이용한다. 이 배열의 값은 노드가 속한 그래프의 정보이자, 대표 노드의 정보를 나타낸다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당연히 배열의 초기값은 자기 자신으로 세팅한다. 연결된 노드가 없을 경우 자기 자신이 부모노드가 되기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;초기화&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 노드들 사이에 관계가 존재하지 않으므로 모든 노드는 자기 자신의 인덱스를 값으로 갖도록 초기화해준다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1680591178314&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int[] parent = {1,2,3,4,5,6,7,8};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Union&lt;/b&gt;&lt;b&gt;&lt;/b&gt;&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dJVVNH/btr74fVTm4h/f5iEa226kb0N5kHb9Sx3x1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dJVVNH/btr74fVTm4h/f5iEa226kb0N5kHb9Sx3x1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dJVVNH/btr74fVTm4h/f5iEa226kb0N5kHb9Sx3x1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdJVVNH%2Fbtr74fVTm4h%2Ff5iEa226kb0N5kHb9Sx3x1%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;383&quot; height=&quot;111&quot; data-origin-width=&quot;566&quot; data-origin-height=&quot;164&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: #781b33;&quot;&gt;&lt;b&gt;&amp;nbsp;두 노드가 같은 그룹에 속하도록 합쳐주는 함수&lt;/b&gt;&lt;/span&gt;이다. 가장 먼저 생각할 수 있는 방법은 한 노드의 값을 다른 노드의 값으로 바꿔주면 된다. (방향성 그래프일 경우 대소비교를 통해 규칙을 커스텀 가능하다.)&lt;/p&gt;
&lt;pre id=&quot;code_1710255637895&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;void union(int a, int b) {
  parent[b] = a;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1680591271176&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int[] arr = {1,1,3,4,5,6,7,8}; // 편증문제 발생&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;그런데 이렇게만 하면 모든 노드가 자식(부모)노드를 하나씩만 가지는 편중문제가 발생할 수 있다. 그러면 트리의 depth가 깊어져서 탐색시간이 길어지고 비효율적인 트리(logn이 아니라 n에 가까운 시간복잡도)가 될 수 있다. 따라서 이를 방지하기 위해서 합치는 연산을 할 때 부모노드가 아니라, &lt;b&gt;부모노드의 대표노드를 찾아서 그 둘을 연결&lt;/b&gt;해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1695975489508&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//a번 노드와 b번 노드를 union
//-&amp;gt; a집합의 대표노드를 b집합의 대표노드로 설정
void union(int a, int b) {
  parent[find(b)] = find(a);
}&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;h3 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;Find&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;Union 단계에서 배열의 값을 바꾸고 나서, &lt;span style=&quot;color: #781b33;&quot;&gt;&lt;b&gt;자신이 속한 집합의 정보를 알기 위해&lt;/b&gt;&lt;/span&gt;서는 (=자신이 속한 집합의 대표 노드를 찾기 위해서는) 다음과 같은 과정을 거친다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어, 2번 노드가 어떤 그룹에 속하는지(=2번 노드의 대표 노드가 무엇인지)를 알기 위해서는 다음과 같이 반복문을 이용하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 배열의 2번 값을 확인한다 -&amp;gt; 1번 (parent[2] == 1)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 배열의 1번 값을 확인한다 -&amp;gt; 1번 (parent[1] == 1)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 그러면 2번 노드가 속한 트리의 대표 노드는 1이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 노드의 루트 노드를 비교해서 서로 다르면 두 노드는 다른 그래프에 속한 것이고, 같다면 동일한 그래프에 속한 것임을 알 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 이렇게 마무리할 경우 반복문을 통해서 확인하고 나오는 과정에 불필요한 반복이 들어간다. 따라서 재귀를 이용하여 n번 노드의 대표 노드 m을 찾았을 때, &lt;span style=&quot;color: #781b33;&quot;&gt;&lt;b&gt;대표 노드로부터 재귀를 빠져나오면서 거치는 모든 노드값의 대표 노드를 m으로 바꾸어주면&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_1695976623848&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//a번 노드가 속한 집합 정보(대표 노드) 찾기
int find(int a) {
  if(a==parent[a]) return a; //대표 노드라면 그대로 반환
  else return parent[a] = find(parent[a]); //대표 노드가 아니라면 부모노드에 대해 재귀탐색, 반환값을 현재 노드(a)의 대표노드로 세팅
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;819&quot; data-origin-height=&quot;342&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/k1gDo/btsGj2cCtho/NMyISuiMGhBeT9In9X4kK1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/k1gDo/btsGj2cCtho/NMyISuiMGhBeT9In9X4kK1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/k1gDo/btsGj2cCtho/NMyISuiMGhBeT9In9X4kK1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fk1gDo%2FbtsGj2cCtho%2FNMyISuiMGhBeT9In9X4kK1%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;606&quot; height=&quot;253&quot; data-origin-width=&quot;819&quot; data-origin-height=&quot;342&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;find(6)을 통해서 재귀를 빠져나오면서 모든 대표 노드들을 갱신해준다. 다시 말하면 find()연산은 자신의 대표노드를 찾을 뿐만 아니라, &lt;span style=&quot;color: #781b33;&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;pre id=&quot;code_1712145243451&quot; class=&quot;java&quot; data-ke-type=&quot;codeblock&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;int find(int a) {
  if(a==parent[a]) return a; 
  else return find(parent[a]);
}&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;find()함수를 이렇게 작성하면 어떻게 될까? 모든 노드값의 대표 노드값을 바꾸는 과정이 생략되어 수행시간이 늘어나게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 재귀가 아니라 while문으로 구현하면 안될까? 신기하게도 while문보다 재귀를 이용한 풀이의 속도가 훨씬 빠르므로 반드시 재귀로 풀도록 하자.&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;대표 노드 배열이 무조건 그룹 정보를 담고있지는 않다&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(&lt;a href=&quot;https://www.acmicpc.net/problem/17352&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;BOJ17352&lt;/a&gt;를 풀다가 안 사실이다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;무슨 말이냐면, 그래프에서 간선의 정보가 나와있을때 union 연산을 통해서 그래프를 묶었다고 하자. 이 문제를 풀기 전 나는 대표 노드 배열에 group 정보가 담길 것이라고 생각했다.&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;387&quot; data-origin-height=&quot;355&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c1GTl9/btsGNaVoZA3/MILeZsW6HYguNvgSSV0VZ1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c1GTl9/btsGNaVoZA3/MILeZsW6HYguNvgSSV0VZ1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1GTl9/btsGNaVoZA3/MILeZsW6HYguNvgSSV0VZ1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc1GTl9%2FbtsGNaVoZA3%2FMILeZsW6HYguNvgSSV0VZ1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;387&quot; height=&quot;355&quot; data-origin-width=&quot;387&quot; data-origin-height=&quot;355&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;div style=&quot;background-color: #1e1f22; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;java&quot; data-ke-language=&quot;java&quot;&gt;&lt;code&gt;private static void union(int a, int b) {
    if(a&amp;lt;b) {
        parent[find(b)] = find(a);
    } else {
        parent[find(a)] = find(b);
    }
}

private static int find(int a) {
    if(parent[a]==a) {
        return a;
    } else {
        return parent[a] = find(parent[a]);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예를 들어 그림과 같은 그래프가 있을 때, 위와 같은 union, find함수를 사용해서 그래프 연결정보를 받아서 union함수를 통해 대표노드 정보를 정렬했다고 하자. 이때 대표노드를 저장한 parent배열만 보아서는 같은 그룹인지 아닌지를 판별할 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;2&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;3&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;5&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;1&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;4&lt;/td&gt;
&lt;td style=&quot;width: 16.6667%; text-align: center;&quot;&gt;4&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;무조건 위와 같이 parent배열에 저장되지는 않는다는 말이다. &lt;b&gt;i와 j가 같은 그룹에 있는지 판별하기 위해서는 parent[i]와 parent[j]를 비교하는 것이 아니라, find(i)와 find(j)를 비교해야 한다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;BOJ 1043) 거짓말&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제의 시간제한이 널널하기에 그냥 반복문으로 풀어도 되지만, union-find를 사용하여 다음과 같이 풀 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1681108939060&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.io.*;
import java.util.*;

public class Main
{
    static int N, M, T;
    static int[] parent; //각 사람별 속한 집합을 표시
    static int[] truePerson; //진실을 아는 사람들의 번호
    static ArrayList&amp;lt;Integer&amp;gt;[] party; //각 파티별로 올 사람을 리스트로 저장
    static int result = 0;
	public static void main(String[] args) throws IOException {
		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
		String[] line_1 = br.readLine().split(&quot; &quot;);
		N = Integer.parseInt(line_1[0]); //사람의 수
		M = Integer.parseInt(line_1[1]); //파티의 개수
		String[] line_2 = br.readLine().split(&quot; &quot;);
		T = Integer.parseInt(line_2[0]);
		
		//truePerson 배열에 진실을 아는 사람들의 번호를 저장
		truePerson = new int[T];
		for(int i=0; i&amp;lt;T; i++) {
		    truePerson[i] = Integer.parseInt(line_2[i+1]);
		}
		
		//각 파티에 오는 사람들의 목록을 리스트로 저장
		party = new ArrayList[M];
		for(int i=0; i&amp;lt;M; i++) {
		    String[] line_M = br.readLine().split(&quot; &quot;);
		    party[i] = new ArrayList&amp;lt;Integer&amp;gt;();
		    int party_size = Integer.parseInt(line_M[0]);
		    for(int j=0; j&amp;lt;party_size; j++) {
		        party[i].add(Integer.parseInt(line_M[j+1])); //파티에 오는 사람들 추가
		    }
		}
		
		//i가 꼭 0부터 반복되어야하나?
		//parent 초기화
		parent = new int[N+1];
		for(int i=0; i&amp;lt;=N; i++) {
		    parent[i] = i;
		}
		
		//party에 속한 사람들이 같은 집합에 속하도록 설정
		//(파티의 기준을 처음 사람으로 하여, 두번째~마지막 사람 모두를 처음 사람과 동일한 파티에 속하도록 설정한다.)
		for(int i=0; i&amp;lt;M; i++) {
		    int firstPerson = party[i].get(0);
		    for(int j=1; j&amp;lt;party[i].size(); j++) {
		        union(firstPerson, party[i].get(j));
		    }
		}
		
		//각 파티별로 거짓말을 할 수 있는지 검사
		for(int i=0; i&amp;lt;M; i++) {
		    boolean isPossible = true;
		    int cur = party[i].get(0);
		    //파티마다
		    for(int j=0; j&amp;lt;truePerson.length; j++) {
		        //만약 진실을 아는 사람중 한명과 첫번째 사람이 같은 집합에 있다면, 
		        if(checkSame(cur, truePerson[j])) {
		            isPossible = false; //해당 파티에서는 거짓말을 할 수 없다.
		            break;
		        }
		    }
		    if(isPossible) result++;
		}
		System.out.println(result);
		
	}
	
	//a와 b를 같은 집합으로 설정
	private static void union(int a, int b) {
	    a = find(a);
	    b = find(b);
	    if(a!=b) parent[b] = a;
	}
	
	//a가 속한 집합 찾기
	private static int find(int a) {
	    if(a==parent[a]) return a;
	    else return parent[a] = find(parent[a]);
	}
	
	//a와 b가 같은 집합에 속하는지 검사
	private static boolean checkSame(int a, int b) {
	    a = find(a);
	    b = find(b);
	    if(a==b) return true;
	    else return false;
	}
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 초기설정:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 사람별로 속한 집합을 나타낼 일차원 배열(parent), 진실을 알고 있는 사람들의 번호를 저장할 일차원 배열(truePerson)과 파티별로 올 사람을 리스트로 저장할 이차원 배열(party)를 이용했다. 이 때 party배열의 경우 각 파티별로 사람의 수가 상이하므로 ArrayList&amp;lt;Integer&amp;gt;형 1차원 배열으로 대체하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 사용한 함수:&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- union&lt;/p&gt;
&lt;pre id=&quot;code_1681109681076&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//a와 b를 같은 집합으로 설정
private static void union(int a, int b) {
    a = find(a);
    b = find(b);
    if(a!=b) parent[b] = a;
}&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;b를 a와 같은 집합으로 세팅하는 union함수이다. 해당 문제에서는 party의 첫번째 사람을 기준으로 하여 파티에 참여한 나머지 사람들을 같은 집합으로 설정하였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- find&lt;/p&gt;
&lt;pre id=&quot;code_1681109784514&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//a가 속한 집합 찾기
private static int find(int a) {
    if(a==parent[a]) return a;
    else return parent[a] = find(parent[a]);
}&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;a의 집합을 찾는 find함수이다. 재귀호출을 사용하여 a가 속한 집합(= a가 속한 파티 = a가 속한 파티의 첫번째 사람의 party[]값)을 찾아서 반환한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 로직 정리:&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1681109961586&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;    //각 파티별로 거짓말을 할 수 있는지 검사
    for(int i=0; i&amp;lt;M; i++) {
        boolean isPossible = true;
        int cur = party[i].get(0);
        //파티마다
        for(int j=0; j&amp;lt;truePerson.length; j++) {
            //만약 진실을 아는 사람중 한명과 첫번째 사람이 같은 집합에 있다면, 
            if(checkSame(cur, truePerson[j])) {
                isPossible = false; //해당 파티에서는 거짓말을 할 수 없다.
                break;
            }
        }
        if(isPossible) result++;
    }
    System.out.println(result);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제를 풀기 위해서 각 파티를 순회탐색하며 &quot;파티에 속한 사람들 중 한명이라도 진실을 아는 사람과 같은 집합에 있다면&quot; 해당 파티는 거짓말을 할 수 없는 파티로 규정하였다.&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;반복문만을 사용하여 문제를 풀기 위해서는 &quot;원래는 진실을 알지 못했지만 진실을 아는 사람과 같은 파티에 속하여 진실을 알게 된 사람&quot;의 경우를 고려해주어야 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1681110205870&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;while(flag) {
    flag = false;
    //리스트가 true인데 리스트 내의 사람은 false인 경우
    for(int i=0; i&amp;lt;M; i++) {
        if(isPartyTrue[i]) {
            for(int j=0; j&amp;lt;list[i].size(); j++) {
                if(!isKnowing[list[i].get(j)]) {
                    isKnowing[list[i].get(j)] = true;
                    flag = true;
                }
            }
        }
    }
    //리스트 내의 사람은 true인데 리스트가 false인 경우
    for(int i=0; i&amp;lt;M; i++) {
        for(int j=0; j&amp;lt;list[i].size(); j++) {
            if(isKnowing[list[i].get(j)] &amp;amp;&amp;amp; !isPartyTrue[i]) {
                isPartyTrue[i] = true;
                flag = true;
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;따라서 반복문을 사용했던 방법인 위 코드는 while문으로 이중 반복문을 감싸서 첫번째 케이스인 &quot;해당 파티는 진실을 아는 파티인데, 그 파티 내의 사람이 진실을 모른다고 되어있어서 그 사람을 진실을 안다고 설정해주어야 하는 경우&quot;와 두번째 케이스인 &quot;해당 파티는 진실을 알지 못하는 파티인데, 그 파티 내의 사람이 진실을 알아서 해당 파티 내의 사람들을 모두 진실을 안다고 설정해주어야 하는 경우&quot;를 계속 검사하여 그러한 케이스가 나오지 않을 때까지 반복해주어야 했다.&lt;/p&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;하지만 union-find를 사용할 경우, 파티에 참여하는 모든 사람들이 union함수를 통하여 집합으로 묶여있기 때문에, find함수를 사용하여 &quot;진실을 아는 사람이 파티에 속해있는지&quot; 여부만 검사해주면 된다.&lt;/p&gt;</description>
      <category>[ CS기초 ]/알고리즘</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/151</guid>
      <comments>https://eckrin.tistory.com/151#entry151comment</comments>
      <pubDate>Tue, 4 Apr 2023 16:08:15 +0900</pubDate>
    </item>
    <item>
      <title>[DB] SQL문 문법 정리 - DDL</title>
      <link>https://eckrin.tistory.com/149</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;SQL&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;데이터베이스 시스템에서 자료를 처리하는 용도로 사용되는 언어. 목적에 따라서 DDL(데이터 정의어 - 테이블이나 관계 생성), DML(데이터 조작어 - 테이블의 데이터 CRUD), DCL(데이터 제어어 - 데이터의 사용 권한 관리)로 구분된다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;DDL (데이터 정의어)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;테이블을 구성하거나, 속성과 기본키(pk)/외래키(fk)를 정의하는 쿼리문을 말한다. 테이블을 생성하는 create문, 테이블 구조를 수정하는 alter문, 그리고 테이블을 삭제하는 drop문이 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;CREATE&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 테이블을 생성하기 위한 쿼리문. 속성명과 데이터타입을 명시해주고 뒤에 속성의 제약을 정의해줄 수 있다. 속성을 모두 정의하고 나서 pk와 fk를 정의해줄 수도 있고, 제약을 정의할 때 같이 정의해줄 수도 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;706&quot; data-origin-height=&quot;272&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kRlc7/btrZ85NYAsA/zmxnGqhWMMkHpARQ2b8ygk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kRlc7/btrZ85NYAsA/zmxnGqhWMMkHpARQ2b8ygk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kRlc7/btrZ85NYAsA/zmxnGqhWMMkHpARQ2b8ygk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkRlc7%2FbtrZ85NYAsA%2FzmxnGqhWMMkHpARQ2b8ygk%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;706&quot; height=&quot;272&quot; data-origin-width=&quot;706&quot; data-origin-height=&quot;272&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;717&quot; data-origin-height=&quot;186&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bd7WSo/btrZ71ZlVY3/qUakyWHrJIRxOJHcG6FxS1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bd7WSo/btrZ71ZlVY3/qUakyWHrJIRxOJHcG6FxS1/img.png&quot; data-alt=&quot;기본키 지정시 두 방법 모두 사용 가능&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bd7WSo/btrZ71ZlVY3/qUakyWHrJIRxOJHcG6FxS1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbd7WSo%2FbtrZ71ZlVY3%2FqUakyWHrJIRxOJHcG6FxS1%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;717&quot; height=&quot;186&quot; data-origin-width=&quot;717&quot; data-origin-height=&quot;186&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;기본키 지정시 두 방법 모두 사용 가능&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;ALTER&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 생성된 테이블의 속성과, 속성에 관한 제약, 기본키와 외래키와 같은 테이블 제약을 변경할 수 있다. ADD, DROP을 통해서 속성을 추가하거나 삭제하고, MODIFY를 통해서는 속성의 데이터타입을 변경하거나, 기본값을 설정하거나 삭제할 수 있다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;517&quot; data-origin-height=&quot;210&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmN7lI/btrZ661aEtJ/vbQjyYcOUYE6WeKKRZZ3d1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmN7lI/btrZ661aEtJ/vbQjyYcOUYE6WeKKRZZ3d1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmN7lI/btrZ661aEtJ/vbQjyYcOUYE6WeKKRZZ3d1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbmN7lI%2FbtrZ661aEtJ%2FvbQjyYcOUYE6WeKKRZZ3d1%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;517&quot; height=&quot;210&quot; data-origin-width=&quot;517&quot; data-origin-height=&quot;210&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;DROP&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: DML의 delete문과 다르게 그냥 테이블 전체를 지운다. 데이터 일부를 삭제하려면 delete문을 이용하자.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;428&quot; data-origin-height=&quot;33&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkSGLJ/btrZ9FIf7T5/q0PW4OKJYs4gMw4a6kq3U1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkSGLJ/btrZ9FIf7T5/q0PW4OKJYs4gMw4a6kq3U1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkSGLJ/btrZ9FIf7T5/q0PW4OKJYs4gMw4a6kq3U1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkSGLJ%2FbtrZ9FIf7T5%2Fq0PW4OKJYs4gMw4a6kq3U1%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;428&quot; height=&quot;33&quot; data-origin-width=&quot;428&quot; data-origin-height=&quot;33&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>[ CS기초 ]/데이터베이스</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/149</guid>
      <comments>https://eckrin.tistory.com/149#entry149comment</comments>
      <pubDate>Mon, 20 Feb 2023 21:23:45 +0900</pubDate>
    </item>
    <item>
      <title>[알고리즘] 누적합(부분 배열 합)</title>
      <link>https://eckrin.tistory.com/148</link>
      <description>&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;1차원 누적합&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음과 같은 배열이 있다고 하자.&lt;/p&gt;
&lt;pre id=&quot;code_1676712731718&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int[] arr = {1,2,3,4,5};&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째부터 다섯 번째 원소까지의 합을 구하기 위해서는, for문을 이용해서 직접 구해줄 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1676713321058&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for(i=1...5) rst+=arr[i]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그런데 만약 arr이 충분히 크다면, dp를 이용하여 누적합을 저장해두고 갖다 쓸 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째~다섯번째 원소합 = (첫번째~다섯번째 원소 누적합) - (첫번째~첫번째 원소 누적합)&lt;/p&gt;
&lt;pre id=&quot;code_1676713282663&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;var s5=0, s1=0
for(i=0...4까지) s5+=arr[i]
for(i=0...0까지) s1+=arr[i]
var arr[1]부터 arr[5]까지 합 = s5-s1&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반화하면 다음과 같은 식이 나온다. 따라서 누적합 배열을 만들어둔다면 이 식을 이용해서 O(1)의 시간복잡도로 원소의 합을 구할 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1676713449667&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;arr[i]+...arr[j] = 누적합[j]-누적합[i-1];&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;boj 11659) 구간 합 구하기 4&lt;/p&gt;
&lt;pre id=&quot;code_1676713584002&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Main {

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        String[] line_1 = br.readLine().split(&quot; &quot;);
        String[] line_2 = br.readLine().split(&quot; &quot;);
        int N = Integer.parseInt(line_1[0]);
        int M = Integer.parseInt(line_1[1]);
        int[] arr = new int[N];
        int[] sum = new int[N];

        for(int i=0; i&amp;lt;N; i++) //배열에 값 저장
            arr[i] = Integer.parseInt(line_2[i]);

        sum[0] = arr[0];
        for(int i=1; i&amp;lt;N; i++) { //1~i까지 누적합
            sum[i] = sum[i-1]+arr[i];
        }

        for(int i=0; i&amp;lt;M; i++) {
            String[] range = br.readLine().split(&quot; &quot;);
            int l = Integer.parseInt(range[0]);
            int r = Integer.parseInt(range[1]);
            System.out.println(sum[r-1]-sum[l-1]+arr[l-1]);
            //(==sum[r-1]-sum[l-2])
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 응용하면 다음과 같은 사고도 할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주어진 input으로 배열을 채워나가고, 그 합의 최대값이 되는 부분을 구하는 문제에서 활용 가능하다. 간단하게 말하면 도서관 열람실을 이용하는 사람들의 사용시간들이 주어지고, 가장 많은 사람들이 도서관을 이용하는 시간(혹은 그때 사람 수)을 구한다고 하자. 일반적으로 대충 다음과 같은 풀이가 가장 먼저 떠오를 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1680661244993&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int p1start=13, p1end=17; //사람 1의 도서관 이용 시작/종료시간
int p2start=15, p2end=20; //사람 2의 도서관 이용 시작/종료시간

for(int i=p1start; i&amp;lt;=p1end; i++) timetable[i]++;
for(int i=p2start; i&amp;lt;=p2end; i++) timetable[i]++;

for(int i=0; i&amp;lt;=23; i++) ans = max&amp;lt;timetable[i]?i:ans; //가장 사용자가 많은 시각
for(int i=0; i&amp;lt;=23; i++) ans = Math.max(timetable[i]); //가장 사용자가 많은 때의 사람수&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;사람의 도서관 이용시간을 해당 시간에 도서관을 이용하는 사람의 수를 나타내는 배열 timetable에 저장하고, timetable을 탐색하면서 가장 큰 값을 갖는 시간(혹은 그때의 사람 수)을 얻으면 된다. 이러한 풀이는 O(n^2)의 시간복잡도를 가지는데, 누적합을 잘 이용하면 이러한 문제를 O(n)에 해결할 수 있게 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1680661491676&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;int p1start=13, p1end=17; //사람 1의 도서관 이용 시작/종료시간
int p2start=15, p2end=20; //사람 2의 도서관 이용 시작/종료시간

timetable[p1start]++;
timetable[p1end]--;
timetable[p2start]++;
timetable[p2end]--;

for(int i=1; i&amp;lt;timetable.size; i++) timetable[i]+=timetable[i-1];

//구해진 timetable은 위와 동일하게 사용&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;사용자의 시작시간에 해당하는 timetable배열에는 1을 더해주고, 종료시간에 해당하는 timetable에는 1을 빼주는 과정만을 반복한다. 즉, 모든 사용시간을 탐색하면서 값을 수정해주지 않기에 O(n)만에 해결 가능하다. 그후에는 timetable배열을 대상으로 누적합을 구해주면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원리는 간단하다(머리로도 쉽게 이해 가능하다). timetable을 해당 시간에 이용하는 사용자 수로 생각하지 말고, 해당 시간에 들어오는 사용자 수로 생각하면 된다. 그러고 나서 누적합을 구해주면 그 누적합이 해당 시간에 도서관을 이용하는 사용자 수가 되는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로그래머스) 호텔 대실&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/155651&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/155651&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1680661779856&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;프로그래머스&quot; data-og-description=&quot;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&quot; data-og-host=&quot;programmers.co.kr&quot; data-og-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/155651&quot; data-og-url=&quot;https://programmers.co.kr/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bnjlT3/hySaV5BtaC/m0mDG7Tk2ULWR6FrMyfg0K/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bd0SPN/hySa6eZd33/JKDFShTO7EQl7WzL4FB6Xk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/155651&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/155651&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bnjlT3/hySaV5BtaC/m0mDG7Tk2ULWR6FrMyfg0K/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bd0SPN/hySa6eZd33/JKDFShTO7EQl7WzL4FB6Xk/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로그래머스&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;programmers.co.kr&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;비슷한 문제를 하나 풀어보자. 사용자들의 대실 시간이 주어지고, 해당 사용자들을 모두 수용하기 위해서는 최소 몇개의 방을 필요로 하는가가 문제이다. 필요한 방의 개수를 구하는데 어떤 방법을 써야하는지 고민했는데, 결국 동시에 최대 몇명의 사람이 들어오는지를 구해주면 되는 문제이다. 그런데 이 문제의 경우 이용시간이 분 단위로 주어지므로, 시간을 분 단위로 풀어서 24(시간)*60(분)+10(청소시간)만큼의 배열을 만들고, 그 배열 위에 이용 시작/종료시간을 표시한 다음 누적합을 구해주면 해당 시간의 이용자 명수를 구할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1680662522858&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;import java.util.*;
//배열 누적합
class Solution {
    static final int DAY=60*24+10+1; //하루+청소시간
    static final int HOUR=60;
    static int[] arr = new int[DAY];
    static int ans;
    public int solution(String[][] book_time) {
        for(int t=0; t&amp;lt;book_time.length; t++) {
            int start_h = Integer.parseInt(book_time[t][0].split(&quot;:&quot;)[0]);
            int start_m = Integer.parseInt(book_time[t][0].split(&quot;:&quot;)[1]);
            int end_h = Integer.parseInt(book_time[t][1].split(&quot;:&quot;)[0]);
            int end_m = Integer.parseInt(book_time[t][1].split(&quot;:&quot;)[1]);
            arr[start_h*HOUR+start_m]++;
            arr[end_h*HOUR+end_m+10]--;
        }
        
        ans = arr[0];
        for(int i=1; i&amp;lt;arr.length; i++) {
            arr[i]+=arr[i-1];
            ans = Math.max(ans, arr[i]);
        }
        
        return ans;
    }

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;2차원 누적합&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;1차원 배열과 유사하게 2차원에서도 누적합을 사용할 수 있다. 현재 인덱스보다 하나 적은 인덱스까지의 누적합에 현재 배열값을 더해주기만 하면 되는 일차원 배열 누적합의 초기화와 다르게, 2차원 배열은 누적합을 사용하기 위한 dp값을 다음과 같이 초기화한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;- s[i][j]: arr[0][0]부터 arr[i][j]까지의 인덱스 값의 합&lt;br /&gt;- 초기화) s[i][j] = s[i-1][j] + s[i][j-1] - s[i-1][j-1] + arr[i][j]&lt;br /&gt;- 점화식) arr[i1][j1] ~ arr[i2][j2]까지의 인덱스값 합&lt;br /&gt;= s[i2][j2] - s[i2][j1-1] - s[i1-1][j2] + arr[i1-1][j1-1]&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;추가로 누적합 2차원문제를 풀때는 (n+1)*(m+1)크기로 배열을 짜도록 하자. idx-1을 조회하는 부분이 많아서 n*m으로 짤 시 분기를 두어 풀어야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;웰노운, 추가 예정) 누적합 활용&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://school.programmers.co.kr/learn/courses/30/lessons/92344&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://school.programmers.co.kr/learn/courses/30/lessons/92344&lt;/a&gt;&lt;/p&gt;</description>
      <category>[ CS기초 ]/알고리즘</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/148</guid>
      <comments>https://eckrin.tistory.com/148#entry148comment</comments>
      <pubDate>Sat, 18 Feb 2023 18:52:27 +0900</pubDate>
    </item>
    <item>
      <title>[DB] 정규화</title>
      <link>https://eckrin.tistory.com/147</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&amp;nbsp;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;잘못 설계된 데이터베이스는 삭제이상, 삽입이상, 수정이상 등의 문제를 일으킬 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;삭제이상: 튜플을 삭제했는데 유지되어야 하는 정보도 연쇄삭제되는 현상&lt;br /&gt;삽입이상: NULL입력시 튜플 삽입이 거부되는 이상현상&lt;br /&gt;수정이상: 튜플 수정시 데이터의 일부만 수정되어 데이터의 불일치현상이 나타나는 현상&lt;/blockquote&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;241&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dzkybm/btrZIE4jTTA/RS85xJ4eO6I7oZnCzLKtM1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dzkybm/btrZIE4jTTA/RS85xJ4eO6I7oZnCzLKtM1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dzkybm/btrZIE4jTTA/RS85xJ4eO6I7oZnCzLKtM1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdzkybm%2FbtrZIE4jTTA%2FRS85xJ4eO6I7oZnCzLKtM1%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;545&quot; height=&quot;241&quot; data-origin-width=&quot;545&quot; data-origin-height=&quot;241&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 삭제이상: 5번 튜플(안중근)의 정보 삭제시 AI융합학부의 정보가 같이 삭제된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 삽입이상: 글로벌미디어 학과에 대한 정보를 삽입하고 싶지만 학생명, 학번이 NULL값이라 삽입되지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- 수정이상: 소프트웨어학부의 과사무실 정보를 수정하기 위해 3번튜플의 과사무실 정보를 변경하면 3,4번튜플에 불일치가 발생한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 이상현상은 테이블의 잘못된 설계로 인해 발생한다. 따라서 테이블의 구조를 수정하여 이상현상이 발생하지 않도록 분리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;정규화&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;이상현상이 발생하지 않는 테이블을 설계하기 위해서는 &lt;b&gt;릴레이션을 분해하여 속성 사이의 의존성을 제거&lt;/b&gt;하는 &lt;b&gt;정규화&lt;/b&gt; 과정을 거쳐야 한다. 즉, 릴레이션 내에 존재하는 결정자는 반드시 키여야 한다. 다시 말해 &lt;b&gt;기본키가 아니면서 결정자인 속성이 있을 때&lt;/b&gt; 이상현상이 발생한다. 정규화는 단계에 따라서 크게 제1정규형~BCNF정규형으로 분류할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;- 제 1정규형&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;릴레이션 R의 모든 속성값이 원자성을 가지면 된다. (값을 하나씩만 가진다)&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;1290&quot; data-origin-height=&quot;246&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bhQs7E/btsHQ2OgX8e/wOdmXH5j1zjFvmdqXutAPK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bhQs7E/btsHQ2OgX8e/wOdmXH5j1zjFvmdqXutAPK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bhQs7E/btsHQ2OgX8e/wOdmXH5j1zjFvmdqXutAPK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbhQs7E%2FbtsHQ2OgX8e%2FwOdmXH5j1zjFvmdqXutAPK%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;572&quot; height=&quot;109&quot; data-origin-width=&quot;1290&quot; data-origin-height=&quot;246&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;위 테이블에서 Cources 속성은 여러개의 속성값을 갖고 있으며, 이는 제 1정규형을 위반한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;- 제 2정규형&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제1정규형인 릴레이션 R이 기본키가 아닌 속성이 기본키에 &lt;b&gt;완전 함수 종속&lt;/b&gt;이면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; 완전 함수 종속이란, &lt;b&gt;기본키가 2개 이상의 속성으로 구성되었을 때&lt;/b&gt;&amp;nbsp;기본키 속성 전체에 종속하고 부분속성에는 종속하지 않는 것을 말한다. 다시 말하자면 기본키를 구성하는 개별 속성이 결정자가 되면 안되고, 결정자가 된다면 해당 속성을 분리하여 새로운 릴레이션을 생성한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(제 1정규형을 끝냈을 때, 기본키가 하나의 속성으로 이루어졌을 경우 제 2정규형을 진행할 필요가 없다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;653&quot; data-origin-height=&quot;164&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c5sNPr/btsHPZSx3fF/cAhfqTz2PdSu6k7iNrwl8k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c5sNPr/btsHPZSx3fF/cAhfqTz2PdSu6k7iNrwl8k/img.png&quot; data-alt=&quot;부분 함수 종속&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c5sNPr/btsHPZSx3fF/cAhfqTz2PdSu6k7iNrwl8k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc5sNPr%2FbtsHPZSx3fF%2FcAhfqTz2PdSu6k7iNrwl8k%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;653&quot; height=&quot;164&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;653&quot; data-origin-height=&quot;164&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;부분 함수 종속&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 테이블에서, (학생번호, 강좌이름)이 기본키로 동작하고 있다. 그런데 이때 강의실은 강좌이름에 종속적임을 알 수 있으며, 이는 부분 함수 종족인 상태를 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;551&quot; data-origin-height=&quot;311&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dl3kJj/btrZH3qjuHd/0uQjoij5yWCwy4k6r8LAKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dl3kJj/btrZH3qjuHd/0uQjoij5yWCwy4k6r8LAKk/img.png&quot; data-alt=&quot;완전 함수 종속되게 릴레이션 분해&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dl3kJj/btrZH3qjuHd/0uQjoij5yWCwy4k6r8LAKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdl3kJj%2FbtrZH3qjuHd%2F0uQjoij5yWCwy4k6r8LAKk%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;551&quot; height=&quot;311&quot; data-origin-width=&quot;551&quot; data-origin-height=&quot;311&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;완전 함수 종속되게 릴레이션 분해&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 컬럼이 기본키에 종속되도록 테이블을 분리했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;- 제 3정규형&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제2정규형인 릴레이션 R이 &lt;b&gt;비이행적 종속&lt;/b&gt;(직접 종속)이 이루어질 때 3정규형이라고 한다. 비이행적 종속이란, 기본키를 제외한 컬럼간에 종속성이 발생하는 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;591&quot; data-origin-height=&quot;162&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mwy6h/btrZGgKvEm5/mYBK0ZecdkTNSzxplJGYNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mwy6h/btrZGgKvEm5/mYBK0ZecdkTNSzxplJGYNK/img.png&quot; data-alt=&quot;속성 사이에 이행적 종속이 일어나는 상태&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mwy6h/btrZGgKvEm5/mYBK0ZecdkTNSzxplJGYNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fmwy6h%2FbtrZGgKvEm5%2FmYBK0ZecdkTNSzxplJGYNK%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;591&quot; height=&quot;162&quot; data-origin-width=&quot;591&quot; data-origin-height=&quot;162&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;속성 사이에 이행적 종속이 일어나는 상태&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pk가 학생 번호일 때, 일반 컬럼인 수강료는 일반 컬럼인 강좌이름에 종속적이다. 따라서 일반 컬럼간에 종속성이 있으므로 위 테이블은 제 3정규형을 위반하고 있다.&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;458&quot; data-origin-height=&quot;286&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b4LfwG/btrZIeZs67z/h4eIm0hdagiLPA6tnpwrpk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b4LfwG/btrZIeZs67z/h4eIm0hdagiLPA6tnpwrpk/img.png&quot; data-alt=&quot;비이행적 종속 제거, 릴레이션 분리하여 비이행적 종속만 일어나도록 릴레이션 분리&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b4LfwG/btrZIeZs67z/h4eIm0hdagiLPA6tnpwrpk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb4LfwG%2FbtrZIeZs67z%2Fh4eIm0hdagiLPA6tnpwrpk%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;458&quot; height=&quot;286&quot; data-origin-width=&quot;458&quot; data-origin-height=&quot;286&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;비이행적 종속 제거, 릴레이션 분리하여 비이행적 종속만 일어나도록 릴레이션 분리&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;- BCNF 정규형&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;강화된 제3정규형으로 이해하면 좋은데, 제3정규형에서 &lt;b&gt;모든 결정자는 후보키여야 한다&lt;/b&gt;는 조건을 만족해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1294&quot; data-origin-height=&quot;304&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsbozx/btsHPzNoL7P/Vo8kdPcRbubNgKhUBVlhrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsbozx/btsHPzNoL7P/Vo8kdPcRbubNgKhUBVlhrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsbozx/btsHPzNoL7P/Vo8kdPcRbubNgKhUBVlhrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbsbozx%2FbtsHPzNoL7P%2FVo8kdPcRbubNgKhUBVlhrk%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;613&quot; height=&quot;144&quot; data-origin-width=&quot;1294&quot; data-origin-height=&quot;304&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;위와 같은 테이블이 있다고 가정할 때, 교수 ID는 강의실을 결정하고, (교수ID, 과목) 또한 강의실을 결정한다. 즉, &lt;b&gt;결정자인 교수ID는 강의실을 결정하지만 후보키가 아니다&lt;/b&gt; (중복이 존재한다). 따라서 위 테이블은 BCNF 정규형을 위반한다.&lt;/p&gt;</description>
      <category>[ CS기초 ]/데이터베이스</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/147</guid>
      <comments>https://eckrin.tistory.com/147#entry147comment</comments>
      <pubDate>Fri, 17 Feb 2023 16:17:54 +0900</pubDate>
    </item>
    <item>
      <title>[Spring] 스프링과 Tomcat</title>
      <link>https://eckrin.tistory.com/146</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;스프링 프레임워크가 처음 출시되었을 때는 의존성 버전을 직접 관리하거나, 서버를 직접 설치해주어야 하는 등의 초기 설정을 직접 해주어야 했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 보완하기 위해서 스프링 부트가 출시되었는데, 이것은 &lt;b&gt;최소한의 설정으로 스프링 기반의 애플리케이션을 독립 실행할 수 있게 돕는 프레임워크&lt;/b&gt;라고 할 수 있다. 우리는 스프링 부트를 통해&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. starter 패키지를 통해 필요한 &lt;b&gt;의존성&lt;/b&gt;을 자동으로 가져올 수 있게 되었을 뿐 아니라,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.기존에는 @ComponentScan, @EnableAutoConfiguration, @Configuration을 통해서 명시적으로 설정해주어야 했던 반면, &lt;b&gt;@SpringBootApplication&lt;/b&gt;을 통해서 이 셋 대신 자동으로 필요한 빈을 설정해줄 수 있게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 또한, &lt;b&gt;내장 웹 서버&lt;/b&gt;를 통해 외부의 추가적인 웹 서버 설정 없이도 애플리케이션을 실행 가능하다. 스프링 부트는 초기(ver 1.0)부터 Apache Tomcat을 기본 내장 WAS로 사용하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;과 같은 이점을 얻을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Tomcat&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;옛날에는 OS에 톰캣을 직접 설치한 후, WAR로 패키징한 파일을 특정 디렉토리(/webapps)에 복사한다. 이 과정에서 필요한 xml 설정 파일을 직접 조작해주어야 했다.&lt;/p&gt;
&lt;pre id=&quot;code_1728960468155&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;implementation 'org.springframework.boot:spring-boot-starter-web'&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;하지만 스프링 부트를 사용하면, 위와 같이 우리가 기본적으로 사용하는 스프링 부트의 web starter 패키지 안에 톰캣이 설정되어 있다.&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;460&quot; data-origin-height=&quot;156&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJpDM3/btsJ4gZHPwb/M7G1El7w7Ee3Iq0Ok3jk01/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJpDM3/btsJ4gZHPwb/M7G1El7w7Ee3Iq0Ok3jk01/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJpDM3/btsJ4gZHPwb/M7G1El7w7Ee3Iq0Ok3jk01/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJpDM3%2FbtsJ4gZHPwb%2FM7G1El7w7Ee3Iq0Ok3jk01%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;460&quot; height=&quot;156&quot; data-origin-width=&quot;460&quot; data-origin-height=&quot;156&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;스프링 프로젝트에서 Dependency를 살펴보면 현재 설치된 스프링 부트 웹 스타터 패키지 3.1.6 하위에 spring-boot-starter-tomcat 패키지가 존재하는 것을 확인할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;797&quot; data-origin-height=&quot;516&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0Z3GE/btsJ4tYQrVO/nuS6XjvUNQTTWQrwRPLo91/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0Z3GE/btsJ4tYQrVO/nuS6XjvUNQTTWQrwRPLo91/img.png&quot; data-alt=&quot;https://www.youtube.com/watch?v=UlF6o3Wbi2k&amp;amp;amp;ab_channel=%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0Z3GE/btsJ4tYQrVO/nuS6XjvUNQTTWQrwRPLo91/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0Z3GE%2FbtsJ4tYQrVO%2FnuS6XjvUNQTTWQrwRPLo91%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;797&quot; height=&quot;516&quot; data-origin-width=&quot;797&quot; data-origin-height=&quot;516&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.youtube.com/watch?v=UlF6o3Wbi2k&amp;amp;ab_channel=%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://eckrin.tistory.com/200&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;서블릿을 분석&lt;/a&gt;하다가 coyote와 connector, catalina라는 패키지 안에 Request객체가 존재하는 것을 확인할 수 있었다. 이 때 &lt;b&gt;coyote&lt;/b&gt;라는 패키지는 실제 네트워크 통신을 담당하는 낮은 수준의 커넥터를 말하고, &lt;b&gt;catalina&lt;/b&gt; 패키지는 서블릿 컨테이너 자체를 말한다. 또한 &lt;b&gt;connector&lt;/b&gt;는 coyote로부터 받은 Request객체를 catalina로 전달하고, catalina를 거쳐 되돌아오는 Response를 coyote로 전달하는 중간 다리 역할을 한다.&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;525&quot; data-origin-height=&quot;575&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYrVbm/btsJ40ozj1A/nHgSLaRK5esdcoch9kGscK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYrVbm/btsJ40ozj1A/nHgSLaRK5esdcoch9kGscK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYrVbm/btsJ40ozj1A/nHgSLaRK5esdcoch9kGscK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYrVbm%2FbtsJ40ozj1A%2FnHgSLaRK5esdcoch9kGscK%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;525&quot; height=&quot;575&quot; data-origin-width=&quot;525&quot; data-origin-height=&quot;575&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;Tomcat은 위와 같이 컨테이너 계층 구조로 이루어져 있으며, 톰캣을 구성하는 컴포넌트들은 Container라는 인터페이스를 구현하고 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>[ Backend ]/Spring</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/146</guid>
      <comments>https://eckrin.tistory.com/146#entry146comment</comments>
      <pubDate>Thu, 16 Feb 2023 02:07:18 +0900</pubDate>
    </item>
    <item>
      <title>OAuth2.0를 이용한 카카오 로그인 구현하기</title>
      <link>https://eckrin.tistory.com/145</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;개요&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;사용자의 인증이 필요한 웹 사이트마다 로그인 기능을 필요로 한다면 민감한 정보에 대한 관리가 필요해지기 때문에 번거로울 수 있고, 보안에 취약해질 수 있다. 따라서 google이나 naver, kakao같은 외부 소셜 서비스를 기반으로 회원가입/로그인 할 수 있는 기능을 많이들 사용하고는 하는데, 이를 위해 접근 권한을 위임받은 것을 OAuth라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;OAuth를 사용하지 않고 소셜 서비스에 인증을 위임하기 위해서는 사용자는 소셜 서비스의 인증을 위한 수단을 서버에 위임해야 했다. 이는 소셜 서비스 외부로 ID/PW(인증)가 노출되게 되는 문제가 있다. 이렇게 서버가 사용자의 인증을 필요로 하는 이유는 소셜 서비스로의 인가를 필요로 하는 것이 목적이므로, &lt;b&gt;인증은 사용자에 직접 위임하고, 인가는 소셜 미디어가 맡도록 하는 것&lt;/b&gt;이 OAuth이다.&lt;/p&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;물론 장점만 있는 것은 아니라서, OAuth를 사용하는 웹서버에서 회원정보를 추가로 활용하기 위해서는 사용자 정보를 외부 소셜 서비스 정보와 연동하는 서비스를 별도 구축해야 하기 때문에 구현에 번거로움이 있기는 하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;인증 Flow&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;872&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/GWluD/btrZdfjhLkm/5ZScHTPKXP2ENOHJXOPcG0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/GWluD/btrZdfjhLkm/5ZScHTPKXP2ENOHJXOPcG0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/GWluD/btrZdfjhLkm/5ZScHTPKXP2ENOHJXOPcG0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FGWluD%2FbtrZdfjhLkm%2F5ZScHTPKXP2ENOHJXOPcG0%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;731&quot; height=&quot;498&quot; data-origin-width=&quot;1280&quot; data-origin-height=&quot;872&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;b&gt;Resource Owner&lt;/b&gt;:&amp;nbsp; Client가 제공하는 서비스를 이용하면서, naver, kakao등의 플랫폼에 회원정보와 리소스를 저장하고 있는 사용자를 말한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;Client&lt;/b&gt;: Authorization Server에 저장된 리소스를 이용하여 Resource Owner에게 서비스를 제공하려는 주체. 개발자가 다루는 서버에 해당한다. (이름이 client인 이유는 authorization server 입장에서 해석했기 때문에 그렇다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;- &lt;b&gt;Authorization Server&lt;/b&gt;: Resource Owner의 회원정보를 가지고, Client를 통해서 인증하면 액세스 토큰을 발급해주는 서버. naver, kakao, google과 같은 대형 플랫폼 서버를 말한다.&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;1. Resource owner가 Client에 서비스 사용을 요청한다.&lt;br /&gt;2. Client는 Authorization서버로 서비스 사용 요청을 받아서 리다이렉트 해준다.&lt;br /&gt;3. 사용자(Resource owner)가 Authorization Server로 OAuth 로그인을 시도한다.&lt;br /&gt;4. 인증 서버에서는 정상 로그인 여부 판별 후 Resource owner에 맞는 &lt;b&gt;Authorization Code&lt;/b&gt;를 콜백해준다. 이 Authorization Code는 인증 요청 단위별로 발급되는 임시 인증코드이다.&lt;br /&gt;&lt;b&gt;-&amp;gt; 1~4: Resource owner의 인증 완료&lt;br /&gt;&lt;/b&gt;&lt;br /&gt;5. Client에서 Auth서버의 자원을 사용하기 위해 받은 Authorization Code과 &lt;b&gt;Client Secret key&lt;/b&gt;를 사용하여 액세스 토큰을 요청한다. Client Secret Key는 클라이언트가 oauth 서비스 사용을 위해 미리 발급받은 코드이다.&lt;br /&gt;6. Auth서버는 전달받은 것들의 유효성을 확인 후에 Auth자원에 접근할 수 있는 권한인 &lt;b&gt;액세스 토큰&lt;/b&gt;을 Client로 전달한다.&lt;br /&gt;7,8: 이후에는 Client는 해당 토큰을 사용하여 자원을 요청하여 자원을 얻을 수 있다.&lt;br /&gt;&lt;b&gt;-&amp;gt; 1~8: 권한 부여(AccessToken 획득)&lt;/b&gt;&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;카카오 로그인 사용해보기&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developers.kakao.com/docs/latest/ko/kakaologin/rest-api&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. Resource owner가 Client에 서비스 사용을 요청,&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;744&quot; data-origin-height=&quot;379&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dWVt8B/btrZmD64WBS/bG3UPNEXKALSJxoQ6BfO0K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dWVt8B/btrZmD64WBS/bG3UPNEXKALSJxoQ6BfO0K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dWVt8B/btrZmD64WBS/bG3UPNEXKALSJxoQ6BfO0K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdWVt8B%2FbtrZmD64WBS%2FbG3UPNEXKALSJxoQ6BfO0K%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;744&quot; height=&quot;379&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;744&quot; data-origin-height=&quot;379&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;background-color: #fcfcfc; color: #666666;&quot;&gt;2. Client는 Authorization서버로 인증을 위한 Authorization Code와 함께 서비스 사용 요청 전달&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666;&quot;&gt;3. 사용자(Resource owner)가 Authorization Server로 OAuth 로그인 시도&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: Resource Owner가 타 플랫폼을 이용한 로그인을 요청하면 Client는 사용자의 브라우저를 Authorization Server의 인증 페이지로 연결한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;614&quot; data-origin-height=&quot;882&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b9qFhX/btrZrYhyPFJ/moegIkKZzzWhOyuuLaqR1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b9qFhX/btrZrYhyPFJ/moegIkKZzzWhOyuuLaqR1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b9qFhX/btrZrYhyPFJ/moegIkKZzzWhOyuuLaqR1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb9qFhX%2FbtrZrYhyPFJ%2FmoegIkKZzzWhOyuuLaqR1K%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;614&quot; height=&quot;882&quot; data-filename=&quot;blob&quot; data-origin-width=&quot;614&quot; data-origin-height=&quot;882&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;background-color: #fcfcfc; color: #666666;&quot;&gt;4. 인증 서버에서는 정상 로그인 여부 판별 후 Authrization Code 콜백(callback)&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 카카오의 경우 쿼리 파라미터형식으로 code값이 콜백된다. code값이 전달되었다는 것은 Authorization 서버에서 로그인 인증처리가 정상적으로 끝났다는 것을 의미하며, 이 code값을 사용해서 accessToken을 요청할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단, 카카오 인증은 되었지만 Client는 아직 Resource Owner의 정보를 받지 않아서 알지 못하는 상태이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666;&quot;&gt;5. Client쪽에서 Auth서버의 자원을 사용하기 위해 Authorization Code과 Client Secret key를 사용하여 액세스 토큰 요청&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666;&quot;&gt;6. Auth서버는 전달받은 것들의 유효성을 확인 후에 Auth자원에 접근할 수 있는 권한인 액세스 토큰을 Client로 전달&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;855&quot; data-origin-height=&quot;234&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/LZDTp/btrZqHhcOkP/T5odsOjbPKxP2WZj9ySVuK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/LZDTp/btrZqHhcOkP/T5odsOjbPKxP2WZj9ySVuK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/LZDTp/btrZqHhcOkP/T5odsOjbPKxP2WZj9ySVuK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FLZDTp%2FbtrZqHhcOkP%2FT5odsOjbPKxP2WZj9ySVuK%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;855&quot; height=&quot;234&quot; data-origin-width=&quot;855&quot; data-origin-height=&quot;234&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;502&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJoJ4w/btrZuCkV0N1/vPK3KaeGVfhB91GHk2GwNK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJoJ4w/btrZuCkV0N1/vPK3KaeGVfhB91GHk2GwNK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJoJ4w/btrZuCkV0N1/vPK3KaeGVfhB91GHk2GwNK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJoJ4w%2FbtrZuCkV0N1%2FvPK3KaeGVfhB91GHk2GwNK%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;856&quot; height=&quot;502&quot; data-origin-width=&quot;856&quot; data-origin-height=&quot;502&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 카카오 개발자 페이지에 나와있는대로 필요한 정보를 담아서 POST요청으로 보내주면 된다. 여기서 redirect_uri는 developers사이트에서 카카오 로그인 탭에 설정해둔 Redirect URI를 그대로 입력해주면 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1676474143668&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//POST방식으로 key=value 데이터를 요청(카카오쪽으로)
//-&amp;gt;Retrofit2, OkHttp, RestTemplate 등 사용가능
@GetMapping(&quot;/auth/kakao/callback&quot;) //redirect되는 주소 받아주기
@ResponseBody
public String getKakaoCallback(@RequestParam String code) {
    RestTemplate rt = new RestTemplate();
    //헤더정보 추가를 위한 HttpHeader 오브젝트 생성
    HttpHeaders headers = new HttpHeaders();
    headers.add(&quot;Content-type&quot;, &quot;application/x-www-form-urlencoded;charset=utf-8&quot;);

    //HttpBody에 key-value값 추가
    MultiValueMap&amp;lt;String, String&amp;gt; params = new LinkedMultiValueMap&amp;lt;&amp;gt;();
    params.add(&quot;grant_type&quot;, &quot;authorization_code&quot;);
    params.add(&quot;client_id&quot;, &quot;-- private client key --&quot;);
    params.add(&quot;redirect_uri&quot;, &quot;http://localhost:8000/auth/kakao/callback&quot;); //redirect 지정한 주소
    params.add(&quot;code&quot;, code);

    //HttpHeader와 HttpBody를 하나의 오브젝트에 담기
    HttpEntity&amp;lt;MultiValueMap&amp;lt;String, String&amp;gt;&amp;gt; kakaoTokenRequest = new HttpEntity&amp;lt;&amp;gt;(params, headers);

    //오브젝트를 담아 Http POST로 요청하기
    ResponseEntity&amp;lt;String&amp;gt; response = rt.exchange(
            &quot;https://kauth.kakao.com/oauth/token&quot;, //요청 url
            HttpMethod.POST,
            kakaoTokenRequest,
            String.class //응답받을 타입
    );
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;x-www-form-urlencoded형태로 보낸다고 했으므로 HttpHeaders에 명시해주고, MultiValueMap에 key-value형태로 담은 다음 HttpEntity로 HttpHeader와 HttpBody를 하나의 오브젝트로 담은 다음 RestTemplate로 요청하였다.&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;837&quot; data-origin-height=&quot;662&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bJYLHI/btrZtrjCWbN/1B3ydZWRVg3s9kSozq1kCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bJYLHI/btrZtrjCWbN/1B3ydZWRVg3s9kSozq1kCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bJYLHI/btrZtrjCWbN/1B3ydZWRVg3s9kSozq1kCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbJYLHI%2FbtrZtrjCWbN%2F1B3ydZWRVg3s9kSozq1kCK%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;837&quot; height=&quot;662&quot; data-origin-width=&quot;837&quot; data-origin-height=&quot;662&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;성공했다면 위와 같은 값들이 response에 담겨서 온다. 여기서 access_token이 resource서버로 자원을 요청할 수 있는 키이고, refresh_token이 토큰 재발급을 요청할 수 있는 토큰이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;//json으로 오는 응답 받기
//-&amp;gt;Gson, Json Simple, ObjectMapper등 사용가능
ObjectMapper objectMapper = new ObjectMapper();
OAuthTokenDto oauthToken = null;
try {
    oauthToken = objectMapper.readValue(response.getBody(), OAuthTokenDto.class);
} catch (JsonMappingException e) {
    throw new RuntimeException(e);
} catch (JsonProcessingException e) {
    throw new RuntimeException(e);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 응답은 json으로 왔으므로 dto를 이용하여 받아주었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;br /&gt;&lt;span style=&quot;background-color: #fcfcfc; color: #666666;&quot;&gt;7,8: 이제 받은 토큰을 사용하여 자원을 요청하면 Client는 자원을 얻을 수 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 카카오 설명 페이지에 보면 Content-type을 application/x-www-form-urlencoded로 보내라고 했으므로 앞서 과정과 유사하게 HttpHeaders와 HttpEntity를 이용하여 헤더에 access token을 넣고 RestTemplate으로 정보를 요청했다.&lt;/p&gt;
&lt;pre id=&quot;code_1676884369905&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;RestTemplate rt = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.add(&quot;Authorization&quot;, &quot;Bearer &quot;+oauthToken.getAccess_token());
headers.add(&quot;Content-type&quot;, &quot;application/x-www-form-urlencoded;charset=utf-8&quot;);

HttpEntity&amp;lt;MultiValueMap&amp;lt;String, String&amp;gt;&amp;gt; kakaoProfileRequest = new HttpEntity&amp;lt;&amp;gt;(headers);

ResponseEntity&amp;lt;String&amp;gt; response2 = rt.exchange(
        &quot;https://kapi.kakao.com/v2/user/me&quot;,
        HttpMethod.POST,
        kakaoProfileRequest,
        String.class //응답받을 타입
);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요청 결과는 ResponseEntity를 이용하여 ObjectMapper로 받아주었다.&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1676884611300&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;ObjectMapper objectMapper = new ObjectMapper();
KakaoProfileDto kakaoProfile;
try {
    kakaoProfile = objectMapper.readValue(response.getBody(), KakaoProfileDto.class);
} catch (JsonMappingException e) {
    throw new RuntimeException(e);
} catch (JsonProcessingException e) {
    throw new RuntimeException(e);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;토큰 요청때와 동일하게 body를 dto로 받아주고, 해당 dto를 사용하여 원하는 서비스 로직을 작성해주면 된다.&lt;/p&gt;</description>
      <category>[ Backend ]/Infra</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/145</guid>
      <comments>https://eckrin.tistory.com/145#entry145comment</comments>
      <pubDate>Mon, 13 Feb 2023 18:24:36 +0900</pubDate>
    </item>
    <item>
      <title>[Java] 자바의 예외(Exception) 처리</title>
      <link>https://eckrin.tistory.com/144</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;1. 에러&lt;/span&gt;(Error)&lt;span&gt;와&lt;/span&gt; &lt;span&gt;예외&lt;/span&gt;(Exception)&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1234&quot; data-origin-height=&quot;564&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bg6x6f/btsxsSwCc3N/M0g5P5cbHmt1K519BPBSQ1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bg6x6f/btsxsSwCc3N/M0g5P5cbHmt1K519BPBSQ1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bg6x6f/btsxsSwCc3N/M0g5P5cbHmt1K519BPBSQ1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbg6x6f%2FbtsxsSwCc3N%2FM0g5P5cbHmt1K519BPBSQ1%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;591&quot; height=&quot;270&quot; data-origin-width=&quot;1234&quot; data-origin-height=&quot;564&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&gt; &lt;/span&gt;자바에서는&lt;span&gt; &lt;/span&gt;잡을&lt;span&gt; &lt;/span&gt;필요도&lt;span&gt; &lt;/span&gt;없고&lt;span&gt;, &lt;/span&gt;회복또한&lt;span&gt; &lt;/span&gt;불가능한&lt;span&gt; &lt;/span&gt;것을&lt;b&gt;&lt;span&gt; &lt;/span&gt;에러&lt;span&gt;(Error)&lt;/span&gt;&lt;/b&gt;라고&lt;span&gt; &lt;/span&gt;하며&lt;span&gt;, &lt;/span&gt;잡을&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있고&lt;span&gt;&amp;nbsp;&lt;/span&gt;회복이&lt;span&gt; &lt;/span&gt;가능한&lt;span&gt; &lt;/span&gt;것을&lt;span&gt; &lt;/span&gt;&lt;b&gt;예외&lt;span&gt;(Exception)&lt;/span&gt;&lt;/b&gt;이라고&lt;span&gt; &lt;/span&gt;부른다&lt;span&gt;. 위&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;그림에서&lt;span&gt; &lt;/span&gt;나타나는&lt;span&gt; &lt;/span&gt;에러들을&lt;span&gt; &lt;/span&gt;보면&lt;span&gt; &lt;/span&gt;쉽게&lt;span&gt; &lt;/span&gt;이해할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있을&lt;span&gt; &lt;/span&gt;것이다&lt;span&gt;. &lt;/span&gt;예를&lt;span&gt; &lt;/span&gt;들어&lt;span&gt; OutOfMemoryError&lt;/span&gt;라는&lt;span&gt; &lt;/span&gt;에러는&lt;span&gt;&amp;rsquo; &lt;/span&gt;메모리&lt;span&gt; &lt;/span&gt;부족&lt;span&gt;&amp;rsquo;&lt;/span&gt;이라는&lt;span&gt; &lt;/span&gt;예상할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;없고&lt;span&gt;, &lt;/span&gt;프로그램상으로&lt;span&gt; &lt;/span&gt;해결할&lt;span&gt; &lt;/span&gt;수도&lt;span&gt; &lt;/span&gt;없는&lt;span&gt; &lt;/span&gt;문제를&lt;span&gt; &lt;/span&gt;나타내고&lt;span&gt; &lt;/span&gt;있다&lt;span&gt;. &lt;/span&gt;아마&lt;span&gt;&amp;nbsp;자연스럽게&amp;nbsp;&lt;/span&gt;느껴질텐데&lt;span&gt;, &lt;/span&gt;이러한&lt;span&gt; &lt;/span&gt;이유로&lt;span&gt; &lt;/span&gt;프로그램을&lt;span&gt; &lt;/span&gt;짤&lt;span&gt; &lt;/span&gt;때&lt;span&gt; &lt;/span&gt;주로&lt;span&gt;&amp;nbsp;개발자&lt;/span&gt;는&lt;span&gt; 에러보다는 &lt;/span&gt;예외에&lt;span&gt; &lt;/span&gt;대해서&lt;span&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span&gt;2. 예외(Exception)의 종류&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;365&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5LkcI/btrYYp08DyF/tQECPN6OBPwnY1sRZo4jUk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5LkcI/btrYYp08DyF/tQECPN6OBPwnY1sRZo4jUk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5LkcI/btrYYp08DyF/tQECPN6OBPwnY1sRZo4jUk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5LkcI%2FbtrYYp08DyF%2FtQECPN6OBPwnY1sRZo4jUk%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;556&quot; height=&quot;334&quot; data-origin-width=&quot;608&quot; data-origin-height=&quot;365&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;자바에서 모든 예외도 객체이므로 Object를 상속받으며, 최상위 예외로는 Throwable클래스를 갖는다. Throwable 클래스는 Exception과 Error로 나뉜다. &lt;b&gt;Error&lt;/b&gt;는 애플리케이션 개발자가 해결할 수 없는 시스템 에러(ex-OutOfMemoryError)이고, &lt;b&gt;Exception&lt;/b&gt;이 프로그래머가 처리 가능한 예외이다. 일반적으로 Exception은 체크 예외(checked exception)과 언체크 예외(unchecked-exception)으로 분류한다.&lt;/p&gt;
&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;- 체크 예외(checked exception)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: &lt;span style=&quot;color: #ee2323;&quot;&gt;컴파일러가 체크하는 예외&lt;/span&gt;이다. 반드시 throws로 예외를 던질 수 있음을 명시하거나, try-catch문으로 예외를 처리해주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 언체크 예외(unchecked exception)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: 컴파일러가 체크하지 않는 언체크 예외이다. Exception의 하위 클래스인 &lt;span style=&quot;color: #ee2323;&quot;&gt;RuntimeException&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;b&gt;1. 개발할 때는 언체크 예외를 사용하자&lt;/b&gt;. 체크 예외를 사용하면 반드시 명시적으로 처리하거나, 던져주어야 하기 때문에 코드가 복잡해질 뿐 아니라, 의존관계에 대한 문제가 발생한다. 따라서 체크 예외는 컨트롤러나 서비스에서 잡아서 처리해야 하는 예외일 경우만 사용하고, 그 외에는 런타임 예외를 사용하여 @ControllerAdvice등으로 일괄적으로 잡아 처리해주도록 하자.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 언체크 예외를 사용할 경우, 컴파일러가 예외처리 로직을 검증해주지 않기 때문에 문서화해놓는것이 좋다. 또한 발생하는 체크 예외를 받아서 언체크예외로 전환해주는 경우에는, 반드시 &lt;b&gt;넘어오는 체크예외를 파라미터 마지막에 포함&lt;/b&gt;하여 로그로 stacktrace가 출력되도록 하자.&lt;/p&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;3. 체크 예외의 사용에 관한 고찰&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면&lt;span&gt; &lt;/span&gt;예외&lt;span&gt;, &lt;/span&gt;그&lt;span&gt; &lt;/span&gt;중에도&lt;span&gt; &lt;/span&gt;체크&lt;span&gt; &lt;/span&gt;예외에&lt;span&gt; &lt;/span&gt;대해서&lt;span&gt; &lt;/span&gt;이야기해보자&lt;span&gt;. &lt;/span&gt;코드를&lt;span&gt; &lt;/span&gt;작성하다&lt;span&gt; &lt;/span&gt;빨간색&lt;span&gt; &lt;/span&gt;밑줄이&lt;span&gt; &lt;/span&gt;그어지고&lt;span&gt;, &lt;/span&gt;사용한&lt;span&gt; &lt;/span&gt;라이브러리&lt;span&gt; &lt;/span&gt;메소드&lt;span&gt; &lt;/span&gt;등에서&lt;span&gt; (&lt;/span&gt;체크&lt;span&gt;)&lt;/span&gt;예외가&lt;span&gt; &lt;/span&gt;발생해서&lt;span&gt; &lt;/span&gt;올라왔다고&lt;span&gt; &lt;/span&gt;하자&lt;span&gt;. &lt;/span&gt;우리는&lt;span&gt; &lt;/span&gt;두가지&lt;span&gt; &lt;/span&gt;선택지가&lt;span&gt; &lt;/span&gt;있다&lt;span&gt;. &lt;/span&gt;잡거나&lt;span&gt;(try-catch), &lt;/span&gt;던지거나&lt;span&gt;(throws). &lt;/span&gt;그렇다면&lt;span&gt; &lt;/span&gt;이&lt;span&gt; &lt;/span&gt;예외는&lt;span&gt; &lt;/span&gt;어디서&lt;span&gt; &lt;/span&gt;잡아서&lt;span&gt; &lt;/span&gt;처리해주어야&lt;span&gt; &lt;/span&gt;하는가&lt;span&gt;? &lt;/span&gt;처음&lt;span&gt; &lt;/span&gt;예외가&lt;span&gt; &lt;/span&gt;발생한&lt;span&gt; &lt;/span&gt;메소드를&lt;span&gt; &lt;/span&gt;호출한&lt;span&gt; &lt;/span&gt;곳에서&lt;span&gt; &lt;/span&gt;처리해야&lt;span&gt; &lt;/span&gt;하는가&lt;span&gt;? &lt;/span&gt;아니면&lt;span&gt; &lt;/span&gt;콜스택의&lt;span&gt; &lt;/span&gt;가장&lt;span&gt; &lt;/span&gt;상위단계에서&lt;span&gt; &lt;/span&gt;잡아&lt;span&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&gt;&lt;br /&gt;&lt;/span&gt;&amp;nbsp;에러처리의&lt;span&gt; &lt;/span&gt;가장&lt;span&gt; &lt;/span&gt;중요한&lt;span&gt; &lt;/span&gt;원칙&lt;span&gt; &lt;/span&gt;중&lt;span&gt; &amp;lsquo;&lt;/span&gt;&lt;b&gt;책임지지&lt;span&gt; &lt;/span&gt;않을&lt;span&gt; &lt;/span&gt;바에는&lt;span&gt; &lt;/span&gt;잡지도&lt;span&gt; &lt;/span&gt;말라&lt;/b&gt;&lt;span&gt;&amp;rsquo;&lt;/span&gt;라는&lt;span&gt; &lt;/span&gt;말이&lt;span&gt; &lt;/span&gt;있다&lt;span&gt;. &lt;/span&gt;예외는&lt;span&gt; &lt;/span&gt;항상&lt;span&gt; &lt;/span&gt;의미있는&lt;span&gt; &lt;/span&gt;방식으로&lt;span&gt; &lt;/span&gt;처리해야&lt;span&gt; &lt;/span&gt;한다는&lt;span&gt; &lt;/span&gt;뜻이다&lt;span&gt;. &lt;/span&gt;예를&lt;span&gt; &lt;/span&gt;들어&lt;span&gt; &lt;/span&gt;서비스&lt;span&gt; &lt;/span&gt;계층에서&lt;span&gt; &lt;/span&gt;&lt;b&gt;비즈니스 규칙을 담은 체크예외&lt;/b&gt;(예를 들면 잔고에 돈이 모자라다는 NotEnoughMoneyException이 있다고 하자)가&lt;span&gt; &lt;/span&gt;발생했다고&lt;span&gt; &lt;/span&gt;가정하면&lt;span&gt; &lt;/span&gt;사용자에게&lt;span&gt; &lt;/span&gt;비즈니스적인&lt;span&gt; &lt;/span&gt;메세지를&lt;span&gt; &lt;/span&gt;보여주거나&lt;span&gt;, &lt;/span&gt;다른&lt;span&gt; &lt;/span&gt;페이지로&lt;span&gt; &lt;/span&gt;리다이렉트하는&lt;span&gt; &lt;/span&gt;방식으로&lt;span&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;span&gt; &lt;/span&gt;작업은&lt;span&gt; &lt;/span&gt;콜스택의&lt;span&gt; &lt;/span&gt;가장&lt;span&gt; &lt;/span&gt;상위단계에서&lt;span&gt; &lt;/span&gt;처리하는&lt;span&gt; &lt;/span&gt;것이&lt;span&gt; &lt;/span&gt;알맞은&lt;span&gt; &lt;/span&gt;처리&lt;span&gt; &lt;/span&gt;방법이라고&lt;span&gt; &lt;/span&gt;예상할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있다&lt;span&gt;. &lt;/span&gt;예외가&lt;span&gt; &lt;/span&gt;처음&lt;span&gt; &lt;/span&gt;발생한&lt;span&gt; &lt;/span&gt;서비스&lt;span&gt; &lt;/span&gt;계층에서&lt;span&gt; 리다이렉트를&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;위해 &lt;/span&gt;컨트롤러의&lt;span&gt; HttpResponse&lt;/span&gt;객체를&lt;span&gt; &lt;/span&gt;참조하는&lt;span&gt; &lt;/span&gt;것은&lt;span&gt; &lt;/span&gt;분명&lt;span&gt; &lt;/span&gt;어색함을&lt;span&gt; &lt;/span&gt;동반하기&lt;span&gt; &lt;/span&gt;때문이다&lt;span&gt;. &lt;/span&gt;화면에&lt;span&gt; &lt;/span&gt;관한&lt;span&gt; &lt;/span&gt;내용&lt;span&gt;(&lt;/span&gt;리다이렉트&lt;span&gt;)&lt;/span&gt;는&lt;span&gt; &lt;/span&gt;서비스&lt;span&gt; &lt;/span&gt;계층의&lt;span&gt; &lt;/span&gt;책임이&lt;span&gt; &lt;/span&gt;아니기에&lt;span&gt;, &lt;/span&gt;계층화된&lt;span&gt; &lt;/span&gt;설계의&lt;span&gt; &lt;/span&gt;목적에도&lt;span&gt; &lt;/span&gt;벗어나게&lt;span&gt; &lt;/span&gt;된다&lt;span&gt;. 뿐만 아니라 비즈니스 예외를 체크예외로 명시할 경우 비즈니스 규칙을 상세하게 명시하고 구현상의 세부사항은 숨기는 것이 좋다는 원칙에도 부합하기에 &lt;/span&gt;이러한&lt;span&gt; &lt;/span&gt;경우&lt;span&gt; throws&lt;/span&gt;를&lt;span&gt; &lt;/span&gt;통해서&lt;span&gt; controller&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;그런데&lt;span&gt; &lt;/span&gt;이번에는&lt;span&gt; &lt;/span&gt;&lt;b&gt;데이터베이스에서&lt;span&gt; &lt;/span&gt;발생&lt;span&gt; &lt;/span&gt;가능한&lt;span&gt; &lt;/span&gt;체크&lt;span&gt; &lt;/span&gt;예외&lt;/b&gt;인&lt;span&gt; SQLException&lt;/span&gt;을&lt;span&gt; &lt;/span&gt;처리해야&lt;span&gt; &lt;/span&gt;한다고&lt;span&gt; &lt;/span&gt;하자&lt;span&gt;. &lt;/span&gt;비즈니스&lt;span&gt; &lt;/span&gt;요구조건이&lt;span&gt; SQLException &lt;/span&gt;발생시&lt;span&gt; &lt;/span&gt;리다이렉트시켜주는&lt;span&gt; &lt;/span&gt;것이라고&lt;span&gt; &lt;/span&gt;가정했을&lt;span&gt; &lt;/span&gt;때&lt;span&gt;, &lt;/span&gt;위와&lt;span&gt; &lt;/span&gt;동일하게&lt;span&gt; &lt;/span&gt;리다이렉트를&lt;span&gt; &lt;/span&gt;위해서&lt;span&gt; HttpResponse&lt;/span&gt;를&lt;span&gt; &lt;/span&gt;참조할&lt;span&gt; &lt;/span&gt;수&lt;span&gt; &lt;/span&gt;있는&lt;span&gt; &lt;/span&gt;컨트롤러에서&lt;span&gt; &lt;/span&gt;체크&lt;span&gt; &lt;/span&gt;예외를&lt;span&gt; &lt;/span&gt;잡아&lt;span&gt; &lt;/span&gt;처리해주었다&lt;span&gt;. 과연&amp;nbsp;&lt;/span&gt;이것이&lt;span&gt;&amp;nbsp;&lt;/span&gt;맞는&lt;span&gt; &lt;/span&gt;처리방식이라고 할 수 있을까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;SQLException&lt;/span&gt;이&lt;span&gt; &lt;/span&gt;컨트롤러까지&lt;span&gt; &lt;/span&gt;올라가는&lt;span&gt; &lt;/span&gt;과정에서&lt;span&gt; &lt;/span&gt;서비스&lt;span&gt; &lt;/span&gt;계층의&lt;span&gt; &lt;/span&gt;메서드들은&lt;span&gt; SQLException&lt;/span&gt;을&lt;span&gt; &lt;/span&gt;던지게&lt;span&gt; &lt;/span&gt;되고&lt;span&gt;, &lt;/span&gt;이것은&lt;span&gt; &lt;/span&gt;&lt;b&gt;중간&lt;span&gt; &lt;/span&gt;단계의&lt;span&gt; &lt;/span&gt;모든&lt;span&gt; &lt;/span&gt;메소드들이&lt;span&gt; JDBC API&lt;/span&gt;에&lt;span&gt; &lt;/span&gt;의존성이&lt;span&gt; &lt;/span&gt;생기게&lt;span&gt; &lt;/span&gt;된다&lt;/b&gt;는&lt;span&gt; &lt;/span&gt;것을&lt;span&gt; &lt;/span&gt;의미한다&lt;span&gt;. &lt;/span&gt;추후&lt;span&gt; JDBC API&lt;/span&gt;가&lt;span&gt; &lt;/span&gt;아닌&lt;span&gt; &lt;/span&gt;다른&lt;span&gt; &lt;/span&gt;구현체로&lt;span&gt; &lt;/span&gt;변경하게&lt;span&gt; &lt;/span&gt;되면&lt;span&gt; SQLException&lt;/span&gt;을&lt;span&gt; &lt;/span&gt;던졌던&lt;span&gt; &lt;/span&gt;모든&lt;span&gt; &lt;/span&gt;메소드들과&lt;span&gt; try-catch&lt;/span&gt;문을&lt;span&gt; &lt;/span&gt;수정해야&lt;span&gt; &lt;/span&gt;한다는&lt;span&gt; &lt;/span&gt;것을&lt;span&gt; &lt;/span&gt;의미한다&lt;span&gt;. &lt;/span&gt;결국&lt;span&gt; &lt;/span&gt;기본적인&lt;span&gt;&amp;nbsp;객체지향의 &lt;/span&gt;추상화까지&lt;span&gt; &lt;/span&gt;위반하게&lt;span&gt; &lt;/span&gt;되는&lt;span&gt; &lt;/span&gt;것이다&lt;span&gt;.&lt;/span&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&gt;&amp;nbsp;이렇게 동일한 체크 예외라도 비즈니스 규칙을 담은 체크 예외와 일반적인 체크 예외의 사용은 차이를 가짐을 알 수 있다.&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;4. 예외를&lt;span&gt; &lt;/span&gt;처리하도록&lt;span&gt; &lt;/span&gt;강제해야&lt;span&gt; &lt;/span&gt;하는가&lt;span&gt;?&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;위 내용의 결론이라고 이야기해도 될 것 같다. 위에서 언급했듯&lt;/span&gt;&lt;/span&gt;&lt;span&gt;, &lt;/span&gt;체크&lt;span&gt; &lt;/span&gt;예외는&lt;span&gt; &lt;/span&gt;발생하는&lt;span&gt; &lt;/span&gt;예외를&lt;span&gt; &lt;/span&gt;잡아서&lt;span&gt; &lt;/span&gt;처리할 것을&lt;span&gt; &lt;/span&gt;강제하고&lt;span&gt; &lt;/span&gt;있다&lt;span&gt;. &lt;/span&gt;이는&lt;span&gt; &lt;/span&gt;한편으로는&lt;span&gt; &lt;/span&gt;예측&lt;span&gt; &lt;/span&gt;가능한&lt;span&gt; &lt;/span&gt;예외에&lt;span&gt; &lt;/span&gt;대한&lt;span&gt; &lt;/span&gt;명시적인&lt;span&gt; &lt;/span&gt;처리를&lt;span&gt;&amp;nbsp;통해&lt;/span&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;프로그램의&lt;span&gt; &lt;/span&gt;안정성에&lt;span&gt; &lt;/span&gt;도움이&lt;span&gt; &lt;/span&gt;되는&lt;span&gt; &lt;/span&gt;것처럼&lt;span&gt; &lt;/span&gt;보이지만&lt;span&gt;, &lt;/span&gt;강제성에&lt;span&gt; &lt;/span&gt;따라오는&lt;span&gt; &lt;/span&gt;여러&lt;span&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;&lt;span&gt;&lt;br /&gt;C#&lt;/span&gt;의&lt;span&gt; &lt;/span&gt;창시자&lt;span&gt; &lt;/span&gt;엔더스&lt;span&gt; &lt;/span&gt;헤일버그는&lt;span&gt; &lt;/span&gt;이러한&lt;span&gt; &lt;/span&gt;체크&lt;span&gt; &lt;/span&gt;예외에&lt;span&gt; &lt;/span&gt;대해서&lt;span&gt; &quot;&lt;/span&gt;버전관리&lt;span&gt;(Versioning)&lt;/span&gt;와&lt;span&gt; &lt;/span&gt;확장성&lt;span&gt;(Scalability)&lt;/span&gt;에&lt;span&gt; &lt;/span&gt;문제를&lt;span&gt; &lt;/span&gt;가지고&lt;span&gt; &lt;/span&gt;있다&quot;라고&lt;span&gt; &lt;/span&gt;언급한다&lt;span&gt;. &lt;/span&gt;결국&lt;span&gt; &lt;/span&gt;둘&lt;span&gt; &lt;/span&gt;다&lt;span&gt; &lt;/span&gt;비슷한&lt;span&gt; &lt;/span&gt;맥락인데&lt;span&gt;, 간단하게 말하면&amp;nbsp;&lt;/span&gt;라이브러리&lt;span&gt;&amp;nbsp;&lt;/span&gt;버전관리로&lt;span&gt; &lt;/span&gt;인해&lt;span&gt; &lt;/span&gt;추가&lt;span&gt;, &lt;/span&gt;삭제되는&lt;span&gt; &lt;/span&gt;체크&lt;span&gt; &lt;/span&gt;예외는&lt;span&gt; &lt;/span&gt;라이브러리를&lt;span&gt; &lt;/span&gt;가져다&lt;span&gt; &lt;/span&gt;쓰는&lt;span&gt; &lt;/span&gt;사용자&lt;span&gt; &lt;/span&gt;코드에까지&lt;span&gt; &lt;/span&gt;영향을&lt;span&gt; &lt;/span&gt;미치게&lt;span&gt; &lt;/span&gt;되며&lt;span&gt;, &lt;/span&gt;대규모&lt;span&gt; &lt;/span&gt;시스템에서&lt;span&gt; &lt;/span&gt;여러&lt;span&gt; &lt;/span&gt;라이브러리를&lt;span&gt; &lt;/span&gt;가져다&lt;span&gt; &lt;/span&gt;사용하면&lt;span&gt;&amp;nbsp;&lt;/span&gt;수십개의&lt;span&gt; &lt;/span&gt;체크&lt;span&gt; &lt;/span&gt;에러를&lt;span&gt; &lt;/span&gt;처리하게&lt;span&gt;&amp;nbsp;&lt;/span&gt;될&lt;span&gt; &lt;/span&gt;수도&lt;span&gt; &lt;/span&gt;있다는&lt;span&gt; &lt;/span&gt;이야기이다&lt;span&gt;.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;&lt;span&gt;&amp;nbsp;이러한&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;이유로&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;불필요한&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;체크&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;예외는&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;사용하지&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;않거나&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;런타임&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;예외로&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;변환하여&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;사용하는&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;프로젝트가&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;많고&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;여러&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;라이브러리에서도&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;체크&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;예외를&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;언체크&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;예외로&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;변환하여&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;던져주고는&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;한다(스프링&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;프레임워크에서는&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;SQLException&lt;/span&gt;을&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;추상화한&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;DataAccessException&lt;/span&gt;이&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;RuntimeException&lt;/span&gt;을&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;상속하고&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;있다&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;).&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;더&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;나아가&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;코틀린에서는&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;체크&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;예외를&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;구분하지&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;않아&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;강제로&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;throws&lt;/span&gt;해줄&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;필요가&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;/span&gt;없게&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;&amp;nbsp;&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;&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&gt;참고:&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.artima.com/articles/the-trouble-with-checked-exceptions&quot;&gt;https://www.artima.com/articles/the-trouble-with-checked-exceptions&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://okky.kr/articles/362305&quot;&gt;https://okky.kr/articles/362305&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://toss.tech/article/kotlin-result&quot;&gt;https://toss.tech/article/kotlin-result&lt;/a&gt;&lt;/p&gt;</description>
      <category>[ Languages ]/Java</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/144</guid>
      <comments>https://eckrin.tistory.com/144#entry144comment</comments>
      <pubDate>Sun, 12 Feb 2023 20:35:25 +0900</pubDate>
    </item>
    <item>
      <title>[DB] 트랜잭션, 동시성 제어, 락</title>
      <link>https://eckrin.tistory.com/143</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;트랜잭션&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;트랜잭션이란 데이터베이스에서 하나의 공통된 목적을 위한 작업을 의미한다. 예를 들면, A에서 B로의 '송금'이라는 거래를 위해서는 A의 잔고에서 임의의 금액을 빼고, B의 잔고에 임의의 금액만큼을 더해주는 2가지 일이 한 가지 일처럼 실행되어야 한다. 두가지 일 중에서 하나의 작업이라도 누락된다면 문제가 발생할 수 있는데, 트랜잭션은 모든 하위 작업을 묶어서 모두 실행되거나, 모두 실행되지 않도록 만든다. 만약에 모든 작업이 성공하여 DB에 변화가 반영되었다면 해당 작업이 &lt;b&gt;커밋&lt;/b&gt;(commit)되었다고 하고, 하나라도 실패해서 작업 이전으로 되돌아가는 것을 &lt;b&gt;롤백&lt;/b&gt;(rollback)이라고 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;트랜잭션의 성질&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션은 ACID로 불리는 4가지 대표적인 성질을 갖는데, 각각 원자성, 일관성, 고립성, 지속성의 특징을 갖는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;1. 원자성&lt;/b&gt;(Atomicity): 트랜잭션에 포함된 작업은 전부 수행되거나 전부 수행되지 말아야 한다는 뜻이다. 일부만 수행되는 일이 있어서는 안된다. DBMS는 회복 관리자 프로그램을 통해 이를 유지한다(All or Nothing)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 일관성&lt;/b&gt;(Consistency): 트랜잭션은 데이터베이스의 일관성을 유지해야 한다. 이 일관성은 테이블 생성 시에 create나 alter문의 무결성 제약조건을 통해서 명시된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. 고립성&lt;/b&gt;(Isolation): 데이터베이스에서는 동시에 여러 트랜잭션이 수행될 수 있는데, 동시에 수행되는 트랜잭션들은 상호 존재를 모르고 독립적으로 수행되는 현상을 고립성이라고 한다. 고립성을 위해서는 DB에 동시에 여러 트랜잭션이 접근할 때 데이터의 일관성이 깨지지 않도록 &lt;b&gt;동시성 제어&lt;/b&gt;가 필요하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;4. 지속성&lt;/b&gt;(Durability): 트랜잭션이 정상적으로 완료 혹은 부분완료된 데이터는 DBMS가 책임지고 데이터베이스에 기록하는 성질을 말한다. 원자성과 유사하게 회복 관리자 프로그램을 통해서 지켜진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;동시성 제어&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;트랜잭션이 동시에 수행될 때, 데이터의 일관성을 해치지 않도록 트랜잭션의 데이터 접근을 제한할 필요가 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션 T1과 T2가 존재한다고 하자. T1과 T2가 동시에 동일한 데이터를 읽을 경우에는 문제가 발생하지 않는다. 하지만 T1이 데이터를 쓰는 동안 T2가 해당 데이터를 읽는다면 &lt;b&gt;오손 읽기, 반복불가능 읽기, 유령 데이터 읽기&lt;/b&gt;와 같은 문제가 발생할 수 있으며, T1과 T2가 동시에 동일한 데이터에 대한 쓰기 작업을 할 경우 &lt;b&gt;갱신손실&lt;/b&gt;을 유발할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 오손 읽기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; T1이 쓰기 작업중인 데이터를 T2가 읽었는데, T1이 해당 작업을 &lt;b&gt;rollback&lt;/b&gt;할 경우에 T2는 rollback되어 무효가 된 데이터를 읽게 되는 현상.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 반복불가능 읽기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; T2가 데이터를 2번 읽었는데, 그 사이에 T1의 &lt;b&gt;쓰기(수정) 작업&lt;/b&gt;이 이루어지는 경우 T2는 쓰기 작업 없이 데이터가 변경되는 문제를 낳게 된다. 즉, T2입장에서는 동일한 select문으로 다른 결과가 도출될 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 유령 데이터 읽기&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; T2가 데이터를 2번 읽었는데, 그 사이에 T1의 데이터 &lt;b&gt;삽입&lt;/b&gt;이 이루어지는 경우 T2는 이전에 없던 데이터가 나타나는 현상이 발생한다. 즉, T2 입장에서는 동일한 select문으로 새로운 데이터가 생성된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 갱신 손실&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;-&amp;gt; T1과 T2가 한 개의 데이터를 동시에 &lt;b&gt;갱신&lt;/b&gt;(update)할 때 발생하며, T1과 T2중에 먼저 커밋된 작업이 손실된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 문제들은 &lt;a href=&quot;https://eckrin.tistory.com/102&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;트랜잭션 고립 수준&lt;/a&gt;을 조절함으로서 일부분을 해결 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;락&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러개의 세션에서 동시에 같은 데이터를 수정하게 되면 갱신 손실이 발생하는데, 트랜잭션 고립 수준에 따라서 허용할 수 있는 오손 읽기, 반복불가능 읽기, 유령데이터 읽기와는 다르게 갱신 손실은 절대 허용되어서는 안된다. 따라서 데이터베이스는 락(Lock)을 사용하여 데이터를 동시에 수정할 수 없게 해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 공유락&lt;/b&gt;(LS): 트랜잭션 읽기 작업시 사용하는 락&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;- 배타락&lt;/b&gt;(LX): 트랜잭션 읽기/쓰기 작업시 사용하는 락&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터에 락이 걸려있지 않으면 트랜잭션은 데이터 X에 락을 걸 수 있는데, &lt;b&gt;읽기만 할 경우 LS(X)&lt;/b&gt;를 요청하고, &lt;b&gt;쓰기를 할 경우 LX(X)&lt;/b&gt;를 요청한다. 공유락(LS)는 다른 트랜잭션의 공유락은 허용하지만(배타락은 허용하지 않는다), 배타락(LX)가 걸려있는 경우 다른 트랜잭션은 공유락(LS)과 배타락(LX) 모두 허용하지 않고 &lt;b&gt;대기&lt;/b&gt;한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션이 종료될 경우 락을 반환하고, 대기하던 트랜잭션이 락을 획득하여 작업을 진행한다.&lt;/p&gt;</description>
      <category>[ CS기초 ]/데이터베이스</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/143</guid>
      <comments>https://eckrin.tistory.com/143#entry143comment</comments>
      <pubDate>Wed, 8 Feb 2023 22:19:19 +0900</pubDate>
    </item>
    <item>
      <title>BOJ 15686) 치킨 배달 -&amp;gt; 조합(Combination) 정리</title>
      <link>https://eckrin.tistory.com/142</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;골드 5인데도 하루를 &lt;span style=&quot;color: #781b33;&quot;&gt;&lt;b&gt;꼬박&lt;/b&gt;&lt;/span&gt; 썼다. 직전에 유사한 문제 (BOJ 14502 연구소 - &lt;a href=&quot;https://www.acmicpc.net/problem/14502&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.acmicpc.net/problem/14502&lt;/a&gt;)를 풀어서 조합을 이용한 풀이를 빨리 떠올렸고, 하다못해 구글링해서 아주 유사한 코드를 발견했음에도 어디가 문제인지 몰라서 한참을 헤맨 문제. (난 알고리즘 팔생각도 없고 코테합만 안정적으로 할 딱 그정도 수준만 되고싶을 뿐인데 참 어렵다.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제 정리&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;간단하게 정리하면, &quot;NxN 크기의 도시에 집들과 치킨집들이 있다. 이 치킨집들의 숫자를 M개까지 줄일 때, '치킨 거리'의 합의 최소값을 구하시오. (치킨 거리란, 임의의 집에서 치킨집까지 거리의 최소값을 의미한다.)&quot;로 정리할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;풀이&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;처음에는 NxN의 인접 행렬로 도시를 표현하고 나서, 처음 존재하는 치킨집을 k개라고 하면 폐업할 치킨집을 구하는 조합(kC(k-M))을 이용해서 폐업 치킨집을 선택하고, 선택된 조합마다 bfs를 사용해서 집-치킨집 거리를 구한 후 최소값을 선택해주면 된다고 생각했다. 하지만 계속 시간초과가 떠서 고쳤던 목록을 나열하겠다.&lt;/p&gt;
&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;1. LinkedList -&amp;gt; ArrayList&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt; LinkedList는 연결 리스트 기반, ArrayList는 배열 기반의 리스트이므로, .get()을 통해서 조회하는데는 인덱스를 바로 활용할 수 있는 ArrayList가 유리하다. 반면 LinkedList는 .remove()를 통해 삭제하는데 좋은 효율을 가진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(참고: &lt;a href=&quot;https://eckrin.tistory.com/136&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://eckrin.tistory.com/136&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;2. 인접 행렬과 인접 리스트&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt; 인접 행렬의 경우 구현이 쉽다는 장점을 가지지만, 해당 알고리즘에서는 집과 치킨집 사이의 거리를 구하기 위해서 좌표만 필요로 하므로(map[i][j]가 0인 지점의 정보를 필요로 하지 않는다), 각각 집과 치킨집의 정보를 저장하는 인접 리스트로 저장하면 필요한 정보만 리스트로 저장할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가로 인접 행렬을 이용한다면 bfs를 사용한 탐색이 자연스럽게 떠오르게 되는데, 인접 리스트로 각각의 정보를 저장해놨다면 bfs를 사용할 필요 없이 O(집개수*치킨집개수)의 고정된 시간복잡도로 최단거리(치킨 거리)를 탐색 가능하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;3. dfs를 이용한 조합(Combination)&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;gt; 문제를 푸는데 결정적인 부분이었다. 14502 연구소 문제에서 활용한 조합은 다음과 같다.&lt;/p&gt;
&lt;pre id=&quot;code_1675797311165&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public static void makeWall(int depth) {
    if(depth==3) {
        rst = Math.max(rst, safe_num - bfs() - 3);
        return;
    }

    //NxMC3
    for(int i=0; i&amp;lt;N; i++) {
        for(int j=0; j&amp;lt;M; j++) {
            if(map[i][j]==0) {
                map[i][j] = 1;
                makeWall(depth+1);
                map[i][j] = 0;
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;NxM크기의 map에서, map[i][j]가 0인 부분 중에서 3개를 골라 1로 바꾸어주는 방식의 백트래킹을 이용한 조합이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기서는 3&amp;lt;=N,M&amp;lt;=8로 N과 M이 매우 적었고, 정확히 3개만을 고르는 로직이므로 별다른 처리 없이 NxM 모두를 순회하면서 백트래킹을 진행하였다. 이 경우 최대 O((8*8)^3)=O(26만)정도의 시간복잡도를 가지기 때문이다.&lt;/p&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;하지만 이번 문제는 최대 50x50 크기의 map을 대상으로 하고, 최대 12개의 치킨집을 고르게 될 수도 있으므로 시간복잡도가 매우 증가해 최대 O((50*50)^12)=O(59,604,644,775,390,625,000,000,000,000)의 시간복잡도를 갖게 될 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금 더 효율적인 방법을 사용하여, 처음 존재하는 치킨집에서 닫을 치킨집을 구하는 방식으로 식을 세운다고 가정해도, 문제 조건에 따라 치킨집이 최대 13개 존재할 수 있으므로 주어지는 M값에 따라서 O(13^6)=O(480만)정도가 필요해지게 된다. 이러면 각 케이스마다 최단 치킨거리를 구하기 위한 로직(O(집개수*치킨집개수)=O(2487*13)=O(32331))을 돌려야 하므로 최종적으로 O(155,188,800,000)내외의 시간복잡도를 가지게 된다.&lt;/p&gt;
&lt;pre id=&quot;code_1675797893634&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private static void combination(int depth, int start) {
    if(depth==(totalM-M)) {
        totalDistance = Math.min(getChickenDistance(), totalDistance);
        return;
    }

    for(int i=0; i&amp;lt;chickenList.size(); i++) {
        if(!isClosed[i]) {
            isClosed[i] = true;
            combination(depth+1, start+1);
            isClosed[i] = false;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;따라서 이러한 경우는 다음과 같이 풀 수 있다.&lt;/p&gt;
&lt;pre id=&quot;code_1675798542557&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private static void combination(int depth, int start) {
    if(depth==(totalM-M)) {
        totalDistance = Math.min(getChickenDistance(), totalDistance);
        return;
    }

    for(int i=start; i&amp;lt;chickenList.size(); i++) { //start부터 탐색
        if(!isClosed[i]) {
            isClosed[i] = true;
            combination(depth+1, i+1); //호출시 i+1부터 재귀
            isClosed[i] = false;
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&quot;조합은 순서가 중요하지 않으므로, 순서를 정해놓고 뽑아주면 된다.&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;start라는 정수가 매개변수에 추가되었는데, 이 변수는 재귀호출이 이루어질때마다 지금까지 고려하여 탐색한 부분을 다시 탐색하지 않도록 한다. 참고로 조합은 시간복잡도가 O(2^n)으로 매우 크다.&lt;/p&gt;</description>
      <category>[ 기타 ]/백준, 프로그래머스</category>
      <author>eckrin</author>
      <guid isPermaLink="true">https://eckrin.tistory.com/142</guid>
      <comments>https://eckrin.tistory.com/142#entry142comment</comments>
      <pubDate>Wed, 8 Feb 2023 04:45:11 +0900</pubDate>
    </item>
  </channel>
</rss>