<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>g_onmin41.log</title>
        <link>https://velog.io/</link>
        <description>이것저것이것 개발자</description>
        <lastBuildDate>Wed, 22 Apr 2026 23:29:11 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>g_onmin41.log</title>
            <url>https://velog.velcdn.com/images/g_onmin41/profile/1f313b2d-b123-4e11-a6c1-5f3e5e0612f5/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. g_onmin41.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/g_onmin41" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[알고리즘]백준 1106_호텔 ]]></title>
            <link>https://velog.io/@g_onmin41/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EB%B0%B1%EC%A4%80-1106%ED%98%B8%ED%85%94</link>
            <guid>https://velog.io/@g_onmin41/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EB%B0%B1%EC%A4%80-1106%ED%98%B8%ED%85%94</guid>
            <pubDate>Wed, 22 Apr 2026 23:29:11 GMT</pubDate>
            <description><![CDATA[<p><a href="#https://www.acmicpc.net/problem/1106">백준 1106_호텔</a></p>
<ul>
<li>dp로 각 인원 별 비용을 최소비용으로 갱신하며 진행</li>
<li>비용순으로 정렬하고 갱신하려 했으나 하다보니 의미없음을 깨달음</li>
<li>그냥 받은 비용별 고객수를 다 돌리는 방법. C명이상일 때 최소비용이니까 +99명(광고별 인원의 최대값 - 1) 까지 체크<ul>
<li>c명일 때 최소비용일수도 있지만 c-1명일 때 100명을 get한 비용을 더한 게 최소일수도 있음<pre><code class="language-java">import java.io.*;
import java.util.*;
</code></pre>
</li>
</ul>
</li>
</ul>
<p>public class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st = new StringTokenizer(br.readLine());</p>
<pre><code>    int C = Integer.parseInt(st.nextToken()); // 목표 고객 수
    int N = Integer.parseInt(st.nextToken()); // 도시(광고) 수

    int[] cost = new int[N];
    int[] customer = new int[N];

    for (int i = 0; i &lt; N; i++) {
        st = new StringTokenizer(br.readLine());
        cost[i] = Integer.parseInt(st.nextToken());
        customer[i] = Integer.parseInt(st.nextToken());
    }

    int limit = C + 99;

    int[] dp = new int[limit + 1];
    Arrays.fill(dp, Integer.MAX_VALUE);
    dp[0] = 0;

    // 모든 고객 수 i에 대해
    for (int i = 0; i &lt;= limit; i++) {
           // 최소값 갱신 안된 애들 패스
        if (dp[i] == Integer.MAX_VALUE) continue;

        // 모든 광고를 다 확인
        for (int j = 0; j &lt; N; j++) {
            int next = i + customer[j]; // 지금 인원수 + 비용내면 get하는 인원수
            // 한도 초과 시 패스
            if (next &gt; limit) continue;
            // 비용 최소로 갱신
            dp[next] = Math.min(dp[next], dp[i] + cost[j]);
        }
    }

    int answer = Integer.MAX_VALUE;

    // C명 이상 중 최소 비용 찾기
    for (int i = C; i &lt;= limit; i++) {
        answer = Math.min(answer, dp[i]);
    }

    System.out.println(answer);
}</code></pre><p>}
```</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] 백준1005_ACM Craft]]></title>
            <link>https://velog.io/@g_onmin41/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%B0%B1%EC%A4%801005ACM-Craft</link>
            <guid>https://velog.io/@g_onmin41/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%B0%B1%EC%A4%801005ACM-Craft</guid>
            <pubDate>Sun, 19 Apr 2026 23:21:00 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/1005">백준 1005 ACM Craft</a></p>
<ul>
<li>특정 출발지에서 도착지까지 가는 비용의 최대값<ul>
<li>특정 건물을 건설하기위해서는 그 전 건물들 건설 =&gt; 더 오래걸리는 시간으로 갱신</li>
</ul>
</li>
<li>출발지는 여러 개 일 수도 있지만 도착지는 하나니까 도착지에서 출발하는 걸로. 안그럼 출발지 따로 저장해서 체크해야됨. <ul>
<li>parents 배열에 그 전 지어야되는 건물_출발지 를 도착지 인덱스의 값으로 저장.</li>
</ul>
</li>
<li>dfs 로 갱신</li>
</ul>
<pre><code class="language-java">import java.io.*;
import java.util.*;

public class Main {

    static int[] buildTime;                 // 각 건물 건설 시간
    static int[] dp;                        // 해당 건물 완성까지 걸리는 총 시간
    static ArrayList&lt;Integer&gt;[] parents;    // 역방향 그래프: parents[to] = to를 짓기 전에 필요한 건물들

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringBuilder sb = new StringBuilder();

        int T = Integer.parseInt(br.readLine());

        while (T-- &gt; 0) {
            StringTokenizer st = new StringTokenizer(br.readLine());
            int N = Integer.parseInt(st.nextToken()); // 건물 개수
            int K = Integer.parseInt(st.nextToken()); // 간선 개수

            buildTime = new int[N + 1];
            dp = new int[N + 1];
            parents = new ArrayList[N + 1];

            for (int i = 1; i &lt;= N; i++) {
                parents[i] = new ArrayList&lt;&gt;();
                dp[i] = -1; 
            }

            st = new StringTokenizer(br.readLine());
            for (int i = 1; i &lt;= N; i++) {
                buildTime[i] = Integer.parseInt(st.nextToken());
            }

            for (int i = 0; i &lt; K; i++) {
                st = new StringTokenizer(br.readLine());
                int from = Integer.parseInt(st.nextToken());
                int to = Integer.parseInt(st.nextToken());

                // 도착지에 출발지 저장
                parents[to].add(from);
            }

            int target = Integer.parseInt(br.readLine());

            // 도착지에서 출발. 
            sb.append(dfs(target)).append(&#39;\n&#39;);
        }

        System.out.print(sb);
    }

    static int dfs(int cur) {
        // 계산 되어있으면 그대로
        if (dp[cur] != -1) return dp[cur];

        // 선행 건물이 없으면 자기 건설 시간만 필요
        if (parents[cur].isEmpty()) {
            dp[cur] = buildTime[cur];
            return dp[cur];
        }

        // 선행 건물 있으면 dfs 로 최대값 계산
        int maxPrev = 0;

        for (int prev : parents[cur]) {
            maxPrev = Math.max(maxPrev, dfs(prev));
        }

        dp[cur] = maxPrev + buildTime[cur];
        return dp[cur];
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] 백준 16195_1,2,3 더하기 9]]></title>
            <link>https://velog.io/@g_onmin41/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%B0%B1%EC%A4%80-16195123-%EB%8D%94%ED%95%98%EA%B8%B0-9</link>
            <guid>https://velog.io/@g_onmin41/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%B0%B1%EC%A4%80-16195123-%EB%8D%94%ED%95%98%EA%B8%B0-9</guid>
            <pubDate>Wed, 15 Apr 2026 11:18:23 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/16195">백준 16195_1,2,3 더하기 9</a></p>
<ul>
<li>1 에서 7까지 사용한 숫자 개수별 더해진 수 다 작성해봄</li>
<li>1 - 1, 2 - 1 1, 3 - 1 2 1, 4 - 0 3 3 1 ... </li>
<li>하다가 일단 찾긴 했는데 왜 인지 고민. </li>
<li>7을 5개로 하면 6을 4개로 한 거에 1붙이고, 5를 4개로 한 거에 2붙이고, 4를 4개로 한거에 3 붙인거.<ul>
<li>이걸 1~4개로 했을 때 더해진 수에 더하면 ㅇㅋ<pre><code class="language-java">import java.io.*;
import java.util.*;
</code></pre>
</li>
</ul>
</li>
</ul>
<p>public class Main {
    // 나눌 거랑 n의 최대값
    static final int MOD = 1_000_000_009;
    static final int MAX = 1000;</p>
<pre><code>public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    StringBuilder sb = new StringBuilder();

    // dp[i][j] = 합이 i이고 정확히 j개를 사용한 경우의 수
    long[][] dp = new long[MAX + 1][MAX + 1];
    // dpp[i][j] = dp[i][1] + dp[i][2] + ... + dp[i][j]
    long[][] dpp = new long[MAX + 1][MAX + 1];


    dp[0][0] = 1;

    // dp 갱신
    for (int i = 1; i &lt;= MAX; i++) {
        for (int j = 1; j &lt;= MAX; j++) {
            long value = 0;

            if (i - 1 &gt;= 0) value = (value + dp[i - 1][j - 1]) % MOD;
            if (i - 2 &gt;= 0) value = (value + dp[i - 2][j - 1]) % MOD;
            if (i - 3 &gt;= 0) value = (value + dp[i - 3][j - 1]) % MOD;

            dp[i][j] = value;
        }
    }

    // dp 더한 dpp 갱신
    for (int i = 0; i &lt;= MAX; i++) {
        for (int j = 1; j &lt;= MAX; j++) {
            dpp[i][j] = (dpp[i][j - 1] + dp[i][j]) % MOD;
        }
    }

    int T = Integer.parseInt(br.readLine());

    while (T-- &gt; 0) {
        StringTokenizer st = new StringTokenizer(br.readLine());
        int n = Integer.parseInt(st.nextToken());
        int m = Integer.parseInt(st.nextToken());

        sb.append(prefix[n][m]).append(&#39;\n&#39;);
    }

    System.out.print(sb);
}</code></pre><p>}
```</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘]백준 1774_우주신과의 교감]]></title>
            <link>https://velog.io/@g_onmin41/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EB%B0%B1%EC%A4%801774</link>
            <guid>https://velog.io/@g_onmin41/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EB%B0%B1%EC%A4%801774</guid>
            <pubDate>Sun, 12 Apr 2026 22:46:17 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/1774">백준 1774_우주신과의 교감</a></p>
<ul>
<li><p>모든 좌표가 연결 + 최소 길이</p>
</li>
<li><p>매 좌표마다 다른 좌표와의 길이 배열 구하고 오름차순 정렬</p>
</li>
<li><p>기존에 연결되어있는 거 제외하고 길이 최소값인 좌표로 연결</p>
<ul>
<li>union find로 연결되어있는지 확인 및 연결</li>
</ul>
</li>
<li><p>union find: 각 집단의 대표로 연결 여부 확인</p>
</li>
<li><p>최소 스패닝 트리: 모든 정점 연결 + 총 비용 최소, 싸이클 없음</p>
<ul>
<li>KrusKal: 비용이 적은 간선부터 고르는 방식</li>
</ul>
</li>
</ul>
<pre><code class="language-java">/**
 * Edge : 간선, 두 좌표의 인덱스랑 거리 저장. 오름차순 정렬 설정.
 * find : 집단 대표 찾기 및  for union find
 * union : 두 좌표가 연결되어있는 지 집단 대표로 체크
 * edges: 좌표간의 Edge들을 길이기준 오름차순 정렬
 * parent 배열에 대표 저장, edges 순회 돌면서 연결안되어있으면 총 길이에 +
 */
import java.io.*;
import java.util.*;

public class Main {

    // 간선: 두 좌표 인덱스, 거리. 
    static class Edge implements Comparable&lt;Edge&gt; {
        int u, v; // 좌표 인덱스
        double w; // 길이

        Edge(int u, int v, double w) {
            this.u = u;
            this.v = v;
            this.w = w;
        }

        // 거리 기준 오름차순 정렬. 거리는 실수범위 -&gt; double 
        @Override
        public int compareTo(Edge o) {
            return Double.compare(this.w, o.w);
        }
    }

    // 대표 좌표 인덱스, rank 는 트리 높이 줄여 탐색 최적화용, 
    static int[] parent;
    static int[] rank;

    // x가 속한 그룹의 대표를 찾는 함수
    static int find(int x) {
        if (parent[x] == x) return x;
        return parent[x] = find(parent[x]); // 대표 갱신_경로 압축 + 반환
    }

