<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>l-wanderer01.log</title>
        <link>https://velog.io/</link>
        <description>항상 왜?를 생각하는 개발자</description>
        <lastBuildDate>Fri, 28 Aug 2026 08:26:33 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>l-wanderer01.log</title>
            <url>https://velog.velcdn.com/images/l-wanderer01/profile/761375ed-5055-43b9-bea8-878c6c25d6f5/social_profile.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. l-wanderer01.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/l-wanderer01" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[[프로그래머스] 단속카메라]]></title>
            <link>https://velog.io/@l-wanderer01/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%8B%A8%EC%86%8D%EC%B9%B4%EB%A9%94%EB%9D%BC</link>
            <guid>https://velog.io/@l-wanderer01/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%8B%A8%EC%86%8D%EC%B9%B4%EB%A9%94%EB%9D%BC</guid>
            <pubDate>Fri, 28 Aug 2026 08:26:33 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/42884?gad_source=1&amp;gad_campaignid=23037984604&amp;gbraid=0AAAAAC_c4nB4UGiPeYC_wP0g_HS668qFm&amp;gclid=CjwKCAjw7p_UBhBlEiwAhpIs7z05osO4VnUaS8TAwjUwLIIs9b4qvYQS0R8tR_0N5JqKy4-R4-VULBoCt2QQAvD_BwE">Programmers - 단속카메라</a></p>
<h3 id="요약">요약</h3>
<p>모든 차를 단속할 수 있도록 설치하는 카메라의 최소 개수를 구하라</p>
<h1 id="접근">접근</h1>
<p>처음에 문제를 봤을 때, 차량마다 출발 지점을 기준으로 정렬을 하고 각 차량마다 겹치는 구간에 카메라를 두면 되겠다고 생각했다.</p>
<p>그러고 문제의 TC를 이 논리로 푸니까 개수가 안맞았다.</p>
<p>20분 동안 어떻게 풀지 고민했는데 답이 안나와서 스터디를 같이 하는 친구가 푼 코드에 주석을 읽고 어느정도 접근 방식을 알고 접근했다.</p>
<p>그 접근 방식은 아래와 같다.</p>
<ol>
<li>정렬을 한다.</li>
<li>자동차의 고속도로 나간 지점에 카메라를 세운다.</li>
<li>새로 세운 카메라를 다음 차량이 잡을 수 있는지를 검사하며 새로운 카메라를 설치할지말지 결정한다.</li>
</ol>
<h1 id="알고리즘">알고리즘</h1>
<ol>
<li>차량별 고속도로를 나간 시점을 기준으로 정렬한다.</li>
<li>최초 카메라 설치를 첫번째 차량이 나간 시점으로 초기화한다.</li>
<li>차량 전체를 순회하며 최근 설치된 카메라로 해당 차량을 탐지할 수 있는지 판단
 3-1. 탐지할 수 없다면, 해당 차량의 탈출이 나가는 시점으로 최근 카메라 설치 위치를 이동
 3-2. 카메라 개수 증가</li>
</ol>
<h1 id="제출-코드">제출 코드</h1>
<pre><code>import java.util.Arrays;

class Solution {
    public int solution(int[][] routes) {
        int answer = 0;
        int[] cameras = new int[routes.length];

        // 고속도로 탈출한 시점으로 정렬
        Arrays.sort(routes, (a, b) -&gt; a[1] - b[1]);

        // 최근에 설치한 카메라 위치
        int newCam = routes[0][1]; // 기본 값으로 첫번째 차량의 고속도로 나간 지점을 잡음
        answer++; // 카메라 개수 1 증가

        for (int i = 1; i &lt; routes.length; i++) {
            // 설치한 카메라가 다음 자동차의 출발지를 못잡는다면, 새로운 카메라 설치
            if (routes[i][0] &gt; newCam) {
                newCam = routes[i][1];
                answer++;
            }
        }

        return answer;
    }
}</code></pre><h1 id="깨달은-점">깨달은 점</h1>
<p>Greedy라는걸 파악하기도 어렵고, 문제를 접근하는 아이디어조차 떠올리기 힘들었다.
Greedy는 미래를 생각안하고, 그 순간순간 가장 좋아보이는 최적의 선택을 하는 알고리즘 설계 기법... 이걸 어떻게 아냐...
많이 접해봐야 이게 그리디 문제인지 알 것 같다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 섬 연결하기]]></title>
            <link>https://velog.io/@l-wanderer01/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%84%AC-%EC%97%B0%EA%B2%B0%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@l-wanderer01/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%84%AC-%EC%97%B0%EA%B2%B0%ED%95%98%EA%B8%B0</guid>
            <pubDate>Thu, 27 Aug 2026 03:27:52 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/42861">Programmers - 섬 연결하기</a></p>
<h3 id="요약">요약</h3>
<p>모든 섬을 연결할 수 있는 전체 간선의 최소 비용</p>
<h1 id="접근">접근</h1>
<p>일단 섬들을 연결하는걸 보고 그래프 관련 문제라는 생각이 들었다.</p>
<ul>
<li>처음에 접근한 방식</li>
</ul>
<ol>
<li>costs를 [0]과 [1]을 기준으로 정렬되도록 만듦</li>
<li>costs를 순회하며, 각 노드가 포함된 최소 비용이 드는 경로를 answer에 누적.</li>
<li>방문한 섬들을 visited 처리</li>
</ol>
<p>이 방식대로 문제를 푸니 전체 섬의 수를 n개라 했을 때, n-1을 순회해야하는데, n을 순회하는 문제가 있었다.</p>
<p>이때 문제 푸는 시간을 1시간 넘게 써서 AI의 도움을 받아 올바르게 접근하고 있는지 물어보니, 이 문제는 MST(최소 신장 트리)를 구현하는 문제였다.</p>
<ul>
<li><strong>MST</strong>란?
가중치가 있는 그래프에서 모든 정점을 <code>가장 작은 비용</code>으로 연결하는 트리</li>
</ul>
<h1 id="알고리즘">알고리즘</h1>
<p>MST를 구현하는 알고리즘은 총 2가지 방법이 있다.</p>
<ol>
<li><p><strong>크루스칼</strong> 알고리즘 (Kruskal)</p>
<p> : 가중치가 가장 작은 간선부터 순차적으로 탐색하며 하나의 그래프를 완성</p>
</li>
<li><p><strong>프림</strong> 알고리즘 (Prim)</p>
<p> : 임의의 노드를 먼저 방문처리한 후 인접한 노드 중 비용이 낮은 간선을 찾아가며 하나의 그래프를 완성</p>
</li>
</ol>
<p>나는 좀 더 직관적인 크루스칼 알고리즘을 선택해 풀었다.</p>
<p>크루스칼 알고리즘의 핵심 아이디어는 각 노드마다 최상단 조상 노드의 정보를 저장하고, 모든 노드의 최상단 조상 노드를 동일하게 만드는 것이다.</p>
<p>이를 위해서 가장 중요한 것은 Find와 Union이다.
<code>find</code> : 현재 노드의 최상단 조상 노드를 찾는 것
<code>union</code> : 두 노드 그룹을 하나의 노드 그룹으로 합치는 것</p>
<p>find는 재귀로 구현하고,
union은 노드 연결 정보를 순회하며 하나의 간선에 연결된 두 노드의 부모 노드가 다르다면 하나로 합치는게 핵심!</p>
<h1 id="최종-코드">최종 코드</h1>
<pre><code>import java.util.Arrays;

class Solution {
    int[] lands;

    public int solution(int n, int[][] costs) {
        int answer = 0;

        // 각 노드의 부모 노드 저장
        lands = new int[n+1];
        for (int i = 0; i &lt; lands.length; i++) {
            // 본인이 부모 노드가 되도록 초기화
            lands[i] = i;
        }

        // costs를 cost 기준으로 정렬
        Arrays.sort(costs, (a1, b1) -&gt; a1[2] - b1[2]);

        // 2. 메인 로직에서의 Union-Find 적용
        for (int i = 0; i &lt; costs.length; i++) {
            // 1. 각 시작노드와 끝 노드를 뽑는다.
            int start = costs[i][0];
            int end = costs[i][1];

            // 각 노드의 부모 노드가 동일한지 확인
            // 2-1. 부모 노드가 동일하다면 skip
            int rootStart = find(start);
            int rootEnd = find(end);
            if (rootStart == rootEnd) continue;
            // 2-2. 부모 노드가 다르다면 두 그룹을 하나로 묶는다.
            else {
                lands[rootEnd] = rootStart;

                answer += costs[i][2];
            }
        }

        return answer;
    }