    // a와 b를 같은 그룹으로 합치는 함수_연결 여부 확인 + rank 갱신
    // 이미 같은 그룹이면 false
    // 새롭게 합쳐졌으면 true
    static boolean union(int a, int b) {
        int ra = find(a); // a의 대표
        int rb = find(b); // b의 대표

        // 대표가 같으면, 이미 같은 그룹이면 합칠 필요 없음
        if (ra == rb) return false;

        // 더 낮은 트리를 높은 트리 대표에 붙여서 성능 개선
        if (rank[ra] &lt; rank[rb]) {
            parent[ra] = rb;
        } else if (rank[ra] &gt; rank[rb]) {
            parent[rb] = ra;
        } else {
            parent[rb] = ra;
            rank[ra]++;
        }

        return true;
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st;

        // 정점 개수, 이미 연결된 간선 개수
        st = new StringTokenizer(br.readLine());
        int n = Integer.parseInt(st.nextToken());
        int m = Integer.parseInt(st.nextToken());

        int[] x = new int[n + 1];
        int[] y = new int[n + 1];

        // 좌표 입력
        for (int i = 1; i &lt;= n; i++) {
            st = new StringTokenizer(br.readLine());
            x[i] = Integer.parseInt(st.nextToken());
            y[i] = Integer.parseInt(st.nextToken());
        }

        // union find초기화
        parent = new int[n + 1];
        rank = new int[n + 1];

        for (int i = 1; i &lt;= n; i++) {
            parent[i] = i; // 처음엔 자기 자신이 대표
        }

        // 이미 연결된 간선 먼저 반영
        for (int i = 0; i &lt; m; i++) {
            st = new StringTokenizer(br.readLine());
            int a = Integer.parseInt(st.nextToken());
            int b = Integer.parseInt(st.nextToken());

            union(a, b);
        }

        // 모든 정점 쌍 사이의 거리로 간선 생성
        List&lt;Edge&gt; edges = new ArrayList&lt;&gt;();


        for (int i = 1; i &lt;= n; i++) {
            for (int j = i + 1; j &lt;= n; j++) {
                long dx = x[i] - x[j];
                long dy = y[i] - y[j];

                // 거리 계산
                double dist = Math.sqrt(dx * dx + dy * dy);

                edges.add(new Edge(i, j, dist));
            }
        }

        // 거리 짧은 순으로 정렬
        Collections.sort(edges);

        double answer = 0.0;

        // 거리 짧은 순으로 체크
        for (Edge e : edges) {
            // 다른 그룹이면_연결 안되어있으면_ 연결 후 거리 +
            if (union(e.u, e.v)) {
                answer += e.w;
            }
        }

        // 소수점 둘째짜리까지 반올림 후 출력
        System.out.printf(&quot;%.2f\n&quot;, answer);
    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘 ]리트코드19_Remove Nth Node From End of List]]></title>
            <link>https://velog.io/@g_onmin41/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%A6%AC%ED%8A%B8%EC%BD%94%EB%93%9C19Remove-Nth-Node-From-End-of-List</link>
            <guid>https://velog.io/@g_onmin41/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%A6%AC%ED%8A%B8%EC%BD%94%EB%93%9C19Remove-Nth-Node-From-End-of-List</guid>
            <pubDate>Sun, 28 Dec 2025 11:37:30 GMT</pubDate>
            <description><![CDATA[<p><a href="https://leetcode.com/problems/remove-nth-node-from-end-of-list/description/">리트코드_19</a></p>
<ul>
<li><p>처음에는 평범하게 접근</p>
</li>
<li><p>순회하면서 길이 측정. 그리고 다시 순회하면서 length-n+1 번째 노드가 마지막 노드면 length-n 번째 노드의 next를 null로. 아니면 length-n+2 노드로. </p>
</li>
</ul>
<pre><code class="language-java">/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        // length가 n일 경우_삭제 대상이 head일 경우
        ListNode dummy = new ListNode(0);
        dummy.next = head;

        // length 계산
        int length = 0;
        ListNode cur = head;
        while (cur != null) {
            length++;
            cur = cur.next;
        }

        // 앞에서부터 (length - n)번째 노드_삭제 대상 이전노드_로 이동
        int target = length - n; // dummy에서 steps만큼 가면 prev에 도착
        ListNode prev = dummy;
        for (int i = 0; i &lt; target; i++) {
            prev = prev.next;
        }

        // 삭제
        prev.next = prev.next.next;

        return dummy.next;
    }
}</code></pre>
<ul>
<li><p>하지만 문제 의도가 그게 아닌 거 같아서 찾아봤는데 투 포인터로 푸는 거더라</p>
</li>
<li><p>포인터 하나를 n만큼 이동시키고 이후에 두 포인터를 동시에 이동시키면 이동거리는 length-n.삭제할 노드의 이전 노드 위치로 이동 가능. </p>
</li>
</ul>
<pre><code class="language-java">/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;

        ListNode first = dummy;
        ListNode second = dummy;

        // first를 n칸 앞으로
        for (int i = 0; i &lt; n; i++) {
            first = first.next; 
        }

        // first가 끝에 닿을 때까지 같이 이동
        while (fast.next != null) {
            first = first.next;
            second = second.next;
        }

        // slow.next가 삭제 대상
        second.next = second.next.next;

        return dummy.next;
    }
}
</code></pre>
<ul>
<li><p>시간복잡도는 똑같이 O(L)</p>
</li>
<li><p>하지만 길이를 모른다는 전제에서는 투포인터를 사용하는 게 문제의도에 적합</p>
<ul>
<li><p>리스트가 너무 크거나, 데이터가 스트리밍으로 올 경우 적합</p>
</li>
<li><p>응용: 중간 노드 찾기, Cycle Detection (Floyd’s Tortoise and Hare, 연결리스트 내 사이클 여부 확인), 런너 기법 (Runner Technique)</p>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[네트워크]Protocol]]></title>
            <link>https://velog.io/@g_onmin41/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%ACProtocol</link>
            <guid>https://velog.io/@g_onmin41/%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%ACProtocol</guid>
            <pubDate>Sun, 28 Dec 2025 10:53:04 GMT</pubDate>
            <description><![CDATA[<h2 id="목차">목차</h2>
<ol>
<li><a href="#%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC-%EA%B5%AC%EC%84%B1%EB%8F%84">네트워크 구성도</a></li>
<li><a href="#Protocol">Protocol</a></li>
<li><a href="#OSI-7%EA%B3%84%EC%B8%B5">OSI 7계층</a></li>
<li><a href="#TCP/IP">TCP/IP</a></li>
<li><a href="#Encapsulation">Encapsulation</a></li>
</ol>
<h2 id="네트워크-구성도">네트워크 구성도</h2>
<blockquote>
<p>네트워크는 크게 받는 쪽(클라이언트) 과 주는 쪽(서버/데이터센터) 으로 구분</p>
</blockquote>
<h3 id="홈-네트워크클라이언트">홈 네트워크(클라이언트)</h3>
<ul>
<li><strong>인터넷</strong> -케이블-&gt; <strong>네트워크 장비(모뎀)</strong> -케이블-&gt; <strong>공유기(Router)</strong> -케이블/매체-&gt; <strong>단말(노트북 등)</strong>
<img src="https://velog.velcdn.com/images/g_onmin41/post/ae517967-e44f-4c3b-8d91-42595e2015c3/image.png" alt=""></li>
</ul>
<p><strong>모뎀:</strong> ISP(통신사) 신호를 디지털 신호로 변환(변조, 복조. 주로 1계층.) </p>
<ul>
<li>ONT(Optical Network Terminal): 광신호를 디지털 신호로 변환</li>
</ul>
<p><strong>공유기(Router)</strong> </p>
<ul>
<li><p>라우팅(내부망 &lt;-&gt; 외부망. IP주소 기반.3계층)</p>
</li>
<li><p>NAT: 사설IP(단말) &lt;-&gt; 공인IP(공유기) 변환. 3~4계층.</p>
</li>
<li><p>DHCP(IP주소,게이트웨이,DNS등 자동으로 임대. 7계층) 수행.</p>
</li>
<li><p>방화벽 기능(3,4,7계층)</p>
</li>
</ul>
<p><strong>단말:</strong> 실제 애플리케이션 실행 주체</p>
<h3 id="데이터-센터-네트워크서버">데이터 센터 네트워크(서버)</h3>
<blockquote>
<p>데이터 센터는 <strong>대량 트래픽 + 고가용성(서비스 중단없이) + 서버 간 통신</strong>(MSA, 분산시스템 등)이 핵심</p>
</blockquote>
<ul>
<li><strong>높은 통신량 수용</strong>(초고속 네트워크. 10G ~ 400G), 장애를 대비한 <strong>이중화</strong>(Redundancy) 필요</li>
</ul>
<p><img src="https://velog.velcdn.com/images/g_onmin41/post/efe058d6-cb60-4a07-8e56-2272d3beffbb/image.png" alt=""></p>
<p><strong>3계층 구조(코어-분배-액세스)</strong></p>
<ul>
<li><p>Access: 서버, PC가 직접 연결. 말단</p>
</li>
<li><p>Distribution: Access를 묶음, 정책, VLAN, 라우팅 일부 처리</p>
</li>
<li><p>Core: 네트워크의 중심, 모든 트래픽이 여기로 모임 (백본)</p>
</li>
<li><p>North-South 트래픽: 외부 인터넷과 서버 간의 통신 (전통적인 트래픽)</p>
</li>
</ul>
<p>서버 간 통신인데도 항상 Core까지 올라갔다가 내려와야 함, 서버 수 ↑ → Core 트래픽 폭증. <strong>병목/복잡성이 증가</strong>.
-&gt; 수직적 구조보다 <strong>수평적으로 서버끼리 빠르게, 균등하게 연결되는 구조 필요</strong></p>
<p><strong>Scale-out</strong>: 서버를 더 강하게(Scale-up) 만드는 게 아니라, 서버 대수를 늘려 처리량을 올리는 방식</p>
<ul>
<li><p>가상화/컨테이너/클라우드에 특화.</p>
</li>
<li><p>네트워크는 이런 확장을 감당해야 하므로 높은 대역폭 + 단순한 확장 구조가 중요</p>
</li>
<li><p>East-West 트래픽: 서버와 서버, 서비스와 서비스 간의 통신</p>
</li>
</ul>
<p><strong>Spine–Leaf 구조</strong></p>
<blockquote>
<p>모든 Leaf는 모든 Spine에 연결된다. Leaf끼리는 직접 연결하지 않는다</p>
</blockquote>
<ul>
<li><p><strong>Leaf (ToR, Top of Rack) 스위치</strong></p>
<ul>
<li><p>서버 랙(Rack) 단위로 붙는 스위치. 해당 랙의 서버통신 담당.</p>
</li>
<li><p>서버 랙(Rack): 서버를 수직으로 장착하는 표준화된 프레임. 보통 전원, 냉각, 네트워크를 랙 단위로 관리. 물리적.
<img src="https://velog.velcdn.com/images/g_onmin41/post/617a11a9-04bf-4902-99c4-e23c73180593/image.png" alt=""></p>
</li>
<li><p>보통 10G Base-T(구리선,랜선으로 10Gbps까지 지원하는 이더넷), 10GBASE-SR/LR(광 케이블 기반), Ethernet 포트_이더넷 프레임 송수신용 인터페이스 사용</p>
</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/g_onmin41/post/e319d4f7-98d9-4efe-b085-4a14c0edb713/image.png" alt=""></p>
<ul>
<li><p><strong>Spine 스위치</strong></p>
<ul>
<li><p>Leaf 스위치들을 서로 연결하는 백본(네트워크의 중심이 되는 고속 통신 경로). 중계 담당.</p>
</li>
<li><p>40G / 100G / 400G등 고대역폭</p>
</li>
</ul>
</li>
<li><p>어떤 서버든 “Leaf → Spine → Leaf”로 일정한 홉 수로 도달</p>
</li>
<li><p>Leaf만 추가해도 서버를 늘릴 수 있어 Scale-out(수평 확장, 가상화, 높은 대역폭) 에 최적화된 구조</p>
</li>
<li><p>leaf 스위치는 2<del>3계층, Spine 스위치는 데이터 설계에 따라 2</del>3계층. </p>
</li>
<li><p>로드밸런서: 해당 구조내에서 서비스 트래픽(클라이언트 &lt;-&gt; 서버) 분산</p>
<ul>
<li><p>4계층: IP, PORT, TCP/UDP 헤더 단위 분산, 빠름, 데이터 미확인 ex) NLB(Network Load Balancer) </p>
</li>
<li><p>7계층: HTTP Header, Cookie, URL, Payload 기반 라우팅, 정교한 라우팅 가능, 콘텐츠 기반 분산 ex) ALB(Application Load Balancer)</p>
</li>
</ul>
</li>
</ul>
<pre><code>3계층 구조에서 서버 A → B

A → Access → Distribution → Core → Distribution → Access → B
(홉 수 많음, 경로 길어짐)


Spine–Leaf에서 서버 A → B
A → Leaf → Spine → Leaf → B
(항상 동일한 홉 수)
</code></pre><h2 id="protocol">Protocol</h2>
<blockquote>
<p>Protocol = 네트워크 <strong>통신 규약</strong></p>
</blockquote>
<ul>
<li>서로 다른 장비와 시스템이 통신하려면 약속된 규칙이 필요</li>
</ul>
<p><strong>물리적 프로토콜:</strong> 비트를 어떤 신호로, 어떤 규칙으로 구성할 지 ex) Ethernet</p>
<p><strong>논리적 프로토콜:</strong> 어디로 보낼지(IP), 어떻게 신뢰성 있게 보낼지(TCP) ex) TCP/IP</p>
<p><strong>애플리케이션 프로토콜:</strong> HTTP, FTP, SMTP 등</p>
<h3 id="프로토콜이-필요한-이유">프로토콜이 필요한 이유</h3>
<ul>
<li><p>과거: 자원이 한정 → 이진수 기반 통신. 서로 다른 네트워크간 통신을 위해 규악 필요.</p>
</li>
<li><p>현재: 애플리케이션 레벨 → 문자열, 구조화된 데이터(JSON 등)</p>
</li>
</ul>
<p>프로토콜 스택: 이 복잡성을 관리하기 위해 계층별 역할 분담이 필요. 계층별로 역할을 나눠 협업. </p>
<p>ex) TCP/IP</p>
<ul>
<li><p>HTTP: 요청/응답의 형식</p>
</li>
<li><p>TCP: 유실 없게, 순서 보장</p>
</li>
<li><p>IP: 목적지까지 라우팅</p>
</li>
<li><p>Ethernet: 같은 네트워크에서 프레임 전달</p>
</li>
</ul>
<h2 id="osi-7계층">OSI 7계층</h2>
<blockquote>
<p>설계/학습을 위한 <strong>이론적 참조 모델(Reference Model)</strong>, 실제 구현은 대부분 TCP/IP 기반</p>
</blockquote>
<ul>
<li><p>1<del>4 데이터플로(하위)계층, 네트워크 엔지니어, 5</del>7 어플리케이션(상위)계층, 애플리케이션 개발자</p>
</li>
<li><p>각 계층마다 <strong>PDU(Protocol Data Unit)</strong> 존재
<img src="https://velog.velcdn.com/images/g_onmin41/post/f1796fa0-4850-4b96-b43a-c2abfaa92357/image.png" alt=""></p>
</li>
</ul>
<p><strong>1계층_물리 계층 (Physical). PDU=Bits</strong></p>
<ul>
<li><p>물리적 연결, 전기신호 들어오면 깔끔하게 <strong>재생성(리피터)</strong>해 내보냄. </p>
</li>
<li><p><strong>주소개념x,</strong> 모든 포트에 같은 전기신호 전송(허브)</p>
</li>
<li><p>허브, 리피터, 케이블, 커넥터, 트랜시버, 탭</p>
</li>
</ul>
<p><strong>2계층_데이터 링크 계층 (Data Link). PDU=Frames</strong>
<img src="https://velog.velcdn.com/images/g_onmin41/post/18f2dfd6-7d98-48b2-ae7d-8d9097258abf/image.png" alt=""></p>
<ul>
<li><p>전기신호 -&gt; <strong>식별가능한 데이터 형태(프레임)</strong>. 주소정보 정의(MAC주소)</p>
</li>
<li><p>** MAC 주소**(NIC에 부여된 고유 식별자, 같은 네트워크에서만 의미있음) 기반 통신.</p>
</li>
<li><p><strong>네트워크 인터페이스 카드(NIC)</strong>: 각 단말, 서버에 붙어있는 네트워크 장치. 프레임 생성 or 전기신호 프레임으로 변환하고 해석. 고유 MAC주소 가짐</p>
<ul>
<li><p>네트워크 카드, 랜카드, PNI(물리 네트워크 인터페이스), 이더넷 카드, 네트워크 어뎁터 등 으로도 부름</p>
</li>
<li><p><strong>오류 탐지(CRC)</strong>: 데이터의 무결성 검사, 검증값(FCS) 계산. (오류 탐지만ㅋ)</p>
</li>
</ul>
</li>
<li><p><strong>스위치</strong>: MAC 주소를 학습해서 필요한 포트로만 전달. MAC Address Table 유지. 트래픽 효율 업.(공유기에 포함) &lt;-&gt; 허브</p>
</li>
<li><p>흐름 제어(Flow Control): 로컬수준에서 수신 처리 속도, 송신 속도로 인한 프레임 유실 방지. </p>
<ul>
<li>ex)  802.3x PAUSE Frame &lt;-&gt; 4계층 TCP의 혼잡제어</li>
</ul>
</li>
</ul>
<p><strong>같은 네트워크 사이에서의 통신</strong> </p>
<pre><code>송신
1. 상위 계층(IP)에서 데이터 내려옴
2. NIC가 Ethernet 프레임 생성
3. 목적지 MAC 설정
4. FCS(CRC) 계산
5. 1계층 신호로 변환하여 전송

스위치
1. 스위치 포트에서 신호 수신
2. 프레임 해석 (L2)
3. 목적지 MAC 확인
4. MAC Address Table 조회
5. 해당 포트로 프레임 전달

수신
1계층 신호로 변환하여 전송
1. 케이블에서 전기신호 수신 (1계층)
2. NIC가 비트를 프레임으로 변환 (2계층)
3. NIC가 목적지 MAC 확인
4. CRC로 오류 탐지
5. 맞으면 → 상위 계층(IP)으로 전달</code></pre><p><strong>3계층_네트워크 계층 (Network). PDU=Packets</strong></p>
<ul>
<li><p><strong>IP 주소 사용</strong> (논리적 주소, 변경 가능), 다른 네트워크까지 전달.</p>
</li>
<li><p>네트워크 주소(172.30) + 호스트 주소(0.1) 구조</p>
<ul>
<li>172.31.0.1</li>
</ul>
</li>
<li><p><strong>라우터</strong>: 서로 다른 네트워크 간 최적 경로로 패킷 전달. 라우터 -&gt; 라우팅 테이블 -&gt; 다음 홉 선택 ...
&lt;-&gt; 스위치: 같은 네트워크</p>
</li>
<li><p><strong>ARP</strong>: IP 주소를 알고 있을 때 물리적인 MAC 주소를 알아내기 위해 사용 </p>
</li>
</ul>
<p><strong>다른 네트워크 사이에서의 통신</strong> </p>
<pre><code>송신
1. 상위 계층(IP)에서 데이터 내려옴
   목적지 IP = 다른 네트워크의 IP
2. 네트워크 계층에서 판단
   - 이 IP는 내 네트워크가 아님 → 게이트웨이(라우터)로 보내야 함
3. ARP를 통해 게이트웨이의 MAC 주소 확인
   - 목적지 MAC = 라우터 MAC
4. NIC가 Ethernet 프레임 생성
   - 출발지 MAC = 내 NIC MAC
   - 목적지 MAC = 라우터 MAC
5. FCS(CRC) 계산
6. 1계층 신호로 변환하여 전송

스위치 (출발지 쪽 LAN)
1. 스위치 포트에서 신호 수신
2. 프레임 해석 (L2)
3. 목적지 MAC 확인 (라우터 MAC)
4. MAC Address Table 조회
5. 라우터가 연결된 포트로 프레임 전달

라우터 (네트워크 경계, L3)
1. 라우터 포트에서 신호 수신
2. 프레임 해석 (L2)
3. 목적지 MAC == 라우터 MAC → 수신 OK
4. Ethernet 헤더 제거
   - (기존 MAC 정보는 여기서 폐기)
5. IP 헤더 확인 (L3)
   - 목적지 IP 기준으로 라우팅 테이블 조회
   - 다음 홉 결정

라우터 → 다음 네트워크로 재전송(MAC 주소 바뀜, IP주소는 유지)
6. 다음 홉의 MAC 주소를 ARP로 확인
7. 새로운 Ethernet 프레임 생성
   - 출발지 MAC = 라우터 MAC
   - 목적지 MAC = 다음 홉(또는 최종 서버) MAC
8. FCS(CRC) 계산
9. 1계층 신호로 변환하여 전송

(중간에 스위치가 또 있다면)
스위치
1. 신호 수신
2. 프레임 해석
3. 목적지 MAC 확인
4. MAC 테이블 조회
5. 해당 포트로 전달

수신
1. 케이블에서 전기신호 수신 (1계층)
2. NIC가 비트를 프레임으로 변환 (2계층)
3. NIC가 목적지 MAC 확인
4. CRC로 오류 탐지
5. 맞으면 → 상위 계층(IP)으로 전달</code></pre><p><strong>4계층_전송 계층 (Transport). PDU=Segments</strong></p>
<ul>
<li><p>(TCP 기준) <strong>데이터 송,수신 신뢰성: 유실 방지, 순서 보장</strong></p>
</li>
<li><p>프로세스 &lt;-&gt; 프로세스 단위 통신</p>
</li>
<li><p><strong>포트 번호</strong>로 프로세스 식별 (ex. 80, 443, 3306. 집 안의 방 번호) &lt;-&gt; MAC주소(NIC. 집주소)</p>
</li>
<li><p><strong>시퀀스 번호(Sequence Number)</strong>: TCP는 데이터를 조각내서 보내며, 각 조각에 보내는 순서_번호를 붙임. 송신측 </p>
</li>
<li><p><strong>ACK 번호(Acknowledgement Number)</strong>: 다음에 받고싶은 시퀀스 번호. 수신측 상태보고. HTTP 요청 단위가 아니라 TCP 세그먼트 단위로 교환</p>
</li>
<li><p>L4 로드밸런서: IP/Port 기반으로 트래픽 분산, 서버 선택. 보안정책 수립. 패킷 통과 or 차단 (TCP/UDP 레벨)</p>
</li>
<li><p>방화벽: 포트 기반 접근 제어를 많이 수행 ex) 22번(SSH)은 특정 IP만 허용</p>
<ul>
<li>L7 로드밸런서/방화벽도 존재</li>
</ul>
</li>
</ul>
<p><strong>5계층_세션 계층 (Session). PDU=Data</strong></p>
<ul>
<li><p>응용 프로세스 간 <strong>연결 생성 / 종료/ 복구</strong></p>
</li>
<li><p>누가 로그인 상태인지, 연결이 살아있는지 같은 상태를 관리하거나, 일정 시간 응답 없으면 끊고 다시 맺는 정책 사용</p>
</li>
<li><p>재전송은 TCP가 많이 담당하지만, 애플리케이션 레벨에서도 재시도(리트라이)를 구현</p>
</li>
</ul>
<p><strong>6계층_표현 계층 (Presentation). PDU=Data</strong></p>
<ul>
<li><p>데이터 형식 통일</p>
</li>
<li><p>인코딩 / 디코딩</p>
</li>
<li><p>암호화, 압축</p>
</li>
<li><p>MIME: Content-Type</p>
</li>
<li><p><strong>TLS 암호화</strong>: 전송 중 데이터가 도청/변조되지 않게 보호, 대칭키를 안전하게 합의하기 위해 핸드셰이크 수행 ex)HTTPS의 S</p>
<ul>
<li>TLS는 TCP 위에서 동작. OSI 관점에서는 6계층으로 설명</li>
</ul>
</li>
</ul>
<p><strong>7계층_응용 계층 (Application). PDU=Data</strong></p>
<ul>
<li><p>사용자가 직접 사용하는 프로토콜</p>
<ul>
<li><p>HTTP: 웹 요청/응답</p>
</li>
<li><p>FTP: 파일 전송</p>
</li>
<li><p>SMTP: 이메일 전송</p>
</li>
<li><p>TELNET: 원격 터미널_암호화X, 데이터 평문(요즘은 보안상 SSH 사용이 일반적_암호화)</p>
</li>
</ul>
</li>
</ul>
<h2 id="tcpip">TCP/IP</h2>
<blockquote>
<p>OSI 7계층을 실제 구현 관점에서 단순화한 모델. 실용성에 중점, 4계층으로 구분</p>
</blockquote>
<table>
<thead>
<tr>
<th>TCP/IP 계층</th>
<th>OSI 계층</th>
</tr>
</thead>
<tbody><tr>
<td>Application</td>
<td>5~7</td>
</tr>
<tr>
<td>Transport</td>
<td>4</td>
</tr>
<tr>
<td>Internet</td>
<td>3</td>
</tr>
<tr>
<td>Network Access</td>
<td>1~2</td>
</tr>
</tbody></table>
<h2 id="encapsulation">Encapsulation</h2>
<blockquote>
<p>데이터를 전송할 때, 각 계층(2~4계층)의 Header를 붙여서 하위 계층으로 전달해주는 것</p>
</blockquote>
<p><strong>Encapsulation</strong>: 7계층 -&gt; 1계층. 보낼 때</p>
<p><strong>Decapsulation</strong>: 1계층 -&gt; 7계층. 받을 때</p>
<ul>
<li><p>패킷 기반 네트워크: 데이터를 패킷 단위로 쪼개서 보냄, 받아서 결합.</p>
</li>
<li><p>2~4계층에서 헤더를 하나씩 추가(비트 단위). 해당 계층 정보 + 상위 프로토콜 지시자 작성.</p>
</li>
</ul>
<h3 id="계층-별-캡슐화-구조">계층 별 캡슐화 구조</h3>
<p><img src="https://velog.velcdn.com/images/g_onmin41/post/dd3067b0-1597-41ea-a881-c0e3d7d18311/image.png" alt=""></p>
<ul>
<li>OSI 7계층<ul>
<li>transport layer: 세그먼트, 4계층 헤더 + 상위 데이터<ul>
<li>TCP 헤더: 포트, 시퀀스/ACK</li>
</ul>
</li>
<li>Network layer: 패킷, 3계층 헤더 + 상위데이터<ul>
<li>IP 헤더: 출발지/목적지 IP</li>
</ul>
</li>
<li>Data Link Layer: 프레임, 2계층 헤더 + 상위데이터 + FCS(오류검출용, FrameTrailer 영역)<ul>
<li>Ethernet 헤더: 출발지/목적지 MAC, EtherType</li>
</ul>
</li>
</ul>
</li>
</ul>
<p><strong>상위 프로토콜 지시자</strong>: 동작하는 계층보다 한 계층 위의 정보</p>
<ul>
<li><p>Port Number: 어떤 프로세스로 보낼지 (4계층)</p>
<ul>
<li>TCP 20,21 : FTP</li>
<li>TCP 22 : SSH</li>
<li>TCP 80 : HTTP</li>
<li>TCP 443 : HTTPS</li>
</ul>
</li>
<li><p>프로토콜 번호 (3계층)</p>
<ul>
<li>1 : ICMP</li>
<li>2 : IGMP</li>
<li>6 : TCP</li>
<li>17 : UDP</li>
</ul>
</li>
<li><p>EtherType: 상위 프로토콜 종류(2계층) ex) IPv4인지, IPv6인지</p>
<ul>
<li>0x0800 : IPv4</li>
<li>0x86DD : IPv6</li>
<li>0x0806 : ARP</li>
</ul>
</li>
</ul>
<pre><code>1. (7계층) HTTP 요청 생성
2. (6계층) TLS 암호화, 데이터 인코딩
3. (5계층) TLS 세션 유지, Keep-Alive
4. (4계층) TCP 세그먼트 생성  
   - 출발지 포트: 임시 포트  
   - 목적지 포트: 443  
   - 시퀀스 번호, ACK 번호
5. (3계층) IP 헤더 추가, 라우팅, NAT
6. (2계층) MAC 주소 기반 프레임 생성 (목적지: 공유기 MAC)
7. (1계층) 전기/무선 신호로 전송

각각 독립적인 네트워크 통신이며, 각 통신마다 OSI 7계층 캡슐화 과정이 반복
1. DNS 조회 - 도메인(example.com)을 IP 주소로 변환

(7) DNS 요청 메시지 생성
(6) (필요 시) 인코딩
(5) 세션
(4) UDP 또는 TCP 세그먼트 생성 (보통 UDP 53)
(3) IP 헤더 추가
(2) MAC 프레임 생성
(1) 전기/무선 신호 전송

2. TCP 3-way handshake - SYN → SYN-ACK → ACK - 서버와 신뢰성 있는 연결 수립 

(4) TCP 세그먼트 (SYN, 시퀀스 번호)
(3) IP
(2) MAC
(1) 신호

3. TLS handshake (HTTPS) - 인증서 검증 - 대칭키 합의 - 이후 통신 암호화 

(7) TLS 메시지
(6) 암호화/표현
(5) 세션
(4) TCP
(3) IP
(2) MAC
(1) 신호
TCP 연결 위에서 TLS가 시작
여러번 왕복

4. HTTP 요청/응답 - GET / POST 요청 전송 - 서버 응답 수신

(7) HTTP 요청 (GET /)
(6) TLS로 암호화
(5) 세션 유지
(4) TCP 세그먼트 (시퀀스/ACK)
(3) IP
(2) MAC
(1) 신호</code></pre><p><strong>MTU</strong>: 네트워크가 허용하는 최대 <strong>패킷 크기</strong> ex) 이더넷 MTU 1500 bytes</p>
<p><strong>MSS</strong>: TCP 레벨에서 허용하는 한번에 보낼 수 있는 <strong>데이터 크기</strong></p>
<ul>
<li>보통 MSS = MTU - (IP 헤더 + TCP 헤더)</li>
</ul>
<p><img src="https://velog.velcdn.com/images/g_onmin41/post/2e7616e9-4d9e-4196-b6df-766b009defea/image.png" alt=""></p>
<p>데이터가 MTU보다 크면 한 번에 전송할 수 없기 때문에 <strong>조각 (Fragmentation)</strong> 되어 전달</p>
<ul>
<li><p>성능 저하를 막기 위해 실무에서는 MTU/MSS 값을 조정해 Fragmentation을 최소화</p>
<ul>
<li><p>IPv4에서는 MTU를 초과한 패킷이 중간 라우터에서 Fragmentation될 수 있지만, 현대 네트워크에서는 Fragmentation을 피하는 설계를 선호</p>
</li>
<li><p>IPv6에서는 중간 라우터 Fragmentation이 허용되지 않으며(성능 저하와 오류 가능성 문제), <strong>경로 상에서 허용되는 최대 MTU를 탐색하고(PMTUD), 송신 측에서 크기를 조절</strong></p>
</li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JAVA] Spring AOP]]></title>
            <link>https://velog.io/@g_onmin41/JAVA-Spring-AOP</link>
            <guid>https://velog.io/@g_onmin41/JAVA-Spring-AOP</guid>
            <pubDate>Sat, 20 Dec 2025 07:07:52 GMT</pubDate>
            <description><![CDATA[<h2 id="목차">목차</h2>
<ol>
<li><a href="#AOP">AOP</a></li>
<li><a href="#Proxy-Pattern">Proxy Pattern</a></li>
<li><a href="#Dynamic-Proxy">Dynamic Proxy</a></li>
<li><a href="#CGLIB">CGLIB</a></li>
<li><a href="#private-method%EC%99%80-AOP">private method와 AOP</a></li>
<li><a href="#self-invocation-%EC%9A%B0%ED%9A%8C%EB%B0%A9%EB%B2%95">self invocation 우회방법</a></li>
</ol>
<h2 id="aop">AOP</h2>
<blockquote>
<p>💡 여러 곳에 흩어지는 공통 관심사(cross-cutting concern)를 핵심 비즈니스 로직과 분리해서 관리하는 프로그래밍 방식. Aspect-Oriented Programming</p>
</blockquote>
<ul>
<li><p>로깅, 트랜잭션, 인증/권한 체크, 성능 측정, 예외 처리 공통으로 처리</p>
</li>
<li><p>유지 보수성 ↑</p>
</li>
<li><p>Client -&gt; Proxy (AOP 적용) -&gt; Target 식으로 적용</p>
<ul>
<li>프록시는 공통 로직 실행, 실제 메서드 호출, 후처리 </li>
</ul>
<p><strong>- AOP 핵심 용어</strong></p>
<table>
<thead>
<tr>
<th>용어</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><strong>Aspect</strong></td>
<td>공통 관심사 모음 (클래스)</td>
</tr>
<tr>
<td><strong>Advice</strong></td>
<td>언제 무엇을 할지 (메서드)</td>
</tr>
<tr>
<td><strong>Join Point</strong></td>
<td>AOP가 끼어들 수 있는 지점 (메서드 실행 등)</td>
</tr>
<tr>
<td><strong>Pointcut</strong></td>
<td>어떤 Join Point에 적용할지 조건</td>
</tr>
<tr>
<td><strong>Target</strong></td>
<td>실제 비즈니스 객체</td>
</tr>
<tr>
<td><strong>Proxy</strong></td>
<td>Target을 감싸는 객체</td>
</tr>
</tbody></table>
</li>
</ul>
<pre><code class="language-java">// @Around는 가장 강력한 Advice
// 메서드 실행 전 / 후 / 예외 발생 시점까지 모두 제어 가능
@Around(&quot;pointcut()&quot;)
// ProceedingJoinPoint: 실제 타겟 메서드를 대리 실행하는 객체
// proceed()를 호출해야만 진짜 비즈니스 로직이 실행됨
public Object around(ProceedingJoinPoint pjp) throws Throwable {
    // before
    Object result = pjp.proceed(); // 실제 메서드 실행
    // after
    return result;
}

@Aspect // 공통관심사 선언, 프록시 생성 대상으로 인식
@Component // spring Bean으로 등록
public class LogAspect {

// com.example.service 패키지 이하의 모든 메서드 실행을 가로챔
    @Around(&quot;execution(* com.example.service..*(..))&quot;)
    // joinPoint = 지금 실행될 메서드 정보
    public Object log(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
    // 실행시간 측정 및 실제 메서드 실행
        Object result = joinPoint.proceed(); 

        long end = System.currentTimeMillis();
        // getSignature() → 메서드 시그니처 정보
        System.out.println(joinPoint.getSignature() + &quot; 실행 시간: &quot; + (end - start));

        return result;
    }
}

// @Service → Spring Bean. com.example.service 패키지 → Pointcut 조건 만족
@Service
public class OrderService {
    public void order() {
        // 비즈니스 로직
    }
}

// pointCut 문법
// execution(접근제어자 반환타입 패키지.클래스.메서드(파라미터))
// execution(* com.example..*Service.*(..))
// * 아무거나, .. 하위 패키지 전체</code></pre>
<ul>
<li><p>프록시 기반으로 Spring Bean만 AOP 적용 가능(new로 직접 만든 객체 x)</p>
</li>
<li><p>메서드 내부 호출은 적용 x(프록시 안 거치기 때문)</p>
</li>
<li><p>인터페이스, 클래스 기준. 인터페이스 있으면 JDK Dynamic Proxy, 없으면 CGLIB</p>
</li>
</ul>
<table>
<thead>
<tr>
<th>구분</th>
<th>AOP</th>
<th>Interceptor</th>
<th>Filter</th>
</tr>
</thead>
<tbody><tr>
<td>레벨</td>
<td>메서드</td>
<td>컨트롤러</td>
<td>서블릿</td>
</tr>
<tr>
<td>대상</td>
<td>Bean</td>
<td>HTTP 요청</td>
<td>요청/응답</td>
</tr>
<tr>
<td>용도</td>
<td>로직 공통화</td>
<td>인증/로깅</td>
<td>인코딩</td>
</tr>
</tbody></table>
<h2 id="proxy-pattern">Proxy Pattern</h2>
<blockquote>
<p>💡 실제 객체(Target) 앞에 대리 객체(Proxy)를 두고, 호출을 가로채서 부가 기능을 수행하는 구조</p>
</blockquote>
<ul>
<li><p>Spring AOP = Proxy 기반 AOP</p>
<ul>
<li>Spring AOP는 바이트코드에 직접 끼어드는 게 아니라, Bean을 감싼 Proxy 객체를 대신 주입</li>
</ul>
<pre><code class="language-java">@Autowired
OrderService orderService;
// 실제로는 OrderServiceProxy 가 주입

interface Service {
  void run();
}

class RealService implements Service {
    public void run() {
        System.out.println(&quot;비즈니스 로직 실행&quot;);
    }
}

class ServiceProxy implements Service {
    private final Service target;

  public ServiceProxy(Service target) {
      this.target = target;
  }

  @Override
  public void run() {
      System.out.println(&quot;[AOP] before&quot;);
      target.run();
      System.out.println(&quot;[AOP] after&quot;);
  }
  }

public class Main {
  public static void main(String[] args) {
      Service service = new ServiceProxy(new RealService());
      service.run();
  }
}</code></pre>
</li>
</ul>
<h2 id="dynamic-proxy">Dynamic Proxy</h2>
<ul>
<li><p>대상 클래스가 인터페이스 구현한 경우 사용</p>
</li>
<li><p>Java 표준(java.lang.reflect.Proxy)</p>
</li>
<li><p>인터페이스 기반</p>
<ul>
<li>인터페이스 메서드만 프록시 가능, 가볍고 안정적</li>
</ul>
</li>
<li><p>실제 구현체 상속 x</p>
</li>
</ul>
<pre><code class="language-java">public interface OrderService {
    void order();
}

public class OrderServiceImpl implements OrderService { }
</code></pre>
<h2 id="cglib">CGLIB</h2>
<p><strong>Code Generation Library</strong></p>
<ul>
<li><p>Spring이 인터페이스가 없는 클래스에 쓰는 방식.</p>
<ul>
<li>클래스 기반 프록시</li>
</ul>
</li>
<li><p>메서드 오버라이딩 방식</p>
</li>
<li><p>final / private 메서드 프록시 불가</p>
</li>
<li><p>대상 클래스를 상속받아 프록시를 만듦</p>
<ul>
<li>OrderService$$EnhancerByCGLIB -&gt; OrderService (class)</li>
</ul>
</li>
<li><p>Spring은 내부적으로 CGLIB 사용</p>
</li>
</ul>
<h2 id="private-method와-aop">private method와 AOP</h2>
<p><strong>private 메서드가 AOP 에 걸리지 않음</strong></p>
<ul>
<li>프록시는 오버라이딩으로 가로채는데, private 메서드는 상속,오버라이딩이 불가능하기 때문</li>
</ul>
<pre><code class="language-java">class Service {
    public void a() {
        b(); // private
    }

    private void b() {}
}
</code></pre>
<ul>
<li><p>CGLIB은 Service를 상속해서 프록시 생성</p>
</li>
<li><p>private 메서드는 자식 클래스에서 접근 불가 → 프록시가 끼어들 수 없음</p>
</li>
<li><p>JDK Dynamic Proxy도 마찬가지</p>
<ul>
<li><p>인터페이스 메서드만 대상</p>
</li>
<li><p>private은 애초에 대상 아님</p>
</li>
</ul>
</li>
</ul>
<h3 id="spring-aop가-적용되지-않는-경우">Spring AOP가 적용되지 않는 경우</h3>
<table>
<thead>
<tr>
<th>상황</th>
<th>이유</th>
</tr>
</thead>
<tbody><tr>
<td>private 메서드</td>
<td>프록시가 오버라이딩 불가</td>
</tr>
<tr>
<td>final 메서드</td>
<td>CGLIB 오버라이딩 불가</td>
</tr>
<tr>
<td>static 메서드</td>
<td>객체 메서드 호출이 아님</td>
</tr>
<tr>
<td>self-invocation</td>
<td>프록시를 거치지 않음</td>
</tr>
<tr>
<td>new로 생성한 객체</td>
<td>Spring Bean 아님</td>
</tr>
</tbody></table>
<h2 id="self-invocation-우회방법">self invocation 우회방법</h2>
<ul>
<li><p>Spring AOP는 프록시 기반, 같은 클래스 내부에서 메서드를 호출(self-invocation)하면 프록시를 거치지 않아 AOP가 적용되지 않는다.</p>
</li>
<li><p>Client -&gt; Proxy -&gt; target.outer() vs target.outer() -&gt; this.inner()</p>
<ul>
<li>프록시 우회</li>
</ul>
</li>
</ul>
<pre><code class="language-java">@Service
public class MyService {

    @Transactional
    public void outer() {
        inner(); 
    }

    @Transactional
    public void inner() {
        // 트랜잭션 대상
    }
}</code></pre>
<p><strong>해결방법_메서드 분리</strong></p>
<pre><code class="language-java">@Service
class InnerService {
    @Transactional
    public void inner() {}
}

@Service
class OuterService {
    private final InnerService innerService;

    public void outer() {
        innerService.inner(); 
    }
}
</code></pre>
<ul>
<li>자기 자신 프록시 주입, AopContext.currentProxy() 등은 순환 참조 위험, 코드 가독성 저하, 테스트/리팩토링 어려움으로 비권장</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘]백준 2156_포도주 시식]]></title>
            <link>https://velog.io/@g_onmin41/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EB%B0%B1%EC%A4%80-2156%ED%8F%AC%EB%8F%84%EC%A3%BC-%EC%8B%9C%EC%8B%9D</link>
            <guid>https://velog.io/@g_onmin41/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EB%B0%B1%EC%A4%80-2156%ED%8F%AC%EB%8F%84%EC%A3%BC-%EC%8B%9C%EC%8B%9D</guid>
            <pubDate>Sat, 20 Dec 2025 06:05:10 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/2156">백준_2156</a></p>