    // 별도의 find 메서드를 만들어 &#39;최종 대표&#39;를 찾는 로직을 구현
    public int find(int land) {
        // 지금 섬이 최종 끝인지 확인
        if (lands[land] == land) {
            return land;
        }

        // 내가 최종 노드가 아니라면, 내 부모의 부모를 찾음
        return find(lands[land]);
    }
}</code></pre><h1 id="새로-배운-점">새로 배운 점</h1>
<p>MST라는 걸 개념만 알고 있었는데, 실제로 구현하려니 너무 어려웠다.
크루스칼 알고리즘은 처음 듣고, 프림 알고리즘은 들어만 봤는데, 실제로 마주하니 어렵다..
그래도 이렇게 기록해나가며 문제를 풀다보면 나중엔 쉽게 잘 풀지 않을까라는 생각 중이다.</p>
<ul>
<li>처음에 접근한 방식으로 푼 코드<pre><code>import java.util.*;
</code></pre></li>
</ul>
<p>class Solution {
    public int solution(int n, int[][] costs) {
        int answer = 0;</p>
<pre><code>    // 이어진 섬들 저장
    boolean[] visited = new boolean[n];

    // costs를 cost 기준으로 정렬
    Arrays.sort(costs);

    for (int i = 0; i &lt; n; i++) {
        int min = Integer.MAX_VALUE;
        for (int j = 0; j &lt; costs.length; j++) {
            int start = costs[j][0];
            int end = costs[j][1];
            if (start == i || end == i) {
                // 같은 경우를 처리하는게 관건
                if (!(visited[start] &amp;&amp; visited[end])) {
                    visited[start] = true;
                    visited[end] = true;
                    min = Math.min(min, costs[j][2]);
                    System.out.println(i + &quot; = &quot; +costs[j][0] + &quot; | &quot; +  costs[j][1] + &quot; : &quot; + min);   
                }
                // System.out.println(i + &quot; = &quot; +costs[j][0] + &quot; | &quot; +  costs[j][1] + &quot; : &quot; + min);
            }
        }
        answer += min;
    }

    return answer;
}</code></pre><p>}
```</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 정수 삼각형]]></title>
            <link>https://velog.io/@l-wanderer01/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%A0%95%EC%88%98-%EC%82%BC%EA%B0%81%ED%98%95</link>
            <guid>https://velog.io/@l-wanderer01/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%A0%95%EC%88%98-%EC%82%BC%EA%B0%81%ED%98%95</guid>
            <pubDate>Mon, 24 Aug 2026 16:34:45 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/43105">Programmers - 정수 삼각형</a></p>
<h3 id="요약">요약</h3>
<p>삼각형의 꼭대기에서 바닥까지 이어지는 경로 중, 대각선 방향으로 한 칸 오른쪽 또는 왼쪽으로만 이동 가능할 때 거쳐간 숫자의 최댓값</p>
<h1 id="접근">접근</h1>
<p>문제만 봤을 땐, DFS로 전부 다 탐색할 수 있겠다라는 생각을 했다.
제약 조건을 보니 N이 1~500까지였다. 이를 최악의 경우를 계산해보니 총 노드이 개수는 약 125,000개가 된다. 이를 DFS로 돌리기란 시간복잡도에서 무조건 문제가 생긴다.</p>
<p>문제를 다시 보니 DP 중 메모이제이션을 활용해 풀면 해결될 것 같다는 생각을 했고, 내가 생각한 알고리즘대로 풀어보니 답이 나왔다.</p>
<p><img src="https://velog.velcdn.com/images/l-wanderer01/post/c23e8ed8-eb79-4c13-b984-5122ce698036/image.jpg" alt=""></p>
<h1 id="알고리즘">알고리즘</h1>
<p>알고리즘 : DP (메모이제이션)</p>
<ol>
<li>위에서 아래로 내려간다 1번 인덱스부터 n번 인덱스까지</li>
<li>0번 인덱스부터 i 인덱스까지 돌면서 값 업데이트</li>
<li>가장 좌측이랑 우측이라면 각각 한번의 연산만 함</li>
<li>모든 값을 업데이트한 뒤, 제일 바닥에서 값이 제일 큰걸 answer에 둔다</li>
</ol>
<h1 id="최종-코드">최종 코드</h1>
<pre><code>class Solution {
    int[][] triangle;

    public int solution(int[][] triangle) {
        int answer = 0;
        this.triangle = triangle;
        // 1. 위에서 아래로 내려간다 1번 인덱스부터 n번 인덱스까지
        int start = triangle[0][0];
        int len = triangle.length;

        for (int i = 1; i &lt; len; i++) {
            // 2. 0번 인덱스부터 i 인덱스까지 돌면서 값 업데이트
            for (int j = 0; j &lt;= i; j++) {
                // 3. 누적합 계산
                triangle[i][j] = memoization(i, j);
            }
        }

        // 4. 모든 값을 업데이트한 뒤, 제일 바닥에서 값이 제일 큰걸 answer에 둔다
        for (int i = 0; i &lt; len; i++) {
            answer = Math.max(answer, triangle[len-1][i]);
        }

        return answer;
    }

    // h : 높이, w : 너비
    public int memoization(int h, int w) {
        int res = triangle[h][w];
        // 3-1. 가장 좌측 한번의 연산만 함
        if (w == 0) {
            res += triangle[h-1][0];
        }
        // 3-2. 우측이라면 한번의 연산만 함
        else if (h == w) {
            res += triangle[h-1][w-1];
        }
        // 3-3. 위의 두 경우가 아니라면 두 부모의 값 중 큰 것과 합한다.
        else {
            int num = Math.max(triangle[h-1][w-1], triangle[h-1][w]);
            res += num;
        }
        return res;
    }
}</code></pre><h1 id="깨달은-점">깨달은 점</h1>
<ul>
<li>DP(메모이제이션)을 개념으로만 알고 있었는데, 실제 문제를 만나고, 손으로 풀어보니 감을 잡았다.</li>
<li>이번 문제는 손으로 푼 풀이를 그대로 코드로 옮기는데 큰 어려움 없이 풀렸다. 
(나... 재능있나...?)</li>
<li>처음 문제를 볼 때, 알고리즘이 떠오르지 않으면 제약조건을 먼저 보자!!</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 여행경로]]></title>
            <link>https://velog.io/@l-wanderer01/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%97%AC%ED%96%89%EA%B2%BD%EB%A1%9C</link>
            <guid>https://velog.io/@l-wanderer01/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EC%97%AC%ED%96%89%EA%B2%BD%EB%A1%9C</guid>
            <pubDate>Tue, 18 Aug 2026 15:30:25 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/43164#">프로그래머스 - 여행경로</a></p>
<h3 id="요약">요약</h3>
<p>ICN부터 시작되는 전체 여행경로를 출력하라.
단, 2개 이상의 경로가 나오는 경우 알파벳 순서가 빠른 경로를 출력할 것</p>
<h1 id="접근">접근</h1>
<p>무조건 &quot;ICN&quot;부터 시작하니까 ICN이 루트 노드가 되는 트리를 탐색하는 것으로 접근을 시작했다.
<img src="https://velog.velcdn.com/images/l-wanderer01/post/d625d8e3-6e04-46ed-83a5-728d3c5b579c/image.JPG" alt="">
테스트 코드를 손으로 풀어봤을 때, 이미지와 같은 경로가 나올 수 있도록 구현한다고 생각을 하고, 이를 직접 알고리즘으로 구현했다.</p>
<h1 id="알고리즘">알고리즘</h1>
<ol>
<li><p><strong>시작</strong>: &quot;ICN&quot; 출발 항공권을 찾아 방문 처리 후 DFS 탐색 시작</p>
</li>
<li><p><strong>조건 확인</strong>: DFS 내에서 현재 도시(nextCity)와 티켓의 출발지가 같은 미방문 티켓 탐색</p>
</li>
<li><p><strong>DFS 진행</strong>: 일치하는 티켓을 방문 처리하고 경로(path)에 추가한 뒤, depth를 1 늘려 재귀 호출</p>
</li>
<li><p><strong>백트래킹</strong>: 끝까지 가지 못하고 막혔다면, 다른 길을 찾기 위해 방문 해제(visited=false) 및 현재 경로에서 제거</p>
</li>
<li><p><strong>종료 및 갱신</strong>: depth가 전체 티켓 수와 같아지면 완성된 경로 반환. 기존 정답이 존재한다면 알파벳 사전순 비교를 통해 조건에 맞는 답으로 갱신</p>
</li>
</ol>
<h1 id="최종-코드">최종 코드</h1>
<pre><code>import java.util.*;

class Solution {
    String[][] tickets;
    // 정답 배열
    String[] answer;
    // 총 티켓 개수
    int len;
    // 방문 여부 확인
    boolean[] visited;
    // 누적 경로 저장
    List&lt;String&gt; path;

    public String[] solution(String[][] tickets) {
        this.tickets = tickets;
        this.len = tickets.length;
        answer = new String[len+1];

        for (int i = 0; i &lt; len; i++) {
            // 방문 배열 초기화
            this.visited = new boolean[len];
            // 누적 경로 초기화
            this.path = new ArrayList&lt;&gt;();
            if (tickets[i][0].equals(&quot;ICN&quot;)) {
                visited[i] = true;
                path.add(tickets[i][0]);
                path.add(tickets[i][1]);
                findPath(1, tickets[i][1]);
            }
        }

        return answer;
    }

    // 방문한 나라 수(depth)와 다음 도시를 매개변수(nextCity)로 받음
    public void findPath(int depth, String nextCity) {
        // 모든 배열 방문 시 종료
        if (depth &gt;= len) {
            if (answer[0] == null) {
                answer = path.toArray(new String[0]);
            }
            // 알파벳 순서로 비교
            for (int i = 0; i &lt; depth; i++) {
                // answer가 사전순으로 더 빠르다 (음수)
                if (answer[i].compareTo(path.get(i)) &lt; 0) {
                    break;
                }
                // answer가 사전순으로 더 느리다 (양수)
                else if (answer[i].compareTo(path.get(i)) &gt; 0) {
                    answer = path.toArray(new String[0]);
                    break;
                }
            }
            return;
        }

        for (int i = 0; i &lt; len; i++) {
            if (!visited[i] &amp;&amp; tickets[i][0].equals(nextCity)) {
                visited[i] = true;
                path.add(tickets[i][1]);
                findPath(depth+1, tickets[i][1]);
                // 가지치기로 다른 경우를 찾기 위해 방문 배열을 false로 처리
                visited[i] = false;
                // 추가한 경로 제거
                path.remove(path.size()-1);
            }
        }
        // 방문 가능한 배열 없을 시 종료
        return;
    }
}</code></pre><h1 id="어려웠던-점">어려웠던 점</h1>
<ul>
<li>List 자료형을 배열로 바꾸는 코드가 자꾸 생각이 안난다... 직접 생각하는데 5분씩은 걸린다. 이를 외울 것!
➡️ <code>list.toArray(new String[0])</code></li>
<li>String[]을 new로 생성하면 <code>null</code>이 저장된다는걸 이제 처음 피부로 깨달음... 어쩐지 <code>.eqauls(&quot;&quot;)</code>로 하니까 자꾸 널포인터익셉션 터지더니...
➡️ String은 String Pool에서 관리하는게 아니면 객체로 생성되기 때문에 null로 초기화가 된다!</li>
<li>dfs에서 visited를 false로 바꿨을 때, 지금 추가했던 path 경로도 제거해줘야한다! (백트래킹해야하니까 이전 상태로 돌려야 한다!)</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 네트워크]]></title>
            <link>https://velog.io/@l-wanderer01/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC</link>
            <guid>https://velog.io/@l-wanderer01/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%EB%84%A4%ED%8A%B8%EC%9B%8C%ED%81%AC</guid>
            <pubDate>Sat, 15 Aug 2026 14:21:55 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/43162">프로그래머스 - 네트워크</a></p>
<h3 id="요약">요약</h3>
<p>총 노드 개수가 n으로 주어지고, 노드들의 연결된 정보가 2차원 배열로 주어질 때, 연결될 수 있는 네트워크 집합들의 총 개수를 구해야한다!</p>
<h1 id="접근">접근</h1>
<p>0번 노드부터 접근하면서 깊이 우선 탐색(DFS)를 돌리면서 방문한 노드들을 visited로 처리하면서 연결하면 되겠다!라는 생각을 가지고 접근</p>
<h1 id="알고리즘">알고리즘</h1>
<ol>
<li>주어지는 n과 computers 배열을 클래스변수로 빼고, 네트워크 개수를 저장할 변수도 클래스 변수로 지정한다.</li>
<li>visited 배열을 생성해 노드 방문 여부를 저장한다.</li>
<li>0번 노드부터 차례로 방문하면서 방문한 node들을 visited = true로 저장한다.</li>
<li>각 노드와 연결된 노드들을 다 방문할때까지 visited 여부를 확인하고 업데이트하는 것을 반복한다.</li>
</ol>
<h1 id="최종-코드">최종 코드</h1>
<pre><code>class Solution {
    int n;
    int[][] computers;
    int answer;
    boolean[] visited;

    public int solution(int n, int[][] computers) {
        this.n = n;
        this.computers = computers;

        visited = new boolean[n];

        for (int i = 0; i &lt; n; i++) {
            findNetwork(i, i);   
        }

        return answer;
    }

    public void findNetwork(int startNode, int node) {
        if (visited[node]) return;

        visited[node] = true;

        for (int i = 0; i &lt; n; i++) {
            // 아직 방문하지 않았으면서 연결된 노드가 있는지 확인
            if (!visited[i] &amp;&amp; computers[node][i] == 1) findNetwork(startNode, i);
        }

        if (startNode == node) {
            answer++;
        }
    }
}</code></pre><h1 id="어려웠던-점">어려웠던 점</h1>
<p>visited 처리는 알겠는데, 언제 네트워크 개수를 증가시켜야할지 판단하는게 어려웠다. DFS 내부에서 answer 카운트를 올리면 재귀를 돌때마다 카운트가 될 것이기에 어떻게 해야할지 고민하다, 처음 시작 노드 정보를 재귀 함수의 input으로 넣고, 연결된 노드 탐색을 모두 끝낸 후 answer를 증가시키는 방식을 택했다.
그런데, 이 코드는 짠 내가 나중에 다시봐도 이해하기 어려운데, 다른 사람이 보면 더 이해하기 어려울 것 같고, for문이 2중으로 들어간다는 측면에서 다시 최적화된 코드를 짜야겠다는 생각이 들었다.
코드가 복잡해 보이는 이유는 answer가 증가되는 방식 때문이라는 생각이 든다. 그래서 아래와 같이 코드를 재구성했다.</p>
<h1 id="최적화-코드">최적화 코드</h1>
<pre><code>class Solution {
    ...

    public int solution(int n, int[][] computers) {
        ...

        for (int i = 0; i &lt; n; i++) {
            if (!visited[i]) {
                // 최초로 노드를 탐색하기 시작할 때, 부모 노드로 삼고 네트워크 개수를 1 증가시킨다.
                answer++;
                findNetwork(i);   
            }
        }

        return answer;
    }

    // 재귀 함수 인자를 node만 받도록 수정
    public void findNetwork(int node) {
        ...
    }
}</code></pre>]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 타겟 넘버]]></title>
            <link>https://velog.io/@l-wanderer01/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%ED%83%80%EA%B2%9F-%EB%84%98%EB%B2%84</link>
            <guid>https://velog.io/@l-wanderer01/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%ED%83%80%EA%B2%9F-%EB%84%98%EB%B2%84</guid>
            <pubDate>Fri, 14 Aug 2026 12:56:43 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/43165">Programmers - 타겟 넘버</a></p>
<p>숫자들이 담긴 배열이 주어지고, 숫자끼리 + 혹은 - 연산을 했을 때, 타겟 넘버가 나오는 경우의 총 횟수를 찾아야 한다!</p>
<h1 id="접근">접근</h1>
<p>처음엔 문제를 봤을 때, 머릿속으로 모든 연산자 조합을 다 구해서 연산하면 되겠다라는 생각을 했는데, 코드를 짜려고 키보드에 손을 올린 순간 뭐부터 해야할지 생각이 안떠올랐다.
일단 DFS로 찾아야하는건 알았는데, 어떻게 코드를 짜야할지 감이 안와서 손으로 그림을 그려보니 구현 방향이 보였다.
<img src="https://velog.velcdn.com/images/l-wanderer01/post/3eda85ba-5f66-4b00-a8c6-fa4e2298e7b5/image.jpg" alt="">
이렇게 <code>포화 이진트리</code>라는게 눈에 보였고, 그러면 DFS를 +연산하는것과 -연산하는걸 분리해서 계산하면 되겠다라는 결론에 도달했다.</p>
<h1 id="알고리즘">알고리즘</h1>
<blockquote>
<ol>
<li>숫자들이 담긴 배열과 타겟 넘버를 클래스 멤버로 선언</li>
<li>루트 node를 기준으로 +연산을 하는 경우와 -연산을 하는 경우로 나눔</li>
<li>leaf node에 도달했다면 최종 합산 값을 타겟 넘버와 비교해 총 횟수를 1 증가시킴</li>
</ol>
</blockquote>
<h1 id="최종-코드">최종 코드</h1>
<pre><code>class Solution {
    int[] numbers;
    int target;
    int answer;
    public int solution(int[] numbers, int target) {
        this.numbers = numbers;
        this.target = target;

        findTargetNumber(0, 0);

        return answer;
    }

    // DFS
    public void findTargetNumber(int depth, int sum) {
        if (depth == numbers.length) {
            if (sum == target) answer++;
            return;
        }

        // 해당 depth에서 값을 더하는 경우
        findTargetNumber(depth+1, sum+numbers[depth]);
        // 해당 depth에서 값을 빼는 경우
        findTargetNumber(depth+1, sum-numbers[depth]);
    }
}</code></pre><h1 id="깨달았던-점">깨달았던 점</h1>
<p>처음으로 손으로 그림을 그려서 문제를 접근하고 생각한 접근법대로 코드를 짰을 때, 한번에 돌아가니까 신기했다. 문제를 보고 바로 키보드에 손이 올라가는게 아니라, 정답을 찾기 위한 알고리즘을 손으로 그려보고 전체 윤곽이 그려진 상태에서 코드를 쳐야한다는 것을 처음 깨달은 순간이었다.
쉬운 문제라 이렇게 손으로 그려서 풀었을지 모르지만, 앞으로 다른 문제들을 만났을 때도 그림으로 먼저 문제를 이해하고 풀어가는 방식으로 풀어봐야겠다.
추가로, 값이 변하지 않는 numbers와 target과 같은 변수는 <code>클래스 변수</code>로 빼서 재귀 함수의 인자를 줄이는 방식으로 코테 풀이 스타일을 일관화 해야겠다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 피로도]]></title>
            <link>https://velog.io/@l-wanderer01/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%ED%94%BC%EB%A1%9C%EB%8F%84</link>
            <guid>https://velog.io/@l-wanderer01/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-%ED%94%BC%EB%A1%9C%EB%8F%84</guid>
            <pubDate>Fri, 14 Aug 2026 03:04:30 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<p><a href="https://school.programmers.co.kr/learn/courses/30/lessons/87946">Programmers - 피로도</a></p>
<h3 id="요약">요약</h3>
<p>초기 피로도가 있고, 각 던전마다 입장 가능한 최소 피로도와 던전 탐험 시 소모되는 피로도가 저장된 던전의 리스트가 입력으로 주어진다.
지금 가진 피로도로 갈 수 있는 총 던전의 개수를 구해야한다!</p>
<h1 id="접근">접근</h1>
<p>던전을 방문하는 모든 경우의 수를 고려해서 들어갈 수 있는 던전의 개수를 세아려 그 중 최댓값을 뽑아야 한다.
따라서 완전탐색 알고리즘으로 구현해야했다. 
완전탐색은 보통 BFS나 DFS로 구현하는데, 이 문제는 모든 경우의 수를 다 탐색해서 조합해 값을 비교할 수 있는 DFS 알고리즘 (재귀)을 선택했다.</p>
<h1 id="알고리즘">알고리즘</h1>
<ol>
<li>현재 피로도와 총 던전 정보를 입력으로 받는다</li>
<li>방문 여부를 체크할 visited 배열을 만든다.</li>
<li>첫번째 던전부터 마지막 던전까지 순차적으로 제일 처음으로 들어가고, 재귀를 통해 모든 조합을 탐색하도록 만든다.
 3-1. 현재 조합의 총 던전 방문 횟수와 지금까지 방문한 최대 방문 던전 횟수의 대소비교
 3-2. 해당 던전을 방문했는지 먼저 체크한다.
 3-3. 현재 피로도가 가려는 던전의 최소 피로도와의 대소비교
 3-4. 들어갈 수 있다면 방문 횟수를 증가시킴 </li>
</ol>
<h1 id="최종-코드">최종 코드</h1>
<pre><code>import java.util.*;

class Solution {
    int answer = -1;
    public int solution(int k, int[][] dungeons) {

        // 현재 체력
        int hp = k;
        // 방문여부 체크
        boolean[] visited = new boolean[dungeons.length];

        // 모든 경우의 수 조회
        dfs(dungeons, visited, hp, 0);

        return answer;
    }

    // 완전탐색 (DFS)
    public void dfs(int[][] dungeons, boolean[] visited, int hp, int cnt) {
        answer = Math.max(answer, cnt);

        for (int i = 0; i &lt; dungeons.length; i++) {
            if (!visited[i] &amp;&amp; dungeons[i][0] &lt;= hp) {
                visited[i] = true;
                dfs(dungeons, visited, hp - dungeons[i][1], cnt+1);
                visited[i] = false;
            }
        }
    }
}

// 알고리즘 : DFS
// 자료구조 : 배열
// 조건 : 지금의 체력이 던전 입장 가능한지</code></pre><h1 id="어려웠던-것">어려웠던 것</h1>
<p>일단 먼저 재귀에 대한 개념이 내 머리에 명확히 남아있지 않다...
hp를 다음 스탭 가기 전에 줄이고, cnt(던전 방문 횟수)를 증가시키고 재귀를 돌렸는데, 완탐이 안돼서 뭐지? 했는데 재귀를 돌리는 함수 호출 내부에서 값 증감을 시켜야 완탐이 가능하다는걸 Gemini의 도움을 통해 알게되었다...
근데 지금해보니, 상관없이 되는데, 이전에 내가 코드를 잘못 짰나보다...</p>
<h1 id="지금의-코드-최적화">지금의 코드 최적화</h1>
<ol>
<li>재귀로 총 4개의 인자를 받는데, 고정된 값인 던전과 방문 배열을 클래스변수로 빼서 처리하면 코드가 깔끔할 것 (단, 실무에선 x)</li>
<li>재귀를 도는데 이미 던전을 다 돌 수 있는 경우를 찾았다면 그 즉시 재귀 종료.</li>
</ol>
<ul>
<li>최적화한 코드<pre><code>import java.util.*;
</code></pre></li>
</ul>
<p>class Solution {
    int answer = -1;
    int hp; // 현재 체력
    boolean[] visited; // 방문 배열
    int[][] dungeons; // 던전 배열
    public int solution(int k, int[][] dungeons) {</p>
<pre><code>    this.hp = k;
    visited = new boolean[dungeons.length];
    this.dungeons = dungeons; // 동일한 변수명이기에 this로 지정
    // 모든 경우의 수 조회
    dfs(hp, 0);

    return answer;
}

// 완전탐색 (DFS)
public void dfs(int hp, int cnt) {
    // 모든 던전을 다 도는 경우를 찾았다면 그 즉시 종료
    if (dungeons.length == answer) return;

    if (answer &lt; cnt) {
        answer = cnt;
    }

    for (int i = 0; i &lt; dungeons.length; i++) {
        if (!visited[i] &amp;&amp; dungeons[i][0] &lt;= hp) {
            visited[i] = true;
            // int currentHp = hp - dungeons[i][1];
            // int currentCnt = cnt+1;
            // dfs(currentHp, currentCnt);
            dfs(hp-dungeons[i][1], cnt+1);

            visited[i] = false;
        }
    }
}</code></pre><p>}
```</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[SWEA] 1861.정사각형 방]]></title>
            <link>https://velog.io/@l-wanderer01/SWEA-1861.%EC%A0%95%EC%82%AC%EA%B0%81%ED%98%95-%EB%B0%A9</link>
            <guid>https://velog.io/@l-wanderer01/SWEA-1861.%EC%A0%95%EC%82%AC%EA%B0%81%ED%98%95-%EB%B0%A9</guid>
            <pubDate>Fri, 14 Aug 2026 01:36:14 GMT</pubDate>
            <description><![CDATA[<h1 id="문제">문제</h1>
<p><a href="https://swexpertacademy.com/main/code/problem/problemDetail.do?contestProbId=AV5LtJYKDzsDFAXc">SWEA-정사각형 방</a></p>
<h3 id="요약">요약</h3>
<p>NxN의 배열이 있고 그 안엔 모두 다른 값의 숫자들이 배치되어있다.
A요소가 상하좌우의 다른 요소와 비교했을 때, 주변에 1 큰 수가 있다면 그 요소와 길을 이을 수 있다.
배열에서 이을 수 있는 길의 최대 길이와 그때의 시작 요소의 값을 출력
(최대 길이의 길이 여러개인 경우 시작 요소의 값이 작은 것을 출력)</p>
<h1 id="접근">접근</h1>
<p>이 문제를 처음 봤을 때, 전체 모든 요소를 탐색해야하고, 시작 요소와 그 길의 최종 길이를 저장해서 값들을 비교해나가면 되겠다!는 생각으로 접근했다.</p>
<p>내가 다시 푼 풀이의 알고리즘은 아래와 같다.</p>
<h1 id="알고리즘">알고리즘</h1>
<p>완전 탐색 (시뮬레이션)</p>
<blockquote>
<ol>
<li>먼저 NxN의 배열 값들에 대한 iuput을 int형 2차원 배열에 저장</li>
<li>상하좌우를 탐색할 수 있게 <code>dr</code>, <code>dc</code> 배열 생성</li>
<li>배열의 요소들을 하나씩 선택해나가며 그 요소를 기준으로 주변 값들이 이어지는지 <code>while</code> 문으로 끝까지 탐색</li>
<li>탐색이 끝나면 지금까지의 최대 길의 길이와 현재 요소에서 이을 수 있는 길의 길이의 값 비교해 최댓값 갱신</li>
<li>모든 요소를 다 볼 때까지 반복</li>
</ol>
</blockquote>
<h1 id="최종-코드">최종 코드</h1>
<pre><code>import java.util.*;
import java.io.*;

// 외길이니 BFS, DFS 쓰지 않는다. visited도 쓰지 않는다.
// 메모이제이션으로 이미 방문한 곳이라면 그 위치에서 갈 수 있는 거리만큼 cnt올린다.

class Solution {
    private static int[][] A;
    private static int N;

    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringBuilder sb = new StringBuilder();
        int T = Integer.parseInt(br.readLine());
        for (int testCase = 1; testCase &lt;= T; testCase++) {
            N = Integer.parseInt(br.readLine()); // 1 ~ 1000
            A = new int[N][N];

            for (int i = 0; i &lt; A.length; i++) {
                StringTokenizer st = new StringTokenizer(br.readLine(), &quot; &quot;);
                for (int j = 0; j &lt; A.length; j++) {
                    A[i][j] = Integer.parseInt(st.nextToken());
                }
            }

            // 최대 이동할 수 있는 방의 개수
            int maxCnt = 0;
            // 최대 이동 가능 칸의 시작값
            int idx = Integer.MAX_VALUE; // 최대 이동을 위해 출발할 방 위치 (숫자) (작은 수를 찾아야 한다)

            // 모든 정점에서 출발해 갈 수 있는 칸으로 탐색
            for (int r = 0; r &lt; A.length; r++) {
                for (int c = 0; c &lt; A.length; c++) {
                    int cnt = go(r, c); // (r,c)에서 출발해 이동 가능한 칸 수
                    if (maxCnt &lt; cnt || (maxCnt == cnt &amp;&amp; idx &gt; A[r][c])) {
                        maxCnt = cnt;
                        idx = A[r][c];
                    }
                }
            }
            sb.append(&quot;#&quot;).append(testCase).append(&quot; &quot;).append(idx).append(&quot; &quot;).append(maxCnt).append(&quot;\n&quot;);
        } // end of tc
        System.out.print(sb.toString());
    } // end of main

    private static int[] dr = {-1, 1, 0, 0}; // 상하좌우
    private static int[] dc = {0, 0, -1, 1};

    /**
     * A[r][c]에서 출발해 최대 이동할 수 있는 방의 개수를 리턴
     * @param r
     * @param c
     * @return
     */
    private static int go(int r, int c) {
        int cnt = 1; // r, c에서 이동할 수 있는 방의 개수

        while(true) {
            int nextNum = A[r][c] + 1;
            boolean flag = false; // 다른 칸으로 이동했는지 확인하는 플래그

            // 현재칸(r,c)에서 인접 칸을 탐색하는데 그 칸에 갈 수 있는가(나보다 1큰가?) - 자주 나오니 코드블럭으로 외울 것
            for (int i = 0; i &lt; dr.length; i++) {
                int nr = r + dr[i];
                int nc = c + dc[i];
                // 배열범위 내인지는 항상 체크!
                if (nr &gt;= 0 &amp;&amp; nc &gt;= 0 &amp;&amp; nr &lt; N &amp;&amp; nc &lt; N &amp;&amp; nextNum == A[nr][nc]) {
                    r = nr;
                    c = nc;
                    cnt++;
                    flag = true;
                    break; // 상하좌우에서 1큰 값이 있다면 다른 곳엔 같은 값이 없기에 멈춘다! (안해도 풀리긴 하지만, 시간 절약을 위해 붙이는게 필요)
                }
            }
            if (!flag) break;
        }

        return cnt;
    }
} // end of class</code></pre><h1 id="어려웠던-것">어려웠던 것</h1>
<p>이 문제는 BFS, DFS 문제가 굳이 아니다!
why? 외길이니까.
외길이 무엇이냐? 갈 수 있는 곳은 4방향 중 한 곳으로밖에 못간다. 배열 내에 중복되는 값이 없기 때문!
따라서 방문 여부(visited)도 탐색할 필요가 없고, 남은 가지를 탐색할 필요가 없다</p>
<p>BFS, DFS : 그래프에서 사용!</p>
<h1 id="다른-접근법">다른 접근법</h1>
<p>수학적으로 접근할수도 있다.
배열의 모든 요소들은 모두 다 다른 값들을 가지고 있고, 1씩 차이가 나기 때문에 아래의 그림과 같이 접근이 가능하다.</p>
<p>[배열]
<img src="https://velog.velcdn.com/images/l-wanderer01/post/f459530e-b07e-4ee9-a600-d3415d03c512/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/l-wanderer01/post/406815d1-a591-4967-b659-dd2553f4565d/image.png" alt="">
이때 요소의 길이는 다음 요소의 행과 열을 각각 비교해서 둘의 차의 합이 1이라면 연속된 것으로 볼 수 있음!!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[엔터프라이즈 애플리케이션의 표준, Spring Boot]]></title>
            <link>https://velog.io/@l-wanderer01/%EC%97%94%ED%84%B0%ED%94%84%EB%9D%BC%EC%9D%B4%EC%A6%88-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98%EC%9D%98-%ED%91%9C%EC%A4%80-Spring-Boot</link>
            <guid>https://velog.io/@l-wanderer01/%EC%97%94%ED%84%B0%ED%94%84%EB%9D%BC%EC%9D%B4%EC%A6%88-%EC%95%A0%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98%EC%9D%98-%ED%91%9C%EC%A4%80-Spring-Boot</guid>
            <pubDate>Sun, 01 Feb 2026 06:35:40 GMT</pubDate>
            <description><![CDATA[<h2 id="개요">개요</h2>
<p>백엔드 개발의 핵심은 <strong>데이터를 어떻게 안전하게 저장하고, 효율적으로 처리하여, 사용자에게 전달할 것인가</strong> 에 있습니다. 다른 백엔드 개발 프레임워크들(Django, Node.js)등도 있지만, Spring Boot가 채택이 되는 이유는 대규모 엔터프라이즈 환경에서의 확실한 안정성과 정형화된 규칙, 예외처리, 인증 및 인가에서 강력한 기능을 제공하기 때문입니다. 따라서 여러 사람이 동일한 프로젝트를 개발하여도 일률적인 규칙에 의해 개발이 가능하고 안정적인 서비스 제공이 가능합니다.</p>
<h2 id="1️⃣-견고한-기초-객체지향과-설계-원칙-java-oop--solid">1️⃣ 견고한 기초: 객체지향과 설계 원칙 (Java OOP &amp; SOLID)</h2>
<p>Spring Boot에 대해 알아보기 전, Spring Boot의 근간이 되는 언어인 Java의 중요 개념에 대해 알아보고자 합니다.
Java는 객체지향 언어로 그 특징으로 캡슐화, 추상화, 다형성, 상속을 가집니다.</p>
<ul>
<li><strong>캡슐화</strong> : 객체 외부에서 내부에 어떤 필드를 가지고 있는지 알지 못하게 하는 것입니다.</li>
<li><strong>추상화</strong>: 메서드의 동작을 선언하지 않고 메서드명만 선언할 수 있는 것입니다.</li>
<li><strong>다형성</strong>: 같은 메서드명을 가졌지만, 다양한 동작을 하는 메서드를 가질 수 있는 것입니다.</li>
<li><strong>상속</strong>: 자식 객체는 부모 객체의 필드 및 메서드를 가질 수 있는 것입니다.</li>
</ul>
<p>캡슐화는 접근 제어자, 추상화는 abstract class, interface, 다형성은 method overriding, method overloading, 상속은 extends를 이용해 구현합니다.</p>
<p>다음으로 객체지향의 5대 원칙인 SOLID 원칙입니다.</p>
<ul>
<li><strong>SRP</strong> (단일 책임 원칙)
: 하나의 클래스는 하나의 책임만을 가져야 한다.</li>
<li><strong>OCP</strong> (개방 폐쇄 원칙)
: 클래스는 확장에는 열려 있고, 수정에는 닫혀 있어야 한다.</li>
<li><strong>LSP</strong> (리스코프 치환 원칙)
: 부모 클래스를 자식 클래스가 대체할 수 있어야 한다.</li>
<li><strong>ISP</strong> (인터페이스 치환 원칙)
: 하나의 범용적 인터페이스가 아닌 여러 개의 인터페이스로 구현해야 한다.</li>
<li><strong>DIP</strong> (의존 역전 원칙)
: 고수준 모듈은 저수준 모듈에 의존하면 안된다.
(상위 모듈은 하위 모듈이 아닌 인터페이스에 의존해야 한다.)</li>
</ul>
<p>결국 Spring Boot는 위와 같은 Java의 객체 지향 특징과 원칙에 따라 구현되어야 합니다.</p>
<h2 id="2️⃣-생산성의-혁신-spring-boot-아키텍처-core--container">2️⃣ 생산성의 혁신: Spring Boot 아키텍처 (Core &amp; Container)</h2>
<p>Spring Boot를 공부하기 전 Spring Boot의 전신인 Spring과 어떤 차이점이 있는지 알아보고자 합니다.
Spring은 동일하게 백엔드 개발에 사용되던 Java 기반 프레임워크입니다. 하지만, 이를 사용하기 위해선 개발에 사용되던 시간보다 설정(환경, 의존성, 서버 실행 등)에 사용되는 시간이 많았습니다.. 이 점 때문에 백엔드 개발의 강력한 프레임워크이지만 사용에 어려움이 있었고, 이를 해결하기 위해 탄생한 것이 Spring Boot입니다.</p>
<ul>
<li>“설정하는 시간을 줄이고, 개발에 할애하는 시간을 늘이자!”</li>
</ul>
<p>Spring Framework의 복잡한 설정을 Spring Boot는 IoC와 DI, Bean을 통해 자동화합니다.</p>
<p><strong>IoC</strong>는 제어의 역전으로 객체의 생성, 생명 주기 관리, 의존성 주입 등을 개발자가 아닌 Spring 컨테이너가 관리하도록 위임하는 것을 의미합니다. 따라서 코드의 결합도를 낮추고, 유연한 아키텍처와 테스트 가능한 구조로 만들 수 있습니다.</p>
<p>이처럼 컨테이너가 외부에서 객체를 관리해주기 때문에 개발자는 특정 기술이나 프레임워크에 종속되지 않은 순수한 자바 객체(POJO (Plain Old Java Object))만으로 비즈니스 로직을 구현할 수 있습니다. 이를 통해 객체지향 원리에 충실한 설계를 가능하게 하며, 단위 테스트를 더욱 용이하게 만듭니다.</p>
<p><strong>의존성 주입(DI)</strong>은 이렇게 관리되는 POJO 객체들이 의존하는 다른 객체를 직접 생성하는 것이 아닌 Spring 컨테이너에서 주입받는 방식을 말합니다. Spring에서는 생성자 주입을 사용해 불변성을 보장하고 순환 참조를 방지하는 것이 권장됩니다.</p>
<p>생성자 주입 방식이 권장되는 이유는 주입 받을 객체를 final로 선언해 불변성을 보장하고, 의존성 주입을 하지 않거나 순환 참조가 발생했을 시, 컴파일 타임에 이를 확인하고 수정이 가능합니다. 또한, 한눈에 파악하기 용이합니다.</p>
<p>DI가 적용되는 흐름은 다음 사진과 같습니다.
<img src="https://velog.velcdn.com/images/l-wanderer01/post/2d5bc095-36c7-4693-8b65-94090a7cc0f4/image.png" alt="">
Spring Boot는 객체를 Bean으로 등록하여 객체를 생성하고 삭제합니다. 객체 생성 후 @PostConstruct로 초기화하고, 소멸 전 @PreDestroy로 리소스를 정리합니다. 이렇게 Bean으로 등록해 객체의 생성과 삭제를 맡기는 이유는 개발자가 객체를 생성한 후 삭제하는 로직을 작성하지 않았을 시, 메모리 누수가 발생하여 서비스 중단되는 일이 발생할 수 있는데 이를 미연에 방지하기 위해 Spring Boot는 IoC를 통해 개발자가 아닌 프레임워크에 이 일을 위임합니다.</p>
<p>Bean 생명 주기 흐름은 아래와 같습니다.
<img src="https://velog.velcdn.com/images/l-wanderer01/post/3b9f4400-7dc7-44d5-957c-1f38ac9a70d2/image.png" alt=""></p>
<p>Spring Boot에선 서비스의 환경(Configuration)을 application.yml 파일을 이용해 설정하는데 동일한 서비스를 여러 환경에서 구동할 수 있도록 Spring Boot는 Profile 기능을 제공합니다. 이를 통해 개발 환경, 테스트 환경, 운영 환경을 코드 변경없이 전환할 수 있습니다.</p>
<p>이는 application-dev.yml, application-test.yml, application-prod.yml 과 같이 환경 설정 파일을 생성하고 각각의 운영환경을 활용해 구동할 수 있는 쉘 스크립트를 짠다면, Profile 기능을 활용해 환경 전환이 가능합니다.</p>
<h2 id="3️⃣-웹-서비스의-표준-rest-api와-mvc-web-layer">3️⃣ 웹 서비스의 표준: REST API와 MVC (Web Layer)</h2>
<p>Spring Boot는 요청을 중앙에서 처리하는 <strong>Dispatcher Servlet</strong>을 중심으로 Controller(요청 처리), Service(비즈니스 로직), Repository(DB 접근)로 역할을 분리해 유지보수성을 높입니다. 이러한 MVC 패턴은 데이터 처리, 화면 처리, 제어 로직을 분리하는 것이 목적입니다.</p>
<p><strong>Controller</strong>는 사용자 요청의 입구로 파라미터를 받고 비즈니스 로직을 호출한 뒤 결과를 반환합니다.</p>
<p><strong>Model</strong>은 Controller가 로직 수행 결과를 View에 전달할 때 사용하는 데이터 저장소입니다.</p>
<p><strong>View</strong>는 사용자에게 보여줄 최종 화면(HTML, JSON 등)을 처리합니다.</p>
<p>계층 간의 분리를 통해 각 컴포넌트는 맡은 역할에 집중하는데 Service는 실질적 비즈니스 로직을 수행하고 Repository는 DB(데이터 저장소)에 접근하는 통로, 이후 설명할 Mapper는 자바 객체와 SQL 쿼리를 연결하는 역할을 수행합니다.</p>
<p>이렇게 분리된 구조를 기반으로 웹 요청을 처리하고, 데이터를 전달하며 화면을 렌더링합니다.</p>
<p>Spring MVC 요청 흐름은 아래와 같습니다.</p>
<p><img src="https://velog.velcdn.com/images/l-wanderer01/post/5324ad07-98ed-45bc-850d-0255b6750633/image.png" alt=""></p>
<p>웹 어플리케이션 개발을 위해선 먼저 Spring Boot의 의존성에 spring-boot-starter-web을 추가해야합니다. <strong>spring-boot-starter-web</strong>은 웹 어플리케이션에 필요한 모든 라이브러리를 한 번에 포함하는 starter로 내장 서버인 Tomcat을 포함해 프론트엔드와의 소통을 위한 RESTful API, 웹 MVC, JSON, 예외 처리, 로깅 등을 지원합니다.</p>
<p>Spring Boot에서 처리된 자원들을 프론트엔드에 서빙하기 위해서 일종의 규약이 필요한데 그것이 <strong>RESTful API</strong> 입니다. RESTful API는 <strong>자원</strong>을 명사(URI)로 <strong>행위</strong>를 HTTP 메서드(GET, POST 등)로 표현하는 아키텍처 스타일입니다.</p>
<p>GET은 조회, POST는 생성, PUT은 전체 수정, PATCH는 부분 수정, DELECT는 삭제를 의미합니다. 이렇게 표현된 규약에 따라 API를 사용하여 프론트엔드와의 통신을 하고 의미를 명확하게 전달합니다.</p>
<p>프론트엔드와의 원활한 통신을 위해 주로 JSON 포맷을 사용합니다. Spring Boot에서 처리한 자원을 Controller에서 @RestController 어노테이션을 이용하면 자원을 XML또는 JSON 형식으로 전달합니다.</p>
<p>이렇게 서빙된 API를 프론트엔드 측에서 쉽게 확인하고 테스트할 수 있도록 돕는 도구를 OAS(OpenAPI Specification)이라고 합니다. 현업에서 많이 사용되는 것은 Swagger로 Spring Boot의 springdoc-openapi 의존성을 활용해 코드로부터 API 명세서(Swagger UI)를 자동 생성해 프론트엔드와 효율적으로 협업합니다.</p>
<h2 id="4️⃣-데이터-중심-개발-jpa와-트랜잭션-persistence-layer">4️⃣ 데이터 중심 개발: JPA와 트랜잭션 (Persistence Layer)</h2>
<p>기존의 Java를 이용한 개발에서는 Java에서 SQL을 직접 실행하여 DB와 연동하는 방식인 JDBC를 이용해 개발했습니다. JdbcTemplate 또는 MyBatis를 이용해 SQL을 직접 작성하고 모든 쿼리를 직접 작성해야했으나 이 어려움을 JPA (Java Persistence API)라는 새로운 기술을 통해 단순화하였습니다. JPA는 SQL을 직접 작성하는 JDBC 방식에서 벗어나, 객체 중심으로 데이터를 다룹니다.</p>
<p><strong>JPA</strong> (ORM, Object-Relational Mapping)를 이용해 Java 객체와 DB 테이블을 매핑합니다. @Entity, @Id, @Column 등의 어노테이션으로 스키마를 정의하고, JpaRepository 인터페이스를 통해 기본적으로 CRUD를 자동합니다.</p>
<p><strong>spring-boot-starter-data-jpa</strong> 의존성을 gradle 파일에 추가하고 JPA 및 DB 연결 정보를 application.yml 파일에 작성해 사용합니다.</p>
<p>JPA는 Spring Data JPA가 제공하는 인터페이스인 <strong>JPA Repository</strong>를 이용해 id를 이용한 데이터 추출(findById), 모든 데이터 조회(findAll) 등의 데이터베이스 조작이 가능합니다. 기본 제공되는 메서드 이외의 개발자가 메서드 키워드를 이용해 원하는 데이터를 가지고오는 메서드를 생성할 수 있습니다.</p>
<p>By(뒤에 오는 조건 필드 지정), And/Or(조건 연결), Like(SQL의 like와 동일한 기능)등의 <strong>메서드 키워드</strong>를 메서드에 작성하면 해당 동작을 하는 쿼리문이 자동 적용됩니다. 복잡한 조건 또는 특정 데이터 형태의 조회가 필요하다면 <strong>@Query</strong> 어노테이션을 통해 JPQL 또는 Native SQL을 이용한 쿼리문 작성도 가능합니다. 하지만 더 나아가 복잡한 동적 쿼리나 타입 안정성이 필요할 때는 자바 코드로 쿼리를 짜는 QueryDSL을 도입하는 것이 일반적입니다.</p>
<p>@Query 어노테이션을 사용하면 직접 쿼리문을 작성할 수 있어 편리하지만, 쿼리가 문자열 형태이기 떄문에 오타가 발생해도 컴파일 시점에 잡아내기 어렵다는 단점이 있습니다. 이를 해결하기 위해 QueryDSL을 함께 사용합니다.</p>
<blockquote>
<p>즉, 간단한 조회는 메서드 이름 규칙으로 해결하고, 복잡한 동적 쿼리는 QueryDSL을 사용해 타입 안정성을 확보합니다.</p>
</blockquote>
<p>JPA는 자동으로 <strong>트랜잭션</strong>을 관리해줍니다. 서비스 계층에서 @Transactional 어노테이션을 통해 데이터 정합성을 보장합니다. 예외 발생 시 자동으로 롤백(Rollback)되며, 서비스 계층에서 트랜잭션의 범위를 설정하는 것이 중요합니다. JPA에서 트랜잭션이 커밋되는 시점은 기본적으로 트랜잭션을 시작한 메서드가 성공적으로 종료될 때입니다.</p>
<p>트랜잭션이 시작되면 JPA는 <strong>영속성 컨텍스트</strong>라는 논리적 영역을 생성하고 엔티티는 해당 영역에, SQL은 <strong>쓰기 지연 SQL 저장소</strong>에 저장됩니다. 트랜잭션이 종료될 때, 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화(Flush)하고 쓰기 지연 저장소의 내용을 불러와 SQL을 실행합니다.</p>
<h2 id="5️⃣-rest-api-보안과-인증-jwt-json-web-token">5️⃣ REST API 보안과 인증: JWT (JSON Web Token)</h2>
<p>기존의 세션 기반 인증 방식은 서버 메모리에 사용자 상태를 저장해야 하므로 사용자가 늘어날수록 서버에 부담이 가고, 여러 대의 서버를 운영할 때 세션을 공유하기 까다롭다는 단점이 있습니다. 이를 해결하기 위해 토큰 기반 인증 방식인 <strong>JWT</strong>를 도입해 확장성을 확보합니다.</p>
<p>JWT는 인증에 필요한 정보들을 JSON 객체에 담아 암호화한 후, 이를 클라이언트에게 전달하는 토큰입니다. 서버는 사용자의 인증 상태를 저장하지 않고(Stateless) 클라이언트가 요청을 보낼 때마다 토큰을 함께 전달하면 서버는 토큰의 유효성만 검증하기에 서버 확장 시, 매우 유리합니다. 또한 토큰 자체에 사용자 식별 정보나 권한 등 필요한 데이터가 포함되어 있어 별도의 데이터 조회가 줄어듭니다.</p>
<p>JWT는 .을 구분자로 사용해 세 부분으로 구성됩니다.</p>
<p><img src="https://velog.velcdn.com/images/l-wanderer01/post/66345475-874b-4549-aaab-68b29b775a20/image.png" alt=""></p>
<p>Header에는 토큰의 타입(JWT)과 사용된 암호화 알고리즘 정보(HS256, RS256 등)가 담깁니다.</p>
<p>Payload에는 실제 전달할 사용자 정보인 Claims가 담겨있고 이 안에는 사용자 ID, 토큰 만료 시간 등을 포함합니다. Payload를 수정하더라도 서버의 Secret Key가 없으면 올바른 서명을 생성할 수 없기에 보안은 유지되지만, Payload는 암호화가 아닌 인코딩된 정보이기에 누구나 정보를 볼 수 있습니다. 
따라서 비밀번호와 같은 민감 정보는 절대 담지 않도록 주의가 필요합니다.</p>
<p>마지막으로 Signature엔 헤더와 페이로드를 합친 후 서버만이 가진 Secret Key로 암호화한 값입니다. 이를 통해 토큰이 중간에 변조되지 않았음을 증명합니다.</p>
<p>동작 방식은 클라이언트가 로그인을 성공하면 서버는 JWT를 생성해 응답합니다. 이후 클라이언트는 HTTP 요청 헤더에 해당 토큰을 실어 보내고, 서버는 Secret Key를 이용해 서명을 검증하는 과정을 통해 사용자를 인증합니다.</p>
<p><em>참고)</em> CORS(Cross-Origin Resource Sharing)
웹 브라우저는 보안 상의 이유로 다른 Origin의 리소스 요청을 기본적으로 차단하는데 프론트엔드와의 연결을 위해 서버 측에서 이를 풀어줘야합니다. 이때 사용하는 것이 CORS로 서버가 ‘내 자원을 특정 외부 도메인에서 접근해도 된다’고 허용해주는 것입니다.</p>
<h2 id="6️⃣-안정성과-운영-횡단-관심사와-모니터링-aspects--ops">6️⃣ 안정성과 운영: 횡단 관심사와 모니터링 (Aspects &amp; Ops)</h2>
<p>Spring boot에선 비즈니스 로직 외의 공통 관심사를 분리하고 시스템 상태를 진단합니다.</p>
<p><strong>AOP</strong> (관점 지향 프로그래밍)를 이용해 로깅, 보안, 트랜잭션 등 반복되는 코드 즉, 공통 관심사를 <strong>Aspect</strong>로 모듈화하여 핵심 로직과 분리합니다. 이는 핵심 비즈니스 로직을 오염시키지 않으면서도 시스템 전반에 일관된 운영 정책을 적용할 수 있게 합니다.</p>
<blockquote>
<p>OOP는 ‘기능’ 단위로 클래스를 구현한다면, AOP는 ‘공통 관심사(Aspect)’를 모듈화해 코드 중복을 줄이고 유지보수가 쉽도록 구현합니다.</p>
</blockquote>
<p>이는 별도의 설정 없이 spring-boot-starter-aop 의존성만 추가한다면 바로 사용 가능합니다. 이때 제공되는 어노테이션은 @Aspect, @Advice, @JoinPoint, @Pointcut 등이 있습니다.</p>
<p>서버 운영 측면에서 사용자가 잘못된 값을 입력하지 않도록 사용자의 <strong>입력값 검증</strong> 또한 중요합니다. 검증 과정 없이 잘못된 데이터가 시스템으로 들어오면, 예기치 못한 오류나 데이터베이스에 원치 않는 값이 저장될 수 있기 때문입니다. 이를 위해 자바 표준 어노테이션인 @Valid 또는 Spring이 제공하는 @Validation 어노테이션을 사용합니다.</p>
<p><em>참고)</em> @Valid는 일반적인 간단한 객체 검증에 사용되고, @Validation은 그룹을 지정해 상황별로 다르게 검증할 때 사용합니다.</p>
<p>@NotNull, @NotBlank, @Max, @Email 등을 사용하여 입력값 검증 로직을 DTO에 선언적으로 적용하고, 잘못된 데이터 유입을 @Valid를 이용해 컨트롤러단에서 즉시 차단합니다. 검증에 실패하면 서버는 400 Bad Request를 클라이언트에게 반환합니다.</p>
<blockquote>
<p>Service 계층이 아닌 Controller에서 검증하는 이유는 Service에서 데이터를 처리하기 전 잘못된 데이터를 가능한 한 시스템의 가장 바깥쪽에서 처리하기 위해 Controller에서 받자마자 검증을 거칩니다. 또한 전역적 예외 처리를 통해 일관된 형식의 응답을 클라이언트에게 보내주기 용이하기 때문입니다.</p>
</blockquote>
<p>Spring Boot는 <strong>예외 처리</strong> 설정을 하지 않았을 때 자동으로 BasicErrorController를 사용해 예외를 자동으로 처리합니다. 이때의 예외 처리를 사용자가 보기 편하도록 개발자가 커스텀할 수 있습니다.</p>
<p>@ControllerAdvice를 사용하여 예외 처리를 전역적으로 중앙화하고, @ExceptionHandler를 이용해 클라이언트에게 일관된 에러 응답을 제공합니다.</p>
<p>예외 처리의 우선순위는 컨트롤러 내의 @ExceptionHandler, @ControllerAdvice 내의 @ExceptionHandler, ResponseStatusException, 스프링 부트의 기본 예외 처리 순으로 처리됩니다.</p>
<p>Spring Boot는 애플리케이션의 상태 확인, 시스템 모니터링, 관리, 운영 자동화 등(서비스가 잘 돌아가고 있는가를 확인)을 위한 운영용 엔드 포인트를 <strong>Actuator</strong>라는 모듈로 제공합니다.</p>
<p>application.yml 파일에서 설정을 통해 /actuator/health, /actuator/metrics 등의 엔드포인트를 통해 애플리케이션의 운영 상태를 실시간으로 모니터링합니다.</p>
<p>뿐만 아니라, Spring Boot에서는 강력한 <strong>로깅 기능</strong>도 제공합니다. 애플리케이션 로그 관리가 중요한 이유는 문제 진단 및 원인 분석에 용이하고, 시스템 모니터링 및 운영 효율화 측면에서 반드시 필요합니다. Spring Boot는 로그 시스템을 자동으로 구성해줘 초기 설정만 한다면 바로 로그 관리가 가능합니다.</p>
<p>SLF4J는 자바 생태계에서 가장 널리 사용되는 로그 파사드(Facade) 라이브러리입니다. 직접 로그를 기록하지 않고 내부적으로 실제 로깅 구현체에 위임하는 방식을 취하고 있습니다.</p>
<p>여기서 로그 파사드란 여러 로깅 프레임워크를 일관된 API로 감싸서 사용할 수 있게 해주는 중간 계층을 의미합니다. 만약 Logback이라는 로그 파사드를 사용하다가 Log4j로 변경하고자 할 때, 코드 변경 없이 build.gradle 또는 pom.xml에서 의존성만 교체한다면 변경 가능합니다.</p>
<h2 id="결론">결론</h2>
<p>지금까지 살펴본 백엔드 개발의 흐름은 단순한 코드 작성을 넘어, <strong>객체지향 원칙</strong>이라는 단단한 기초 위에 <strong>Spring Boot</strong>라는 효율적인 도구를 얹고, <strong>JPA</strong>와 <strong>JWT</strong>, <strong>AOP</strong>와 같은 현대적인 기술 스택을 조화롭게 결합하는 과정입니다.</p>
<p>결국 백엔드 개발자에게 요구되는 역량은 특정 기술의 단순한 사용법 숙지가 아니라, <strong>&quot;왜 이 기술이 도입되었는가?&quot;</strong> 에 대한 본질적인 이해를 바탕으로 최적의 아키텍처를 설계하는 능력입니다. 이 글에서 다룬 핵심 개념들은 견고하고 확장 가능한 서비스를 구축하기 위한 든든한 이정표가 되어줄 것입니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[자원 서빙을 위한 핵심 언어, Java]]></title>
            <link>https://velog.io/@l-wanderer01/%EC%9E%90%EC%9B%90-%EC%84%9C%EB%B9%99%EC%9D%84-%EC%9C%84%ED%95%9C-%ED%95%B5%EC%8B%AC-%EC%96%B8%EC%96%B4-Java</link>
            <guid>https://velog.io/@l-wanderer01/%EC%9E%90%EC%9B%90-%EC%84%9C%EB%B9%99%EC%9D%84-%EC%9C%84%ED%95%9C-%ED%95%B5%EC%8B%AC-%EC%96%B8%EC%96%B4-Java</guid>
            <pubDate>Sat, 31 Jan 2026 05:52:38 GMT</pubDate>
            <description><![CDATA[<p><strong>서비스를 개발한다면 어떤 플로우로 짜야할까?</strong>
<img src="https://velog.velcdn.com/images/l-wanderer01/post/3adbd0e1-983c-4db7-9859-36b01a345b27/image.png" alt="">
<strong>프록시 서버</strong>: 사용자의 요청을 받음 (진입점) → “내부와 외부를 구분”
<strong>NAT</strong>: 백엔드에서 처리한 응답을 사용자에게 return 해주는 서버
<strong>프론트엔드</strong>: 사용자와 가까운 쪽 (Vue.js)
<strong>백엔드</strong>: 자원이 있는 쪽 (Spring boot)</p>
<ul>
<li>자원(Resource): 서비스를 이용하기 위한 정보 처리 및 사용자 정보 등</li>
</ul>
<p><strong>Java</strong>는 자원을 다루는 백엔드 개발을 위한 Spring Boot의 초석이 되는 핵심 언어입니다. 특히 대규모 트래픽과 복잡한 비즈니스 로직을 처리해야 하는 엔터프라이즈 환경에서, Java는 엄격한 예외 처리, 객체지향 설계 등 견고한 기능을 통해 시스템의 높은 안정성과 유지보수성을 보장해 줍니다.</p>
<p>Java : “역할을 가진 객체들을 조합하는 언어”</p>
<ol>
<li>Java 프로그램은 클래스 단위로 구성</li>
<li>클래스는 객체로 만들어서 사용</li>
<li>Main은 프로그램의 시작점</li>
<li>패키지는 클래스의 논리적 위치<ul>
<li>패키지: 클래스의 논리적 묶음</li>
</ul>
</li>
<li>Import로 다른 클래스를 연결</li>
<li>디렉토리 구조가 중요</li>
</ol>
<p><strong>클래스</strong>
앞선 Java 설명에 대한 내용을 보면 클래스에 대한 여러 언급이 있고, Java 프로그램 작성 시 class를 먼저 선언하고 시작합니다.
<img src="https://velog.velcdn.com/images/l-wanderer01/post/48401216-a6c9-4fb9-9aee-7f785a9018bb/image.png" alt=""></p>
<p>클래스의 개념이 탄생하기 전에는 서비스를 개발하기 위해 기존 방식대로 구조를 기능과 데이터로 나누어 개발을 진행 한 후, 구현하려 했으나 정상적으로 서비스가 요구사항 제대로 구현되지 않았습니다.</p>
<p>따라서 클래스의 개념이 탄생하게 되었고, java는 클래스 기반 언어이기에 class를 먼저 선언 후 내용을 작성해 나갑니다.</p>
<p><strong>객체</strong>
작성한 클래스를 활용할 때 아래와 같이 ‘Animal a1 = new Dog();’와 같이 객체를 생성하게 됩니다.
<img src="https://velog.velcdn.com/images/l-wanderer01/post/c1e1d1ff-190e-430c-83cb-e086a51d8d46/image.png" alt="">
클래스를 이용해 객체를 생성할 때, 무작정 객체를 생성하게 되면 많은 객체를 생성하게 되고 결국 객체들이 많은 메모리를 차지해 메모리 오버플로우가 발생합니다. 따라서 객체를 생성하는 방식에 대한 고민을 하게되었고, 이에 객체 생성 패턴이 필요하게 되었습니다.
객체 생성 패턴에는 <strong>싱글톤, 팩토리, 빌더 패턴</strong>이 있습니다.</p>
<p>1️⃣ 싱글톤 (Singleton)
싱글톤 방식은 객체 생성을 1번만 하고 외부에서 new 연산자로 객체를 생성할 수 없도록 생성자를 private으로 만듭니다. 프로그램 내에 단 하나의 인스턴스만 존재해야할 때 사용하는 것이 좋습니다.
<img src="https://velog.velcdn.com/images/l-wanderer01/post/78c1e796-4523-40bd-b701-b2f622a9864e/image.png" alt=""></p>
<p>2️⃣ 팩토리 (Factory)
팩토리 방식은 객체 생성 로직을 한 곳에 모아서 관리하는 패턴입니다. New를 통해 객체 생성을 여러 곳에서 하지 않고, “무엇을 만들지”를 공장에 맡기는 방식으로 구현합니다. 따라서 객체 생성 책임을 분리하여 코드 결합도를 낮추고, 변경에 강하게 만들 수 있습니다.</p>
<p>이 패턴은 객체 생성 로직을 클라이언트 코드(사용하는 쪽)로부터 숨기고 싶거나, 조건(if/else)에 따라 생성해야 할 객체의 클래스가 달라질 때, 나중에 새로운 타입의 객체가 추가되어도 기존 코드를 수정하고 싶지 않을 경우 사용하면 좋습니다.
<img src="https://velog.velcdn.com/images/l-wanderer01/post/1c7770b4-709b-4392-b201-43e639c7a26e/image.png" alt=""></p>
<p>3️⃣ 빌더 (Builder)
빌더 패턴은 필드가 많거나 선택적 값이 많은 객체를 안전하게 생성하기 위한 패턴입니다.</p>
<p>만약 생성자 패턴으로 객체를 생성하게 된다면 순서가 틀려도 컴파일 에러가 발생하지 않는 운영 중 발견되는 최악의 버그가 발생할 수 있습니다. 이를 빌더 패턴을 통해 이름으로 값을 지정하게 된다면 이 문제를 해결할 수 있습니다.</p>
<p>이 패턴은 생성자 인자가 너무 많거나 필수 인자, 선택 인자가 섞여 있을 경우 등에 사용한다면 이점을 발휘할 수 있습니다. 
<img src="https://velog.velcdn.com/images/l-wanderer01/post/7abe412a-beb4-4fc0-9b73-3394666f1ff1/image.png" alt=""></p>
<ul>
<li>빌더 패턴을 사용한 객체 생성!
<img src="https://velog.velcdn.com/images/l-wanderer01/post/5ca9fb1c-0c47-4d55-9c22-0350ff8683fa/image.png" alt=""></li>
</ul>
<p>최종적으로 위 생성 패턴들을 이용했을 때, 불필요한 메모리 소모를 감소할 수 있습니다.</p>
<ul>
<li>참고</li>
</ul>
<ol>
<li>‘코드 중복, 의존성 증가, 테스트 어려움’의 문제를 객체 생성 패턴으로 해결할 수 있습니다.</li>
<li>생성한 객체 중 일부는 아래와 같이 객체를 닫아줘야 합니다.
<img src="https://velog.velcdn.com/images/l-wanderer01/post/2b42e0df-bbf4-44b8-8161-6b01d83d223f/image.png" alt=""></li>
</ol>
<p><strong>상속 / 다형성</strong>
객체 지향 프로그래밍에서 객체를 효율적으로 활용하기 위해 상속과 다형성을 사용합니다. 이 둘은 밀접하게 연관되어 있습니다.</p>
<p>먼저 상속(Inheritance)은 구조적인 재사용을 담당합니다. 부모 클래스에 공통 기능을 정의하고, 이를 상속받은 자식 클래스에서 차이점만 구현함으로써 코드 중복을 줄이고 계층 구조를 만듭니다.</p>
<p>다형성(Polymorphism)은 이 구조 위에서 동작하는 유연한 활용 기술입니다. 상속받은 여러 자식 객체들을 부모 타입 하나로 묶어서 관리하되, 실제 실행 시점(Runtime)에는 각 자식 객체에 맞는 고유한 동작(메서드)이 동적으로 실행되도록 합니다.</p>
<p><strong>빌드(Build) / 디렉토리 구조</strong>
앞선 서비스 플로우를 참고했을 때, DB와 자원 처리를 담당하는 Spring이 연결되어 있습니다. DB를 서비스에 연결하기 위해선Java의 jdbc 를 build를 해야합니다.</p>
<p>빌드란 Java 소스코드로부터 실행 가능한 프로그램으로 변환하는 것을 말합니다. 이때 사용하는 도구로 Gradle과 Maven이 있지만 Gradle을 사용하는 것을 권고하고 있습니다. Maven은 언어를 XML을 사용하는데 해당 언어는 의존성이 복잡하기에 Groovy/Kotlin을 기반으로 하는 Gradle을 사용합니다.</p>
<p>Build를 할 때 ‘./gradlew build’ 명령을 사용하게 되는데, 이 때, build.gradle파일이 위치한 곳에서 위의 명령을 입력해야 합니다. 따라서 디렉토리 구조가 중요합니다.</p>
<p><img src="https://velog.velcdn.com/images/l-wanderer01/post/ed9a3f22-b8b7-41de-8f15-73408cd33c49/image.png" alt=""></p>
<p>이제까지 배운 내용을 바탕으로 백엔드 로직을 java를 활용해 간단한 로그인 인증 서비스를 구현해보았습니다. 사용자 인증 정보를 저장할 DB는 MariaDB를 Docker에서 컨테이너로 구동, 사용자 요청 처리를 담당하는 Backend는 pure java, 사용자 입력을 받는 Frontend는 html, css, js를 이용해 Python 명령을 이용해 구동했습니다.</p>
<p>인증을 위해 세션 및 토큰을 더욱 간단한 방법인 spring boot가 아닌 pure 자바를 사용하는 이유는 spring boot는 인증 기능 외에도 프레임워크 차원에서 제공하는 수많은 라이브러리를 함께 로드해야 합니다. 따라서 이러한 무거운 로딩 과정으로 인해 초기 구동 및 요청 처리 시 시간 지연(Latency)이 발생합니다.</p>
<p>Pure 자바를 이용하게 되면 불필요한 외부 라이브러리 없이 필수적인 인증 로직만을 구현할 수 있습니다. 가벼운 구조를 통해 인증 및 인가(Authentication &amp; Authorization) 서비스를 더욱 신속하게 처리하여 전체적인 소요 시간을 단축할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 햄버거 만들기]]></title>
            <link>https://velog.io/@l-wanderer01/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Java-%ED%96%84%EB%B2%84%EA%B1%B0-%EB%A7%8C%EB%93%A4%EA%B8%B0</link>
            <guid>https://velog.io/@l-wanderer01/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Java-%ED%96%84%EB%B2%84%EA%B1%B0-%EB%A7%8C%EB%93%A4%EA%B8%B0</guid>
            <pubDate>Sat, 27 Dec 2025 07:37:32 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/l-wanderer01/post/a4e2bb6c-7348-4142-96a2-92e05e7719bf/image.jpg" alt=""></p>
<h2 id="1-문제">1. 문제</h2>
<blockquote>
<p>&nbsp;&nbsp;&nbsp;&nbsp; 햄버거 가게에서 일을 하는 상수는 햄버거를 포장하는 일을 합니다. 함께 일을 하는 다른 직원들이 햄버거에 들어갈 재료를 조리해 주면 조리된 순서대로 상수의 앞에 아래서부터 위로 쌓이게 되고, 상수는 순서에 맞게 쌓여서 완성된 햄버거를 따로 옮겨 포장을 하게 됩니다. 상수가 일하는 가게는 정해진 순서(아래서부터, 빵 – 야채 – 고기 - 빵)로 쌓인 햄버거만 포장을 합니다. 상수는 손이 굉장히 빠르기 때문에 상수가 포장하는 동안 속 재료가 추가적으로 들어오는 일은 없으며, 재료의 높이는 무시하여 재료가 높이 쌓여서 일이 힘들어지는 경우는 없습니다.
&nbsp;&nbsp;&nbsp;&nbsp;예를 들어, 상수의 앞에 쌓이는 재료의 순서가 [야채, 빵, 빵, 야채, 고기, 빵, 야채, 고기, 빵]일 때, 상수는 여섯 번째 재료가 쌓였을 때, 세 번째 재료부터 여섯 번째 재료를 이용하여 햄버거를 포장하고, 아홉 번째 재료가 쌓였을 때, 두 번째 재료와 일곱 번째 재료부터 아홉 번째 재료를 이용하여 햄버거를 포장합니다. 즉, 2개의 햄버거를 포장하게 됩니다.
&nbsp;&nbsp;&nbsp;&nbsp;상수에게 전해지는 재료의 정보를 나타내는 정수 배열 ingredient가 주어졌을 때, 상수가 포장하는 햄버거의 개수를 return 하도록 solution 함수를 완성하시오.</p>
</blockquote>
<pre><code>* 제한사항
1. 1 ≤ ingredient의 길이 ≤ 1,000,000
2. ingredient의 원소는 1, 2, 3 중 하나의 값이며, 순서대로 빵, 야채, 고기를 의미합니다.</code></pre><h2 id="2-풀이">2. 풀이</h2>
<pre><code>class Solution {
    public int solution(int[] ingredient) {
        int answer = 0;
        int[] stack = new int[ingredient.length];
        int top = 0; // 가장 상단 index 저장

        for (int i : ingredient) {
            stack[top] = i;

            if (top &gt;= 3 &amp;&amp;
                stack[top - 3] == 1 &amp;&amp;
                stack[top - 2] == 2 &amp;&amp;
                stack[top - 1] == 3 &amp;&amp;
                stack[top] == 1) {

                top -= 4; // 저장됐던 재료 제거
                answer++;

            }

            top++;
        }

        return answer;
    }
}</code></pre><blockquote>
<p><strong>접근 방식</strong></p>
</blockquote>
<ol>
<li>재료의 개수만큼 값을 저장할 수 있는 리스트(stack) 생성</li>
<li>가장 상단에 위치한 재료의 index 저장 (top)</li>
<li>재료를 순차적으로 접근하며 stack에 재료를 저장</li>
<li>stack의 상단에서 3번째 위치부터 상단 위치까지의 재료의 순서 확인 (빵 – 야채 – 고기 - 빵)
&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp 4-1. 순서가 같다면 top의 위치를 옮기며 해당 재료를 제거한 후 햄버거의 개수를 1 증가시킴
&amp;nbsp&amp;nbsp&amp;nbsp&amp;nbsp 4-2. 순서가 다르다면 다음 재료를 쌓음</li>
<li>상단(top)의 위치를 1 증가시킴</li>
<li>햄버거의 개수를 Return함</li>
</ol>
<h2 id="3-깨달은-점">3. 깨달은 점</h2>
<blockquote>
<p>문제를 봤을 때, 자료구조 선정을 잘 한다면 복잡한 사고 과정없이 문제를 쉽게 접근할 수 있다!
Java 라이브러리를 이용해 Stack을 사용할수도 있지만, List를 활용해 Stack과 같이 사용할수도 있다.</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 체육복]]></title>
            <link>https://velog.io/@l-wanderer01/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Java-%EC%B2%B4%EC%9C%A1%EB%B3%B5</link>
            <guid>https://velog.io/@l-wanderer01/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Java-%EC%B2%B4%EC%9C%A1%EB%B3%B5</guid>
            <pubDate>Tue, 16 Dec 2025 06:00:12 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/l-wanderer01/post/c516d3b2-6d97-42a0-afb0-7a4e535144f1/image.jpg" alt=""></p>