<ul>
<li>저번에 풀었던 계단 오르기와 비슷한 포멧</li>
<li>dp 2차원 배열로 선언. 이번 턴 안마셨을때, 마셨을 때, 마셨는데 이번턴이 연속으로 마신 거 일때를 각각 인덱스 0,1,2의 값에 넣음. <ul>
<li>이번턴 안마셨을 때 dp[i][0] -&gt; 안 마시는 건 자유. dp[i-1]의 값 중 최대값 </li>
<li>마셨을 때 dp[i][1] -&gt; 이전 턴에 안마셨을 때. dp[i-1][0] + lst[i]</li>
<li>연속으로 마셨을 때 -&gt; 이전 턴에 마셨을 때, dp[i-1][1] + lst[i]</li>
</ul>
</li>
</ul>
<pre><code class="language-java">import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        int N = Integer.parseInt(br.readLine());

        List&lt;Integer&gt; lst = new ArrayList&lt;&gt;();
        // 인덱스 1부터 시작 맞추기
        lst.add(0); 

        int[][] dp = new int[N + 1][3];

        for (int i = 0; i &lt; N; i++) {
            int n = Integer.parseInt(br.readLine());
            lst.add(n);
        }

        // N이 1인 경우 초기값 세팅
        dp[1][0] = 0;
        dp[1][2] = 0;
        dp[1][1] = lst.get(1);

        for (int j = 2; j &lt;= N; j++) {
        // 안마시는 건 자유. j번째 잔을 안 마심: 이전 상태 제한 없음
            dp[j][0] = Math.max(dp[j-1][0],Math.max(dp[j - 1][2],dp[j-1][1]));
             // j번째 잔을 마심(연속 1잔): j-1은 안 마셔야 함
            dp[j][1] = dp[j - 1][0] + lst.get(j);
            // j번째 잔을 마심(연속 2잔): j-1은 연속 1잔이어야 함
            dp[j][2] = dp[j - 1][1] + lst.get(j);
        }

        // dp[N]의 값들 중 최대값 출력
        int answer = Math.max(dp[N][0],Math.max(dp[N][1], dp[N][2]) );
        System.out.print(answer);
    }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘]백준 11722_가장 긴 감소하는 부분 수열]]></title>
            <link>https://velog.io/@g_onmin41/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EB%B0%B1%EC%A4%80-11722%EA%B0%80%EC%9E%A5-%EA%B8%B4-%EA%B0%90%EC%86%8C%ED%95%98%EB%8A%94-%EB%B6%80%EB%B6%84-%EC%88%98%EC%97%B4</link>
            <guid>https://velog.io/@g_onmin41/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EB%B0%B1%EC%A4%80-11722%EA%B0%80%EC%9E%A5-%EA%B8%B4-%EA%B0%90%EC%86%8C%ED%95%98%EB%8A%94-%EB%B6%80%EB%B6%84-%EC%88%98%EC%97%B4</guid>
            <pubDate>Sun, 14 Dec 2025 06:16:55 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/11722">백준_11722</a></p>
<ul>
<li>인덱스 간격이 1인 값부터 비교해서 앞의 값 &gt; 뒤의 값이면 cnt[앞 인덱스] += cnt[뒤 인덱스]로 누적하고,
그 다음엔 간격을 2, 3… n-1까지 늘려가며
가장 크게 누적된 값을 답으로 리턴</li>
<li>하지만 중복해서 더해주는 문제 발생<ul>
<li>ex) lst 3,2,1 에서 cnt_lst 1,1,1 로 초기화 후 재귀끝내면 4,2,1이 되버림. 의도는 3,2,1. 출력은 expected = 3, output=4</li>
<li>1이 이미 카운트됐는데 중복 카운트.</li>
</ul>
</li>
</ul>
<pre><code class="language-java">import java.io.*;
import java.util.*;

public class Main {

    // 재귀로 갱신
    static int count(int[] l, int[] c, int n, int cpN, int max_n) {
        if (cpN == n) {
            return max_n;
        }

    // 뒤에서 부터. 
        for (int i = n - 1; i &gt;= cpN; i--) {
            if (l[i - cpN] &gt; l[i]) {
                c[i - cpN] += c[i];
            }
            if (max_n &lt; c[i - cpN]) {
                max_n = c[i - cpN];
            }
        }

        return count(l, c, n, cpN + 1, max_n);
    }

    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        int N = Integer.parseInt(br.readLine());
        int[] lst = new int[N];

        StringTokenizer st = new StringTokenizer(br.readLine());
        for (int i = 0; i &lt; N; i++) {
            lst[i] = Integer.parseInt(st.nextToken());
        }

        int[] cnt_lst = new int[N];
        // 기본적으로 카운트 1씩
        Arrays.fill(cnt_lst, 1);

        int result = count(lst, cnt_lst, N, 1, 1);
        System.out.println(result);
    }
}</code></pre>
<ul>
<li>다시 풀기 </li>
<li>dp[i] = i에서 끝나는 가장 긴 감소 부분 수열의 길이</li>
<li>j &lt; i 이고 arr[j] &gt; arr[i] 라면 dp[i] = max(dp[i], dp[j] + 1)</li>
<li>가능한 이전 원소들 중에서 가장 긴 것 하나만 이어붙임</li>
</ul>
<pre><code class="language-java">import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) throws Exception {
        // 입력 받기
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));


        int N = Integer.parseInt(br.readLine());
        int[] arr = new int[N];

        StringTokenizer st = new StringTokenizer(br.readLine());
        for (int i = 0; i &lt; N; i++) {
            arr[i] = Integer.parseInt(st.nextToken());
        }

        // dp 1로 초기화. 일단 하나 카운트라는 뜻
        int[] dp = new int[N];
        Arrays.fill(dp, 1); 

        int answer = 1;

        for (int i = 0; i &lt; N; i++) {
            // j &lt; i, arr[j] &gt; arr[i]
            for (int j = 0; j &lt; i; j++) {
                if (arr[j] &gt; arr[i]) {               
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
            // 최대값 갱신
            answer = Math.max(answer, dp[i]);
        }

        System.out.println(answer);
    }
}