<h2 id="1-문제">1. 문제</h2>
<blockquote>
<p>점심시간에 도둑이 들어, 일부 학생이 체육복을 도난당했습니다. 다행히 여벌 체육복이 있는 학생이 이들에게 체육복을 빌려주려 합니다. 학생들의 번호는 체격 순으로 매겨져 있어, 바로 앞번호의 학생이나 바로 뒷번호의 학생에게만 체육복을 빌려줄 수 있습니다. 예를 들어, 4번 학생은 3번 학생이나 5번 학생에게만 체육복을 빌려줄 수 있습니다. 체육복이 없으면 수업을 들을 수 없기 때문에 체육복을 적절히 빌려 최대한 많은 학생이 체육수업을 들어야 합니다.
전체 학생의 수 n, 체육복을 도난당한 학생들의 번호가 담긴 배열 lost, 여벌의 체육복을 가져온 학생들의 번호가 담긴 배열 reserve가 매개변수로 주어질 때, 체육수업을 들을 수 있는 학생의 최댓값을 return 하도록 solution 함수를 작성해주세요.</p>
</blockquote>
<pre><code>* 제한사항
1. 전체 학생의 수는 2명 이상 30명 이하입니다.
2. 체육복을 도난당한 학생의 수는 1명 이상 n명 이하이고 중복되는 번호는 없습니다.
3. 여벌의 체육복을 가져온 학생의 수는 1명 이상 n명 이하이고 중복되는 번호는 없습니다.
4. 여벌 체육복이 있는 학생만 다른 학생에게 체육복을 빌려줄 수 있습니다.
5. 여벌 체육복을 가져온 학생이 체육복을 도난당했을 수 있습니다. 이때 이 학생은 체육복을 하나만 도난당했다고 가정하며, 남은 체육복이 하나이기에 다른 학생에게는 체육복을 빌려줄 수 없습니다.</code></pre><h2 id="2-풀이">2. 풀이</h2>
<h3 id="💻-스스로-푼-풀이">💻 스스로 푼 풀이</h3>
<pre><code>1. 체육복을 가진 인원을 저장할 변수 count를 선언함 (전체 인원 수(n) - lost의 길이)
2. lost와 reserve를 정렬함.
3. 접근할 인덱스를 저장할 변수 idx를 선언함.
4. 모든 lost에 대해 reserve에 있는 값들을 순차적으로 탐색함
    3-1. lost[i]의 요소와 reserve[idx]의 요소의 차의 절댓값이 1이하인 경우를 찾음
    3-2. idx와 count를 1 증가 시키고, 반복문을 멈춤