/*

N = 6
arr = [10, 30, 10, 20, 20, 10]

arr = [10, 30, 10, 20, 20, 10]
dp  = [ 1,  1,  1,  1,  1,  1]  // 모두 자기 자신만 포함
answer = 1

i=0
dp = [1, 1, 1, 1, 1, 1]
answer = 1

i=1
dp = [1, 1, 1, 1, 1, 1]
answer = 1

i=2
dp = [1, 1, 2, 1, 1, 1]
answer = 2

i=3
dp = [1, 1, 2, 2, 1, 1]
answer = 2

i=4
dp = [1, 1, 2, 2, 2, 1]
answer = 2

i=5
dp = [1, 1, 2, 2, 2, 3]
answer = 3

결과 = 3

*/</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java]Random vs SecureRandom
]]></title>
            <link>https://velog.io/@g_onmin41/JavaRandom-vs-SecureRandom</link>
            <guid>https://velog.io/@g_onmin41/JavaRandom-vs-SecureRandom</guid>
            <pubDate>Sun, 14 Dec 2025 05:47:30 GMT</pubDate>
            <description><![CDATA[<h2 id="목차">목차</h2>
<ol>
<li><a href="#Random">Random</a></li>
<li><a href="#SecureRandom">SecureRandom</a></li>
</ol>
<p>난수 생성 관련 클래스 정리</p>
<p><strong>- Random :</strong> 빠르지만 예측 가능한 의사난수</p>
<p><strong>- SecureRandom :</strong> 보안 목적에 적합한 암호학적 난수(CSPRNG)</p>
<h2 id="random">Random</h2>
<blockquote>
<p>💡 Random 클래스는 48비트 seed 기반 LCG(Linear Congruential Generator)를 사용하여 의사난수를 생성</p>
</blockquote>
<h3 id="특징">특징</h3>
<ul>
<li><p>의사난수_유사난수 생성기(PRNG, Pseudo Random Number Generator)</p>
</li>
<li><p>LCG 알고리즘 사용 → 예측 가능</p>
</li>
<li><p>48비트, seed 값 기반 → seed가 같으면 항상 같은 난수 순서</p>
<ul>
<li>0 ~ (2^48 - 1) 의 내부 상태값 가짐</li>
</ul>
</li>
<li><p>성능이 빠르고 가벼움</p>
</li>
<li><p>비보안 환경에서 적절</p>
</li>
</ul>
<p><strong>의사난수:</strong> 난수처럼 보이게 하기 위해 어떠한 알고리즘을 사용한 규칙적인 난수</p>
<h3 id="사용-예시">사용 예시</h3>
<p><strong>랜덤성이 중요하지만 보안적 의미는 없는 경우</strong></p>
<ul>
<li><p>게임 내 랜덤 요소 (주사위, 몬스터 스폰)</p>
</li>
<li><p>확률 시뮬레이션</p>
</li>
<li><p>테스트 데이터 생성</p>
</li>
<li><p>UI 애니메이션 등 보안과 무관한 난수</p>
</li>
</ul>
<h3 id="lcg-알고리즘">LCG 알고리즘</h3>
<ul>
<li>선형합동생성기(1차식, 나머지연산)<pre><code class="language-java">X(n+1) = (a * X(n) + c) mod m

</code></pre>
</li>
</ul>
<p>/*
필요조건
m &gt; 0
0 &lt; a &lt; m
0 &lt; c &lt; m
0&lt;= X0 &lt; m (X의 초기값)</p>
<p>자바 내부에서 사용하는 상수
m = 2^48
a = 25214903917
c = 11</p>
<p>/*</p>
<pre><code>- 빠르고 간단하지만 출력된 난수 몇 개만 보면 seed를 거꾸로 계산 가능


```java
    @Test
    @DisplayName(&quot;Random함수 테스트&quot;)
    void randomTest() {
        int i = 0;
        while (i &lt; 3) {
            Random random = new Random(10);
            for (int j = 0; j &lt; 5; j++) {
               System.out.println(random.nextDouble());
               i++;
            }
        }
    } 
    //임의의 값 넣지않으면 System.nanoTime()으로 시드생성
    /*
    0.730967787376657
    0.8314409887870612
    0.3971743421847056
    출력 같은 값 반복
    */</code></pre><ul>
<li>공격자가 시드 생성시간을 알고있거나, seed값을 알고있으면 위험</li>
</ul>
<h3 id="apache-randomstringutils">Apache RandomStringUtils</h3>
<ul>
<li>문자/숫자 랜덤 문자열을 간단한 메서드 호출로 생성해주는 클래스</li>
</ul>
<pre><code class="language-java">public class RandomStringUtils {
    private static final Random RANDOM = new Random();
}
</code></pre>
<ul>
<li>RandomStringUtils도 내부적으로 Random을 사용, 보안적인 토큰/세션ID/인증코드 생성에는 사용 불가</li>
</ul>
<h3 id="공식문서-경고">공식문서 경고</h3>
<blockquote>
<p>java.util.Random 인스턴스는 암호학적으로 안전하지 않습니다.
민감한 보안 응용 프로그램에서는 SecureRandom을 사용하십시오.</p>
</blockquote>
<h2 id="securerandom">SecureRandom</h2>
<blockquote>
<p>💡 SecureRandom은 운영체제 엔트로피(Entropy)를 기반으로 한 암호학적 난수 생성기(CSPRNG_Cryptographically Secure Pseudo-Random Number Generator).</p>
</blockquote>
<h3 id="특징-1">특징</h3>
<ul>
<li><p>OS가 수집한 무작위 데이터(Entropy Source_무작위성 요소)기반으로 난수 생성</p>
<ul>
<li>Linux: /dev/random, /dev/urandom</li>
<li>window: BCryptGenRandom(), Non Blocking</li>
<li>Mac: /dev/random, /dev/urandom, arc4random() Non Blocking</li>
<li>키보드 입력 간격, 마우스 움직임, 디스크 I/O 타이밍 등을 수집.seed 자동 관리</li>
</ul>
</li>
<li><p>암호학적 PRNG 알고리즘(CSPRNG) 적용</p>
<ul>
<li>SHA1PRNG(SHA1 사용). 예전 버전에서 사용<ul>
<li>난수를 만들 때마다, 내부 상태값에 64비트 카운터를 붙여서 SHA-1 해시를 계산. 160비트</li>
</ul>
</li>
<li>NativePRNG, DRBG, BSD random subsystem, Windows CryptoAPI 등 <ul>
<li>운영체제 또는 플랫폼이 제공하는 안전한 난수 생성 메커니즘 사용</li>
</ul>
</li>
</ul>
</li>
<li><p>seed 내부적으로 안전하게 초기화</p>
</li>
<li><p>암호학 표준(FIPS 140-2 등)에 부합</p>
</li>
<li><p>보안 강도(Security Strength)는 128~256비트 수준</p>
<ul>
<li><p>OS 및 사용되는 PRNG 알고리즘에 따라 달라짐</p>
</li>
<li><p>Random에 비해 상대적으로 느림</p>
</li>
</ul>
</li>
<li><p>Java 7+ 부터 스레드 안전</p>
<ul>
<li>내부적으로 동기화(synchronization) 되어 있기 때문에
여러 스레드가 동시에 사용해도 안전</li>
<li>매 요청마다 객체 생성할 필요 x</li>
</ul>
</li>
</ul>
<h3 id="사용-예시-1">사용 예시</h3>
<p><strong>보안적으로 중요한 난수 필요 시</strong></p>
<ul>
<li><p>세션 토큰 생성</p>
</li>
<li><p>인증/인가 코드</p>
</li>
<li><p>OAuth state 값</p>
</li>
<li><p>비밀번호 salt 생성(비밀번호 해싱 시 필요한 난수)</p>
</li>
<li><p>암호화 키 생성 </p>
</li>
<li><p>Nonce(일회용 숫자) 값 생성</p>
</li>
</ul>
<h3 id="생성-방식">생성 방식</h3>
<pre><code class="language-java">import java.security.SecureRandom;

// 기본 알고리즘을 사용하여 SecureRandom 객체 생성 (운영체제에 따라 다를 수 있음)
SecureRandom secureRandom = new SecureRandom();
byte[] randomBytes = new byte[16];  //128비트 보안강도
secureRandom.nextBytes(randomBytes); // 16바이트의 암호학적으로 강력한 난수(바이트 배열) 생성


// 특정 알고리즘 이름을 명시적으로 지정하여 해당 알고리즘을 사용하는 SecureRandom 객체를 요청
SecureRandom secureRandom = SecureRandom.getInstance(&quot;NativePRNG&quot;);


// Java가 시스템에서 사용 가능한 가장 강력한 암호학적 난수 생성 알고리즘을 찾아 해당 인스턴스를 반환하도록 요청
// 가장 강력한 엔트로피 소스를 찾아야 하므로 객체를 처음 생성할 때 성능이 느려질 수 있음. (특히 엔트로피가 부족한 환경에서)

import java.security.SecureRandom;


// 반복실행 시 매번 새로운 SecureRandom 값 생성.
public class SecureRandomTest {
    public static void main(String[] ags) {
        for (int i = 0; i &lt; 10; i++) {
            secureRandomTest();
        }
    }

    public static void secureRandomTest() {
        SecureRandom ran = new SecureRandom();
        byte[] randomBytes = new byte[16];
        // 랜덤 비트 생성 후 배열 채움
        ran.nextBytes(randomBytes);

        for (byte b : randomBytes) {
        // 난수 16진수 출력
            System.out.printf(&quot;%02x &quot;, b);
        }
        System.out.println();
    }
}

/*

3f a9 12 7c 8d e4 5b 91 2a c0 6e f1 88 4d 73 b2
a4 0e 9b 5c 17 6a f8 2d 93 b1 4e 7f 0c d5 68 aa
9d 3c f0 61 2b 8e 14 7a c9 55 e2 0f b6 4a d8 91
6b 1f 8c e3 49 a0 d2 77 5e 9a 34 f5 0b 61 c8 2d
f1 84 2a 9c 7d 03 b5 e8 4f 6a d0 91 5c 3e 27 ab
매번 실행할 때마다 다르게 나옴
*/</code></pre>
<h3 id="securerandom-블로킹-이슈--해결법">SecureRandom 블로킹 이슈 &amp; 해결법</h3>
<p>Linux의 /dev/random 은 엔트로피가 충분하지 않으면 blocking 발생.
따라서 대부분의 시스템은 <strong>/dev/urandom 사용을 권장.</strong></p>
<p><strong>/dev/random</strong></p>
<ul>
<li><p>엔트로피가 충분할 때만 반환</p>
</li>
<li><p>엔트로피 부족 시 블록 → 성능 저하 / 응답 지연</p>
</li>
</ul>
<p><strong>/dev/urandom</strong></p>
<ul>
<li><p>엔트로피 부족해도 non-blocking, 계속 출력</p>
</li>
<li><p>대부분의 서버/웹 애플리케이션에서는 사실상 충분히 안전 + 성능 안정적</p>
</li>
<li><p>지금은 new SecureRandom()을 사용하게 되면, /dev/urandom 를 자동으로 호출.</p>
</li>
</ul>
<h3 id="참조">참조</h3>
<ul>
<li><p><a href="https://kdhyo98.tistory.com/48#google_vignette">[Java] Random보단 SecureRandom 를 사용하자.</a></p>
</li>
<li><p><a href="https://weights-learn-develop.tistory.com/70">Java Random, SecureRandom, RandomStringUtil 살펴보기</a></p>
</li>
<li><p><a href="#https://velog.io/@kn9012/CS-Random-vs-SecureRandom">[CS] Random vs SecureRandom</a></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[JAVA]Call By Value]]></title>
            <link>https://velog.io/@g_onmin41/JAVACall-By-Value</link>
            <guid>https://velog.io/@g_onmin41/JAVACall-By-Value</guid>
            <pubDate>Sun, 07 Dec 2025 22:57:22 GMT</pubDate>
            <description><![CDATA[<h2 id="목차">목차</h2>
<ol>
<li><a href="#Call-By-Value-vs-Call-By-Reference">Call By Value vs Call By Reference</a></li>
<li><a href="#currentMillis()-vs-nanoTime();">currentMillis() vs nanoTime()</a></li>
</ol>
<h2 id="call-by-value-vs-call-by-reference">Call By Value vs Call By Reference</h2>
<p><strong>Call By value</strong>: 값에 대한 복사
<strong>Call By Reference</strong>: 참조 자체를 전달. 참조 자체를 넘겨서 원본을 직접 변경 가능</p>
<ul>
<li>자바는 <strong>주소값을 복사</strong>해서 넘김 </li>
</ul>
<pre><code class="language-java">public class main
{
    public static void main(String[] args)
    {
        Sample sample = new Sample();

        int var = 1; // primitive 타입 변수 int
        int[] arr = { 1 }; // reference 타입 변수 int[] 배열

        // 변수 자체를 보냄 (call by value)
        add_value(var);
        System.out.println(var); // 1 : 값 변화가 없음

        // 배열 자체를 보냄 (call by reference)
        add_reference(arr);
        System.out.println(arr[0]); // 101 : 값이 변화함
    }

    static void add_value(int var_arg) {
        var_arg += 100;
    }

    static void add_reference(int[] arr_arg) {
        arr_arg[0] += 100;
    }
}</code></pre>
<ul>
<li><p>원시 타입 → 값 복사 → 원본 불변</p>
</li>
<li><p>참조 타입 → 참조값 복사 → 같은 객체 수정 가능</p>
</li>
</ul>
<h3 id="자바는--call-by-reference가-없음포인터-접근x">자바는  Call By Reference가 없음.(포인터 접근x)</h3>
<ul>
<li><p>C언어는 포인터를 통해 주소값을 가져와 엑세스 가능</p>
</li>
<li><p>자바에서는 주소값 복사를 통해서만 이용가능</p>
<ul>
<li>원시값이 복사되느냐, 주소값이 복사되느냐의 차이</li>
<li>원시타입은 stack, 참조타입은 Heap, stack에 있는 변수가 객체의 주소값 가짐.</li>
<li>참조값 복사 시 stack영역에 생성되지만 Heap영역에 같은 객체 바라봄.</li>
</ul>
</li>
<li><p>비슷한 거 아닌가? -&gt; Call By Reference는 참조 자체를 넘김.참조값 복사 x</p>
</li>
</ul>
<pre><code class="language-java">public class CallByExampleTest {     
    @Test    
    void referenceTest() {        
    // 스택에 참조값인 arr 지역변수, 힙에 { 10 }
      int[] arr = { 10 }; // 0x001     
      // 참조값 복사해서 넘김. 스택에 참조값인 arrArg 지역변수, 0x001
      newArr(arr);         
      assertThat(arr[0]).isEqualTo(10);    
    }     
      void newArr(int[] arrArg) {       
    // 새 배열 객체 힙에 생성. arrArg 참조값변경
        arrArg = new int[]{ 20 }; // 0x002    
    }
}
/*
Call By Reference
void newArr(int[] arrArg) {
    arrArg = new int[]{ 20 }; // 원본 arr도 바뀌어야됨. 동일한 메모리위치, 직접 조작 가능.
}


*/</code></pre>
<h2 id="currentmillis-vs-nanotime">currentMillis() vs nanoTime()</h2>
<h2 id="systemcurrentmillis-현재시간과-utc-차이-밀리세컨드-값-반환날짜와-날짜사이-갭-측정"><strong>System.currentMillis:</strong> 현재시간과 UTC 차이. 밀리세컨드 값 반환(날짜와 날짜사이 갭 측정)</h2>
<pre><code class="language-java">long curLong = System.currentTimeMillis();
System.out.println(curLong);

SimpleDateFormat sdf = new SimpleDateFormat(&quot;yyyy-mm-dd HH:mm:ss&quot;); // HH 24시간  hh 12시간
String curTime = sdf.format(new Date(curLong));
System.out.println(curTime);

// 시간을 측정할 실행 코드
long endLong = System.currentTimeMillis();

// 밀리초 -&gt; 초 변환
double seconds = (endLong - curLong)/1000;
System.out.println(seconds);</code></pre>
<p><strong>System.nanoTime:</strong> 10억분의 초. JVM이 시작시간 - 종료시간 long타입으로 반환(개발한 프로그램의 실행시간 구할 때 사용_성능 측정)</p>
<ul>
<li>서버 간 통신시간 측정에는 사용x.JVM마다 측정 기준값 다름.<pre><code class="language-java">long startTime = System.nanoTime();
</code></pre>
</li>
</ul>
<p>// 시간을 측정할 실행 코드</p>
<p>long endTime = System.nanoTime();</p>
<p>long runTime = endTime - startTime;</p>
<p>// 나노초 -&gt; 초 변환
double seconds = (endTime - startTime) / 1000000000.0;
System.out.println(seconds);</p>
<p>```</p>
<h3 id="참조">참조</h3>
<ul>
<li><p><a href="https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%9E%90%EB%B0%94%EB%8A%94-Call-by-reference-%EA%B0%9C%EB%85%90%EC%9D%B4-%EC%97%86%EB%8B%A4-%E2%9D%93">☕ 자바는 Call by reference 개념이 없다 ❓</a></p>
</li>
<li><p><a href="https://lottodangchum.tistory.com/93">[JAVA] System.currentTimeMillis()와 System.nanoTime()으로 시간 측정하기
</a></p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘] 백준 11727_2xn 타일링 2]]></title>
            <link>https://velog.io/@g_onmin41/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%B0%B1%EC%A4%80-117272xn-%ED%83%80%EC%9D%BC%EB%A7%81-2</link>
            <guid>https://velog.io/@g_onmin41/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98-%EB%B0%B1%EC%A4%80-117272xn-%ED%83%80%EC%9D%BC%EB%A7%81-2</guid>
            <pubDate>Fri, 05 Dec 2025 13:43:45 GMT</pubDate>
            <description><![CDATA[<p><a href="">백준_11727</a></p>
<ul>
<li>dp 문제</li>
<li>일단 적다가 갑자기 번뜩</li>
<li>역시 dp문제는 규칙 보일때까지 적어야되나보다</li>
</ul>
<pre><code class="language-java">import java.io.*;

public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int N = Integer.parseInt(br.readLine());
        int MOD = 10007;
        if(N == 1) {
            System.out.println(1);
            return;
        } 
        if (N==2) {
            System.out.println(3);
            return;
        } 
        int[] dp = new int[N+1];
        dp[1] = 1;
        dp[2] = 3;
        for (int i = 3;i &lt;=N; i++) {
            dp[i] = (dp[i-1]+dp[i-2]*2)% MOD;
        }
        System.out.println(dp[N]);

    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘]백준 10884_쉬운 계단 수]]></title>
            <link>https://velog.io/@g_onmin41/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EB%B0%B1%EC%A4%80-10884%EC%89%AC%EC%9A%B4-%EA%B3%84%EB%8B%A8-%EC%88%98</link>
            <guid>https://velog.io/@g_onmin41/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EB%B0%B1%EC%A4%80-10884%EC%89%AC%EC%9A%B4-%EA%B3%84%EB%8B%A8-%EC%88%98</guid>
            <pubDate>Fri, 05 Dec 2025 13:05:21 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/10844">백준_10884</a></p>
<ul>
<li><p>dp 문제 </p>
</li>
<li><p>갱신해가면서 + MOD 나머지 처리 계속.</p>
</li>
<li><p>초기값 설정 유의</p>
</li>
</ul>
<pre><code class="language-java">import java.io.*;

public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int N = Integer.parseInt(br.readLine());

        int MOD = 1_000_000_000;
        long[][] dp = new long[N + 1][10];
        long result = 0;
        dp[1][0] = 0;

        for(int i = 1; i &lt;= 9; i++) {
            dp[1][i] = 1;
        }
        for(int i = 2; i &lt;= N; i++) {
            dp[i][0] = dp[i-1][1] % MOD;
            dp[i][9] = dp[i-1][8] % MOD;
            for(int j = 1; j &lt;= 8; j++){
                dp[i][j] = (dp[i-1][j-1] + dp[i-1][j+1]) % MOD;
            }
        }
        for(int i = 0;i &lt;= 9; i++){
            result = (result + dp[N][i]) % MOD;
        }
        System.out.println(result);

    }
}</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘]리트코드 622_Design Circular Queue]]></title>
            <link>https://velog.io/@g_onmin41/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EB%A6%AC%ED%8A%B8%EC%BD%94%EB%93%9C-622Design-Circular-Queue</link>
            <guid>https://velog.io/@g_onmin41/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98%EB%A6%AC%ED%8A%B8%EC%BD%94%EB%93%9C-622Design-Circular-Queue</guid>
            <pubDate>Thu, 04 Dec 2025 23:11:22 GMT</pubDate>
            <description><![CDATA[<p><a href="https://leetcode.com/problems/design-circular-queue/description/">리트코드 622</a></p>
<ul>
<li>원형 큐 구현 문제</li>
<li>크기가 정해진 리스트에 head, tail을 갱신해가며 FIFO 배열을 만드는 문제</li>
<li>head, tail 갱신 시 index = (index + 1) % capacity 로 갱신</li>
</ul>
<h2 id="원형-큐">원형 큐</h2>
<p><strong>배열 큐의 문제점</strong></p>
<ul>
<li>deQueue() 할 때 O(n) 재배치 문제
맨 앞 요소를 삭제하면 → 뒤의 모든 요소를 한 칸씩 앞으로 이동시켜야 한다. 
즉, deQueue()가 O(n)
-&gt; 선형 큐에서는 front, rear 로 해결 </li>
</ul>
<p><strong>front/rear를 사용하는 선형 큐의 문제</strong></p>
<ul>
<li>데이터의 삽입과 삭제 반복 -&gt; front, rear 계속 증가
꺼낸 데이터가 있던 배열의 인덱스 사용 어려움, 인덱스 증가하다 배열의 사이즈 도달 시 사용어려움</li>
</ul>
<p><strong>원형 큐</strong></p>
<ul>
<li><p>배열의 양 끝을 연결해 원처럼 사용하는 큐</p>
</li>
<li><p>head, tail 인덱스 갱신</p>
<pre><code>index = (index + 1) % capacity;</code></pre></li>
<li><p>배열 끝에 도달하면 0으로 돌아감</p>
</li>
<li><p>배열공간 원처럼 반복하여 사용</p>
</li>
<li><p>삽입/삭제 모두 O(1) → 인덱스만 이동</p>
</li>
<li><p>연결 리스트로 구현 시  head, tail 노드 따로 놓아 tail.next = head 같은 방식으로 구현</p>
</li>
</ul>
<pre><code class="language-java">class MyCircularQueue {

    private int[] arr;
    private int head; // 가장 앞 요소 인덱스
    private int tail; // 가장 뒷 요소
    private int size; // 현재 들어있는 요소 개수.head== tail 일 때 처리(비어있음, 하나만 있음, 꽉참)
    private int capacity;  // 배열의 전체 크기

    public MyCircularQueue(int k) {
        this.capacity = k;
        this.arr = new int[k];
        this.head = 0;
        this.tail = -1;
        this.size = 0;
    }

    // 원소 추가, tail
    public boolean enQueue(int value) {
        if (isFull()) return false;

        tail = (tail + 1) % capacity;
        arr[tail] = value;
        size++;
        return true;
    }

    // 원소 제거, gead
    public boolean deQueue() {
        if (isEmpty()) return false;

        head = (head + 1) % capacity;
        size--;
        return true;
    }

    public int Front() {
        if (isEmpty()) return -1;
        return arr[head];
    }

    public int Rear() {
        if (isEmpty()) return -1;
        return arr[tail];
    }

    public boolean isEmpty() {
        return size == 0;
    }

    public boolean isFull() {
        return size == capacity;
    }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘]706_Design HashMap]]></title>
            <link>https://velog.io/@g_onmin41/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98706Design-HashMap</link>
            <guid>https://velog.io/@g_onmin41/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%98706Design-HashMap</guid>
            <pubDate>Sun, 30 Nov 2025 09:05:53 GMT</pubDate>
            <description><![CDATA[<p><a href="https://leetcode.com/problems/design-hashmap/description/">리트코드_706</a></p>
<ul>
<li><p>Separate Chaining(분리 체인법)</p>
<ul>
<li>충돌이 발생하면 link로 연결</li>
</ul>
</li>
<li><p>Coalesced Chaining(Open Addressing)(합병 체인법, 오픈 주소법)</p>
<pre><code class="language-java">class MyHashMap {

  // 노드 정의 (연결 리스트로 체이닝)
  private static class Node {
      int key;
      int value;
      Node next;

      Node(int key, int value, Node next) {
          this.key = key;
          this.value = value;
          this.next = next;
      }
  }

  private static final int SIZE = 10000;   // 버킷 수
  private Node[] table;                    // 해시 버킷 배열

  public MyHashMap() {
      table = new Node[SIZE];
  }

  // 해시 함수
  private int hash(int key) {
      return key % SIZE;
  }

  // idx는 table 인덱스, key는 해시하기 전 key. 
  // table[idx] 비어있으면 노드 추가, 있으면 순회 후 갱신 or 뒤에 추가
  public void put(int key, int value) {
      int idx = hash(key);

      // 버킷이 비어 있으면 새 노드 삽입
      if (table[idx] == null) {
          table[idx] = new Node(key, value, null);
          return;
      }

      // 연결 리스트 순회하면서 key 존재 여부 확인
      Node cur = table[idx];
      Node prev = null;

      while (cur != null) {
          if (cur.key == key) {
              // 이미 존재하는 key면 값만 갱신
              cur.value = value;
              return;
          }
          prev = cur;
          cur = cur.next;
      }

      // 존재하지 않으면 리스트 맨 뒤에 새 노드 추가
      prev.next = new Node(key, value, null);
  }

  public int get(int key) {
      int idx = hash(key);
      Node cur = table[idx];

      while (cur != null) {
          if (cur.key == key) {
              return cur.value;
          }
          cur = cur.next;
      }

      return -1;  // key가 없을 때
  }

  public void remove(int key) {
      int idx = hash(key);
      Node cur = table[idx];
      Node prev = null;

      while (cur != null) {
          if (cur.key == key) {
              // 첫 번째 노드 삭제
              if (prev == null) {
                  table[idx] = cur.next;
              } else {
                  prev.next = cur.next;
              }
              return;
          }
          prev = cur;
          cur = cur.next;
      }
  }
}
</code></pre>
</li>
</ul>
<p>```</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘]22_Generate Parentheses]]></title>
            <link>https://velog.io/@g_onmin41/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%9822Generate-Parentheses</link>
            <guid>https://velog.io/@g_onmin41/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%9822Generate-Parentheses</guid>
            <pubDate>Sun, 30 Nov 2025 08:13:02 GMT</pubDate>
            <description><![CDATA[<p><a href="https://leetcode.com/problems/generate-parentheses/description/?source=submission-ac">리트코드_22</a></p>
<ul>
<li>n 쌍의 괄호 주어짐. 이걸로 나올 수 있는 잘 닫힌? 잘 구성된 괄호 문자열들 리턴</li>
<li>순서 x, 그냥 dfs로 분기. </li>
<li>left, right 괄호 개수 기준으로 분기. ( 는 개수만 남아있으면 넣고 ) 는 ( 이것보다 개수가 많을때만 사용가능. 안그럼 안닫힌 괄호 발생.<pre><code class="language-java">import java.util.*;
</code></pre>
</li>
</ul>
<p>class Solution {</p>
<pre><code>public List&lt;String&gt; generateParenthesis(int n) {
    List&lt;String&gt; res = new ArrayList&lt;&gt;();
    StringBuilder stack = new StringBuilder();
    // 시작은 무조건 &#39;(&#39;
    stack.append(&#39;(&#39;);
    dfs(n-1, n, stack, res);
    return res;
}</code></pre><p>// 왼쪽 괄호, 오른쪽 괄호 개수로 분기
    private void dfs(int left, int right, StringBuilder stack, List<String> res) {</p>
<pre><code>    if (left == 0 &amp;&amp; right == 0) {
        res.add(stack.toString());
        return;
    }

    // &#39;(&#39;를 더 쓸 수 있으면 시도. &#39;(&#39;이거는 남아만 있으면 언제써도 상관x
    if (left &gt; 0) {
        int len = stack.length();   // 현재 길이 기억
        stack.append(&#39;(&#39;);
        dfs(left - 1, right, stack, res);
        stack.setLength(len);       // 되돌리기
    }

    // &#39;)&#39;는 이미 쓴 &#39;(&#39;보다 더 많이 남아 있을 때만 쓸 수 있음
    if (right &gt; left) {
        int len = stack.length();
        stack.append(&#39;)&#39;);
        dfs(left, right - 1, stack, res);
        stack.setLength(len);
    }
}</code></pre><p>}</p>
<p>```</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Java] 오버라이딩 vs 오버로딩]]></title>
            <link>https://velog.io/@g_onmin41/Java-%EC%98%A4%EB%B2%84%EB%9D%BC%EC%9D%B4%EB%94%A9-vs-%EC%98%A4%EB%B2%84%EB%A1%9C%EB%94%A9</link>
            <guid>https://velog.io/@g_onmin41/Java-%EC%98%A4%EB%B2%84%EB%9D%BC%EC%9D%B4%EB%94%A9-vs-%EC%98%A4%EB%B2%84%EB%A1%9C%EB%94%A9</guid>
            <pubDate>Sun, 30 Nov 2025 07:30:06 GMT</pubDate>
            <description><![CDATA[<h2 id="목차">목차</h2>
<ol>
<li><a href="#%EC%98%A4%EB%B2%84%EB%9D%BC%EC%9D%B4%EB%94%A9-vs-%EC%98%A4%EB%B2%84%EB%A1%9C%EB%94%A9">오버라이딩 vs 오버로딩</a></li>
<li><a href="#%EB%A9%94%EC%84%9C%EB%93%9C-%EA%B2%B0%EC%A0%95%EC%8B%9C%EC%A0%90">메서드 결정시점</a></li>
</ol>
<h2 id="오버라이딩-vs-오버로딩">오버라이딩 vs 오버로딩</h2>
<p> <img src="https://velog.velcdn.com/images/g_onmin41/post/7deb4fc5-0350-45b5-ae74-bb8c7fb1e24e/image.png" alt=""></p>
<h3 id="오버라이딩">오버라이딩</h3>
<blockquote>
<p>💡 오버라이딩: 상위 객체가 가진 메서드를 하위 객체가 재정의 하는 것(다형성, 확장성)</p>
</blockquote>
<p><strong>오버라이딩의 조건</strong></p>
<ul>
<li>오버라이딩하고자 하는 함수가 상위 클래스에 있어야한다.</li>
<li>메서드 이름, 매개변수의 수와 자료형이 같아야한다.</li>
<li>메서드 리턴형이 같아야한다.</li>
<li>일반적인 부모 클래스의 기능을 자식 클래스에서 수정하는 것과 추상 메서드를 상속 받아 실질적인 기능을 정의하는 방법 존재.</li>
</ul>
<pre><code class="language-java">class Animal {
    void sound() {
        System.out.println(&quot;동물이 소리를 냅니다&quot;);
    }
}

class Dog extends Animal {
    @Override
    void sound() {
        System.out.println(&quot;멍멍!&quot;);
    }
}

Animal a = new Dog();
a.sound();  // &quot;멍멍!&quot;


// 추상메서드 상속

abstract class Animal {
    abstract void sound();  // 구현 없음
}

class Cat extends Animal {
    @Override
    void sound() {
        System.out.println(&quot;야옹!&quot;);
    }
}

Animal a = new Cat();
a.sound(); // &quot;야옹!&quot;
</code></pre>
<p><strong>@Override 어노테이션</strong></p>
<p>어노테이션 없어도 오버라이딩 가능, 그럼 @Override는 왜 씀?</p>
<ul>
<li><p>시스템에서 오버라이딩한 메서드라고 알리는 역할로 <strong>오버라이딩이 잘못된 경우 컴파일 오류 발생</strong>.</p>
<ul>
<li>오타, 부모 클래스 이름 변경, 매개변수 수정, 제거 등</li>
</ul>
</li>
<li><p>@Override를 표시함으로써 해당 메서드가 오버라이딩하였다는 것을 쉽게 파악 가능</p>
</li>
</ul>
<h3 id="오버라이딩-접근-제어자">오버라이딩 접근 제어자</h3>
<p><img src="https://velog.velcdn.com/images/g_onmin41/post/4ead2c0e-6c5a-4403-873a-1faba6d09807/image.png" alt=""></p>
<blockquote>
<p>💡 <strong>객체지향 설계원칙, SOLID – 리스코프 치환 원칙(LSP)</strong>
하위 타입은 상위 타입을 언제나 대체할 수 있어야 한다.
→ 즉, 부모 타입으로 호출하던 기능이 자식 타입으로도 문제없이 동작해야 한다.</p>
</blockquote>
<p><strong>규칙</strong></p>
<ul>
<li>오버라이딩되는 자식 메서드는 부모 메서드보다 접근 제어자를 더 좁게 만들 수 없다.(부모 ≤ 자식)</li>
<li>public &gt; protected &gt; default &gt; private의 확장순서에 맞춰서 접근제어자 작성</li>
<li>부모 타입(Parent)으로 호출하던 기능은 자식 타입(Child)으로도 동등하게 접근 가능해야함</li>
</ul>
<pre><code class="language-java">class Parent {

    // 부모 메서드는 protected
    protected void doSomething() {
        System.out.println(&quot;Parent: 작업 수행&quot;);
    }
}

class Child1 extends Parent {

    //기본(default) 접근자로 오버라이딩 → protected보다 좁아짐 → 컴파일 에러
    @Override
    void doSomething() {  // default
        System.out.println(&quot;Child1: 기본 접근자는 더 좁아서 override 불가&quot;);
    }
}

class Child2 extends Parent {

    // private으로 오버라이딩 → 접근 범위가 부모보다 좁아져서 컴파일 에러
    @Override
    private void doSomething() {
        System.out.println(&quot;Child2: private은 더 좁아서 override 불가&quot;);
    }
}

class Child3 extends Parent {

    // OK — 부모(protected)와 동일한 접근 제어자
    @Override
    protected void doSomething() {
        System.out.println(&quot;Child3: 동일 접근 제어자 사용&quot;);
    }
}

class Child4 extends Parent {

    // OK — 부모보다 더 넓은 접근 범위(public)
    @Override
    public void doSomething() {
        System.out.println(&quot;Child4: public으로 접근 범위 확장&quot;);
    }
}
</code></pre>
<h3 id="오버로딩">오버로딩</h3>
<blockquote>
<p>💡 오버로딩:  같은 이름의 함수에 매개변수를 다르게 주는 것. 사용성, 편의성 확보.</p>
</blockquote>
<p><strong>오버로딩의 조건</strong></p>
<ul>
<li>함수의 이름이 같아야 한다.</li>
<li>매개변수의 수가 달라야 한다.</li>
<li>매개변수의 수가 같으면 자료형이 달라야한다.</li>
<li>리턴값만을 다르게 갖는 오버로딩은 작성할 수 없다.</li>
</ul>
<pre><code class="language-java">void add(int num_1, int num_2)
{
    printf(&quot;%d&quot;, num_1 + num_2);
    return 0;
}
void add(float num_1, float num_2)
{
    printf(&quot;%f&quot;, num_1 + num_2);
    return 0;
}</code></pre>
<ul>
<li>메서드의 이름을 절약<ul>
<li>println() 메서드는 오버로딩 되어있기 때문에 int형 인자, string형 인자, boolean형 인자, char형 인자 모두 받아서 동작할 수 있다.</li>
</ul>
</li>
</ul>
<pre><code class="language-java">public class MyPrintStream {

    public void println(int x) {
        print(String.valueOf(x));
    }

    public void println(boolean x) {
        print(String.valueOf(x));
    }

    public void println(char x) {
        print(String.valueOf(x));
    }

    public void println(String x) {
        print(x);
    }

    public void println(double x) {
        print(String.valueOf(x));
    }

    public void println(Object obj) {
        print(String.valueOf(obj));
    }

    // 실제 출력은 여기서 처리 (공통 로직)
    private void print(String s) {
        System.out.println(&quot;[MyPrintStream] &quot; + s);
    }
}

public class Main {
    public static void main(String[] args) {
        MyPrintStream out = new MyPrintStream();

        out.println(10);           // int
        out.println(&quot;Hello&quot;);      // String
        out.println(true);         // boolean
        out.println(&#39;A&#39;);          // char
        out.println(3.14);         // double
        out.println(new int[]{1,2,3}); // Object
    }
}</code></pre>
<h2 id="메서드-결정시점">메서드 결정시점</h2>
<p><strong>컴파일 타임 (Compile-time)</strong>: 소스 코드가 컴파일될 때 발생하는 시간. 주로 구문 오류 및 타입 오류를 검사.</p>
<ul>
<li><p>주요 활동: 구문 분석, 타입 체크, 코드 최적화, 바이트코드 생성</p>
</li>
<li><p>오류 유형: 문법 오류, 타입 오류</p>
</li>
</ul>
<p><strong>런타임 (Run-time)</strong>: 프로그램이 실제로 실행되는 시간. 실행 중에 발생하는 오류를 처리.</p>
<ul>
<li><p>주요 활동: 프로그램 실행, 동적 할당, 입출력 작업, 예외 처리</p>
</li>
<li><p>오류 유형: NullPointerException, ArrayIndexOutOfBoundsException, ArithmeticException</p>
</li>
</ul>
<h3 id="필드-vs-메서드-실행시점">필드 vs 메서드 (실행시점)</h3>
<p>메서드는 객체의 실제 타입(런타임 타입)을 따르고, 필드는 선언된 타입(컴파일 시점)을 따라감. </p>
<pre><code class="language-java">class Parent {
    String str = &quot;Parent&quot;;

    String getStr() {
        return str;
    }
}

class Child extends Parent {
    String str = &quot;Child&quot;;

    String getStr() {
        return str;
    }
}

public class Main {
    public static void main(String[] args) {
    // 선언된 타입, 객체의 실제 타입
        Parent parent = new Child();
        Child child = new Child();

        System.out.println(parent.getStr());
        System.out.println(child.getStr());
        System.out.println(parent.str);
        System.out.println(child.str);
    }
}

/*
출력 결과

Child
Child
Parent
Child
*/</code></pre>
<p>*<em>메서드 호출과 필드 접근이 서로 다른 방식으로 동작하는 이유
*</em></p>
<ul>
<li><p>메서드 오버라이딩과 <strong>다형성</strong></p>
<ul>
<li>런타임 시점에 실제 객체 타입에 따라 호출, 다형성을 구현.</li>
<li>프로그램의 유연성과 확장성을 증가, 메서드 호출 시 실제 객체가 가진 구체적인 동작을 보장.</li>
</ul>
</li>
<li><p>필드 숨김<strong>(Shadowing)</strong></p>
<ul>
<li>필드는 다형성을 따르지 않고, 필드 이름이 같을 경우 자식 클래스에서 부모 클래스의 필드를 숨김</li>
<li>Java는 필드를 오버라이딩하지 않고 별개의 필드로 취급하여, 컴파일 시점에서 결정된 타입의 필드를 사용.</li>
</ul>
</li>
</ul>
<p>즉, 메서드는 런타임의 실제 타입을 반영하여 프로그램의 유연성을 제공, 필드는 컴파일 시점에서 정적으로 결정되어 성능과 명확성을 유지.</p>
<h3 id="오버로딩-vs-오버라이딩바인딩-시점">오버로딩 vs 오버라이딩(바인딩 시점)</h3>
<ul>
<li><strong>오버로딩</strong>된 메소드는 <strong>컴파일(Compile) 시에</strong> 어떤 메소드를 실행할지 결정.<ul>
<li><strong>컴파일 시점</strong>: 전달된 인자의 정적 타입, 개수, 순서를 기준으로 호출할 메서드를 컴파일러가 확정. 선택된 메서드는 실행 중 변경x. (정적 바인딩)</li>
<li><strong>런타임 시점</strong>: 이미 컴파일 때 결정된 메서드를 그대로 실행. (따라서 런타임에는 선택 과정 x)</li>
</ul>
</li>
<li><strong>오버라이딩</strong>된 메소드는 <strong>런타임(Runtime) 시에</strong> 어떤 메소드를 실행할지 결정.<ul>
<li><strong>컴파일 시점</strong>: 해당 메서드가 정적 타입에 존재하는지 문법적, 타입적 체크만 수행. 어떤 구현(부모/자식)을 호출할지는 결정x.</li>
<li><strong>런타임 시점</strong>: 실제 객체의 타입을 기준으로 호출할 메서드 구현을 결정.(동적 바인딩(Dynamic Binding) or <strong>동적 디스패치(Dynamic Dispatch)</strong>).</li>
</ul>
</li>
</ul>
<pre><code class="language-java">import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.Vector;

public class Overload {
    // 오버로딩.
    public static void get(List&lt;?&gt; list) {
        System.out.println(&quot;List&quot;);
    }

    public static void get(ArrayList&lt;?&gt; list) {
        System.out.println(&quot;ArrayList&quot;);
    }

    public static void get(LinkedList&lt;?&gt; list) {
        System.out.println(&quot;LinkedList&quot;);
    }

    public static void main(String[] args) {
    // 배열 원소의 정적 타입 List&lt;?&gt;
        List&lt;?&gt;[] listArray = { new ArrayList&lt;&gt;(), new LinkedList&lt;&gt;(), new Vector&lt;&gt;() };
        Arrays.stream(listArray).forEach(list -&gt; get(list));
    }
}

/*

모두 List 타입으로 보이기 때문에 결과는 모두 &quot;List&quot; 출력

*/</code></pre>
<pre><code class="language-java">import java.util.ArrayList;
import java.util.Arrays;

// 오버라이딩
public class MyList&lt;E&gt; extends ArrayList&lt;E&gt; {
    private static final long serialVersionUID = 1L;

    /* (non-Javadoc)
     * @see java.util.ArrayList#get(int)
     */
    @Override
    public E get(int index) {
        System.out.print(&quot;MyList get method returns = &quot;);
        return super.get(index);
    }

    @SuppressWarnings(&quot;unchecked&quot;)
    public static void main(String[] args) {
        ArrayList&lt;String&gt;[] listArray = new ArrayList[2];
        // 컴파일러는 배열 원소를 ArrayList&lt;String&gt;으로 봄
        // 오버라이딩된 메서드는 실제 타입 기준
        listArray[0] = new ArrayList&lt;String&gt;();
        listArray[1] = new MyList&lt;String&gt;();
        Arrays.stream(listArray).forEach(list -&gt; list.add(&quot;first object&quot;));
        Arrays.stream(listArray).forEach(list -&gt; System.out.println(list.get(0)));
    }
}

/*

ArrayList → &quot;first object&quot;
MyList   → &quot;MyList get method returns = first object&quot;

*/</code></pre>
<p><strong>오버로딩·오버라이딩의 결정 시점 차이</strong></p>
<ul>
<li><p>컴파일 시점에는 listArray의 각 요소가 ArrayList인지 LinkedList인지 알 수 없고, 오직 정적 타입(List) 만 알고 있다. 즉, 오버로딩에서는 컴파일러가 List 타입 파라미터를 받는 메서드를 선택.</p>
</li>
<li><p>런타임 시점에는 new ArrayList(), new MyList() 로 생성된 실제 객체 타입을 알 수 있음. 이때 오버라이딩된 메서드는 동적 바인딩을 통해 실제 객체의 메서드가 실행된다</p>
</li>
</ul>
<h3 id="참고">참고</h3>
<ol>
<li><p><a href="https://velog.io/@dallok97/%EC%98%A4%EB%B2%84%EB%A1%9C%EB%94%A9-%EC%98%A4%EB%B2%84%EB%9D%BC%EC%9D%B4%EB%94%A9">오버로딩(Overloading) 오버라이딩(Overriding)</a></p>
</li>
<li><p><a href="https://woo0doo.tistory.com/15">오버로딩(Overloading),오버라이딩(Overriding)이란? + 차이점
</a></p>
</li>
<li><p><a href="https://kyoulho.tistory.com/412">[Java] 메서드와 필드 접근: 런타임과 컴파일 타임의 차이</a></p>
</li>
<li><p><a href="https://sungman.tistory.com/9">오버로딩 &amp; 오버라이딩의 결정 시점 차이 (Overloading &amp; Overriding)</a></p>
</li>
<li><p><a href="https://velog.io/@wkdrnsgh1/%EC%98%A4%EB%B2%84%EB%A1%9C%EB%94%A9-%EC%98%A4%EB%B2%84%EB%9D%BC%EC%9D%B4%EB%94%A9-%EA%B2%B0%EC%A0%95-%EC%8B%9C%EC%A0%90%EC%B0%A8%EC%9D%B4">오버로딩, 오버라이딩 결정 시점차이</a></p>
</li>
<li><p><a href="https://mangkyu.tistory.com/228">[Java] 부모 클래스의 메소드 오버라이딩이 더 큰 범위의 접근 제어자만 가능한 이유 or 더 좁은 범위로 변경할 수 없는 이유</a></p>
</li>
<li><p><a href="https://velog.io/@bombab/%EC%98%A4%EB%B2%84%EB%9D%BC%EC%9D%B4%EB%94%A9%EC%97%90%EC%84%9C-%EC%A0%91%EA%B7%BC-%EC%A0%9C%EC%96%B4%EC%9E%90%EC%9D%98-%EB%B2%94%EC%9C%84">메서드 오버라이딩에서 접근 제어자의 범위</a></p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[[Frontend] React-Query]]></title>
            <link>https://velog.io/@g_onmin41/Frontend-React-Query</link>
            <guid>https://velog.io/@g_onmin41/Frontend-React-Query</guid>
            <pubDate>Sun, 23 Nov 2025 10:57:27 GMT</pubDate>
            <description><![CDATA[<h2 id="목차">목차</h2>
<ol>
<li><a href="#react-query">React Query</a></li>
<li><a href="#%EC%A3%BC%EC%9A%94-%EB%A9%94%EC%84%9C%EB%93%9C">주요 메서드</a></li>
<li><a href="#querykey-%EC%84%A4%EA%B3%84-%ED%8C%81">queryKey 설계 팁</a></li>
<li><a href="#swr%EA%B3%BC%EC%9D%98-%EB%B9%84%EA%B5%90">SWR과의 비교</a></li>
</ol>
<hr>
<h2 id="react-query">React Query</h2>
<blockquote>
<p>💡 fetching, caching, 서버 데이터 동기화를 지원하는 라이브러리. 비동기 쿼리의 전 과정 관리</p>
</blockquote>
<h3 id="사용-이유">사용 이유</h3>
<ul>
<li>서버 상태(Server State) 중심의 상태 관리</li>
<li>전역 상태 관리 라이브러리로 관리하기 어려운 서버 데이터 처리에 특화</li>
<li>캐싱을 통한 성능 최적화</li>
<li>네트워크 재연결, 포커스 변화 등 자동 refetch</li>
<li>무한 스크롤, 페이지네이션 등의 복잡한 패턴을 쉽게 구현</li>
</ul>
<h3 id="캐싱">캐싱</h3>
<ul>
<li>반복적인 비동기 데이터 호출 방지<ul>
<li>fresh ↔ stale 상태와 캐시 수명 구분</li>
<li>적절하게 서버데이터 갱신 필요 (fresh한 데이터로)</li>
</ul>
</li>
<li>서버에 대한 부하 감소</li>
<li>서버 상태 관리 (예를들면 로딩중, 에러, 성공 등의 상태)를 간편하게 처리</li>
</ul>
<h3 id="refetch-트리거-옵션">Refetch 트리거 옵션</h3>
<ul>
<li><code>refetchOnWindowFocus</code>: 브라우저 포커스 시 refetch (default: true)</li>
<li><code>refetchOnMount</code>: 컴포넌트가 새로 mount되면 refetch (default: true)</li>
<li><code>refetchOnReconnect</code>: 네트워크 재연결 시 refetch (default: true)</li>
</ul>
<h3 id="데이터-수명-관련-옵션">데이터 수명 관련 옵션</h3>
<ul>
<li><code>staleTime</code>  <ul>
<li>데이터가 fresh → stale 로 바뀌는 시간  </li>
<li>stale 상태일 때만 refetch 실행  </li>
</ul>
</li>
<li><code>cacheTime</code>  <ul>
<li>데이터가 inactive(컴포넌트 언마운트 등) 이후 캐시에 남아있는 시간  </li>
<li>기본값: 5분  </li>
<li>이후 GC가 실행되어 메모리에서 제거됨  </li>
</ul>
</li>
</ul>
<hr>
<h3 id="client-데이터와-server-데이터간의-분리">Client 데이터와 Server 데이터간의 분리</h3>
<ul>
<li>프로젝트 규모가 커지게 되면 전역 상태 관리 라이브러리로 관리하기 힘들어짐</li>
<li>해당 라이브러리들은 Client쪽에 로직 집중. </li>
<li>React Query를 함께 사용하며 Client와 Server 데이터 분리</li>
</ul>
<hr>
<h3 id="contextapi-기반">ContextApi 기반</h3>
<ul>
<li>앱 전체를 <code>QueryClientProvider</code>로 감싸고 내부에서 QueryClient가 key 기반으로 캐싱</li>
<li>Context Store와 비슷한 역할</li>
<li>getQueryData, setQueryData, fetchQuery 등 데이터 관리 메서드 존재</li>
</ul>
<h2 id="주요-메서드">주요 메서드</h2>
<h3 id="queryclient">QueryClient</h3>
<ul>
<li>ContextApi 와 비슷한 역할</li>
<li>전역 scope로 감싸줘야됨</li>
</ul>
<pre><code class="language-TypeScript">import {
  QueryClient,
  QueryClientProvider,
} from &#39;@tanstack/react-query&#39;
import DelayedData from &#39;~/components/DelayedData&#39;

const queryClient = new QueryClient()

export default function App() {
  return (
    &lt;QueryClientProvider client={queryClient}&gt;
      &lt;DelayedData /&gt;
    &lt;/QueryClientProvider&gt;
  )
}</code></pre>
<h3 id="usequery">useQuery</h3>
<ul>
<li>queryKey에는 unique key를 포함한 배열 들어감<ul>
<li>다중 쿼리 키를 사용시 순서 중요</li>
</ul>
</li>
<li>key가 변경되면 새로운 쿼리로 취급됨</li>
<li>그 다음부터는 함수 내부 파라미터로 값 전달</li>
<li>실제 호출하고자하는 비동기 함수 들어감. Promise 반환하는 함수</li>
<li>응답은 Api 성공, 실패여부, 반환값 포함한 객체</li>
</ul>
<p><img src="https://velog.velcdn.com/images/g_onmin41/post/1c838e0e-45bd-44e7-9584-29ee78048747/image.png" alt="">
<img src="https://velog.velcdn.com/images/g_onmin41/post/5cdf7454-6f14-4cd7-8e52-50105a48f973/image.png" alt=""></p>
<ul>
<li>캐시된 데이터 없으면 서버에 요청. 캐시되어있으면 해당 데이터 사용.<ul>
<li>fresh인 경우만. stale일 경우 서버에 요청해 가져옴</li>
<li>staleTime으로 시간 지정, isStale로 여부 확인</li>
<li>prop, params 값이 다르면 따로 요청</li>
</ul>
</li>
</ul>
<table>
<thead>
<tr>
<th>상태</th>
<th>의미</th>
</tr>
</thead>
<tbody><tr>
<td><code>isPending</code></td>
<td>아직 첫 데이터가 없음 (초기 로딩)</td>
</tr>
<tr>
<td><code>isFetching</code></td>
<td>네트워크 요청이 실행 중 (refetch 포함)</td>
</tr>
</tbody></table>
<ul>
<li>enabled 옵션으로 조건부 쿼리 생성(특정 조건에서 true로)</li>
</ul>
<pre><code class="language-TypeScript">import {
  QueryClient,
  QueryClientProvider,
  useQuery,
} from &#39;@tanstack/react-query&#39;

const queryClient = new QueryClient()

export default function App() {
  return (
    &lt;QueryClientProvider client={queryClient}&gt;
      &lt;Example /&gt;
    &lt;/QueryClientProvider&gt;
  )
}

function Example() {
  const { isPending, error, data } = useQuery({
    queryKey: [&#39;repoData&#39;],
    queryFn: () =&gt;
      fetch(&#39;https://api.github.com/repos/tannerlinsley/react-query&#39;).then(
        (res) =&gt; res.json(),
      ),
  })

  if (isPending) return &#39;Loading...&#39;

  if (error) return &#39;An error has occurred: &#39; + error.message

  return (
    &lt;div&gt;
      &lt;h1&gt;{data.name}&lt;/h1&gt;
      &lt;p&gt;{data.description}&lt;/p&gt;
      &lt;strong&gt;👀 {data.subscribers_count}&lt;/strong&gt;{&#39; &#39;}
      &lt;strong&gt;✨ {data.stargazers_count}&lt;/strong&gt;{&#39; &#39;}
      &lt;strong&gt;🍴 {data.forks_count}&lt;/strong&gt;
    &lt;/div&gt;
  )
}</code></pre>
<ul>
<li>select 사용법</li>
</ul>
<pre><code class="language-ts">import { useQuery } from &#39;@tanstack/react-query&#39;

type Users = User[]
interface User {
  id: string
  name: string
  age: number
}

export default function UserNames() {
  const { data } = useQuery&lt;Users, Error, string[]&gt;({
    queryKey: [&#39;users&#39;],
    queryFn: async () =&gt; {
      const res = await fetch(&#39;https://api.heropy.dev/v0/users&#39;)
      const { users } = await res.json()
      return users
    },
    staleTime: 1000 * 10,
    select: data =&gt; data.map(user =&gt; user.name)
  })
  return (
    &lt;&gt;
      &lt;h2&gt;User Names&lt;/h2&gt;
      &lt;ul&gt;{data?.map((name, i) =&gt; &lt;li key={i}&gt;{name}&lt;/li&gt;)}&lt;/ul&gt;
    &lt;/&gt;
  )
}</code></pre>
<ul>
<li><p>placeholderData: prev =&gt; prev 로 임시로 표기할 데이터 지정 가능</p>
</li>
<li><p>structuralSharing: 변경되지않은 데이터 재사용</p>
</li>
<li><p>meta: 쿼리에 대한 추가정보 제공</p>
</li>
<li><p>isFetching(쿼리함수), isPending(서버요청), isLoading(쿼리 첫번째 가져오기)</p>
</li>
<li><p>refetch 시 데이터 새롭게 갱신</p>
<ul>
<li>staleTime 기반으로 데이터 가져오기는 queryClient.fetchQuery() 사용. </li>
<li>캐시된 데이터는 getQueryData() 메서드 사용. 없으면 undefined</li>
<li>ensureQueryData()는 없으면 자동으로 fetchQuery로 데이터 가져옴</li>
</ul>
</li>
</ul>
<h3 id="usequeries">useQueries</h3>
<ul>
<li>여러 개의 useQuery 한번에 실행 시 Promise.all()처럼 묶어서 실행</li>
</ul>
<pre><code class="language-TypeScript">const ids = [1, 2, 3]
const results = useQueries({
  queries: ids.map((id) =&gt; ({
    queryKey: [&#39;post&#39;, id],
    queryFn: () =&gt; fetchPost(id),
    staleTime: Infinity,
  })),
})

// 두 query에 대한 반환값이 배열로 묶여 반환된다!!

// 만일 반환된 배열에 대해 통합된 값을 불러오고 싶다면, 아래와 같이 combine 설정을 통해 데이터를 한 번에 반환할 수 있다. 이외에도 배열을 다루는 메서드들을 이용해 반환값에 대한 전처리를 수행할 수 있다!

const ids = [1, 2, 3]
const combinedQueries = useQueries({
  queries: ids.map((id) =&gt; ({
    queryKey: [&#39;post&#39;, id],
    queryFn: () =&gt; fetchPost(id),
  })),
  combine: (results) =&gt; {
    return {
      data: results.map((result) =&gt; result.data),
      pending: results.some((result) =&gt; result.isPending),
    }
  },
})</code></pre>
<h3 id="usemutation">useMutation</h3>
<ul>
<li><p>PUT, UPDATE, DELETE와 같이 값 변경 시 사용하는 API</p>
</li>
<li><p>반환값은 useQuery와 동일. 처음 사용 시 post 비동기 함수 넣고, 두번째 인자로 상황 별 분기설정 들어감</p>
</li>
<li><p>실제 사용 시에는 mutation.mutate 메서드를 사용하고, 첫 번째 인자로 API 호출 시에 전달해주어야하는 데이터를 넣어주면 됨</p>
</li>
<li><p>요청 실패 시 자동 재시도, 낙관적 업데이트(일단 UI 업데이트)등의 기능도 지원</p>
<pre><code class="language-TypeScript">function App() {
const mutation = useMutation({
  mutationFn: (newTodo) =&gt; {
    return axios.post(&#39;/todos&#39;, newTodo)
  },
  onSuccess: () =&gt; queryClient.invalidateQueries([&#39;todos&#39;])
})

return (
  &lt;div&gt;
    {mutation.isLoading ? (
      &#39;Adding todo...&#39;
    ) : (
      &lt;&gt;
        {mutation.isError ? (
          &lt;div&gt;An error occurred: {mutation.error.message}&lt;/div&gt;
        ) : null}

        {mutation.isSuccess ? &lt;div&gt;Todo added!&lt;/div&gt; : null}

        &lt;button
          onClick={() =&gt; {
            mutation.mutate({ id: new Date(), title: &#39;Do Laundry&#39; })
          }}
        &gt;
          Create Todo
        &lt;/button&gt;
      &lt;/&gt;
    )}
  &lt;/div&gt;
)
}</code></pre>
</li>
</ul>
<h3 id="useinfinitequery">useInfiniteQuery</h3>
<ul>
<li>무한 스크롤 or 더보기로 추가데이터 요청 시 사용</li>
<li>useInfiniteQuery 의 data는 pages 배열과 pageParams로 구성. data.pages[0], data.pages[1]처럼 각 페이지 데이터에 접근해서 flat하게 렌더링하면 된다.</li>
<li>useQuery 모든 옵션 사용 +<ul>
<li><code>getNextPageParam</code>: 다음 페이지를 가져올 때 사용할 파라미터를 반환</li>
<li><code>getPreviousPageParam</code>: 이전 페이지용 파라미터(필요하다면)</li>
<li><code>initialPageParam</code>: 첫 요청에 사용할 초기 파라미터</li>
<li>maxPages</li>
</ul>
</li>
<li>반환 속성<ul>
<li>fetchNextPage</li>
<li>fetchPreviousPage</li>
<li>hasNextPage</li>
<li>hasPreviousPage    </li>
<li>isFetchingNextPage</li>
<li>isFetchingPreviousPage</li>
</ul>
</li>
</ul>
<pre><code class="language-ts">const {
  data,
  fetchNextPage,
  hasNextPage,
  isFetchingNextPage
} = useInfiniteQuery({
  queryKey: [&#39;movies&#39;],
  queryFn: fetchMovies,
  getNextPageParam: lastPage =&gt; lastPage.nextCursor
})</code></pre>
<pre><code class="language-ts">import { Fragment, useState, useEffect, useRef } from &#39;react&#39;
import { useInfiniteQuery } from &#39;@tanstack/react-query&#39;

// ...

export default function MovieList() {
  const [searchText, setSearchText] = useState(&#39;&#39;)
  const [queryText, setQueryText] = useState(&#39;&#39;)
  const observerEl = useRef&lt;HTMLDivElement | null&gt;(null)

  const {
    data,
    // isLoading,
    isFetching,
    // isFetched,
    hasNextPage,
    fetchNextPage
  } = useInfiniteQuery&lt;Page&gt;({
    // ...
  })

  useEffect(() =&gt; {
    const currentObserverEl = observerEl.current
    const io = new IntersectionObserver(entries =&gt; {
      if (entries[0].isIntersecting &amp;&amp; hasNextPage) {
        fetchNextPage()
      }
    })
    if (currentObserverEl) {
      io.observe(currentObserverEl)
    }
    return () =&gt; {
      if (currentObserverEl) {
        io.disconnect()
      }
    }
  }, [hasNextPage, fetchNextPage])

  // ...

  return (
    &lt;&gt;
      {/* ... */}
      {/* {isLoading ? &lt;div&gt;로딩 중..&lt;/div&gt; : null}
      {isFetched &amp;&amp; hasNextPage &amp;&amp; (
        &lt;button
          disabled={isFetching}
          onClick={() =&gt; fetchNextPage()}&gt;
          {isFetching ? &#39;로딩 중..&#39; : &#39;더 보기!&#39;}
        &lt;/button&gt;
      )} */}
      {isFetching ? &lt;div&gt;로딩 중..&lt;/div&gt; : null}
      &lt;div
        ref={observerEl}
        style={{
          display: isFetching ? &#39;none&#39; : &#39;block&#39;,
          height: &#39;20px&#39;
        }}
      /&gt;
    &lt;/&gt;
  )
}</code></pre>
<ul>
<li>react-interection-observer 사용 시 더 간결</li>
</ul>
<pre><code class="language-ts">import { useState, useEffec, useCallback } from &#39;react&#39;
import { useInfiniteQuery } from &#39;@tanstack/react-query&#39;
import { useInView } from &#39;react-intersection-observer&#39;

// ...

export default function MovieList() {
  const [searchText, setSearchText] = useState(&#39;&#39;)
  const [queryText, setQueryText] = useState(&#39;&#39;)
  const { ref, inView } = useInView()

  // ...

  // useEffect(() =&gt; {
  //   const currentObserverEl = observerEl.current
  //   const io = new IntersectionObserver(entries =&gt; {
  //     if (entries[0].isIntersecting &amp;&amp; hasNextPage) {
  //       fetchNextPage()
  //     }
  //   })
  //   if (currentObserverEl) {
  //     io.observe(currentObserverEl)
  //   }
  //   return () =&gt; {
  //     if (currentObserverEl) {
  //       io.disconnect()
  //     }
  //   }
  // }, [hasNextPage, fetchNextPage])

  useEffect(() =&gt; {
    if (inView &amp;&amp; hasNextPage) {
      fetchNextPage()
    }
  }, [inView, hasNextPage])

  // ...

  return (
    &lt;&gt;
      {/* ... */}
      {isFetching ? &lt;div&gt;로딩 중..&lt;/div&gt; : null}
      &lt;div
        ref={ref}
        style={{
          display: isFetching ? &#39;none&#39; : &#39;block&#39;,
          height: &#39;20px&#39;
        }}
      /&gt;
    &lt;/&gt;
  )
}</code></pre>
<h2 id="querykey-설계-팁">queryKey 설계 팁</h2>
<p>React Query의 성능과 캐싱 전략은 <strong>queryKey 설계에 크게 영향 받음</strong>.</p>
<h3 id="배열-형태-권장">배열 형태 권장</h3>
<pre><code class="language-ts">[&#39;todos&#39;]
[&#39;todos&#39;, userId]
[&#39;product&#39;, productId, &#39;reviews&#39;]</code></pre>
<h3 id="리소스-이름--변수-조합">리소스 이름 + 변수 조합</h3>
<p>REST 방식과 유사한 패턴이 가장 직관적.</p>
<pre><code class="language-ts">[&#39;user&#39;, userId]
[&#39;orders&#39;, userId, page]</code></pre>
<h3 id="객체-대신-배열-요소로-명확히-분리">객체 대신 배열 요소로 명확히 분리</h3>
<pre><code class="language-ts">// 비추천 ❌
[&#39;todos&#39;, { page, filter }]

// 추천 ⭕
[&#39;todos&#39;, page, filter]</code></pre>
<h3 id="의존성-정확히-반영하기">의존성 정확히 반영하기</h3>
<p>쿼리의 조건이 되는 값은 반드시 key에 포함해야 한다.</p>
<pre><code class="language-ts">useQuery({
  queryKey: [&#39;search&#39;, keyword],
  enabled: !!keyword
})</code></pre>
<h3 id="서버-자원resource-단위로-key-구성">서버 자원(Resource) 단위로 key 구성</h3>
<ul>
<li>하나의 자원 = 하나의 key  </li>
<li>서버 응답 형태가 어떻게 생겼는지는 중요하지 않음  </li>
<li>&quot;무엇을 식별하는가&quot;에 집중하는 게 핵심</li>
</ul>
<hr>
<h2 id="swr과의-비교">SWR과의 비교</h2>
<h3 id="provider-사용-여부">Provider 사용 여부</h3>
<ul>
<li><strong>React Query</strong>: 필수 (<code>QueryClientProvider</code>)</li>
<li><strong>SWR</strong>: 기본 사용 시 Provider 불필요, 필요하면 <code>SWRConfig</code></li>
</ul>
<h3 id="fetcher-방식">Fetcher 방식</h3>
<ul>
<li><strong>React Query</strong>: 쿼리마다 <code>queryFn</code> 지정  </li>
<li><strong>SWR</strong>: 전역 fetcher 설정 후 <code>useSWR(key)</code> 사용</li>
</ul>
<h3 id="devtools-지원">Devtools 지원</h3>
<p>React Query Devtools로 캐싱, 상태 확인이 매우 쉬움</p>
<h3 id="무한-스크롤-기능">무한 스크롤 기능</h3>
<p>React Query는 <code>useInfiniteQuery</code>를 공식 제공<br>(SWR은 커스텀 구현 필요)</p>
<h3 id="selectors">Selectors</h3>
<p><code>select</code> 옵션으로 raw data → 가공 데이터로 매핑 가능</p>
<h3 id="데이터-최적화">데이터 최적화</h3>
<p>여러 컴포넌트에서 동일한 쿼리를 사용할 경우<br>React Query는 refetch를 batch로 처리해 렌더링 성능 향상</p>
<h3 id="gc-기반-메모리-관리">GC 기반 메모리 관리</h3>
<p>cacheTime 기반으로 자동 GC 실행</p>
<hr>
<p>🔗 참고
<a href="https://velog.io/@kandy1002/React-Query-%ED%91%B9-%EC%B0%8D%EC%96%B4%EB%A8%B9%EA%B8%B0">[React-Query] React-Query 개념잡기</a></p>
<p><a href="https://www.heropy.dev/p/HZaKIE">TanStack Query(React Query) 핵심 정리</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘]21276_계보 복원가 호석]]></title>
            <link>https://velog.io/@g_onmin41/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%9821276%EA%B3%84%EB%B3%B4-%EB%B3%B5%EC%9B%90%EA%B0%80-%ED%98%B8%EC%84%9D</link>
            <guid>https://velog.io/@g_onmin41/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%9821276%EA%B3%84%EB%B3%B4-%EB%B3%B5%EC%9B%90%EA%B0%80-%ED%98%B8%EC%84%9D</guid>
            <pubDate>Sun, 23 Nov 2025 07:28:44 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/21276">백준_21276</a></p>
<ul>
<li><p>N명. 트리형태. 각 노드는 부모, 조상 다 기억</p>
</li>
<li><p>N개의 이름.</p>
</li>
<li><p>M개의 정보. X Y꼴로 X의 조상에 Y가 있음. 중복 x</p>
</li>
<li><p>가문의 개수 K 출력</p>
</li>
<li><p>가문의 시조 이름 공백으로 구분 + 사전순</p>
</li>
<li><p>N개의 이름 사전순으로 이름 자식수 자식들이름(사전순) 출력</p>
</li>
<li><p>트리 만들고 루트의 자식노드 개수 출력. 이름 사전순 출력. N개의 이름에 자식 수 , 자식 이름 저장 후 사전순으로 출력.</p>
<ul>
<li>각 노드 별 indegree 저장(조상의 개수). 0인 애들 시조로 저장</li>
<li>q에 넣고 자식들 indegree-- 하면서 0 되면 직계자손으로 children에 저장</li>
</ul>
</li>
</ul>
<pre><code class="language-java">import java.io.*;
import java.util.*;

public class Main {
    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringBuilder sb = new StringBuilder();

        int N = Integer.parseInt(br.readLine().trim());

        // 이름 입력
        StringTokenizer st = new StringTokenizer(br.readLine());
        String[] original = new String[N];
        for (int i = 0; i &lt; N; i++) {
            original[i] = st.nextToken();
        }

        // 이름 사전순 정렬
        String[] names = original.clone();
        Arrays.sort(names);

        // 이름 → 인덱스(0..N-1)
        Map&lt;String, Integer&gt; nameToIdx = new HashMap&lt;&gt;();
        for (int i = 0; i &lt; N; i++) {
            nameToIdx.put(names[i], i);
        }

        int M = Integer.parseInt(br.readLine().trim());

        // 그래프: 조상 → 자손
        List&lt;Integer&gt;[] graph = new ArrayList[N];
        List&lt;Integer&gt;[] children = new ArrayList[N]; // 직계 자식만 저장
        for (int i = 0; i &lt; N; i++) {
            graph[i] = new ArrayList&lt;&gt;();
            children[i] = new ArrayList&lt;&gt;();
        }

        int[] indegree = new int[N];

        // 조상 정보 입력
        for (int i = 0; i &lt; M; i++) {
            st = new StringTokenizer(br.readLine());
            String X = st.nextToken(); // 자손
            String Y = st.nextToken(); // 조상

            int x = nameToIdx.get(X);
            int y = nameToIdx.get(Y);

            // Y → X (조상 → 자손)
            graph[y].add(x);
            indegree[x]++;
        }

        // 위상 정렬 + 시조 찾기
        Queue&lt;Integer&gt; q = new ArrayDeque&lt;&gt;();
        List&lt;Integer&gt; roots = new ArrayList&lt;&gt;();

        for (int i = 0; i &lt; N; i++) {
            if (indegree[i] == 0) {
                roots.add(i);   // indegree 0 → 시조
                q.add(i);
            }
        }

        while (!q.isEmpty()) {
            int cur = q.poll();
            for (int nxt : graph[cur]) {
                indegree[nxt]--;
                if (indegree[nxt] == 0) {
                    // cur이 nxt의 &#39;직계 부모&#39;
                    children[cur].add(nxt);
                    q.add(nxt);
                }
            }
        }

        // 시조 개수 + 시조 이름 출력
        sb.append(roots.size()).append(&#39;\n&#39;);
        for (int i = 0; i &lt; roots.size(); i++) {
            sb.append(names[roots.get(i)]);
            if (i + 1 &lt; roots.size()) sb.append(&#39; &#39;);
        }
        sb.append(&#39;\n&#39;);

        // 각 사람에 대해: 이름, 자식 수, 자식들 이름(사전순)
        for (int i = 0; i &lt; N; i++) {
            Collections.sort(children[i]); 
            sb.append(names[i]).append(&#39; &#39;).append(children[i].size());
            for (int child : children[i]) {
                sb.append(&#39; &#39;).append(names[child]);
            }
            sb.append(&#39;\n&#39;);
        }

        System.out.print(sb.toString());
    }
}
</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[[알고리즘]2233_사과나무]]></title>
            <link>https://velog.io/@g_onmin41/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%982233%EC%82%AC%EA%B3%BC%EB%82%98%EB%AC%B4</link>
            <guid>https://velog.io/@g_onmin41/%EC%95%8C%EA%B3%A0%EB%A6%AC%EC%A6%982233%EC%82%AC%EA%B3%BC%EB%82%98%EB%AC%B4</guid>
            <pubDate>Sun, 23 Nov 2025 06:20:30 GMT</pubDate>
            <description><![CDATA[<p><a href="https://www.acmicpc.net/problem/2233">백준_2233</a></p>
<ul>
<li><p>벌레들 루트에서 DFS로 탐색, 오른쪽 먼저 방문. </p>
</li>
<li><p>새로운 노드 방문 시 0, 모든 자식노드 방문 후 리턴할 때 1. 나열한 하나의 이진 수열.</p>
</li>
<li><p>한번만 가지쳐서 썩은 사과 제거, 멀쩡한 사과 최소로 -&gt; 가장 가까운 공통부모 찾기</p>
</li>
<li><p>그럼 이진 수열을 트리로 만들고 썩은 사과들의 노드에 가장 가까운 공통 부모노드</p>
<ul>
<li><p>그냥 이진수의 0값 인덱스를 부모노드 인덱스로 생각하면 되겠다는 생각</p>
</li>
<li><p>괄호 닫기 느낌으로 스택으로 트리 만들기</p>
</li>
</ul>
</li>
<li><p>x랑 y가 0값 인덱스인지 1값 인덱스인지 모르니까 0값으로 변환, 통일</p>
</li>
<li><p>그리고 공통부모도 0값 인덱스랑 1값 인덱스 반환 필요</p>
<ul>
<li>두 배열 만들어서 각각 저장. 변환용</li>
</ul>
</li>
<li><p>공통부모는 같은 depth로 맞추고 값 같아질때까지, 이후 같은 높이일 때 같은 노드인 경우 나올때까지 부모로 감. 같을 때 최소 공통 부모</p>
</li>
</ul>
<pre><code class="language-java">import java.io.*;
import java.util.*;

public class Main {

    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        int N = Integer.parseInt(br.readLine()); // 정점 개수

        // 0,1 이진 수열 (길이 = 2N)
        String bin = br.readLine().trim();
        int M = bin.length(); // 실제 이진수 길이

        // x, y 위치. 시작 인덱스 0으로 설정
        StringTokenizer st = new StringTokenizer(br.readLine());
        int x = Integer.parseInt(st.nextToken()) - 1;
        int y = Integer.parseInt(st.nextToken()) - 1;

        // 0 인덱스 -&gt; 짝이 되는 1 의 인덱스
        int[] zeroToOne = new int[M];
        // 1 인덱스 -&gt; 짝이 되는 0의 인덱스
        int[] oneToZero = new int[M];
        Arrays.fill(zeroToOne, -1);
        Arrays.fill(oneToZero, -1);

        // 각 0 위치의 부모 0 위치 인덱스
        int[] parent = new int[M];
        // 공통 부모 찾기용
        int[] depth = new int[M];
        Arrays.fill(parent, -1);

        Deque&lt;Integer&gt; stack = new ArrayDeque&lt;&gt;();

        // 0/1 수열에서 부모 설정
        for (int i = 0; i &lt; M; i++) {
            char c = bin.charAt(i);

            if (c == &#39;0&#39;) {
                // parent / depth 설정
                if (stack.isEmpty()) {
                    // 루트
                    parent[i] = i;   // 루트는 자기 자신을 부모로
                    depth[i] = 0;
                } else {
                    parent[i] = stack.peekLast();          // 스택 top이 부모 노드의 0 위치
                    depth[i] = depth[parent[i]] + 1;
                }
                stack.offerLast(i);
            } else {
                int openIdx = stack.pollLast();
                zeroToOne[openIdx] = i;  // 이 0의 닫힘 위치는 i
                oneToZero[i] = openIdx;  // 이 1의 여는 위치는 openIdx
            }
        }



        // 주어진 x, y 인덱스를 해당 노드의 0 위치로 변환
        int u = (bin.charAt(x) == &#39;0&#39;) ? x : oneToZero[x];
        int v = (bin.charAt(y) == &#39;0&#39;) ? y : oneToZero[y];

        // 가장 가까운 공통 부모 찾기
        int lca = lca(u, v, parent, depth);

        int ZeroPos = lca;               // 이 부모의 0 위치
        int OnePos = zeroToOne[lca];   // 이 부모의 1 위치

        // 시작위치 1로 수정
        System.out.println((ZeroPos + 1) + &quot; &quot; + (OnePos + 1));
    }

    // 가장 가까운 공통 부모 찾기
    private static int lca(int a, int b, int[] parent, int[] depth) {
        while (depth[a] &gt; depth[b]) {
            a = parent[a];
        }
        while (depth[b] &gt; depth[a]) {
            b = parent[b];
        }

        while (a != b) {
            a = parent[a];
            b = parent[b];
        }
        return a;
    }
}

</code></pre>
]]></description>
        </item>
    </channel>
</rss>