5. 체육복을 가질 수 있는 인원을 return</code></pre><p>위의 방법으로 풀이했을 시, 4개의 TC가 통과되지 않았다.
원인을 생각해보니 제한사항 마지막 부분을 잘못 이해한 것이었다. 여벌 체육복을 가져온 학생이 도난을 당했을 시, 본인이 가져온 여벌 체육복이 우선 선택된다는 것을 간과하여 위의 풀이는 문제를 올바르게 해결하지 못한다.</p>
<h3 id="🔅-hashset을-사용한-풀이">🔅 HashSet을 사용한 풀이</h3>
<pre><code>import java.util.*;

class Solution {
    public int solution(int n, int[] lost, int[] reserve) {
        Set&lt;Integer&gt; lostSet = new HashSet&lt;&gt;();
        Set&lt;Integer&gt; reserveSet = new HashSet&lt;&gt;();

        for (int l : lost) lostSet.add(l);
        for (int r : reserve) reserveSet.add(r);

        // 1단계: 교집합 제거
        Iterator&lt;Integer&gt; it = lostSet.iterator();
        while (it.hasNext()) {
            int s = it.next();
            if (reserveSet.contains(s)) {
                it.remove();
                reserveSet.remove(s);
            }
        }

        int answer = n - lostSet.size();

        // 2단계: 빌려주기
        List&lt;Integer&gt; lostList = new ArrayList&lt;&gt;(lostSet);
        Collections.sort(lostList);

        for (int s : lostList) {
            if (reserveSet.contains(s - 1)) {
                reserveSet.remove(s - 1);
                answer++;
            } else if (reserveSet.contains(s + 1)) {
                reserveSet.remove(s + 1);
                answer++;
            }
        }

        return answer;
    }
}</code></pre><h4 id="새롭게-알게된-것">새롭게 알게된 것</h4>
<blockquote>
<p><strong>HashSet의 메서드</strong>
<code>HashSet.add()</code> : HashSet에 값 추가
<code>HashSet.remove()</code> : HashSet에서 값 제거
<code>Iterator.hasNext()</code> : 가져올 객체가 있으면 true, 없으면 false를 리턴
<code>Iterator.next()</code> : Iterator에서 하나의 객체를 가져옴
<code>HashSet.iterator()</code> : 전체 객체를 대상으로 한 번씩 반복해서 가져오는 반복자</p>
</blockquote>
<ul>
<li>Set에는 순서가 없기 때문에 index로 접근이 불가함. 따라서, Iterator로 변경하여 순서대로 값을 접근하여 비교해야 한다!</li>
</ul>
<h2 id="3-깨달은-점">3. 깨달은 점</h2>
<blockquote>
<p>문제에 대해 처음 접근할 때, 처음에 자료를 저장할 자료구조 선정을 잘한다면 이후 코드를 작성하는데에 있어 원활히 해결되니 자료구조를 잘 선택하자!</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[[프로그래머스] 완주하지 못한 선수 (자료구조의 중요성)]]></title>
            <link>https://velog.io/@l-wanderer01/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Java-%EC%99%84%EC%A3%BC%ED%95%98%EC%A7%80-%EB%AA%BB%ED%95%9C-%EC%84%A0%EC%88%98-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0%EC%9D%98-%EC%A4%91%EC%9A%94%EC%84%B1</link>
            <guid>https://velog.io/@l-wanderer01/%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%A8%B8%EC%8A%A4-Java-%EC%99%84%EC%A3%BC%ED%95%98%EC%A7%80-%EB%AA%BB%ED%95%9C-%EC%84%A0%EC%88%98-%EC%9E%90%EB%A3%8C%EA%B5%AC%EC%A1%B0%EC%9D%98-%EC%A4%91%EC%9A%94%EC%84%B1</guid>
            <pubDate>Sun, 14 Dec 2025 10:30:59 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/l-wanderer01/post/8f3074ce-a573-440f-bbdb-9d00a0266e2c/image.jpg" alt=""></p>
<h2 id="1-문제">1. 문제</h2>
<blockquote>
<p>수많은 마라톤 선수들이 마라톤에 참여하였습니다. 단 한 명의 선수를 제외하고는 모든 선수가 마라톤을 완주하였습니다.
마라톤에 참여한 선수들의 이름이 담긴 배열 participant와 완주한 선수들의 이름이 담긴 배열 completion이 주어질 때, 완주하지 못한 선수의 이름을 return 하도록 solution 함수를 작성해주세요.</p>
</blockquote>
<pre><code>* 제한사항
마라톤 경기에 참여한 선수의 수는 1명 이상 100,000명 이하입니다.
completion의 길이는 participant의 길이보다 1 작습니다.
참가자의 이름은 1개 이상 20개 이하의 알파벳 소문자로 이루어져 있습니다.
참가자 중에는 동명이인이 있을 수 있습니다.</code></pre><h2 id="2-풀이">2. 풀이</h2>
<h3 id="💻-스스로-생각한-풀이">💻 스스로 생각한 풀이</h3>
<pre><code>import java.util.*;

class Solution {
    public String solution(String[] participant, String[] completion) {
        Arrays.sort(participant);
        Arrays.sort(completion);

        for (int i = 0; i &lt; completion.length; i++) {
            if (!participant[i].equals(completion[i])) return participant[i];
        }

        return participant[participant.length - 1];
    }
}</code></pre><br>
문제는 문제없이 통과되었으나, 시간복잡도 측면에서 오래걸리는 문제가 있어 다른 방법으로 풀 수 있는 방법에 대해 찾아보다, HashMap을 사용하면 시간복잡도를 줄일 수 있다는 것을 알게 되었다.
<br>

<h3 id="🔅-hashmap을-사용한-풀이">🔅 HashMap을 사용한 풀이</h3>
<pre><code>import java.util.*;

class Solution {
    public String solution(String[] participant, String[] completion) {
        HashMap&lt;String, Integer&gt; map = new HashMap&lt;&gt;();

        for (String str : participant) {
            map.put(str, map.getOrDefault(str, 0) + 1);
        }

        for (String str : completion) {
            map.put(str, map.get(str) - 1);
        }

        for (String key : map.keySet()) {
            if (map.get(key) &gt; 0) {
                return key;
            }
        }
        return &quot;&quot;;
    }
}</code></pre><h4 id="✅-접근법">✅ 접근법</h4>
<ol>
<li>참가자 전원을 Map에 저장
 <code>map.getOrDefault(key, default)</code> : map에 값을 넣을 때, 값이 없으면 0을 넣음</li>
<li>완주자들을 map에서 찾아 값을 줄임
 <code>map.get(key)</code> : key값의 value를 가져옴</li>
<li>최종 Map에서 값이 1인 Key값을 return!
 <code>map.keySet()</code> : map의 모든 Key 집합을 가져옴</li>
</ol>
<blockquote>
</blockquote>
<p><strong>스스로 푼 방식의 시간복잡도</strong>
: 정렬 + 선형 탐색 = O(nlogn) + O(n) = O(nlogn)</p>
<ul>
<li>정렬에는 비교 연산이 존재</li>
</ul>
<blockquote>
<p><strong>HashMap으로 푼 방식의 시간복잡도</strong>
: 해시 탐색 = O(n)
HashMap의 put / get 연산 : 평균 O(1)</p>
</blockquote>
<ul>
<li>HashMap에서 값을 찾을 때엔 <strong>비교 연산 없이</strong> 해시값 계산 후 바로 접근</li>
</ul>
<p>HashMap은 key를 바로 저장하지 않고, <strong>key → 해시값 → 배열 인덱스</strong>로 변환
➡️ 값을 찾기 위해 처음부터 끝까지 탐색하지 않고, 계산 한 번으로 들어갈 자리를 앎
따라서, 시간복잡도는 평균 O(1), 최악 O(n)이 됨</p>
<h2 id="3-깨달은-점">3. 깨달은 점</h2>
<blockquote>
<p>문제에 대해 접근할 때, 데이터를 어떻게 저장하고 접근할지에 대해 먼저 고민을 하자.
각 자료구조의 특징을 잘 파악해도 시간 복잡도 측면에서 큰 이득을 얻을 수 있다!</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[Stream을 이용한 연산과 for문의 성능 비교]]></title>
            <link>https://velog.io/@l-wanderer01/Stream%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%97%B0%EC%82%B0%EA%B3%BC-for%EB%AC%B8%EC%9D%98-%EC%84%B1%EB%8A%A5-%EB%B9%84%EA%B5%90</link>
            <guid>https://velog.io/@l-wanderer01/Stream%EC%9D%84-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%97%B0%EC%82%B0%EA%B3%BC-for%EB%AC%B8%EC%9D%98-%EC%84%B1%EB%8A%A5-%EB%B9%84%EA%B5%90</guid>
            <pubDate>Mon, 05 May 2025 09:36:31 GMT</pubDate>
            <description><![CDATA[<p>코딩테스트를 풀어가면서 다른 사람들의 풀이를 보다가 나는 for문으로 풀고 다른 사람들은 stream의 forEach문으로 문제를 풀어내는 것을 보고 for문과 forEach의 차이가 궁금해져 알아보게 되었습니다.</p>
<p>for문이 forEach문보다 먼저 개발되었다는 것은 모두가 아는 사실입니다. 그렇다면 for문이 코드를 작성하는데 큰 문제가 없는데 forEach문이 나오게 된 배경을 살펴봅시다.
그렇다면 for문으로도 문제없이 코드를 작성할 수 있는데, 왜 굳이 forEach가 등장하게 되었을까요?</p>
<blockquote>
<p>모든 기능 개발의 원천에는 개발자들의 <strong>귀차니즘</strong>이 존재합니다.</p>
</blockquote>
<p>forEach문도 마찬가지입니다. for문으로 여러 줄을 작성하던 것을 더 간단하고 읽기 쉽게 만든 것이 forEach문입니다.</p>
<p>for문은 아래와 같이 코드가 어쩔 수 없이 길어집니다.</p>
<pre><code>// 배열의 합의 제곱이 배열 요소들의 곱보다 크다면, 1 반환, 아니면 0 반환</code></pre><h3 id="for문-예시">for문 예시</h3>
<pre><code>class Solution {
    public int solution(int[] num_list) {
        int mul = 1;
        int sum = 0;

        for (int i = 0; i &lt; num_list.length; i++) {
            sum += num_list[i];
            mul *= num_list[i];
        }

        return sum * sum &gt; mul ? 1 : 0;
    }
}</code></pre><h3 id="향상된-for문-예시-for-each">향상된 for문 예시 (for-each)</h3>
<pre><code>// 향상된 for문
class Solution {
    public int solution(int[] num_list) {
        int mul = 1;
        int sum = 0;

        for (int i : num_list) {
            sum += i;
            mul *= i;
        }

        return sum * sum &gt; mul ? 1 : 0;
    }
}</code></pre><p>🌟 <strong>tip)</strong> 향상된 for문을 사용하게 되면 인덱스를 사용하지 않아도 되기에 <code>IndexOutOfBoundsException</code>이 발생하지 않아 코드의 <strong>안정성</strong>이 올라갑니다.</p>
<p>향상된 for문을 활용하면 코드가 조금은 깔끔해진 느낌이 든다. 그러나 이것보다 간략히 코드를 작성할 수 있다면?</p>
<p>Stream을 활용하면 향상된 for문보다 더 짧은 코드를 제공합니다.</p>
<h3 id="stream-api를-사용한-코드">stream API를 사용한 코드</h3>
<pre><code>class Solution {
    public int solution(int[] num_list) {
        int mul = 1;
        int sum = 0;

        sum = Arrays.stream(num_list).sum();
        mul = Arrays.stream(num_list).reduce(1, (i, j) -&gt; i * j);

        return sum * sum &gt; mul ? 1 : 0;
    }
}</code></pre><p>위의 코드와 같이 코드의 길이가 많이 줄어듭니다. 따라서 가독성이 향상됩니다! stream API는 Java 8부터 도입된 기능으로, 데이터를 함수형 스타일로 처리할 수 있게 도와줍니다. 내부적으로 반복문을 추상화한 것이며, 일반적으로 for문보단 약간 느리지만, 차이는 대부분의 경우 미미하며 코드의 간결성과 함수형 스타일이 장점으로 언급됩니다.</p>
<ul>
<li>다만 간단한 집계 연산이나 변환에는 유용하지만 stream은 연산 중간에 <code>break</code>, <code>continue</code>를 사용하기 까다롭다는 점이 있고, stream을 사용하면 경우에 따라 <code>map/filter</code> 등으로 복잡해지는 경우 오히려 가독성을 해칠 수 있습니다.</li>
<li>또한 stream은 for문보다 약간의 오버헤드가 있을 수 있으므로, 성능이 민감한 경우에는 주의가 필요합니다.</li>
</ul>
<p>그렇다면 stream이 좋은가? for문이 좋은가? 이 문제는 상황에 따라 다릅니다. stream을 사용한다고 해서 항상 가독성이 뛰어난 것도 아니고, 항상 for문을 사용하기에는 귀찮음이 동반될 수 있습니다.</p>
<blockquote>
<p>따라서 반드시 for문 혹은 stream을 사용하는 것이 아닌 상황에 맞게 올바른 문법을 채택하는게 권장됩니다.
➡️ <strong>가독성, 성능, 제어 흐름의 유연성 등을 고려</strong></p>
</blockquote>
<p>💡 개발에서 중요한 건 “지금 이 문법이 이 상황에 가장 적절한가?&quot;를 판단하는 것!</p>
<p><code>이미지 출처</code> : [Baeldung 블로그] (<a href="https://velog.io/@haleyjun/%EB%B0%98%EB%B3%B5%EB%AC%B8%EC%9D%98-%EC%A2%85%EB%A5%98">https://velog.io/@haleyjun/%EB%B0%98%EB%B3%B5%EB%AC%B8%EC%9D%98-%EC%A2%85%EB%A5%98</a>)
<code>참고 출처</code> : [랸나 [JAVA] For과 Stream은 어떤 차이가 있는걸까?] (<a href="https://velog.io/@foureaf/JAVA-For%EA%B3%BC-Stream%EC%9D%80-%EC%96%B4%EB%96%A4-%EC%B0%A8%EC%9D%B4%EA%B0%80-%EC%9E%88%EB%8A%94%EA%B1%B8%EA%B9%8C">https://velog.io/@foureaf/JAVA-For%EA%B3%BC-Stream%EC%9D%80-%EC%96%B4%EB%96%A4-%EC%B0%A8%EC%9D%B4%EA%B0%80-%EC%9E%88%EB%8A%94%EA%B1%B8%EA%B9%8C</a>)</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Docker란?]]></title>
            <link>https://velog.io/@l-wanderer01/Docker%EB%9E%80</link>
            <guid>https://velog.io/@l-wanderer01/Docker%EB%9E%80</guid>
            <pubDate>Sun, 04 May 2025 11:44:23 GMT</pubDate>
            <description><![CDATA[<h1 id="docker란-무엇인가">Docker란 무엇인가?</h1>
<blockquote>
<p>도커란 컨테이너를 활용해 분리된 환경에서 프로그램이 동작할 수 있도록 돕는 프로그램</p>
</blockquote>
<p>도커의 장점, 도커를 사용하는 이유! <strong>이식성</strong></p>
<p>이식성이란? 어떤 프로그램을 다른 환경에서 쉽게 설치 및 실행이 가능한 것을 의미한다.</p>
<p>즉, 명령어 한 줄로 어떤 환경에서든 프로그램(e.g, MySQL)을 문제 없이 설치 및 실행이 가능하다.</p>
<h1 id="docker에서의-중요한-개념">Docker에서의 중요한 개념</h1>
<h2 id="container">Container</h2>
<p><img src="https://velog.velcdn.com/images/l-wanderer01/post/9e9f8570-4672-41a2-9e98-720a2fe7fd1d/image.png" alt=""></p>
<blockquote>
<p>컨테이너란? 쉽게 말해 미니 컴퓨터이다.</p>
</blockquote>
<p>컨테이너 별로 서로 다른 환경을 제공한다.
(e.g., 한 컨테이너에서 카카오톡을 설치했다고 해서, 다른 컨테이너에서도 카카오톡이 설치되지는 않는다. 다른 컨테이너에 카카오톡을 설치해야만 해당 컨테이너에서 카카오톡이 실행 가능하다.)
➡️ 즉 독립성을 제공한다.</p>
<p><strong>독립성</strong> : 분리해 서로가 영향을 끼지치 않는 것
이때 저장 공간과 네트워크를 컨테이너마다 분리한다.</p>
<h2 id="image">Image</h2>
<p><img src="https://velog.velcdn.com/images/l-wanderer01/post/bdbacc47-1c21-4966-a798-7ceaf12f5e82/image.png" alt=""></p>
<blockquote>
<p>이미지란? 닌텐도의 게임칩과 같다.</p>
</blockquote>
<p>프로그램 설치를 위해 필요한 설정, 버전 정보 등을 포함하고 있다.</p>
<p>예를 들어, MySQL 이미지를 다운받고 실행했을 때, 해당 이미지가 컨테이너를 통해 실행이 되고, 별도의 설치 없이 MySQL을 사용할 수 있다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Spring Boot 기초]]></title>
            <link>https://velog.io/@l-wanderer01/Spring-Boot-%EA%B8%B0%EC%B4%88</link>
            <guid>https://velog.io/@l-wanderer01/Spring-Boot-%EA%B8%B0%EC%B4%88</guid>
            <pubDate>Fri, 21 Mar 2025 12:47:30 GMT</pubDate>
            <description><![CDATA[<h3 id="핵심">핵심</h3>
<p>1️⃣ Spring과 Spring Boot의 차이!</p>
<p>2️⃣ MVC 패턴 개념!</p>
<p>3️⃣ DTO를 활용해 데이터를 다룸</p>
<h1 id="1-spring과-spring-boot의-차이">1. Spring과 Spring Boot의 차이</h1>
<p>참고) 둘 다 Java 기반 웹 애플리케이션 프레임워크</p>
<table>
<thead>
<tr>
<th>비교 항목</th>
<th>Spring Framework</th>
<th>Spring Boot</th>
</tr>
</thead>
<tbody><tr>
<td>설정 방식</td>
<td>XML, Java Config 필요</td>
<td>자동 설정 (<code>@SpringBootApplication</code>)</td>
</tr>
<tr>
<td>웹 서버</td>
<td>수동 설정 필요</td>
<td>내장 Tomcat 자동 제공</td>
</tr>
<tr>
<td>의존성 관리</td>
<td>수동 설정 필요</td>
<td><code>spring-boot-starter-*</code> 제공</td>
</tr>
<tr>
<td>프로젝트 구조</td>
<td>복잡한 설정 필요</td>
<td>간단한 구조</td>
</tr>
<tr>
<td>목적</td>
<td>확장성과 유연성 중점</td>
<td>빠른 개발과 설정 최소화</td>
</tr>
</tbody></table>
<blockquote>
<p>🔥 <strong>Spring Boot는 Spring을 더 쉽게 사용할 수 있도록 만든 버전</strong></p>
</blockquote>
<p>참고) <strong>Spring 프레임워크</strong> : Servlet과 JSP의 복잡한 설정, 코드 중복, 유지보수 어려움 등의 문제로 탄생</p>
<h3 id="1-1-spring의-핵심-기능">1-1. Spring의 핵심 기능</h3>
<p>→ OOP의 원칙을 따르며 객체 간의 의존성을 줄이고, 유지보수를 쉽게 할 수 있도록 도움</p>
<ul>
<li><strong>OOP solid</strong>
  → <strong>객체지향 설계</strong>에서 지켜줘야할 5개의 <strong>소프트웨어 개발 원칙</strong>!<br><blockquote>
<p>   <strong>S</strong>RP(Single Responsibility Principle) : 단일 책임 원칙<br>
   <strong>O</strong>CP(Open Closed Principle) : 개방 폐쇄 원칙<br><br>   <strong>L</strong>SP(Liskov Substitution Principle) : 리스코프 치환 원칙<br>
   <strong>I</strong>SP(Interface Segregation Principle) : 인터페이스 분리 원칙<br>
   <strong>D</strong>IP(Dependency Inversion Principle) : 의존 역전 원칙<br></p>
</blockquote>
</li>
</ul>
<p> ⇒ 여러 디자인 패턴들이 SOLID 설계 원칙에 입각해 만들어짐</p>
<ol>
<li><p>DI(의존성 주입)
 → 객체가 직접 다른 객체를 생성하지 않고, 외부에서 주입받는 방식(Spring에서는 의존성을 자동으로 주입해줌)
 ➡️ 객체 간의 결합도를 낮추고 유지보수를 쉽게 만듦</p>
<blockquote>
<p><code>new</code> 키워드로 직접 객체 생성 시 의존성이 발생 → <code>생성자</code>를 이용해 의존성을 주입</p>
</blockquote>
<pre><code class="language-java">   class B {
       public void print() {
           System.out.println(&quot;B 클래스의 메서드 실행&quot;);
       }
   }

   class A {
       private B b;

       public A(B b) { // 생성자를 이용해 B 객체를 주입
           this.b = b;
       }

       public void execute() {
           b.print();
       }
   }</code></pre>
<blockquote>
<ul>
<li>A 클래스는 B를 직접 생성하지 않는다!</li>
<li>B의 구현이 바뀌더라도 A는 영향을 받지 않음!</li>
</ul>
</blockquote>
</li>
<li><p>AOP(관점 지향 프로그래밍)</p>
<p> → 공통적으로 적용해야 하는 기능을 분리해 코드 중복을 줄이고 유지보수를 쉽게하는 프로그래밍 기법</p>
<p> ➡️ 중복된 코드를 하나의 공통 기능으로 분리해 유지보수성을 높이기 위해 <strong>AOP</strong> 사용!</p>
</li>
<li><p>Spring MVC</p>
<p> → 웹 애플리케이션에서 <code>Model-View-Control</code>  구조를 지원</p>
</li>
<li><p>Spring Security</p>
<p> → Spring 기반 애플리케이션의 보안을 담당하는 프레임워크</p>
<p> ➡️ 로그인, 로그아웃, 권한 부여, 요청 보호 등의 기능을 쉽게 구현할 수 있도록 도와줌</p>
<p> 참고) Django의 <code>django.contrib.auth</code> 가 제공하는 기능과 비슷한 역할</p>
</li>
<li><p>Spring Data JPA</p>
<p> → Spring에서 JPA(Java Persistence API)를 더 쉽게 사용할 수 있도록 도와주는 라이브러리</p>
<p> ➡️ SQL을 직접 작성하지 않아도 자동으로 데이터베이스와 연결하고, CRUD를 수행 가능!</p>
</li>
</ol>
<h3 id="1-2-spring의-단점">1-2. Spring의 단점</h3>
<ol>
<li><p>초기 설정(XML 설정)이 복잡</p>
</li>
<li><p>초기 학습 난이도 ⬆️</p>
<ul>
<li>DI, AOP, MVC 등 많은 개념이 있어 처음 배우는 사람이 이해하기 어려움</li>
</ul>
</li>
<li><p>초기 부팅이 오래 걸림</p>
<ul>
<li><p>Spring은 애플리케이션이 시작될 때 모든 객체(Bean)를 미리 생성하고 설정하는 과정이 존재</p>
<p>⇒ Java 프로젝트보다 부팅 속도가 느릴 수 있음</p>
</li>
</ul>
</li>
</ol>
<h1 id="2-spring-boot">2. Spring Boot</h1>
<h3 id="2-1-spring-boot">2-1. Spring Boot</h3>
<blockquote>
<p>🔨    Spring 프레임워크를 더 쉽고 빠르게 사용할 수 있도록 도와주는 도구</p>
</blockquote>
<p>→ Spring의 복잡한 설정을 최소화</p>
<h3 id="2-2-spring-boot의-특징">2-2 Spring Boot의 특징</h3>
<ol>
<li><p>자동 설정</p>
<p> ___Application.java: <code>@SpringBootApplication</code> 한 줄로 설정 가능</p>
<p> build.gradle: <code>spring-boot-starter-*</code>  의존성을 추가하면 자동으로 관련 설정이 적용됨</p>
</li>
<li><p>내장 웹 서버 제공(Built-in Web Server)</p>
<ul>
<li>웹 서버를 내장하고 있어 따로 설정하지 않아도 됨
➡️ Spring Boot는 실행하면 자동으로 서버가 실행됨</li>
</ul>
</li>
<li><p>의존성 자동 관리</p>
<p> build.gradle: <code>spring-boot-starter-*</code> : 의존성 관리를 쉽게 할 수 있도록 패키지를 제공</p>
</li>
<li><p>독립 실행 가능</p>
<p> <strong>JAR 파일</strong>로 패키징하면 실행 파일처럼 동작할 수 있음</p>
<p> → AWS 서버에 올려 실행시킬 때, JAR 파일로 패키징해 서버를 실행!</p>
</li>
<li><p>환경설정의 유연함</p>
<p> <code>application.properties</code> 또는 <code>application.yml</code> 을 사용해 설정 가능</p>
</li>
</ol>
<h3 id="2-3-spring-boot의-주요-기능">2-3. Spring Boot의 주요 기능</h3>
<ol>
<li><p><strong>내장 웹 서버</strong> 지원</p>
<p> Tomcat, Jetty, Undertow와 같은 웹 서버를 내장하고 있어 따로 설정할 필요 x</p>
</li>
<li><p><strong>REST API</strong> 개발(Spring MVC)</p>
<p> 간단한 컨트롤러만 만들어도 API가 동작</p>
</li>
<li><p>Spring Boot + <strong>JPA</strong> 사용(DB 연동)</p>
<p> Spring Data JPA와 함께 사용하면 데이터베이스 연동이 간단함</p>
</li>
</ol>
<h3 id="spring-boot-실습">Spring Boot 실습</h3>
<ol>
<li><p><strong>Spring Initialzr 접속</strong></p>
<p> <a href="https://start.spring.io/">https://start.spring.io/</a></p>
</li>
<li><p><strong>Project Type 선택</strong></p>
<p> Java 프로젝트에서는 라이브러리(의존성) 관리, 빌드, 테스트, 배포를 자동화 하는 도구가 필요</p>
<p> <strong>⇒ Maven, Gradle</strong></p>
<ul>
<li><p><strong>Maven</strong></p>
<ul>
<li><p>XML(pom.xml) 기반의 전통적인 빌드 도구</p>
</li>
<li><p><code>pom.xml</code> 을 사용해 프로젝트를 빌드하고, 의존성을 관리
<img src="https://velog.velcdn.com/images/l-wanderer01/post/7156e536-8e18-45f2-9901-31f7e135c8b8/image.png" alt=""></p>
<p>XML 형식 : 가독성이 낮고, 길어질수록 설정이 복잡</p>
</li>
</ul>
</li>
</ul>
</li>
</ol>
<pre><code>- **Gradle**
    - `build.gradle` 을 사용해 Maven에 비해 더 빠르고 유연함!
    - Gradle이 속도 면에서 Maven보다 빠름
    ![](https://velog.velcdn.com/images/l-wanderer01/post/e1fb7f9e-d20b-41d0-82a6-5233d0ab707d/image.png)


    Maven에 비해 작관적이고, 읽기 쉬움</code></pre><ol start="3">
<li><strong>Spring Boot Version 선택</strong></li>
</ol>
<ul>
<li>정식 버전을 주로 사용</li>
<li>SNAPSHOT 버전: 개발 중인 미완성 버전, 불안정</li>
<li>M 버전(Milestone Release) : 정식 릴리즈 전 중요한 기능을 테스트하는 중간 단계(베타 버전과 유사, But 정식 출시가 아니므로 주의가 필요)</li>
</ul>
<ol start="4">
<li><strong>Project Metadata 입력</strong></li>
</ol>
<ul>
<li>Group: 패키지의 그룹 ID ➡️ 회사, 조직, 프로젝트 단위를 나타냄</li>
<li>Artifact: 프로젝트의 고유한 이름(JAR 파일의 기본 이름이 됨)</li>
<li>Name: 프로젝트의 기본 이름(기본적으로 Artifact와 동일하게 설정됨)</li>
<li>Description: 프로젝트에 대한 간단한 설명
<code>pom.xml</code> or <code>build.gradle</code> 파일에 포함</li>
<li>Package name: 프로젝트의 기본 패키지 이름(Group + Artifact 조합)</li>
<li>Packaging: 프로젝트를 빌드할 때 생성되는 최종 실행 파일의 형식을 결정<ul>
<li>Jar(Java Archive) - 하나의 독립 실행 가능한 <code>.jar</code> 파일 생성</li>
<li>War(Web Application Archive) - 기존의 애플리케이션 서버에 <code>.war</code> 파일을 생성해 배포 가능</li>
</ul>
</li>
</ul>
<ol start="5">
<li><strong>Dependencies(의존성) 주입</strong></li>
</ol>
<ul>
<li>프로젝트가 정상적으로 실행되기 위해 필요한 라이브러리들 모음
Maven or Gradle을 사용해 필요한 라이브러리를 자동으로 다운로드하고 관리할 수 있도록 도움</li>
</ul>
<p>🌟 <strong>Spring Boot 웹 프로젝트 Dependencies</strong></p>
<ol>
<li>Spring Web</li>
</ol>
<p>가장 기본적인 의존성, 내장 Tomcat 서버와 함께 RESTful API를 쉽게 만들 수 있도록 지원</p>
<p>➡️ 컨트롤러를 만들어 HTTP 요청 처리 가능</p>
<hr>
<ol start="2">
<li>Lombok                </li>
</ol>
<p><code>Getter</code>, <code>Setter</code>, <code>생성자</code>, <code>toString</code> 등의 메서드를 자동으로 생성해주는 라이브러리</p>
<p>➡️ DTO와 Entity 클래스를 만들 때 불필요한 코드 작성을 줄임</p>
<hr>
<ol start="3">
<li>Spring Boot Validation</li>
</ol>
<p>사용자 입력값을 검증하는 라이브러리(잘못된 입력 시 오류 반환)</p>
<p>➡️ <code>@Valid</code> or <code>@NotNull</code> 등을 사용해 REST API의 요청 데이터를 검증 가능</p>
<hr>
<ol start="4">
<li>Spring Data JPA</li>
</ol>
<p>MySQL과 같은 관계형 데이터베이스를 쉽게 다룰 수 있도록 JPA 지원</p>
<hr>
<ol start="5">
<li>MySQL JDBC Driver</li>
</ol>
<p>Spring Boot에서 MySQL과 연동하기 위해 필요한 JDBC 드라이버</p>
<p>💡 본인이 사용하는 DB에 맞는 JDBC Driver 추가</p>
<hr>
<h1 id="3-mvc-패턴">3. MVC 패턴</h1>
<blockquote>
<p>🔥 애플리케이션을 <strong>Model, View, Controller</strong>로 나누어 구성하는 방식
→ 유지보수성과 확장성 ⬆️, 코드의 역할을 명확히 구분❗️</p>
</blockquote>
<h3 id="3-1-mvc-패턴의-구성요소">3-1. MVC 패턴의 구성요소</h3>
<ul>
<li><p><strong>Model</strong></p>
<p>  📌 애플리케이션의 핵심 <strong>데이터</strong>와 <strong>비즈니스 로직</strong>을 처리</p>
<ul>
<li>구성요소</li>
</ul>
<ol>
<li><p>Entity : 데이터베이스 테이블과 연결되는 객체 (파일명: <code>User.java</code>)</p>
<p> → Getter, Setter 필수</p>
</li>
<li><p>Repository : 데이터 엑세스를 담당하는 계층 (파일명: <code>UserRepository.java</code>)</p>
</li>
<li><p>Service : 비즈니스 로직을 처리하는 계층 (파일명: <code>UserService.java</code>)</p>
</li>
</ol>
</li>
<li><p><strong>Controller</strong></p>
<p>  📌 사용자의 요청을 받아 Model(Service)과 View(JSON, HTML)사이에서 <strong>데이터를 주고받는 역할</strong></p>
<p>  <strong>Model</strong>과 <strong>View</strong>의 중간다리 역할!</p>
<p>  👉 <strong>사용자의 요청을 받고 응답을 반환하는 역할</strong></p>
<p>  👉 <strong>비즈니스 로직을 직접 수행하지 않고, Service에 요청을 전달</strong></p>
<ul>
<li>Controller와 Service
<img src="https://velog.velcdn.com/images/l-wanderer01/post/8871c94a-80a7-46b7-9407-4ba1162d7f29/image.png" alt=""></li>
</ul>
</li>
</ul>
<blockquote>
<p><code>Controller</code> 는 <strong>HTTP 요청 처리</strong>
   <code>Service</code> 는 실제 <strong>비즈니스 로직 담당</strong></p>
</blockquote>
<ul>
<li><p><strong>View</strong></p>
<p>  📌 사용자가 볼 수 있는 <strong>화면</strong> 또는 <strong>응답 데이터</strong>(JSON)</p>
<ul>
<li>Thymeleaf 또는 JSON 응답 방식으로 구현<ul>
<li>Thymeleaf: HTML 기반 View → 프론트엔드가 없는 프로젝트에서 사용</li>
<li>JSON 응답: 프론트엔드와 통신할 때 사용</li>
</ul>
</li>
</ul>
</li>
</ul>
<pre><code>├── src
│   ├── main
│   │   ├── java
│   │   │   └── com.companyname.projectname
│   │   │       ├── post
│   │   │       │   ├── controller
│   │   │       │   │   └── PostController.java
│   │   │       │   ├── dto
│   │   │       │   │   ├── PostRequestDto.java
│   │   │       │   │   ├── PostResponseDto.java
│   │   │       │   │   ├── PostListResponseDto.java
│   │   │       │   │   ├── PostSaveRequestDto.java
│   │   │       │   │   └── PostUpdateRequestDto.java
│   │   │       │   ├── model
│   │   │       │   │   └── Post.java
│   │   │       │   ├── repository
│   │   │       │   │   └── PostRepository.java
│   │   │       │   └── service
│   │   │       │       └── PostService.java
│   │   │       └── ProjectNameApplication.java
│   │   └── resources
│   └── test</code></pre><h3 id="3-2-spring-boot에서-mvc-흐름">3-2. Spring Boot에서 MVC 흐름</h3>
<p>   <img src="https://velog.velcdn.com/images/l-wanderer01/post/0317408d-ba7d-4f5a-abaa-6248dda4c8b2/image.png" alt=""></p>
<ol>
<li><p><strong>Client</strong>가 GET 요청을 보냄</p>
</li>
<li><p><strong>Controller</strong>가 요청을 받아 <strong>Service</strong> 호출</p>
</li>
<li><p><strong>Service</strong>가 <strong>Repository</strong>를 통해 데이터베이스에서 데이터를 조회</p>
<ul>
<li>Service
  👉 <strong>비즈니스 로직을 담당하는 계층</strong>
  👉 <strong>Controller에서 받은 데이터를 가공하거나 데이터베이스에 저장</strong></li>
</ul>
</li>
<li><p>조회된 데이터를 <strong>Controller</strong>로 반환</p>
</li>
<li><p><strong>Controller</strong>가 JSON 데이터를 <strong>Client</strong>에게 응답</p>
</li>
</ol>
<h3 id="3-3-mvc-패턴-실습">3-3. MVC 패턴 실습</h3>
<ol>
<li><p>데이터 모델(DTO) 구축</p>
</li>
<li><p>데이터 저장소(Service) 구축</p>
<p> → 회원 데이터를 모델에 저장하는 서비스 클래스 생성</p>
</li>
<li><p>Controller 생성(JSON 응답 반환)</p>
<p> <code>@RestController</code> : HTML 대신 JSON 응답 반환</p>
<p> <code>@GetMapping</code> : 모든 회원 데이터를 JSON으로 반환</p>
<p> <code>@PostMapping</code> + <code>@RequestBody</code> : JSON 데이터를 받아 처리</p>
</li>
</ol>
<h1 id="4-dto-개념-및-활용">4. DTO 개념 및 활용</h1>
<h3 id="4-1-dto의-개념">4-1. DTO의 개념</h3>
<blockquote>
<p>🔥 <strong>Data Transfer Object</strong>
<strong>데이터를 객체로 변환</strong>하여 전송하는 객체
주로 <strong>Controller</strong> ↔ <strong>Service</strong> ↔ <strong>Repository</strong> 사이에서 데이터를 주고받을 때 사용</p>
</blockquote>
<ul>
<li>Entity를 직접 사용하지 않고, DTO를 통해 데이터를 가공 후 API 응답으로 변환
→ DTO는 Entity를 보호하면서 API에 <strong>필요한 데이터만 전달</strong>할 수 있도록 설계됨
매우 민감한 password 와 같은 정보를 선택적으로 주고받을 수 있음!</li>
</ul>
<h3 id="4-2-dto를-사용하는-이유">4-2 DTO를 사용하는 이유</h3>
<ol>
<li>보안 강화</li>
<li>Entity와 요청/응답 데이터를 분리해 유지보수성 ⬆️</li>
<li>여러 개의 Entity 데이터를 조합해 응답 가능(데이터 가공)</li>
<li>쉬운 유효성 검사( <code>@Valid</code> )</li>
<li>불필요한 데이터 노출 방지</li>
</ol>
<h3 id="4-3-dto-vs-entity">4-3. DTO vs Entity</h3>
<table>
<thead>
<tr>
<th>항목</th>
<th>DTO</th>
<th>Entity</th>
</tr>
</thead>
<tbody><tr>
<td>역할</td>
<td>데이터 <strong>전달</strong> 전용</td>
<td><strong>데이터베이스</strong>와 직접 연결</td>
</tr>
<tr>
<td>목적</td>
<td>Controller ↔ Service ↔ Repository 사이에서 <strong>데이터 교환</strong></td>
<td>데이터베이스 테이블과 <strong>매핑</strong></td>
</tr>
<tr>
<td>비즈니스 로직 포함 여부</td>
<td>X (데이터만 포함)</td>
<td>O (비즈니스 로직 포함 가능)</td>
</tr>
<tr>
<td>DB 테이블과 연결 여부</td>
<td>X</td>
<td>O (JPA <code>@Entity</code> 사용)</td>
</tr>
<tr>
<td>보안성</td>
<td>필요 없는 데이터 필터링 가능</td>
<td>모든 데이터가 노출될 가능성 존재</td>
</tr>
<tr>
<td>유지보수성</td>
<td>DTO만 변경 가능(Entity와 <strong>독립적인</strong> 관계)</td>
<td>Entity 변경 시 <strong>API 전체 수정</strong> 필요</td>
</tr>
<tr>
<td>사용 위치</td>
<td>API 요청/응답, <strong>데이터 가공</strong>용</td>
<td><strong>데이터 저장 및 조회</strong>용</td>
</tr>
<tr>
<td>사용 방식</td>
<td>JSON ↔ DTO 변환</td>
<td>Entity ↔ DTO 변환 후 사용</td>
</tr>
</tbody></table>
<p>→ <strong>Entity</strong>: 데이터베이스에 저장될 데이터 구조</p>
<p>→ <strong>DTO</strong>: Entity에서 필요한 데이터만 <strong>“선별”</strong>하여 클라이언트에 저장</p>
<p><strong>🚨 Entity를 직접 사용 X</strong></p>
<ul>
<li>Entity 리스트</li>
</ul>
<pre><code class="language-java">[
    User{id=1, username=&quot;Alice&quot;, email=&quot;alice@example.com&quot;},
    User{id=2, username=&quot;Bob&quot;, email=&quot;bob@example.com&quot;}
]</code></pre>
<ul>
<li>DTO 사용 후 변환된 리스트</li>
</ul>
<pre><code class="language-java">[
    UserResponseDto{username=&quot;Alice&quot;, email=&quot;alice@example.com&quot;},
    UserResponseDto{username=&quot;Bob&quot;, email=&quot;bob@example.com&quot;}
]</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[OSI 모델과 TCP/IP 프로토콜]]></title>
            <link>https://velog.io/@l-wanderer01/OSI-%EB%AA%A8%EB%8D%B8%EA%B3%BC-TCPIP-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C</link>
            <guid>https://velog.io/@l-wanderer01/OSI-%EB%AA%A8%EB%8D%B8%EA%B3%BC-TCPIP-%ED%94%84%EB%A1%9C%ED%86%A0%EC%BD%9C</guid>
            <pubDate>Tue, 18 Mar 2025 12:54:30 GMT</pubDate>
            <description><![CDATA[<h1 id="1-프로토콜-계층구조-protocol-layer">1. 프로토콜 계층구조 (Protocol Layer)</h1>
<h3 id="프로토콜이란">프로토콜이란?</h3>
<blockquote>
<p>컴퓨터 또는 전자 기기 간의 원활한 통신을 위해 지키기로 한 규약</p>
</blockquote>
<ul>
<li>두 개체 간의 통신을 할 때 필요
📌 개체란? 통신에 참여하는 기계
<img src="https://velog.velcdn.com/images/l-wanderer01/post/5d6cf34d-f553-4c44-a81c-715bbe4b7396/image.png" alt="">
Maria는 <code>스페인어</code>만 하고, Ann은 <code>영어</code>만 한다고 가정을 했을 때, 둘 다 <code>수화</code>를 할 수 있다면, 이때 프로토콜은 <code>수화</code></li>
<li><strong>통신</strong>은 여러 계층 구조로 분할이 가능한데, 이때 각 계층은 <em>각각의 프로토콜 </em>이 필요함!<h3 id="💡-떨어진-환경에서-서로-다른-언어를-사용하는-두-사람이-통신하는-과정">💡 떨어진 환경에서 서로 다른 언어를 사용하는 두 사람이 통신하는 과정</h3>
<img src="https://velog.velcdn.com/images/l-wanderer01/post/3af97183-51db-49ab-bca4-4c0eb4d13f78/image.png" alt=""></li>
</ul>
<h1 id="2-osi-모델-model">2. OSI 모델 (Model)</h1>
<blockquote>
<p>국제표준화기구(ISO) 표준이 <strong>OSI</strong> 모델</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/l-wanderer01/post/dcfe91f2-393e-494b-8369-515119e5b212/image.png" alt=""></p>
<table>
<thead>
<tr>
<th align="center"></th>
<th align="center">Layer명</th>
<th align="center">역할</th>
<th align="center">Device</th>
<th align="center">예시</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><strong>1계층</strong></td>
<td align="center">물리 계층</td>
<td align="center">0과 1을 아날로그로 변환</td>
<td align="center">허브</td>
<td align="center">도로, 택배 트럭, <br>택배 상자</td>
</tr>
<tr>
<td align="center"><strong>2계층</strong></td>
<td align="center">데이터 링크</td>
<td align="center">hop-to-hop delivery를 위해 <br>bit를 프레임으로 묶음</td>
<td align="center">스위치, 브릿지</td>
<td align="center">상자에 붙이는  송장</br>(MAC 주소)</td>
</tr>
<tr>
<td align="center"><strong>3계층</strong></td>
<td align="center">네트워크 계층</td>
<td align="center">전달되는 길을 찾음</td>
<td align="center">라우터</td>
<td align="center">주소와 우편번호를 보고 길을 찾음<br>(IP 주소, 라우팅)</td>
</tr>
<tr>
<td align="center"><strong>4계층</strong></td>
<td align="center">전송 계층</td>
<td align="center">전송 시 사고가 나도 <br>수습이 가능하도록 만듦</td>
<td align="center">포트 번호</td>
<td align="center">택배를 나눠 보내거나 묶어서 보내기<br>(TCP/UDP)</td>
</tr>
<tr>
<td align="center"><strong>5계층</strong></td>
<td align="center">세션 계층</td>
<td align="center">통신 세션을 설정, 유지, 종료</td>
<td align="center">로그인 유지, 세션 유지</td>
<td align="center">고객과 택배 회사의 연락<br>(세션 유지)</td>
</tr>
<tr>
<td align="center"><strong>6계층</strong></td>
<td align="center">표현 계층</td>
<td align="center">번역, 암호화, 압축</td>
<td align="center">TLS/SSL(HTTPS 보안), <br>파일 인코딩 변환</td>
<td align="center">주소를 한글에서 영어로 변환<br>(데이터 변환, 암호화)</td>
</tr>
<tr>
<td align="center"><strong>7계층</strong></td>
<td align="center">응용 계층</td>
<td align="center">아래 계층에 요구를 전달</td>
<td align="center">웹 브라우저, 이메일, FTP 서버</td>
<td align="center">고객이 택배를 주문하고 받음<br>(웹 브라우저, 이메일)</td>
</tr>
<tr>
<td align="center">참고)</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="center">- 네트워크 계층은 해당 길이 올바른 길인지는 모름, Dijkstra 알고리즘 사용</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
<tr>
<td align="center">- 트랜잭션: 일련의 과정, 과정 중 <code>error</code>가 발생했다면 정상적인 상태로 &quot;<strong>Rollback</strong>&quot; 가능해야 함.</td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
<td align="center"></td>
</tr>
</tbody></table>
<blockquote>
<p>1계층부터 4계층까지는 네트워크, 5계층부터 7계층은 코딩으로 구현해야한다!
<br>통신은 <strong>계층-대-계층(Layer-to-Layer)</strong>으로 이루어지며 해당되지 않는 계층의 내용은 보지 못한다!</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/l-wanderer01/post/a1c7e164-b92a-40c5-aecd-27c8a3a989d8/image.png" alt="">
➡️ 정보를 보낼 때, Layer를 지날 때마다 Header를 붙이고 Target에 도착하면 Header를 제거하며 Layer를 통과(<em>계층마다 프로토콜이 다름</em> )</p>
<h1 id="3-tcpip-프로토콜-그룹protocol-suite">3. TCP/IP 프로토콜 그룹(Protocol Suite)</h1>
<blockquote>
<p>TCP/IP 프로토콜 그룹은 OSI 모델보다 먼저 개발됨</p>
</blockquote>
<ul>
<li>TCP/IP 프로토콜의 계층은 OSI 모델의 계층과 정확하게 일치하지 않는다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/l-wanderer01/post/5dfd5d9e-9196-4358-961c-c902c427cefc/image.png" alt=""></p>
<table>
<thead>
<tr>
<th align="center">TCP/IP 프로토콜</th>
<th align="center">Layer명</th>
<th align="center">통신 단위</th>
</tr>
</thead>
<tbody><tr>
<td align="center"><strong>1계층</strong></td>
<td align="center">물리 계층</td>
<td align="center">Bit</td>
</tr>
<tr>
<td align="center"><strong>2계층</strong></td>
<td align="center">데이터 링크 계층</td>
<td align="center">Frame</td>
</tr>
<tr>
<td align="center"><strong>3계층</strong></td>
<td align="center">네트워크 계층</td>
<td align="center">Datagram</td>
</tr>
<tr>
<td align="center"><strong>4계층</strong></td>
<td align="center">전송 계층</td>
<td align="center">Segment, User Datagram</td>
</tr>
<tr>
<td align="center"><strong>5계층</strong></td>
<td align="center">응용 계층</td>
<td align="center">Message</td>
</tr>
<tr>
<td align="center">참고) 전송 계층(4 Layer): 신뢰성을 높여줌 (reliable)</td>
<td align="center"></td>
<td align="center"></td>
</tr>
</tbody></table>
<h1 id="4-주소지정-addressing">4. 주소지정 (Addressing)</h1>
<p><img src="https://velog.velcdn.com/images/l-wanderer01/post/d17308f2-006e-4fa6-97ce-4c90eac7b28c/image.png" alt=""></p>
<blockquote>
<p>TCP/IP 프로토콜을 이용한 인터넷은 4개의 서로 다른 계층의 주소가 사용됨
<br>1. 물리 주소 (Physical address) ➡️ 2계층
<br>2. 논리 주소 (Logical address) ➡️ 3계층
<br>3. 포트 주소 (Port address) ➡️ 4계층
<br>4. 응용-특수 주소 (Application-specific Address) ➡️ 5계층</p>
</blockquote>
<h2 id="41-물리-주소">4.1 물리 주소</h2>
<p><img src="https://velog.velcdn.com/images/l-wanderer01/post/ee70daa2-22ed-4bc9-8bb3-7777b275f340/image.png" alt=""></p>
<ul>
<li><p>두 node는 <strong>링크(link)</strong>로 연결되어있음(LAN에 연결)
프레임(Frame)은 헤더 내에 물리 주소를 포함, 수신된 데이터는 프레임에 <strong>캡슐화</strong>됨</p>
</li>
<li><p>프레임은 <strong>LAN</strong>을 통해 전달</p>
</li>
<li><p>헤더 안의 목적지 주소가 본인이 아니라면 해당 컴퓨터는 Data를 폐기함</p>
</li>
<li><p>물리 주소는 하나의 물리 네트워크 내에서 <strong>고유</strong>해야 한다</p>
</li>
<li><p>물리 주소는 <strong>48bit(6 byte)</strong>, 16진법 숫자 12개로 구성되어 사용
<img src="https://velog.velcdn.com/images/l-wanderer01/post/d3ceb712-1f20-49b3-acfe-22306a045dd9/image.png" alt=""></p>
<h2 id="42-논리-주소">4.2 논리 주소</h2>
<p><img src="https://velog.velcdn.com/images/l-wanderer01/post/086ff045-2c62-4d85-9cb8-b4efd5a430ba/image.png" alt=""></p>
<blockquote>
<p>🚨 물리 주소는 hop-to-hop delivery를 하며 변하지만, 논리 주소는 <strong>변하지 않는다</strong>!</p>
</blockquote>
</li>
<li><p>각 장치(컴퓨터 or 라우터)는 각 물리적 연결에 대해 논리 주소와 물리 주소를 쌍으로 갖는다.</p>
</li>
<li><p>라우터는 3개의 네트워크를 연결하고 있으므로, 3쌍(논리 주소, 물리 주소)의 주소를 가진다.</p>
</li>
</ul>
<h2 id="43-포트-주소">4.3 포트 주소</h2>
<p><img src="https://velog.velcdn.com/images/l-wanderer01/post/68178074-3841-4ab2-918c-3fd094c8b560/image.png" alt=""></p>
<blockquote>
<p><strong>포트 주소</strong>도 hop-to-hop delivery할 때, 변하지 않는다.</p>
</blockquote>
<ul>
<li>두 컴퓨터가 동일한 응용 프로그램(a, j)를 사용하고 있지만, FTP를 사용하는 응용 프로그램의 경우, 하나는 <strong>Client 프로그램</strong>, 다른 하나는 <strong>Server 프로그램</strong>이기에 <em>포트 주소가 다를 수 있다는 것에 유의 </em>해야 한다.</li>
<li>포트 주소는 하나의 10진수로 표기 (0 ~ 65535, $2^{16}$개)
<img src="https://velog.velcdn.com/images/l-wanderer01/post/0ded223f-c41f-4914-b6d0-3cadf924bc8c/image.png" alt=""></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[CI/CD 구축 과정과 자동화 경험 + Test code의 필요성]]></title>
            <link>https://velog.io/@l-wanderer01/CICD-%EA%B5%AC%EC%B6%95-%EA%B3%BC%EC%A0%95%EA%B3%BC-%EC%9E%90%EB%8F%99%ED%99%94-%EA%B2%BD%ED%97%98-Test-code%EC%9D%98-%ED%95%84%EC%9A%94%EC%84%B1</link>
            <guid>https://velog.io/@l-wanderer01/CICD-%EA%B5%AC%EC%B6%95-%EA%B3%BC%EC%A0%95%EA%B3%BC-%EC%9E%90%EB%8F%99%ED%99%94-%EA%B2%BD%ED%97%98-Test-code%EC%9D%98-%ED%95%84%EC%9A%94%EC%84%B1</guid>
            <pubDate>Sat, 15 Feb 2025 09:38:11 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/l-wanderer01/post/8088092a-eda8-42a2-be91-2d3e491f5482/image.png" alt=""></p>
<h2 id="🔗-cicd-구축의-필요성">🔗 CI/CD 구축의 필요성</h2>
<p>3주간 팀원들과 함께한 프로젝트를 배포한 상태로 배포된 사이트에서 모든 기능들과 UI/UX가 정상적으로 반영되었는지 확인하고 테스트하는 과정에서 Git push, Git pull을 자주 반복하다보니 이를 자동화하는게 필요하다고 느꼈고, CI/CD를 구축 해야겠다고 생각했다.</p>
<blockquote>
<p>_나는 이번 프로젝트를 진행하며 CI/CD라는 것을 들어만 보았고, 이게 무엇을 의미하고 어떤 기능을 하는지 전혀 몰랐다...
CI/CD 파이프라인 구축을 담당한 나와 팀원 한분과 총 두 명이서 작업을 하였다.
<code>Github Actions는 함께 공부하고, 나는 배포 버전의 안정화를 위해 test 코드를 작성하였고, 팀원은 배포 자동화를 위해 Actions의 Settings 설정과 code를 작성하는 것으로 분업하였다.</code>
_</p>
</blockquote>
<h3 id="❓-cicd-파이프라인이란">❓ CI/CD 파이프라인이란</h3>
<p><strong>개념</strong>: CI/CD 파이프라인은 지속적 통합과 지속적 제공을 결합하여 코딩, 테스트, 배포 전반에 걸쳐 소프트웨어 제공을 자동화합니다.</p>
<blockquote>
<p>이 말을 처음 들었을 땐 와닿지 않았다. <em>&#39;자동화가 왜 필요하지?&#39;</em> 라는 생각을 했지만 프로젝트를 진행하고 배포를 완료하니 수정을 할 때마다 git의 main 브랜치에 push 혹은 pull request를 날려야하고, 서버 컴퓨터에 직접 접근을 해 main 브랜치의 내용을 pull 받아야하는 번거로움이 있었다. 이를 찾아보니 <code>CI/CD 파이프라인</code>을 구축한다는 이야기가 있었고, 따라서 CI/CD 파이프라인을 구축하였다.</p>
</blockquote>
<h3 id="☀️-cicontinuous-integration-지속적인-통합">☀️ CI(Continuous Integration): 지속적인 통합</h3>
<p><code>지속적인 통합</code>이란 수정한 코드를  배포하기 위해 git 브랜치에 push를 하는 행위로 코드를 병합하는 과정을 의미한다. 이에 따라 test를 진행하고 conflict를 발견하는 단계를 포함한다.</p>
<h3 id="🌕-cdcontinuous-deliverydeployment-지속적인-배포">🌕 CD(Continuous Delivery/Deployment): 지속적인 배포</h3>
<p><code>지속적인 배포</code>란 CI 단계에서 test를 통과한 후 실제로 서버 컴퓨터에 접속을 해 실제 서비스에 반영을 하는 과정을 의미한다.</p>
<h2 id="🔨-cicd-구축-툴">🔨 CI/CD 구축 툴</h2>
<p>찾아보니 CI/CD를 구축하기 위한 툴에 주로 많이 사용하는 것이 Github Actions, Jenkins를 사용하는 것 같다. 우리는 Github을 많이 사용해서 더욱 익숙한 <code>Github Actions</code>를 사용하기로 했다.
<img src="https://velog.velcdn.com/images/l-wanderer01/post/9c99252c-45ed-4f3a-8735-9a1cc248a883/image.png" alt="">
Github Actions를 사용하기 위해선 Actions에서 개발한 프로젝트에 적절한 템플릿을 고르고, Settings에서 적절한 설정을 해줘야 했다.</p>
<h3 id="1-settings-설정">1. Settings 설정</h3>
<p><code>Settings ➡️ Security ➡️ Secrets and variables ➡️ Actions</code></p>
<ul>
<li>New Repository Secret을 클릭하면 새로운 시크릿을 생성할 수 있다.
이때 생성하는 것들은 Git에 올라가지 않도록 했던 &quot;.env&quot; 파일 안의 값들이라고 생각하면 된다.
<code>예를 들면, 서버의 IP 값, DB 명, DB 비밀번호 등이 있다.</code><blockquote>
<p>Name: 시크릿 이름, Secret: 보안이 중요한 값</p>
</blockquote>
</li>
</ul>
<h3 id="2-actions">2. Actions</h3>
<p>Secret을 모두 설정해주었다면 Actions에 들어가 적절한 템플릿을 골라주면 된다.
우리가 개발한 프로젝트는 Django를 이용해 만들어서 Django 템플릿을 이용했다.</p>
<pre><code>name: Django CI

on:
  push:
    branches: [ &quot;main&quot; ]
  pull_request:
    branches: [ &quot;main&quot; ]

jobs:
  build:

    runs-on: ubuntu-latest
    strategy:
      max-parallel: 4
      matrix:
        python-version: [3.7, 3.8, 3.9]

    steps:
    - uses: actions/checkout@v4
    - name: Set up Python ${{ matrix.python-version }}
      uses: actions/setup-python@v3
      with:
        python-version: ${{ matrix.python-version }}
    - name: Install Dependencies
      run: |
        python -m pip install --upgrade pip
        pip install -r requirements.txt
    - name: Run Tests
      run: |
        python manage.py test
</code></pre><ul>
<li>위의 내용이 Django 템플릿을 고르면 나오는 코드이다.
<del>~</del></li>
</ul>
<ol>
<li>name: Git Actions의 이름</li>
<li>on: git이 수행할 명령과 브랜치 지정</li>
<li>jobs: 실제로 자동화해 수행할 작업들</li>
<li>jobs/build/name: 실행될 job의 이름</li>
<li>jobs/build/run: 실행될 job의 동작</li>
</ol>
<ul>
<li>크게 내가 이해한 코드들이다. 다른 부분들은 다른 블로그의 내용들을 참고<pre><code></code></pre></li>
</ul>
<p>위의 Actions에 코드의 안정성 높이기 위해 test 코드가 필요하다는 것을 바로 느꼈다.</p>
<blockquote>
<p>test 코드를 넣지 않으면 잘못된 코드가 자동으로 배포 버전에 반영이 되어 에러를 일으킬 수 있다라고 직감적으로 느끼고, 급하게 test 코드를 작성했다...😵‍💫 다음부터는 기능을 개발하면 test 코드도 함께 개발해야겠다...😭</p>
</blockquote>
<p>Django에서는 test 코드를 서비스를 만들며 만든 각 App마다 tests.py가 생성되고 그 곳에 적절한 코드를 작성하면 된다!</p>
<ul>
<li><strong>test 코드를 작성하면 생기는 장점!!</strong> : 기능 개발 시에 발견하지 못했던 예외에 대한 부분들을 test에서 찾을 수 있다! ➡️ <code>이 점을 느끼고 다음부터 기능 개발할 때엔 test 코드도 함께 작성하려한다!</code></li>
</ul>
<p>test 코드를 작성하는데 애를 먹었지만 결국 모든 테스트를 통과시키는 것을 확인했고, Github Actions 코드도 적절한 명령들을 넣어 CI/CD를 완료하였다.</p>
<h2 id="✍️-마무리하며">✍️ 마무리하며</h2>
<blockquote>
<p>학교를 다니며 주변 사람들이 CI/CD를 해보는 경험이 실무에서 큰 도움이 된다는 말을 자주 들었는데, 이번 프로젝트를 하며 CI/CD라는 이름만 알고 있던 내가 CI/CD를 구축하며 한 단계 성장하는 좋은 경험을 하였다.🍀 다음에 CI/CD를 구축할 기회가 있다면 그때는 <code>Jenkins</code>를 사용해 봐야겠다!!
그리고 앞으로 기능 개발을 해나갈 때, test 코드도 함께 작성하며 예외처리 및 안전한 코드를 개발해나갈 것!😎</p>
</blockquote>
]]></description>
        </item>
    </channel>
</rss>